Commit 3c1f9683 authored by Filipa Lacerda's avatar Filipa Lacerda

Merge branch 'master' into list-multiple-clusters

* master: (158 commits)
  Improve output for extra queries in specs
  Fixed new group milestone breadcrumb
  Try to find the merge-base against the canonical master
  Add FetchSourceBranch Gitaly call
  Backport QA code that belongs to CE from EE Geo
  Add QUERY_RECORDER_DEBUG environment variable to improve performance debugging
  Add support of Mermaid
  Update VERSION to 10.3.0-pre
  Update CHANGELOG.md for 10.2.0
  default fill color for SVGs
  Fix reply quote keyboard shortcut on MRs
  ignore hashed repos (for now) when using `rake gitlab:cleanup:repos`
  Use Redis cache for branch existence checks
  Update CONTRIBUTING.md: Link definition of done to criteria
  Use `make install` for Gitaly setups in non-test environments
  FileUploader should check for hashed_storage?(:attachments) to use disk_path
  Set the default gitlab-shell timeout to 3 hours
  Update composite pipelines index to include "id"
  Use arrays in Pipeline#latest_builds_with_artifacts
  Fix blank states using old css
  ...
parents afcfe182 0c972f56
...@@ -193,7 +193,7 @@ review-docs-deploy: ...@@ -193,7 +193,7 @@ review-docs-deploy:
name: review-docs/$CI_COMMIT_REF_NAME name: review-docs/$CI_COMMIT_REF_NAME
# DOCS_REVIEW_APPS_DOMAIN and DOCS_GITLAB_REPO_SUFFIX are secret variables # DOCS_REVIEW_APPS_DOMAIN and DOCS_GITLAB_REPO_SUFFIX are secret variables
# Discussion: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14236/diffs#note_40140693 # Discussion: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14236/diffs#note_40140693
url: http://preview-$CI_COMMIT_REF_SLUG.$DOCS_REVIEW_APPS_DOMAIN/$DOCS_GITLAB_REPO_SUFFIX url: http://$DOCS_GITLAB_REPO_SUFFIX-$CI_COMMIT_REF_SLUG.$DOCS_REVIEW_APPS_DOMAIN/$DOCS_GITLAB_REPO_SUFFIX
on_stop: review-docs-cleanup on_stop: review-docs-cleanup
script: script:
- ./trigger-build-docs deploy - ./trigger-build-docs deploy
...@@ -256,7 +256,7 @@ flaky-examples-check: ...@@ -256,7 +256,7 @@ flaky-examples-check:
USE_BUNDLE_INSTALL: "false" USE_BUNDLE_INSTALL: "false"
NEW_FLAKY_SPECS_REPORT: rspec_flaky/report-new.json NEW_FLAKY_SPECS_REPORT: rspec_flaky/report-new.json
stage: post-test stage: post-test
allow_failure: yes allow_failure: true
retry: 0 retry: 0
only: only:
- branches - branches
...@@ -416,11 +416,10 @@ ee_compat_check: ...@@ -416,11 +416,10 @@ ee_compat_check:
- /^[\d-]+-stable(-ee)?/ - /^[\d-]+-stable(-ee)?/
- branches@gitlab-org/gitlab-ee - branches@gitlab-org/gitlab-ee
- branches@gitlab/gitlab-ee - branches@gitlab/gitlab-ee
allow_failure: no
retry: 0 retry: 0
artifacts: artifacts:
name: "${CI_JOB_NAME}_${CI_COMIT_REF_NAME}_${CI_COMMIT_SHA}" name: "${CI_JOB_NAME}_${CI_COMIT_REF_NAME}_${CI_COMMIT_SHA}"
when: on_failure when: always
expire_in: 10d expire_in: 10d
paths: paths:
- ee_compat_check/patches/*.patch - ee_compat_check/patches/*.patch
...@@ -475,7 +474,7 @@ migration:path-mysql: ...@@ -475,7 +474,7 @@ migration:path-mysql:
<<: *pull-cache <<: *pull-cache
stage: test stage: test
script: script:
- bundle exec rake db:rollback STEP=120 - bundle exec rake db:rollback STEP=119
- bundle exec rake db:migrate - bundle exec rake db:migrate
db:rollback-pg: db:rollback-pg:
...@@ -578,7 +577,7 @@ codequality: ...@@ -578,7 +577,7 @@ codequality:
script: script:
- cp .rubocop.yml .rubocop.yml.bak - cp .rubocop.yml .rubocop.yml.bak
- grep -v "rubocop-gitlab-security" .rubocop.yml.bak > .rubocop.yml - grep -v "rubocop-gitlab-security" .rubocop.yml.bak > .rubocop.yml
- docker run --env CODECLIMATE_CODE="$PWD" --volume "$PWD":/code --volume /var/run/docker.sock:/var/run/docker.sock --volume /tmp/cc:/tmp/cc codeclimate/codeclimate analyze -f json > raw_codeclimate.json - docker run --env CODECLIMATE_CODE="$PWD" --volume "$PWD":/code --volume /var/run/docker.sock:/var/run/docker.sock --volume /tmp/cc:/tmp/cc codeclimate/codeclimate:0.69.0 analyze -f json > raw_codeclimate.json
- cat raw_codeclimate.json | docker run -i stedolan/jq -c 'map({check_name,fingerprint,location})' > codeclimate.json - cat raw_codeclimate.json | docker run -i stedolan/jq -c 'map({check_name,fingerprint,location})' > codeclimate.json
- mv .rubocop.yml.bak .rubocop.yml - mv .rubocop.yml.bak .rubocop.yml
artifacts: artifacts:
......
Please read this!
Before opening a new issue, make sure to search for keywords in the issues
filtered by the "feature proposal" label:
For the Community Edition issue tracker:
- https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name%5B%5D=feature+proposal
For the Enterprise Edition issue tracker:
- https://gitlab.com/gitlab-org/gitlab-ee/issues?label_name%5B%5D=feature+proposal
and verify the issue you're about to submit isn't a duplicate.
Please remove this notice if you're confident your issue isn't a duplicate.
------
### Description ### Description
(Include problem, use cases, benefits, and/or goals) (Include problem, use cases, benefits, and/or goals)
...@@ -25,26 +6,4 @@ Please remove this notice if you're confident your issue isn't a duplicate. ...@@ -25,26 +6,4 @@ Please remove this notice if you're confident your issue isn't a duplicate.
### Links / references ### Links / references
### Documentation blurb /label ~"feature proposal"
#### Overview
What is it?
Why should someone use this feature?
What is the underlying (business) problem?
How do you use this feature?
#### Use cases
Who is this for? Provide one or more use cases.
### Feature checklist
Make sure these are completed before closing the issue,
with a link to the relevant commit.
- [ ] [Feature assurance](https://about.gitlab.com/handbook/product/#feature-assurance)
- [ ] Documentation
- [ ] Added to [features.yml](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/data/features.yml)
/label ~"feature proposal"
\ No newline at end of file
This diff is collapsed.
...@@ -543,6 +543,7 @@ When having your code reviewed and when reviewing merge requests please take the ...@@ -543,6 +543,7 @@ When having your code reviewed and when reviewing merge requests please take the
etc.), they should conform to our [Licensing guidelines][license-finder-doc]. etc.), they should conform to our [Licensing guidelines][license-finder-doc].
See the instructions in that document for help if your MR fails the See the instructions in that document for help if your MR fails the
"license-finder" test with a "Dependencies that need approval" error. "license-finder" test with a "Dependencies that need approval" error.
1. The merge request meets the [definition of done](#definition-of-done).
## Definition of done ## Definition of done
......
...@@ -263,6 +263,8 @@ gem 'gettext_i18n_rails', '~> 1.8.0' ...@@ -263,6 +263,8 @@ gem 'gettext_i18n_rails', '~> 1.8.0'
gem 'gettext_i18n_rails_js', '~> 1.2.0' gem 'gettext_i18n_rails_js', '~> 1.2.0'
gem 'gettext', '~> 3.2.2', require: false, group: :development gem 'gettext', '~> 3.2.2', require: false, group: :development
gem 'batch-loader'
# Perf bar # Perf bar
gem 'peek', '~> 1.0.1' gem 'peek', '~> 1.0.1'
gem 'peek-gc', '~> 0.0.2' gem 'peek-gc', '~> 0.0.2'
...@@ -343,7 +345,7 @@ group :development, :test do ...@@ -343,7 +345,7 @@ group :development, :test do
gem 'benchmark-ips', '~> 2.3.0', require: false gem 'benchmark-ips', '~> 2.3.0', require: false
gem 'license_finder', '~> 2.1.0', require: false gem 'license_finder', '~> 3.1', require: false
gem 'knapsack', '~> 1.11.0' gem 'knapsack', '~> 1.11.0'
gem 'activerecord_sane_schema_dumper', '0.2' gem 'activerecord_sane_schema_dumper', '0.2'
...@@ -398,7 +400,7 @@ group :ed25519 do ...@@ -398,7 +400,7 @@ group :ed25519 do
end end
# Gitaly GRPC client # Gitaly GRPC client
gem 'gitaly-proto', '~> 0.52.0', require: 'gitaly' gem 'gitaly-proto', '~> 0.54.0', require: 'gitaly'
gem 'toml-rb', '~> 0.3.15', require: false gem 'toml-rb', '~> 0.3.15', require: false
......
...@@ -73,6 +73,7 @@ GEM ...@@ -73,6 +73,7 @@ GEM
thread_safe (~> 0.3, >= 0.3.1) thread_safe (~> 0.3, >= 0.3.1)
babosa (1.0.2) babosa (1.0.2)
base32 (0.3.2) base32 (0.3.2)
batch-loader (1.1.1)
bcrypt (3.1.11) bcrypt (3.1.11)
bcrypt_pbkdf (1.0.0) bcrypt_pbkdf (1.0.0)
benchmark-ips (2.3.0) benchmark-ips (2.3.0)
...@@ -83,6 +84,7 @@ GEM ...@@ -83,6 +84,7 @@ GEM
bindata (2.4.1) bindata (2.4.1)
binding_of_caller (0.7.2) binding_of_caller (0.7.2)
debug_inspector (>= 0.0.1) debug_inspector (>= 0.0.1)
blankslate (2.1.2.4)
bootstrap-sass (3.3.6) bootstrap-sass (3.3.6)
autoprefixer-rails (>= 5.2.1) autoprefixer-rails (>= 5.2.1)
sass (>= 3.3.4) sass (>= 3.3.4)
...@@ -274,7 +276,7 @@ GEM ...@@ -274,7 +276,7 @@ GEM
po_to_json (>= 1.0.0) po_to_json (>= 1.0.0)
rails (>= 3.2.0) rails (>= 3.2.0)
gherkin-ruby (0.3.2) gherkin-ruby (0.3.2)
gitaly-proto (0.52.0) gitaly-proto (0.54.0)
google-protobuf (~> 3.1) google-protobuf (~> 3.1)
grpc (~> 1.0) grpc (~> 1.0)
github-linguist (4.7.6) github-linguist (4.7.6)
...@@ -354,10 +356,10 @@ GEM ...@@ -354,10 +356,10 @@ GEM
rake rake
grape_logging (1.7.0) grape_logging (1.7.0)
grape grape
grpc (1.6.6) grpc (1.7.2)
google-protobuf (~> 3.1) google-protobuf (~> 3.1)
googleapis-common-protos-types (~> 1.0.0) googleapis-common-protos-types (~> 1.0.0)
googleauth (~> 0.5.1) googleauth (>= 0.5.1, < 0.7)
haml (4.0.7) haml (4.0.7)
tilt tilt
haml_lint (0.26.0) haml_lint (0.26.0)
...@@ -451,11 +453,13 @@ GEM ...@@ -451,11 +453,13 @@ GEM
actionmailer (>= 3.2) actionmailer (>= 3.2)
letter_opener (~> 1.0) letter_opener (~> 1.0)
railties (>= 3.2) railties (>= 3.2)
license_finder (2.1.0) license_finder (3.1.1)
bundler bundler
httparty httparty
rubyzip rubyzip
thor thor
toml (= 0.1.2)
with_env (> 1.0)
xml-simple xml-simple
licensee (8.7.0) licensee (8.7.0)
rugged (~> 0.24) rugged (~> 0.24)
...@@ -571,6 +575,8 @@ GEM ...@@ -571,6 +575,8 @@ GEM
activerecord (>= 4.0, < 5.2) activerecord (>= 4.0, < 5.2)
parser (2.4.0.0) parser (2.4.0.0)
ast (~> 2.2) ast (~> 2.2)
parslet (1.5.0)
blankslate (~> 2.0)
path_expander (1.0.1) path_expander (1.0.1)
peek (1.0.1) peek (1.0.1)
concurrent-ruby (>= 0.9.0) concurrent-ruby (>= 0.9.0)
...@@ -898,6 +904,8 @@ GEM ...@@ -898,6 +904,8 @@ GEM
tilt (2.0.6) tilt (2.0.6)
timecop (0.8.1) timecop (0.8.1)
timfel-krb5-auth (0.8.3) timfel-krb5-auth (0.8.3)
toml (0.1.2)
parslet (~> 1.5.0)
toml-rb (0.3.15) toml-rb (0.3.15)
citrus (~> 3.0, > 3.0) citrus (~> 3.0, > 3.0)
truncato (0.7.10) truncato (0.7.10)
...@@ -952,6 +960,7 @@ GEM ...@@ -952,6 +960,7 @@ GEM
builder builder
expression_parser expression_parser
rinku rinku
with_env (1.1.0)
xml-simple (1.1.5) xml-simple (1.1.5)
xpath (2.1.0) xpath (2.1.0)
nokogiri (~> 1.3) nokogiri (~> 1.3)
...@@ -974,6 +983,7 @@ DEPENDENCIES ...@@ -974,6 +983,7 @@ DEPENDENCIES
awesome_print (~> 1.2.0) awesome_print (~> 1.2.0)
babosa (~> 1.0.2) babosa (~> 1.0.2)
base32 (~> 0.3.0) base32 (~> 0.3.0)
batch-loader
bcrypt_pbkdf (~> 1.0) bcrypt_pbkdf (~> 1.0)
benchmark-ips (~> 2.3.0) benchmark-ips (~> 2.3.0)
better_errors (~> 2.1.0) better_errors (~> 2.1.0)
...@@ -1026,7 +1036,7 @@ DEPENDENCIES ...@@ -1026,7 +1036,7 @@ DEPENDENCIES
gettext (~> 3.2.2) gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0) gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.2.0) gettext_i18n_rails_js (~> 1.2.0)
gitaly-proto (~> 0.52.0) gitaly-proto (~> 0.54.0)
github-linguist (~> 4.7.0) github-linguist (~> 4.7.0)
gitlab-flowdock-git-hook (~> 1.0.1) gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-markup (~> 1.6.2) gitlab-markup (~> 1.6.2)
...@@ -1058,7 +1068,7 @@ DEPENDENCIES ...@@ -1058,7 +1068,7 @@ DEPENDENCIES
knapsack (~> 1.11.0) knapsack (~> 1.11.0)
kubeclient (~> 2.2.0) kubeclient (~> 2.2.0)
letter_opener_web (~> 1.3.0) letter_opener_web (~> 1.3.0)
license_finder (~> 2.1.0) license_finder (~> 3.1)
licensee (~> 8.7.0) licensee (~> 8.7.0)
lograge (~> 0.5) lograge (~> 0.5)
loofah (~> 2.0.3) loofah (~> 2.0.3)
......
10.2.0-pre 10.3.0-pre
/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, no-var, quotes, vars-on-top, no-unused-vars, no-new, max-len */ /* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, no-var, quotes, vars-on-top, no-unused-vars, no-new, max-len */
/* global EditBlob */ /* global EditBlob */
/* global NewCommitForm */ import NewCommitForm from '../new_commit_form';
import EditBlob from './edit_blob'; import EditBlob from './edit_blob';
import BlobFileDropzone from '../blob/blob_file_dropzone'; import BlobFileDropzone from '../blob/blob_file_dropzone';
......
/* eslint-disable one-var, quote-props, comma-dangle, space-before-function-paren */ /* eslint-disable one-var, quote-props, comma-dangle, space-before-function-paren */
/* global BoardService */
import _ from 'underscore'; import _ from 'underscore';
import Vue from 'vue'; import Vue from 'vue';
import VueResource from 'vue-resource'; import VueResource from 'vue-resource';
import Flash from '../flash'; import Flash from '../flash';
import { __ } from '../locale';
import FilteredSearchBoards from './filtered_search_boards'; import FilteredSearchBoards from './filtered_search_boards';
import eventHub from './eventhub'; import eventHub from './eventhub';
import sidebarEventHub from '../sidebar/event_hub';
import './models/issue'; import './models/issue';
import './models/label'; import './models/label';
import './models/list'; import './models/list';
...@@ -14,7 +15,7 @@ import './models/milestone'; ...@@ -14,7 +15,7 @@ import './models/milestone';
import './models/assignee'; import './models/assignee';
import './stores/boards_store'; import './stores/boards_store';
import './stores/modal_store'; import './stores/modal_store';
import './services/board_service'; import BoardService from './services/board_service';
import './mixins/modal_mixins'; import './mixins/modal_mixins';
import './mixins/sortable_default_options'; import './mixins/sortable_default_options';
import './filters/due_date_filters'; import './filters/due_date_filters';
...@@ -77,11 +78,16 @@ $(() => { ...@@ -77,11 +78,16 @@ $(() => {
}); });
Store.rootPath = this.boardsEndpoint; Store.rootPath = this.boardsEndpoint;
// Listen for updateTokens event
eventHub.$on('updateTokens', this.updateTokens); eventHub.$on('updateTokens', this.updateTokens);
eventHub.$on('newDetailIssue', this.updateDetailIssue);
eventHub.$on('clearDetailIssue', this.clearDetailIssue);
sidebarEventHub.$on('toggleSubscription', this.toggleSubscription);
}, },
beforeDestroy() { beforeDestroy() {
eventHub.$off('updateTokens', this.updateTokens); eventHub.$off('updateTokens', this.updateTokens);
eventHub.$off('newDetailIssue', this.updateDetailIssue);
eventHub.$off('clearDetailIssue', this.clearDetailIssue);
sidebarEventHub.$off('toggleSubscription', this.toggleSubscription);
}, },
mounted () { mounted () {
this.filterManager = new FilteredSearchBoards(Store.filter, true); this.filterManager = new FilteredSearchBoards(Store.filter, true);
...@@ -112,6 +118,46 @@ $(() => { ...@@ -112,6 +118,46 @@ $(() => {
methods: { methods: {
updateTokens() { updateTokens() {
this.filterManager.updateTokens(); this.filterManager.updateTokens();
},
updateDetailIssue(newIssue) {
const sidebarInfoEndpoint = newIssue.sidebarInfoEndpoint;
if (sidebarInfoEndpoint && newIssue.subscribed === undefined) {
newIssue.setFetchingState('subscriptions', true);
BoardService.getIssueInfo(sidebarInfoEndpoint)
.then(res => res.json())
.then((data) => {
newIssue.setFetchingState('subscriptions', false);
newIssue.updateData({
subscribed: data.subscribed,
});
})
.catch(() => {
newIssue.setFetchingState('subscriptions', false);
Flash(__('An error occurred while fetching sidebar data'));
});
}
Store.detail.issue = newIssue;
},
clearDetailIssue() {
Store.detail.issue = {};
},
toggleSubscription(id) {
const issue = Store.detail.issue;
if (issue.id === id && issue.toggleSubscriptionEndpoint) {
issue.setFetchingState('subscriptions', true);
BoardService.toggleIssueSubscription(issue.toggleSubscriptionEndpoint)
.then(() => {
issue.setFetchingState('subscriptions', false);
issue.updateData({
subscribed: !issue.subscribed,
});
})
.catch(() => {
issue.setFetchingState('subscriptions', false);
Flash(__('An error occurred when toggling the notification subscription'));
});
}
} }
}, },
}); });
......
<script>
import './issue_card_inner'; import './issue_card_inner';
import eventHub from '../eventhub';
const Store = gl.issueBoards.BoardsStore; const Store = gl.issueBoards.BoardsStore;
export default { export default {
name: 'BoardsIssueCard', name: 'BoardsIssueCard',
template: `
<li class="card"
:class="{ 'user-can-drag': !disabled && issue.id, 'is-disabled': disabled || !issue.id, 'is-active': issueDetailVisible }"
:index="index"
:data-issue-id="issue.id"
@mousedown="mouseDown"
@mousemove="mouseMove"
@mouseup="showIssue($event)">
<issue-card-inner
:list="list"
:issue="issue"
:issue-link-base="issueLinkBase"
:root-path="rootPath"
:update-filters="true" />
</li>
`,
components: { components: {
'issue-card-inner': gl.issueBoards.IssueCardInner, 'issue-card-inner': gl.issueBoards.IssueCardInner,
}, },
...@@ -56,12 +42,30 @@ export default { ...@@ -56,12 +42,30 @@ export default {
this.showDetail = false; this.showDetail = false;
if (Store.detail.issue && Store.detail.issue.id === this.issue.id) { if (Store.detail.issue && Store.detail.issue.id === this.issue.id) {
Store.detail.issue = {}; eventHub.$emit('clearDetailIssue');
} else { } else {
Store.detail.issue = this.issue; eventHub.$emit('newDetailIssue', this.issue);
Store.detail.list = this.list; Store.detail.list = this.list;
} }
} }
}, },
}, },
}; };
</script>
<template>
<li class="card"
:class="{ 'user-can-drag': !disabled && issue.id, 'is-disabled': disabled || !issue.id, 'is-active': issueDetailVisible }"
:index="index"
:data-issue-id="issue.id"
@mousedown="mouseDown"
@mousemove="mouseMove"
@mouseup="showIssue($event)">
<issue-card-inner
:list="list"
:issue="issue"
:issue-link-base="issueLinkBase"
:root-path="rootPath"
:update-filters="true" />
</li>
</template>
/* global Sortable */ /* global Sortable */
import boardNewIssue from './board_new_issue'; import boardNewIssue from './board_new_issue';
import boardCard from './board_card'; import boardCard from './board_card.vue';
import eventHub from '../eventhub'; import eventHub from '../eventhub';
import loadingIcon from '../../vue_shared/components/loading_icon.vue'; import loadingIcon from '../../vue_shared/components/loading_icon.vue';
......
...@@ -5,12 +5,13 @@ ...@@ -5,12 +5,13 @@
import Vue from 'vue'; import Vue from 'vue';
import Flash from '../../flash'; import Flash from '../../flash';
import eventHub from '../../sidebar/event_hub'; import eventHub from '../../sidebar/event_hub';
import AssigneeTitle from '../../sidebar/components/assignees/assignee_title'; import assigneeTitle from '../../sidebar/components/assignees/assignee_title';
import Assignees from '../../sidebar/components/assignees/assignees'; import assignees from '../../sidebar/components/assignees/assignees';
import DueDateSelectors from '../../due_date_select'; import DueDateSelectors from '../../due_date_select';
import './sidebar/remove_issue'; import './sidebar/remove_issue';
import IssuableContext from '../../issuable_context'; import IssuableContext from '../../issuable_context';
import LabelsSelect from '../../labels_select'; import LabelsSelect from '../../labels_select';
import subscriptions from '../../sidebar/components/subscriptions/subscriptions.vue';
const Store = gl.issueBoards.BoardsStore; const Store = gl.issueBoards.BoardsStore;
...@@ -117,11 +118,11 @@ gl.issueBoards.BoardSidebar = Vue.extend({ ...@@ -117,11 +118,11 @@ gl.issueBoards.BoardSidebar = Vue.extend({
new DueDateSelectors(); new DueDateSelectors();
new LabelsSelect(); new LabelsSelect();
new Sidebar(); new Sidebar();
gl.Subscription.bindAll('.subscription');
}, },
components: { components: {
assigneeTitle,
assignees,
removeBtn: gl.issueBoards.RemoveIssueBtn, removeBtn: gl.issueBoards.RemoveIssueBtn,
'assignee-title': AssigneeTitle, subscriptions,
assignees: Assignees,
}, },
}); });
...@@ -17,6 +17,11 @@ class ListIssue { ...@@ -17,6 +17,11 @@ class ListIssue {
this.assignees = []; this.assignees = [];
this.selected = false; this.selected = false;
this.position = obj.relative_position || Infinity; this.position = obj.relative_position || Infinity;
this.isFetching = {
subscriptions: true,
};
this.sidebarInfoEndpoint = obj.issue_sidebar_endpoint;
this.toggleSubscriptionEndpoint = obj.toggle_subscription_endpoint;
if (obj.milestone) { if (obj.milestone) {
this.milestone = new ListMilestone(obj.milestone); this.milestone = new ListMilestone(obj.milestone);
...@@ -73,6 +78,14 @@ class ListIssue { ...@@ -73,6 +78,14 @@ class ListIssue {
return gl.issueBoards.BoardsStore.state.lists.filter(list => list.findIssue(this.id)); return gl.issueBoards.BoardsStore.state.lists.filter(list => list.findIssue(this.id));
} }
updateData(newData) {
Object.assign(this, newData);
}
setFetchingState(key, value) {
this.isFetching[key] = value;
}
update (url) { update (url) {
const data = { const data = {
issue: { issue: {
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
import Vue from 'vue'; import Vue from 'vue';
class BoardService { export default class BoardService {
constructor ({ boardsEndpoint, listsEndpoint, bulkUpdatePath, boardId }) { constructor ({ boardsEndpoint, listsEndpoint, bulkUpdatePath, boardId }) {
this.boards = Vue.resource(`${boardsEndpoint}{/id}.json`, {}, { this.boards = Vue.resource(`${boardsEndpoint}{/id}.json`, {}, {
issues: { issues: {
...@@ -88,6 +88,14 @@ class BoardService { ...@@ -88,6 +88,14 @@ class BoardService {
return this.issues.bulkUpdate(data); return this.issues.bulkUpdate(data);
} }
static getIssueInfo(endpoint) {
return Vue.http.get(endpoint);
}
static toggleIssueSubscription(endpoint) {
return Vue.http.post(endpoint);
}
} }
window.BoardService = BoardService; window.BoardService = BoardService;
import axios from 'axios'; import axios from '../../lib/utils/axios_utils';
import setAxiosCsrfToken from '../../lib/utils/axios_utils';
export default class ClusterService { export default class ClusterService {
constructor(options = {}) { constructor(options = {}) {
setAxiosCsrfToken();
this.options = options; this.options = options;
this.appInstallEndpointMap = { this.appInstallEndpointMap = {
helm: this.options.installHelmEndpoint, helm: this.options.installHelmEndpoint,
...@@ -18,7 +15,6 @@ export default class ClusterService { ...@@ -18,7 +15,6 @@ export default class ClusterService {
} }
installApplication(appId) { installApplication(appId) {
const endpoint = this.appInstallEndpointMap[appId]; return axios.post(this.appInstallEndpointMap[appId]);
return axios.post(endpoint);
} }
} }
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-arrow-callback, wrap-iife, no-shadow, consistent-return, one-var, one-var-declaration-per-line, camelcase, default-case, no-new, quotes, no-duplicate-case, no-case-declarations, no-fallthrough, max-len */ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-arrow-callback, wrap-iife, no-shadow, consistent-return, one-var, one-var-declaration-per-line, camelcase, default-case, no-new, quotes, no-duplicate-case, no-case-declarations, no-fallthrough, max-len */
import { s__ } from './locale'; import { s__ } from './locale';
/* global ProjectSelect */ import projectSelect from './project_select';
import IssuableIndex from './issuable_index'; import IssuableIndex from './issuable_index';
/* global Milestone */ import Milestone from './milestone';
import IssuableForm from './issuable_form'; import IssuableForm from './issuable_form';
import LabelsSelect from './labels_select'; import LabelsSelect from './labels_select';
/* global MilestoneSelect */ /* global MilestoneSelect */
/* global NewBranchForm */ import NewBranchForm from './new_branch_form';
/* global NotificationsForm */ /* global NotificationsForm */
/* global NotificationsDropdown */ /* global NotificationsDropdown */
import groupAvatar from './group_avatar'; import groupAvatar from './group_avatar';
...@@ -18,16 +18,14 @@ import groupsSelect from './groups_select'; ...@@ -18,16 +18,14 @@ import groupsSelect from './groups_select';
/* global Search */ /* global Search */
/* global Admin */ /* global Admin */
import NamespaceSelect from './namespace_select'; import NamespaceSelect from './namespace_select';
/* global NewCommitForm */ import NewCommitForm from './new_commit_form';
/* global NewBranchForm */
import Project from './project'; import Project from './project';
import projectAvatar from './project_avatar'; import projectAvatar from './project_avatar';
/* global MergeRequest */ /* global MergeRequest */
/* global Compare */ /* global Compare */
/* global CompareAutocomplete */ /* global CompareAutocomplete */
/* global ProjectFindFile */ /* global ProjectFindFile */
/* global ProjectNew */ import ProjectNew from './project_new';
/* global ProjectShow */
import projectImport from './project_import'; import projectImport from './project_import';
import Labels from './labels'; import Labels from './labels';
import LabelManager from './label_manager'; import LabelManager from './label_manager';
...@@ -91,6 +89,8 @@ import Members from './members'; ...@@ -91,6 +89,8 @@ import Members from './members';
import memberExpirationDate from './member_expiration_date'; import memberExpirationDate from './member_expiration_date';
import DueDateSelectors from './due_date_select'; import DueDateSelectors from './due_date_select';
import Diff from './diff'; import Diff from './diff';
import ProjectLabelSubscription from './project_label_subscription';
import ProjectVariables from './project_variables';
(function() { (function() {
var Dispatcher; var Dispatcher;
...@@ -187,7 +187,7 @@ import Diff from './diff'; ...@@ -187,7 +187,7 @@ import Diff from './diff';
initIssuableSidebar(); initIssuableSidebar();
break; break;
case 'dashboard:milestones:index': case 'dashboard:milestones:index':
new ProjectSelect(); projectSelect();
break; break;
case 'projects:milestones:show': case 'projects:milestones:show':
case 'groups:milestones:show': case 'groups:milestones:show':
...@@ -197,7 +197,7 @@ import Diff from './diff'; ...@@ -197,7 +197,7 @@ import Diff from './diff';
break; break;
case 'dashboard:issues': case 'dashboard:issues':
case 'dashboard:merge_requests': case 'dashboard:merge_requests':
new ProjectSelect(); projectSelect();
initLegacyFilters(); initLegacyFilters();
break; break;
case 'groups:issues': case 'groups:issues':
...@@ -206,7 +206,7 @@ import Diff from './diff'; ...@@ -206,7 +206,7 @@ import Diff from './diff';
const filteredSearchManager = new gl.FilteredSearchManager(page === 'groups:issues' ? 'issues' : 'merge_requests'); const filteredSearchManager = new gl.FilteredSearchManager(page === 'groups:issues' ? 'issues' : 'merge_requests');
filteredSearchManager.setup(); filteredSearchManager.setup();
} }
new ProjectSelect(); projectSelect();
break; break;
case 'dashboard:todos:index': case 'dashboard:todos:index':
new Todos(); new Todos();
...@@ -317,7 +317,6 @@ import Diff from './diff'; ...@@ -317,7 +317,6 @@ import Diff from './diff';
break; break;
case 'projects:merge_requests:show': case 'projects:merge_requests:show':
new Diff(); new Diff();
shortcut_handler = new ShortcutsIssuable(true);
new ZenMode(); new ZenMode();
initIssuableSidebar(); initIssuableSidebar();
...@@ -327,6 +326,8 @@ import Diff from './diff'; ...@@ -327,6 +326,8 @@ import Diff from './diff';
window.mergeRequest = new MergeRequest({ window.mergeRequest = new MergeRequest({
action: mrShowNode.dataset.mrAction, action: mrShowNode.dataset.mrAction,
}); });
shortcut_handler = new ShortcutsIssuable(true);
break; break;
case 'dashboard:activity': case 'dashboard:activity':
new gl.Activities(); new gl.Activities();
...@@ -339,7 +340,8 @@ import Diff from './diff'; ...@@ -339,7 +340,8 @@ import Diff from './diff';
container: '.js-commit-pipeline-graph', container: '.js-commit-pipeline-graph',
}).bindEvents(); }).bindEvents();
initNotes(); initNotes();
initChangesDropdown(); const stickyBarPaddingTop = 16;
initChangesDropdown(document.querySelector('.navbar-gitlab').offsetHeight - stickyBarPaddingTop);
$('.commit-info.branches').load(document.querySelector('.js-commit-box').dataset.commitPath); $('.commit-info.branches').load(document.querySelector('.js-commit-box').dataset.commitPath);
break; break;
case 'projects:commit:pipelines': case 'projects:commit:pipelines':
...@@ -484,7 +486,7 @@ import Diff from './diff'; ...@@ -484,7 +486,7 @@ import Diff from './diff';
if ($el.find('.dropdown-group-label').length) { if ($el.find('.dropdown-group-label').length) {
new GroupLabelSubscription($el); new GroupLabelSubscription($el);
} else { } else {
new gl.ProjectLabelSubscription($el); new ProjectLabelSubscription($el);
} }
}); });
break; break;
...@@ -520,7 +522,7 @@ import Diff from './diff'; ...@@ -520,7 +522,7 @@ import Diff from './diff';
// Initialize expandable settings panels // Initialize expandable settings panels
initSettingsPanels(); initSettingsPanels();
case 'groups:settings:ci_cd:show': case 'groups:settings:ci_cd:show':
new gl.ProjectVariables(); new ProjectVariables();
break; break;
case 'ci:lints:create': case 'ci:lints:create':
case 'ci:lints:show': case 'ci:lints:show':
...@@ -623,7 +625,6 @@ import Diff from './diff'; ...@@ -623,7 +625,6 @@ import Diff from './diff';
case 'show': case 'show':
new Star(); new Star();
new ProjectNew(); new ProjectNew();
new ProjectShow();
new NotificationsDropdown(); new NotificationsDropdown();
break; break;
case 'wikis': case 'wikis':
......
...@@ -227,25 +227,27 @@ export default { ...@@ -227,25 +227,27 @@ export default {
/> />
<div <div
class="blank-state blank-state-no-icon" class="blank-state-row"
v-if="!isLoading && state.environments.length === 0"> v-if="!isLoading && state.environments.length === 0">
<h2 class="blank-state-title js-blank-state-title"> <div class="blank-state-center">
You don't have any environments right now. <h2 class="blank-state-title js-blank-state-title">
</h2> You don't have any environments right now.
<p class="blank-state-text"> </h2>
Environments are places where code gets deployed, such as staging or production. <p class="blank-state-text">
<br /> Environments are places where code gets deployed, such as staging or production.
<a :href="helpPagePath"> <br />
Read more about environments <a :href="helpPagePath">
Read more about environments
</a>
</p>
<a
v-if="canCreateEnvironmentParsed"
:href="newEnvironmentPath"
class="btn btn-create js-new-environment-button">
New environment
</a> </a>
</p> </div>
<a
v-if="canCreateEnvironmentParsed"
:href="newEnvironmentPath"
class="btn btn-create js-new-environment-button">
New environment
</a>
</div> </div>
<div <div
......
...@@ -14,7 +14,6 @@ export default () => { ...@@ -14,7 +14,6 @@ export default () => {
}); });
new LabelsSelect(); new LabelsSelect();
new IssuableContext(sidebarOptions.currentUser); new IssuableContext(sidebarOptions.currentUser);
gl.Subscription.bindAll('.subscription');
new DueDateSelectors(); new DueDateSelectors();
window.sidebar = new Sidebar(); window.sidebar = new Sidebar();
}; };
/* eslint-disable no-new */ /* eslint-disable no-new */
import LabelsSelect from './labels_select'; import LabelsSelect from './labels_select';
/* global MilestoneSelect */ /* global MilestoneSelect */
/* global SubscriptionSelect */ import subscriptionSelect from './subscription_select';
import UsersSelect from './users_select'; import UsersSelect from './users_select';
import issueStatusSelect from './issue_status_select'; import issueStatusSelect from './issue_status_select';
...@@ -11,5 +10,5 @@ export default () => { ...@@ -11,5 +10,5 @@ export default () => {
new LabelsSelect(); new LabelsSelect();
new MilestoneSelect(); new MilestoneSelect();
issueStatusSelect(); issueStatusSelect();
new SubscriptionSelect(); subscriptionSelect();
}; };
/* eslint-disable class-methods-use-this, no-new */ /* eslint-disable class-methods-use-this, no-new */
/* global MilestoneSelect */ /* global MilestoneSelect */
/* global SubscriptionSelect */
import IssuableBulkUpdateActions from './issuable_bulk_update_actions'; import IssuableBulkUpdateActions from './issuable_bulk_update_actions';
import './milestone_select'; import './milestone_select';
import issueStatusSelect from './issue_status_select'; import issueStatusSelect from './issue_status_select';
import './subscription_select'; import subscriptionSelect from './subscription_select';
import LabelsSelect from './labels_select'; import LabelsSelect from './labels_select';
const HIDDEN_CLASS = 'hidden'; const HIDDEN_CLASS = 'hidden';
...@@ -48,7 +47,7 @@ export default class IssuableBulkUpdateSidebar { ...@@ -48,7 +47,7 @@ export default class IssuableBulkUpdateSidebar {
new LabelsSelect(); new LabelsSelect();
new MilestoneSelect(); new MilestoneSelect();
issueStatusSelect(); issueStatusSelect();
new SubscriptionSelect(); subscriptionSelect();
} }
setupBulkUpdateActions() { setupBulkUpdateActions() {
......
...@@ -102,6 +102,11 @@ export default { ...@@ -102,6 +102,11 @@ export default {
required: false, required: false,
default: 'issue', default: 'issue',
}, },
canAttachFile: {
type: Boolean,
required: false,
default: true,
},
}, },
data() { data() {
const store = new Store({ const store = new Store({
...@@ -234,6 +239,7 @@ export default { ...@@ -234,6 +239,7 @@ export default {
:project-path="projectPath" :project-path="projectPath"
:project-namespace="projectNamespace" :project-namespace="projectNamespace"
:show-delete-button="showDeleteButton" :show-delete-button="showDeleteButton"
:can-attach-file="canAttachFile"
/> />
<div v-else> <div v-else>
<title-component <title-component
......
...@@ -17,6 +17,11 @@ ...@@ -17,6 +17,11 @@
type: String, type: String,
required: true, required: true,
}, },
canAttachFile: {
type: Boolean,
required: false,
default: true,
},
}, },
components: { components: {
markdownField, markdownField,
...@@ -36,7 +41,8 @@ ...@@ -36,7 +41,8 @@
</label> </label>
<markdown-field <markdown-field
:markdown-preview-path="markdownPreviewPath" :markdown-preview-path="markdownPreviewPath"
:markdown-docs-path="markdownDocsPath"> :markdown-docs-path="markdownDocsPath"
:can-attach-file="canAttachFile">
<textarea <textarea
id="issue-description" id="issue-description"
class="note-textarea js-gfm-input js-autosize markdown-area" class="note-textarea js-gfm-input js-autosize markdown-area"
......
...@@ -41,6 +41,11 @@ ...@@ -41,6 +41,11 @@
required: false, required: false,
default: true, default: true,
}, },
canAttachFile: {
type: Boolean,
required: false,
default: true,
},
}, },
components: { components: {
lockedWarning, lockedWarning,
...@@ -83,7 +88,8 @@ ...@@ -83,7 +88,8 @@
<description-field <description-field
:form-state="formState" :form-state="formState"
:markdown-preview-path="markdownPreviewPath" :markdown-preview-path="markdownPreviewPath"
:markdown-docs-path="markdownDocsPath" /> :markdown-docs-path="markdownDocsPath"
:can-attach-file="canAttachFile" />
<edit-actions <edit-actions
:form-state="formState" :form-state="formState"
:can-destroy="canDestroy" :can-destroy="canDestroy"
......
...@@ -29,8 +29,8 @@ export default class JobMediator { ...@@ -29,8 +29,8 @@ export default class JobMediator {
this.poll = new Poll({ this.poll = new Poll({
resource: this.service, resource: this.service,
method: 'getJob', method: 'getJob',
successCallback: this.successCallback.bind(this), successCallback: response => this.successCallback(response),
errorCallback: this.errorCallback.bind(this), errorCallback: () => this.errorCallback(),
}); });
if (!Visibility.hidden()) { if (!Visibility.hidden()) {
...@@ -57,7 +57,7 @@ export default class JobMediator { ...@@ -57,7 +57,7 @@ export default class JobMediator {
successCallback(response) { successCallback(response) {
this.state.isLoading = false; this.state.isLoading = false;
return response.json().then(data => this.store.storeJob(data)); return this.store.storeJob(response.data);
} }
errorCallback() { errorCallback() {
......
import Vue from 'vue'; import axios from '../../lib/utils/axios_utils';
import VueResource from 'vue-resource';
Vue.use(VueResource);
export default class JobService { export default class JobService {
constructor(endpoint) { constructor(endpoint) {
this.job = Vue.resource(endpoint); this.job = endpoint;
} }
getJob() { getJob() {
return this.job.get(); return axios.get(this.job);
} }
} }
import axios from 'axios'; import axios from 'axios';
import csrf from './csrf'; import csrf from './csrf';
export default function setAxiosCsrfToken() { axios.defaults.headers.common[csrf.headerKey] = csrf.token;
axios.defaults.headers.common[csrf.headerKey] = csrf.token;
} // Maintain a global counter for active requests
// see: spec/support/wait_for_requests.rb
axios.interceptors.request.use((config) => {
window.activeVueResources = window.activeVueResources || 0;
window.activeVueResources += 1;
return config;
});
// Remove the global counter
axios.interceptors.response.use((config) => {
window.activeVueResources -= 1;
return config;
});
export default axios;
...@@ -3,7 +3,9 @@ import { normalizeHeaders } from './common_utils'; ...@@ -3,7 +3,9 @@ import { normalizeHeaders } from './common_utils';
/** /**
* Polling utility for handling realtime updates. * Polling utility for handling realtime updates.
* Service for vue resouce and method need to be provided as props * Requirements: Promise based HTTP client
*
* Service for promise based http client and method need to be provided as props
* *
* @example * @example
* new Poll({ * new Poll({
......
...@@ -29,7 +29,6 @@ import './commit/image_file'; ...@@ -29,7 +29,6 @@ import './commit/image_file';
// lib/utils // lib/utils
import { handleLocationHash } from './lib/utils/common_utils'; import { handleLocationHash } from './lib/utils/common_utils';
import './lib/utils/datetime_utility'; import './lib/utils/datetime_utility';
import './lib/utils/pretty_time';
import './lib/utils/url_utility'; import './lib/utils/url_utility';
// behaviors // behaviors
...@@ -59,11 +58,7 @@ import './line_highlighter'; ...@@ -59,11 +58,7 @@ import './line_highlighter';
import initLogoAnimation from './logo'; import initLogoAnimation from './logo';
import './merge_request'; import './merge_request';
import './merge_request_tabs'; import './merge_request_tabs';
import './milestone';
import './milestone_select'; import './milestone_select';
import './namespace_select';
import './new_branch_form';
import './new_commit_form';
import './notes'; import './notes';
import './notifications_dropdown'; import './notifications_dropdown';
import './notifications_form'; import './notifications_form';
...@@ -71,22 +66,15 @@ import './pager'; ...@@ -71,22 +66,15 @@ import './pager';
import './preview_markdown'; import './preview_markdown';
import './project_find_file'; import './project_find_file';
import './project_import'; import './project_import';
import './project_label_subscription';
import './project_new';
import './project_select';
import './project_show';
import './project_variables';
import './projects_dropdown'; import './projects_dropdown';
import './projects_list'; import './projects_list';
import './syntax_highlight'; import './syntax_highlight';
import './render_math'; import './render_math';
import './render_mermaid';
import './render_gfm'; import './render_gfm';
import './right_sidebar'; import './right_sidebar';
import './search'; import './search';
import './search_autocomplete'; import './search_autocomplete';
import './smart_interval';
import './subscription';
import './subscription_select';
import initBreadcrumbs from './breadcrumb'; import initBreadcrumbs from './breadcrumb';
import './dispatcher'; import './dispatcher';
......
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-use-before-define, camelcase, quotes, object-shorthand, no-shadow, no-unused-vars, comma-dangle, no-var, prefer-template, no-underscore-dangle, consistent-return, one-var, one-var-declaration-per-line, default-case, prefer-arrow-callback, max-len */
/* global Sortable */ /* global Sortable */
import Flash from './flash'; import Flash from './flash';
(function() { export default class Milestone {
this.Milestone = (function() { constructor() {
function Milestone() { this.bindTabsSwitching();
this.bindTabsSwitching();
// Load merge request tab if it is active // Load merge request tab if it is active
// merge request tab is active based on different conditions in the backend // merge request tab is active based on different conditions in the backend
this.loadTab($('.js-milestone-tabs .active a')); this.loadTab($('.js-milestone-tabs .active a'));
this.loadInitialTab(); this.loadInitialTab();
} }
bindTabsSwitching() {
return $('a[data-toggle="tab"]').on('show.bs.tab', (e) => {
const $target = $(e.target);
Milestone.prototype.bindTabsSwitching = function() { location.hash = $target.attr('href');
return $('a[data-toggle="tab"]').on('show.bs.tab', (e) => { this.loadTab($target);
const $target = $(e.target); });
}
// eslint-disable-next-line class-methods-use-this
loadInitialTab() {
const $target = $(`.js-milestone-tabs a[href="${location.hash}"]`);
location.hash = $target.attr('href'); if ($target.length) {
this.loadTab($target); $target.tab('show');
}
}
// eslint-disable-next-line class-methods-use-this
loadTab($target) {
const endpoint = $target.data('endpoint');
const tabElId = $target.attr('href');
if (endpoint && !$target.hasClass('is-loaded')) {
$.ajax({
url: endpoint,
dataType: 'JSON',
})
.fail(() => new Flash('Error loading milestone tab'))
.done((data) => {
$(tabElId).html(data.html);
$target.addClass('is-loaded');
}); });
}; }
}
Milestone.prototype.loadInitialTab = function() { }
const $target = $(`.js-milestone-tabs a[href="${location.hash}"]`);
if ($target.length) {
$target.tab('show');
}
};
Milestone.prototype.loadTab = function($target) {
const endpoint = $target.data('endpoint');
const tabElId = $target.attr('href');
if (endpoint && !$target.hasClass('is-loaded')) {
$.ajax({
url: endpoint,
dataType: 'JSON',
})
.fail(() => new Flash('Error loading milestone tab'))
.done((data) => {
$(tabElId).html(data.html);
$target.addClass('is-loaded');
});
}
};
return Milestone;
})();
}).call(window);
/* eslint-disable func-names, space-before-function-paren, no-var, one-var, prefer-rest-params, max-len, vars-on-top, wrap-iife, consistent-return, comma-dangle, one-var-declaration-per-line, quotes, no-return-assign, prefer-arrow-callback, prefer-template, no-shadow, no-else-return, max-len, object-shorthand */ /* eslint-disable func-names, space-before-function-paren, no-var, one-var, prefer-rest-params, max-len, vars-on-top, wrap-iife, consistent-return, comma-dangle, one-var-declaration-per-line, quotes, no-return-assign, prefer-arrow-callback, prefer-template, no-shadow, no-else-return, max-len, object-shorthand */
import RefSelectDropdown from '~/ref_select_dropdown'; import RefSelectDropdown from './ref_select_dropdown';
(function() { export default class NewBranchForm {
this.NewBranchForm = (function() { constructor(form, availableRefs) {
function NewBranchForm(form, availableRefs) { this.validate = this.validate.bind(this);
this.validate = this.validate.bind(this); this.branchNameError = form.find('.js-branch-name-error');
this.branchNameError = form.find('.js-branch-name-error'); this.name = form.find('.js-branch-name');
this.name = form.find('.js-branch-name'); this.ref = form.find('#ref');
this.ref = form.find('#ref'); new RefSelectDropdown($('.js-branch-select'), availableRefs); // eslint-disable-line no-new
new RefSelectDropdown($('.js-branch-select'), availableRefs); // eslint-disable-line no-new this.setupRestrictions();
this.setupRestrictions(); this.addBinding();
this.addBinding(); this.init();
this.init(); }
addBinding() {
return this.name.on('blur', this.validate);
}
init() {
if (this.name.length && this.name.val().length > 0) {
return this.name.trigger('blur');
} }
}
NewBranchForm.prototype.addBinding = function() { setupRestrictions() {
return this.name.on('blur', this.validate); var endsWith, invalid, single, startsWith;
startsWith = {
pattern: /^(\/|\.)/g,
prefix: "can't start with",
conjunction: "or"
}; };
endsWith = {
NewBranchForm.prototype.init = function() { pattern: /(\/|\.|\.lock)$/g,
if (this.name.length && this.name.val().length > 0) { prefix: "can't end in",
return this.name.trigger('blur'); conjunction: "or"
}
}; };
invalid = {
NewBranchForm.prototype.setupRestrictions = function() { pattern: /(\s|~|\^|:|\?|\*|\[|\\|\.\.|@\{|\/{2,}){1}/g,
var endsWith, invalid, single, startsWith; prefix: "can't contain",
startsWith = { conjunction: ", "
pattern: /^(\/|\.)/g, };
prefix: "can't start with", single = {
conjunction: "or" pattern: /^@+$/g,
}; prefix: "can't be",
endsWith = { conjunction: "or"
pattern: /(\/|\.|\.lock)$/g,
prefix: "can't end in",
conjunction: "or"
};
invalid = {
pattern: /(\s|~|\^|:|\?|\*|\[|\\|\.\.|@\{|\/{2,}){1}/g,
prefix: "can't contain",
conjunction: ", "
};
single = {
pattern: /^@+$/g,
prefix: "can't be",
conjunction: "or"
};
return this.restrictions = [startsWith, invalid, endsWith, single];
}; };
return this.restrictions = [startsWith, invalid, endsWith, single];
}
NewBranchForm.prototype.validate = function() { validate() {
var errorMessage, errors, formatter, unique, validator; var errorMessage, errors, formatter, unique, validator;
const indexOf = [].indexOf; const indexOf = [].indexOf;
this.branchNameError.empty(); this.branchNameError.empty();
unique = function(values, value) { unique = function(values, value) {
if (indexOf.call(values, value) === -1) { if (indexOf.call(values, value) === -1) {
values.push(value); values.push(value);
}
return values;
};
formatter = function(values, restriction) {
var formatted;
formatted = values.map(function(value) {
switch (false) {
case !/\s/.test(value):
return 'spaces';
case !/\/{2,}/g.test(value):
return 'consecutive slashes';
default:
return "'" + value + "'";
}
});
return restriction.prefix + " " + (formatted.join(restriction.conjunction));
};
validator = (function(_this) {
return function(errors, restriction) {
var matched;
matched = _this.name.val().match(restriction.pattern);
if (matched) {
return errors.concat(formatter(matched.reduce(unique, []), restriction));
} else {
return errors;
}
};
})(this);
errors = this.restrictions.reduce(validator, []);
if (errors.length > 0) {
errorMessage = $("<span/>").text(errors.join(', '));
return this.branchNameError.append(errorMessage);
} }
return values;
}; };
formatter = function(values, restriction) {
return NewBranchForm; var formatted;
})(); formatted = values.map(function(value) {
}).call(window); switch (false) {
case !/\s/.test(value):
return 'spaces';
case !/\/{2,}/g.test(value):
return 'consecutive slashes';
default:
return "'" + value + "'";
}
});
return restriction.prefix + " " + (formatted.join(restriction.conjunction));
};
validator = (function(_this) {
return function(errors, restriction) {
var matched;
matched = _this.name.val().match(restriction.pattern);
if (matched) {
return errors.concat(formatter(matched.reduce(unique, []), restriction));
} else {
return errors;
}
};
})(this);
errors = this.restrictions.reduce(validator, []);
if (errors.length > 0) {
errorMessage = $("<span/>").text(errors.join(', '));
return this.branchNameError.append(errorMessage);
}
}
}
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-return-assign, max-len */ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-return-assign, max-len */
(function() { export default class NewCommitForm {
this.NewCommitForm = (function() { constructor(form) {
function NewCommitForm(form) { this.form = form;
this.form = form; this.renderDestination = this.renderDestination.bind(this);
this.renderDestination = this.renderDestination.bind(this); this.branchName = form.find('.js-branch-name');
this.branchName = form.find('.js-branch-name'); this.originalBranch = form.find('.js-original-branch');
this.originalBranch = form.find('.js-original-branch'); this.createMergeRequest = form.find('.js-create-merge-request');
this.createMergeRequest = form.find('.js-create-merge-request'); this.createMergeRequestContainer = form.find('.js-create-merge-request-container');
this.createMergeRequestContainer = form.find('.js-create-merge-request-container'); this.branchName.keyup(this.renderDestination);
this.branchName.keyup(this.renderDestination); this.renderDestination();
this.renderDestination(); }
}
NewCommitForm.prototype.renderDestination = function() { renderDestination() {
var different; var different;
different = this.branchName.val() !== this.originalBranch.val(); different = this.branchName.val() !== this.originalBranch.val();
if (different) { if (different) {
this.createMergeRequestContainer.show(); this.createMergeRequestContainer.show();
if (!this.wasDifferent) { if (!this.wasDifferent) {
this.createMergeRequest.prop('checked', true); this.createMergeRequest.prop('checked', true);
}
} else {
this.createMergeRequestContainer.hide();
this.createMergeRequest.prop('checked', false);
} }
return this.wasDifferent = different; } else {
}; this.createMergeRequestContainer.hide();
this.createMergeRequest.prop('checked', false);
return NewCommitForm; }
})(); return this.wasDifferent = different;
}).call(window); }
}
<script> <script>
import Icon from '../../vue_shared/components/icon.vue';
export default { export default {
computed: { component: {
lockIcon() { Icon,
return gl.utils.spriteIcon('lock');
},
}, },
}; };
</script> </script>
<template> <template>
<div class="disabled-comment text-center"> <div class="disabled-comment text-center">
<span class="issuable-note-warning"> <span class="issuable-note-warning inline">
<span class="icon" v-html="lockIcon"></span> <icon
name="lock"
:size="16"
class="icon">
</icon>
<span>This issue is locked. Only <b>project members</b> can comment.</span> <span>This issue is locked. Only <b>project members</b> can comment.</span>
</span> </span>
</div> </div>
......
...@@ -267,9 +267,11 @@ ...@@ -267,9 +267,11 @@
/> />
<div <div
class="blank-state blank-state-no-icon" class="blank-state-row"
v-if="shouldRenderNoPipelinesMessage"> v-if="shouldRenderNoPipelinesMessage">
<h2 class="blank-state-title js-blank-state-title">No pipelines to show.</h2> <div class="blank-state-center">
<h2 class="blank-state-title js-blank-state-title">No pipelines to show.</h2>
</div>
</div> </div>
<div <div
......
/* eslint-disable func-names, space-before-function-paren, no-var, consistent-return, no-new, prefer-arrow-callback, no-return-assign, one-var, one-var-declaration-per-line, object-shorthand, no-else-return, newline-per-chained-call, no-shadow, vars-on-top, prefer-template, max-len */ /* eslint-disable func-names, space-before-function-paren, no-var, consistent-return, no-new, prefer-arrow-callback, no-return-assign, one-var, one-var-declaration-per-line, object-shorthand, no-else-return, newline-per-chained-call, no-shadow, vars-on-top, prefer-template, max-len */
/* global ProjectSelect */
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
import projectSelect from './project_select';
export default class Project { export default class Project {
constructor() { constructor() {
...@@ -46,7 +46,7 @@ export default class Project { ...@@ -46,7 +46,7 @@ export default class Project {
} }
static projectSelectDropdown () { static projectSelectDropdown () {
new ProjectSelect(); projectSelect();
$('.project-item-select').on('click', e => Project.changeProject($(e.currentTarget).val())); $('.project-item-select').on('click', e => Project.changeProject($(e.currentTarget).val()));
} }
......
/* eslint-disable wrap-iife, func-names, space-before-function-paren, object-shorthand, comma-dangle, one-var, one-var-declaration-per-line, no-restricted-syntax, max-len, no-param-reassign */ export default class ProjectLabelSubscription {
constructor(container) {
this.$container = $(container);
this.$buttons = this.$container.find('.js-subscribe-button');
(function(global) { this.$buttons.on('click', this.toggleSubscription.bind(this));
class ProjectLabelSubscription { }
constructor(container) {
this.$container = $(container);
this.$buttons = this.$container.find('.js-subscribe-button');
this.$buttons.on('click', this.toggleSubscription.bind(this));
}
toggleSubscription(event) { toggleSubscription(event) {
event.preventDefault(); event.preventDefault();
const $btn = $(event.currentTarget); const $btn = $(event.currentTarget);
const $span = $btn.find('span'); const $span = $btn.find('span');
const url = $btn.attr('data-url'); const url = $btn.attr('data-url');
const oldStatus = $btn.attr('data-status'); const oldStatus = $btn.attr('data-status');
$btn.addClass('disabled'); $btn.addClass('disabled');
$span.toggleClass('hidden'); $span.toggleClass('hidden');
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: url url,
}).done(() => { }).done(() => {
let newStatus, newAction; let newStatus;
let newAction;
if (oldStatus === 'unsubscribed') { if (oldStatus === 'unsubscribed') {
[newStatus, newAction] = ['subscribed', 'Unsubscribe']; [newStatus, newAction] = ['subscribed', 'Unsubscribe'];
} else { } else {
[newStatus, newAction] = ['unsubscribed', 'Subscribe']; [newStatus, newAction] = ['unsubscribed', 'Subscribe'];
} }
$span.toggleClass('hidden'); $span.toggleClass('hidden');
$btn.removeClass('disabled'); $btn.removeClass('disabled');
this.$buttons.attr('data-status', newStatus); this.$buttons.attr('data-status', newStatus);
this.$buttons.find('> span').text(newAction); this.$buttons.find('> span').text(newAction);
this.$buttons.map((button) => { this.$buttons.map((button) => {
const $button = $(button); const $button = $(button);
if ($button.attr('data-original-title')) { if ($button.attr('data-original-title')) {
$button.tooltip('hide').attr('data-original-title', newAction).tooltip('fixTitle'); $button.tooltip('hide').attr('data-original-title', newAction).tooltip('fixTitle');
} }
return button; return button;
});
}); });
} });
} }
}
global.ProjectLabelSubscription = ProjectLabelSubscription;
})(window.gl || (window.gl = {}));
This diff is collapsed.
...@@ -2,79 +2,73 @@ ...@@ -2,79 +2,73 @@
import Api from './api'; import Api from './api';
import ProjectSelectComboButton from './project_select_combo_button'; import ProjectSelectComboButton from './project_select_combo_button';
(function () { export default function projectSelect() {
this.ProjectSelect = (function () { $('.ajax-project-select').each(function(i, select) {
function ProjectSelect() { var placeholder;
$('.ajax-project-select').each(function(i, select) { const simpleFilter = $(select).data('simple-filter') || false;
var placeholder; this.groupId = $(select).data('group-id');
const simpleFilter = $(select).data('simple-filter') || false; this.includeGroups = $(select).data('include-groups');
this.groupId = $(select).data('group-id'); this.allProjects = $(select).data('all-projects') || false;
this.includeGroups = $(select).data('include-groups'); this.orderBy = $(select).data('order-by') || 'id';
this.allProjects = $(select).data('all-projects') || false; this.withIssuesEnabled = $(select).data('with-issues-enabled');
this.orderBy = $(select).data('order-by') || 'id'; this.withMergeRequestsEnabled = $(select).data('with-merge-requests-enabled');
this.withIssuesEnabled = $(select).data('with-issues-enabled');
this.withMergeRequestsEnabled = $(select).data('with-merge-requests-enabled');
placeholder = "Search for project"; placeholder = "Search for project";
if (this.includeGroups) { if (this.includeGroups) {
placeholder += " or group"; placeholder += " or group";
} }
$(select).select2({ $(select).select2({
placeholder: placeholder, placeholder: placeholder,
minimumInputLength: 0, minimumInputLength: 0,
query: (function (_this) { query: (function (_this) {
return function (query) { return function (query) {
var finalCallback, projectsCallback; var finalCallback, projectsCallback;
finalCallback = function (projects) { finalCallback = function (projects) {
var data;
data = {
results: projects
};
return query.callback(data);
};
if (_this.includeGroups) {
projectsCallback = function (projects) {
var groupsCallback;
groupsCallback = function (groups) {
var data; var data;
data = { data = groups.concat(projects);
results: projects return finalCallback(data);
};
return query.callback(data);
}; };
if (_this.includeGroups) { return Api.groups(query.term, {}, groupsCallback);
projectsCallback = function (projects) {
var groupsCallback;
groupsCallback = function (groups) {
var data;
data = groups.concat(projects);
return finalCallback(data);
};
return Api.groups(query.term, {}, groupsCallback);
};
} else {
projectsCallback = finalCallback;
}
if (_this.groupId) {
return Api.groupProjects(_this.groupId, query.term, projectsCallback);
} else {
return Api.projects(query.term, {
order_by: _this.orderBy,
with_issues_enabled: _this.withIssuesEnabled,
with_merge_requests_enabled: _this.withMergeRequestsEnabled,
membership: !_this.allProjects,
}, projectsCallback);
}
}; };
})(this), } else {
id: function(project) { projectsCallback = finalCallback;
if (simpleFilter) return project.id; }
return JSON.stringify({ if (_this.groupId) {
name: project.name, return Api.groupProjects(_this.groupId, query.term, projectsCallback);
url: project.web_url, } else {
}); return Api.projects(query.term, {
}, order_by: _this.orderBy,
text: function (project) { with_issues_enabled: _this.withIssuesEnabled,
return project.name_with_namespace || project.name; with_merge_requests_enabled: _this.withMergeRequestsEnabled,
}, membership: !_this.allProjects,
dropdownCssClass: "ajax-project-dropdown" }, projectsCallback);
}
};
})(this),
id: function(project) {
if (simpleFilter) return project.id;
return JSON.stringify({
name: project.name,
url: project.web_url,
}); });
if (simpleFilter) return select; },
return new ProjectSelectComboButton(select); text: function (project) {
}); return project.name_with_namespace || project.name;
} },
dropdownCssClass: "ajax-project-dropdown"
return ProjectSelect; });
})(); if (simpleFilter) return select;
}).call(window); return new ProjectSelectComboButton(select);
});
}
/* eslint-disable func-names, space-before-function-paren, wrap-iife */
(function() {
this.ProjectShow = (function() {
function ProjectShow() {}
return ProjectShow;
})();
}).call(window);
// I kept class for future
(() => {
const HIDDEN_VALUE_TEXT = '******';
class ProjectVariables { const HIDDEN_VALUE_TEXT = '******';
constructor() {
this.$revealBtn = $('.js-btn-toggle-reveal-values'); export default class ProjectVariables {
this.$revealBtn.on('click', this.toggleRevealState.bind(this)); constructor() {
} this.$revealBtn = $('.js-btn-toggle-reveal-values');
this.$revealBtn.on('click', this.toggleRevealState.bind(this));
}
toggleRevealState(e) { toggleRevealState(e) {
e.preventDefault(); e.preventDefault();
const oldStatus = this.$revealBtn.attr('data-status'); const oldStatus = this.$revealBtn.attr('data-status');
let newStatus = 'hidden'; let newStatus = 'hidden';
let newAction = 'Reveal Values'; let newAction = 'Reveal Values';
if (oldStatus === 'hidden') { if (oldStatus === 'hidden') {
newStatus = 'revealed'; newStatus = 'revealed';
newAction = 'Hide Values'; newAction = 'Hide Values';
} }
this.$revealBtn.attr('data-status', newStatus); this.$revealBtn.attr('data-status', newStatus);
const $variables = $('.variable-value'); const $variables = $('.variable-value');
$variables.each((_, variable) => { $variables.each((_, variable) => {
const $variable = $(variable); const $variable = $(variable);
let newText = HIDDEN_VALUE_TEXT; let newText = HIDDEN_VALUE_TEXT;
if (newStatus === 'revealed') { if (newStatus === 'revealed') {
newText = $variable.attr('data-value'); newText = $variable.attr('data-value');
} }
$variable.text(newText); $variable.text(newText);
}); });
this.$revealBtn.text(newAction); this.$revealBtn.text(newAction);
}
} }
}
window.gl = window.gl || {};
window.gl.ProjectVariables = ProjectVariables;
})();
...@@ -2,12 +2,13 @@ ...@@ -2,12 +2,13 @@
// Render Gitlab flavoured Markdown // Render Gitlab flavoured Markdown
// //
// Delegates to syntax highlight and render math // Delegates to syntax highlight and render math & mermaid diagrams.
// //
(function() { (function() {
$.fn.renderGFM = function() { $.fn.renderGFM = function() {
this.find('.js-syntax-highlight').syntaxHighlight(); this.find('.js-syntax-highlight').syntaxHighlight();
this.find('.js-render-math').renderMath(); this.find('.js-render-math').renderMath();
this.find('.js-render-mermaid').renderMermaid();
return this; return this;
}; };
......
// Renders diagrams and flowcharts from text using Mermaid in any element with the
// `js-render-mermaid` class.
//
// Example markup:
//
// <pre class="js-render-mermaid">
// graph TD;
// A-- > B;
// A-- > C;
// B-- > D;
// C-- > D;
// </pre>
//
import Flash from './flash';
$.fn.renderMermaid = function renderMermaid() {
if (this.length === 0) return;
import(/* webpackChunkName: 'mermaid' */ 'blackst0ne-mermaid').then((mermaid) => {
mermaid.initialize({
loadOnStart: false,
theme: 'neutral',
});
mermaid.init(undefined, this);
}).catch((err) => {
Flash(`Can't load mermaid module: ${err}`);
});
};
<script> <script>
import Flash from '../../../flash'; import Flash from '../../../flash';
import editForm from './edit_form.vue'; import editForm from './edit_form.vue';
import Icon from '../../../vue_shared/components/icon.vue';
export default { export default {
components: { components: {
editForm, editForm,
Icon,
}, },
props: { props: {
isConfidential: { isConfidential: {
...@@ -26,11 +28,8 @@ export default { ...@@ -26,11 +28,8 @@ export default {
}; };
}, },
computed: { computed: {
faEye() { confidentialityIcon() {
const eye = this.isConfidential ? 'fa-eye-slash' : 'fa-eye'; return this.isConfidential ? 'eye-slash' : 'eye';
return {
[eye]: true,
};
}, },
}, },
methods: { methods: {
...@@ -49,7 +48,11 @@ export default { ...@@ -49,7 +48,11 @@ export default {
<template> <template>
<div class="block issuable-sidebar-item confidentiality"> <div class="block issuable-sidebar-item confidentiality">
<div class="sidebar-collapsed-icon"> <div class="sidebar-collapsed-icon">
<i class="fa" :class="faEye" aria-hidden="true"></i> <icon
:name="confidentialityIcon"
:size="16"
aria-hidden="true">
</icon>
</div> </div>
<div class="title hide-collapsed"> <div class="title hide-collapsed">
Confidentiality Confidentiality
...@@ -70,11 +73,21 @@ export default { ...@@ -70,11 +73,21 @@ export default {
:update-confidential-attribute="updateConfidentialAttribute" :update-confidential-attribute="updateConfidentialAttribute"
/> />
<div v-if="!isConfidential" class="no-value sidebar-item-value"> <div v-if="!isConfidential" class="no-value sidebar-item-value">
<i class="fa fa-eye sidebar-item-icon"></i> <icon
name="eye"
:size="16"
aria-hidden="true"
class="sidebar-item-icon inline">
</icon>
Not confidential Not confidential
</div> </div>
<div v-else class="value sidebar-item-value hide-collapsed"> <div v-else class="value sidebar-item-value hide-collapsed">
<i aria-hidden="true" class="fa fa-eye-slash sidebar-item-icon is-active"></i> <icon
name="eye-slash"
:size="16"
aria-hidden="true"
class="sidebar-item-icon inline is-active">
</icon>
This issue is confidential This issue is confidential
</div> </div>
</div> </div>
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
/* global Flash */ /* global Flash */
import editForm from './edit_form.vue'; import editForm from './edit_form.vue';
import issuableMixin from '../../../vue_shared/mixins/issuable'; import issuableMixin from '../../../vue_shared/mixins/issuable';
import Icon from '../../../vue_shared/components/icon.vue';
export default { export default {
props: { props: {
...@@ -35,11 +36,12 @@ export default { ...@@ -35,11 +36,12 @@ export default {
components: { components: {
editForm, editForm,
Icon,
}, },
computed: { computed: {
lockIconClass() { lockIcon() {
return this.isLocked ? 'fa-lock' : 'fa-unlock'; return this.isLocked ? 'lock' : 'lock-open';
}, },
isLockDialogOpen() { isLockDialogOpen() {
...@@ -66,11 +68,12 @@ export default { ...@@ -66,11 +68,12 @@ export default {
<template> <template>
<div class="block issuable-sidebar-item lock"> <div class="block issuable-sidebar-item lock">
<div class="sidebar-collapsed-icon"> <div class="sidebar-collapsed-icon">
<i <icon
class="fa" :name="lockIcon"
:class="lockIconClass" :size="16"
aria-hidden="true" aria-hidden="true"
></i> class="sidebar-item-icon is-active">
</icon>
</div> </div>
<div class="title hide-collapsed"> <div class="title hide-collapsed">
...@@ -98,10 +101,12 @@ export default { ...@@ -98,10 +101,12 @@ export default {
v-if="isLocked" v-if="isLocked"
class="value sidebar-item-value" class="value sidebar-item-value"
> >
<i <icon
name="lock"
:size="16"
aria-hidden="true" aria-hidden="true"
class="fa fa-lock sidebar-item-icon is-active" class="sidebar-item-icon inline is-active">
></i> </icon>
{{ __('Locked') }} {{ __('Locked') }}
</div> </div>
...@@ -109,10 +114,12 @@ export default { ...@@ -109,10 +114,12 @@ export default {
v-else v-else
class="no-value sidebar-item-value hide-collapsed" class="no-value sidebar-item-value hide-collapsed"
> >
<i <icon
name="lock-open"
:size="16"
aria-hidden="true" aria-hidden="true"
class="fa fa-unlock sidebar-item-icon" class="sidebar-item-icon inline">
></i> </icon>
{{ __('Unlocked') }} {{ __('Unlocked') }}
</div> </div>
</div> </div>
......
...@@ -3,6 +3,7 @@ import Store from '../../stores/sidebar_store'; ...@@ -3,6 +3,7 @@ import Store from '../../stores/sidebar_store';
import Mediator from '../../sidebar_mediator'; import Mediator from '../../sidebar_mediator';
import eventHub from '../../event_hub'; import eventHub from '../../event_hub';
import Flash from '../../../flash'; import Flash from '../../../flash';
import { __ } from '../../../locale';
import subscriptions from './subscriptions.vue'; import subscriptions from './subscriptions.vue';
export default { export default {
...@@ -21,7 +22,7 @@ export default { ...@@ -21,7 +22,7 @@ export default {
onToggleSubscription() { onToggleSubscription() {
this.mediator.toggleSubscription() this.mediator.toggleSubscription()
.catch(() => { .catch(() => {
Flash('Error occurred when toggling the notification subscription'); Flash(__('Error occurred when toggling the notification subscription'));
}); });
}, },
}, },
......
...@@ -14,6 +14,10 @@ export default { ...@@ -14,6 +14,10 @@ export default {
type: Boolean, type: Boolean,
required: false, required: false,
}, },
id: {
type: Number,
required: false,
},
}, },
components: { components: {
loadingButton, loadingButton,
...@@ -32,7 +36,7 @@ export default { ...@@ -32,7 +36,7 @@ export default {
}, },
methods: { methods: {
toggleSubscription() { toggleSubscription() {
eventHub.$emit('toggleSubscription'); eventHub.$emit('toggleSubscription', this.id);
}, },
}, },
}; };
......
class Subscription {
constructor(containerElm) {
this.containerElm = containerElm;
const subscribeButton = containerElm.querySelector('.js-subscribe-button');
if (subscribeButton) {
// remove class so we don't bind twice
subscribeButton.classList.remove('js-subscribe-button');
subscribeButton.addEventListener('click', this.toggleSubscription.bind(this));
}
}
toggleSubscription(event) {
const button = event.currentTarget;
const buttonSpan = button.querySelector('span');
if (!buttonSpan || button.classList.contains('disabled')) {
return;
}
button.classList.add('disabled');
const isSubscribed = buttonSpan.innerHTML.trim().toLowerCase() !== 'subscribe';
const toggleActionUrl = this.containerElm.dataset.url;
$.post(toggleActionUrl, () => {
button.classList.remove('disabled');
// hack to allow this to work with the issue boards Vue object
if (document.querySelector('html').classList.contains('issue-boards-page')) {
gl.issueBoards.boardStoreIssueSet(
'subscribed',
!gl.issueBoards.BoardsStore.detail.issue.subscribed,
);
} else {
buttonSpan.innerHTML = isSubscribed ? 'Subscribe' : 'Unsubscribe';
}
});
}
static bindAll(selector) {
[].forEach.call(document.querySelectorAll(selector), elm => new Subscription(elm));
}
}
window.gl = window.gl || {};
window.gl.Subscription = Subscription;
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, quotes, object-shorthand, no-unused-vars, no-shadow, one-var, one-var-declaration-per-line, comma-dangle, max-len */ export default function subscriptionSelect() {
$('.js-subscription-event').each((i, element) => {
const fieldName = $(element).data('field-name');
class SubscriptionSelect { return $(element).glDropdown({
constructor() { selectable: true,
$('.js-subscription-event').each(function(i, el) { fieldName,
var fieldName; toggleLabel(selected, el, instance) {
fieldName = $(el).data("field-name"); let label = 'Subscription';
return $(el).glDropdown({ const $item = instance.dropdown.find('.is-active');
selectable: true, if ($item.length) {
fieldName: fieldName, label = $item.text();
toggleLabel: (function(_this) {
return function(selected, el, instance) {
var $item, label;
label = 'Subscription';
$item = instance.dropdown.find('.is-active');
if ($item.length) {
label = $item.text();
}
return label;
};
})(this),
clicked: function(options) {
return options.e.preventDefault();
},
id: function(obj, el) {
return $(el).data("id");
} }
}); return label;
},
clicked(options) {
return options.e.preventDefault();
},
id(obj, el) {
return $(el).data('id');
},
}); });
} });
} }
window.SubscriptionSelect = SubscriptionSelect;
...@@ -6,10 +6,9 @@ ...@@ -6,10 +6,9 @@
Sample configuration: Sample configuration:
<icon <icon
:img-src="userAvatarSrc" name="retry"
:img-alt="tooltipText" :size="32"
:tooltip-text="tooltipText" css-classes="top"
tooltip-placement="top"
/> />
*/ */
......
<script> <script>
import Icon from '../../../vue_shared/components/icon.vue';
export default { export default {
props: { props: {
isLocked: { isLocked: {
...@@ -14,12 +16,16 @@ ...@@ -14,12 +16,16 @@
}, },
}, },
components: {
Icon,
},
computed: { computed: {
iconClass() { warningIcon() {
return { if (this.isConfidential) return 'eye-slash';
'fa-eye-slash': this.isConfidential, if (this.isLocked) return 'lock';
'fa-lock': this.isLocked,
}; return '';
}, },
isLockedAndConfidential() { isLockedAndConfidential() {
...@@ -30,12 +36,13 @@ ...@@ -30,12 +36,13 @@
</script> </script>
<template> <template>
<div class="issuable-note-warning"> <div class="issuable-note-warning">
<i <icon
aria-hidden="true" :name="warningIcon"
class="fa icon" :size="16"
:class="iconClass" class="icon inline"
v-if="!isLockedAndConfidential" aria-hidden="true"
></i> v-if="!isLockedAndConfidential">
</icon>
<span v-if="isLockedAndConfidential"> <span v-if="isLockedAndConfidential">
{{ __('This issue is confidential and locked.') }} {{ __('This issue is confidential and locked.') }}
......
...@@ -25,6 +25,11 @@ ...@@ -25,6 +25,11 @@
type: String, type: String,
required: false, required: false,
}, },
canAttachFile: {
type: Boolean,
required: false,
default: true,
},
}, },
data() { data() {
return { return {
...@@ -129,6 +134,7 @@ ...@@ -129,6 +134,7 @@
<markdown-toolbar <markdown-toolbar
:markdown-docs-path="markdownDocsPath" :markdown-docs-path="markdownDocsPath"
:quick-actions-docs-path="quickActionsDocsPath" :quick-actions-docs-path="quickActionsDocsPath"
:can-attach-file="canAttachFile"
/> />
</div> </div>
</div> </div>
......
...@@ -50,7 +50,9 @@ ...@@ -50,7 +50,9 @@
<template> <template>
<div class="md-header"> <div class="md-header">
<ul class="nav-links clearfix"> <ul class="nav-links clearfix">
<li :class="{ active: !previewMarkdown }"> <li
class="md-header-tab"
:class="{ active: !previewMarkdown }">
<a <a
class="js-write-link" class="js-write-link"
href="#md-write-holder" href="#md-write-holder"
...@@ -59,7 +61,9 @@ ...@@ -59,7 +61,9 @@
Write Write
</a> </a>
</li> </li>
<li :class="{ active: previewMarkdown }"> <li
class="md-header-tab"
:class="{ active: previewMarkdown }">
<a <a
class="js-preview-link" class="js-preview-link"
href="#md-preview-holder" href="#md-preview-holder"
...@@ -68,56 +72,52 @@ ...@@ -68,56 +72,52 @@
Preview Preview
</a> </a>
</li> </li>
<li class="pull-right"> <li class="md-header-toolbar">
<div class="toolbar-group"> <toolbar-button
<toolbar-button tag="**"
tag="**" button-title="Add bold text"
button-title="Add bold text" icon="bold" />
icon="bold" /> <toolbar-button
<toolbar-button tag="*"
tag="*" button-title="Add italic text"
button-title="Add italic text" icon="italic" />
icon="italic" /> <toolbar-button
<toolbar-button tag="> "
tag="> " :prepend="true"
:prepend="true" button-title="Insert a quote"
button-title="Insert a quote" icon="quote" />
icon="quote" /> <toolbar-button
<toolbar-button tag="`"
tag="`" tag-block="```"
tag-block="```" button-title="Insert code"
button-title="Insert code" icon="code" />
icon="code" /> <toolbar-button
<toolbar-button tag="* "
tag="* " :prepend="true"
:prepend="true" button-title="Add a bullet list"
button-title="Add a bullet list" icon="list-bulleted" />
icon="list-bulleted" /> <toolbar-button
<toolbar-button tag="1. "
tag="1. " :prepend="true"
:prepend="true" button-title="Add a numbered list"
button-title="Add a numbered list" icon="list-numbered" />
icon="list-numbered" /> <toolbar-button
<toolbar-button tag="* [ ] "
tag="* [ ] " :prepend="true"
:prepend="true" button-title="Add a task list"
button-title="Add a task list" icon="task-done" />
icon="task-done" /> <button
</div> v-tooltip
<div class="toolbar-group"> aria-label="Go full screen"
<button class="toolbar-btn toolbar-fullscreen-btn js-zen-enter"
v-tooltip data-container="body"
aria-label="Go full screen" tabindex="-1"
class="toolbar-btn js-zen-enter" title="Go full screen"
data-container="body" type="button">
tabindex="-1" <icon
title="Go full screen" name="screen-full">
type="button"> </icon>
<icon </button>
name="screen-full">
</icon>
</button>
</div>
</li> </li>
</ul> </ul>
</div> </div>
......
...@@ -9,6 +9,11 @@ ...@@ -9,6 +9,11 @@
type: String, type: String,
required: false, required: false,
}, },
canAttachFile: {
type: Boolean,
required: false,
default: true,
},
}, },
}; };
</script> </script>
...@@ -41,7 +46,10 @@ ...@@ -41,7 +46,10 @@
are supported are supported
</template> </template>
</div> </div>
<span class="uploading-container"> <span
v-if="canAttachFile"
class="uploading-container"
>
<span class="uploading-progress-container hide"> <span class="uploading-progress-container hide">
<i <i
class="fa fa-file-image-o toolbar-button-icon" class="fa fa-file-image-o toolbar-button-icon"
......
...@@ -40,7 +40,7 @@ ...@@ -40,7 +40,7 @@
<button <button
v-tooltip v-tooltip
type="button" type="button"
class="toolbar-btn js-md hidden-xs" class="toolbar-btn js-md"
tabindex="-1" tabindex="-1"
data-container="body" data-container="body"
:data-md-tag="tag" :data-md-tag="tag"
......
...@@ -56,6 +56,12 @@ ...@@ -56,6 +56,12 @@
} }
} }
.blank-state-center {
padding-top: 20px;
padding-bottom: 20px;
text-align: center;
}
.blank-state { .blank-state {
padding: 20px; padding: 20px;
border: 1px solid $border-color; border: 1px solid $border-color;
...@@ -66,7 +72,10 @@ ...@@ -66,7 +72,10 @@
align-items: center; align-items: center;
padding: 50px 30px; padding: 50px 30px;
} }
}
.blank-state,
.blank-state-center {
.blank-state-icon { .blank-state-icon {
svg { svg {
display: block; display: block;
......
...@@ -2,7 +2,9 @@ ...@@ -2,7 +2,9 @@
.cgray { color: $common-gray; } .cgray { color: $common-gray; }
.clgray { color: $common-gray-light; } .clgray { color: $common-gray-light; }
.cred { color: $common-red; } .cred { color: $common-red; }
svg.cred { fill: $common-red; }
.cgreen { color: $common-green; } .cgreen { color: $common-green; }
svg.cgreen { fill: $common-green; }
.cdark { color: $common-gray-dark; } .cdark { color: $common-gray-dark; }
.text-secondary { .text-secondary {
color: $gl-text-color-secondary; color: $gl-text-color-secondary;
...@@ -428,6 +430,7 @@ img.emoji { ...@@ -428,6 +430,7 @@ img.emoji {
/** COMMON CLASSES **/ /** COMMON CLASSES **/
.prepend-top-0 { margin-top: 0; } .prepend-top-0 { margin-top: 0; }
.prepend-top-5 { margin-top: 5px; } .prepend-top-5 { margin-top: 5px; }
.prepend-top-8 { margin-top: $grid-size; }
.prepend-top-10 { margin-top: 10px; } .prepend-top-10 { margin-top: 10px; }
.prepend-top-15 { margin-top: 15px; } .prepend-top-15 { margin-top: 15px; }
.prepend-top-default { margin-top: $gl-padding !important; } .prepend-top-default { margin-top: $gl-padding !important; }
......
...@@ -40,12 +40,6 @@ ...@@ -40,12 +40,6 @@
a:hover { a:hover {
background-color: $link-hover-background; background-color: $link-hover-background;
color: $gl-text-color; color: $gl-text-color;
.settings-avatar {
svg {
fill: $gl-text-color;
}
}
} }
.avatar-container { .avatar-container {
...@@ -138,10 +132,6 @@ ...@@ -138,10 +132,6 @@
color: $gl-text-color-secondary; color: $gl-text-color-secondary;
} }
svg {
fill: $gl-text-color-secondary;
}
.nav-item-name { .nav-item-name {
flex: 1; flex: 1;
} }
...@@ -224,10 +214,6 @@ ...@@ -224,10 +214,6 @@
&:hover { &:hover {
color: $gl-text-color; color: $gl-text-color;
svg {
fill: $gl-text-color;
}
} }
} }
...@@ -338,7 +324,6 @@ ...@@ -338,7 +324,6 @@
align-items: center; align-items: center;
svg { svg {
fill: $gl-text-color-secondary;
margin-right: 8px; margin-right: 8px;
} }
...@@ -349,10 +334,6 @@ ...@@ -349,10 +334,6 @@
&:hover { &:hover {
background-color: $border-color; background-color: $border-color;
color: $gl-text-color; color: $gl-text-color;
svg {
fill: $gl-text-color;
}
} }
} }
......
...@@ -305,16 +305,11 @@ ...@@ -305,16 +305,11 @@
color: $gl-text-color; color: $gl-text-color;
border-color: $dropdown-input-focus-border; border-color: $dropdown-input-focus-border;
outline: none; outline: none;
svg {
fill: $gl-text-color;
}
} }
svg { svg {
height: 14px; height: 14px;
width: 14px; width: 14px;
fill: $gl-text-color-secondary;
vertical-align: middle; vertical-align: middle;
} }
......
...@@ -30,10 +30,6 @@ ...@@ -30,10 +30,6 @@
&.dropdown.open > a { &.dropdown.open > a {
color: $color-900; color: $color-900;
background-color: $color-alternate; background-color: $color-alternate;
svg {
fill: currentColor;
}
} }
&.line-separator { &.line-separator {
...@@ -51,10 +47,6 @@ ...@@ -51,10 +47,6 @@
color: $color-200; color: $color-200;
> a { > a {
svg {
fill: $color-200;
}
&.header-user-dropdown-toggle { &.header-user-dropdown-toggle {
.header-user-avatar { .header-user-avatar {
border-color: $color-200; border-color: $color-200;
......
...@@ -235,10 +235,6 @@ ...@@ -235,10 +235,6 @@
opacity: 1; opacity: 1;
color: $white-light; color: $white-light;
svg {
fill: currentColor;
}
&.header-user-dropdown-toggle .header-user-avatar { &.header-user-dropdown-toggle .header-user-avatar {
border-color: $white-light; border-color: $white-light;
} }
...@@ -269,14 +265,6 @@ ...@@ -269,14 +265,6 @@
font-size: 20px; font-size: 20px;
} }
} }
&.active > a,
&.dropdown.open > a {
svg {
fill: currentColor;
}
}
} }
} }
} }
...@@ -289,10 +277,6 @@ ...@@ -289,10 +277,6 @@
text-decoration: none; text-decoration: none;
outline: 0; outline: 0;
color: $white-light; color: $white-light;
svg {
fill: currentColor;
}
} }
> a { > a {
...@@ -307,10 +291,6 @@ ...@@ -307,10 +291,6 @@
border-radius: $border-radius-default; border-radius: $border-radius-default;
height: 32px; height: 32px;
font-weight: $gl-font-weight-bold; font-weight: $gl-font-weight-bold;
svg {
fill: currentColor;
}
} }
&.line-separator { &.line-separator {
......
.ci-status-icon-success, .ci-status-icon-success,
.ci-status-icon-passed { .ci-status-icon-passed {
color: $green-500; color: $green-500;
svg {
fill: $green-500;
}
} }
.ci-status-icon-failed { .ci-status-icon-failed {
color: $gl-danger; color: $gl-danger;
svg {
fill: $gl-danger;
}
} }
.ci-status-icon-pending, .ci-status-icon-pending,
.ci-status-icon-failed_with_warnings, .ci-status-icon-failed_with_warnings,
.ci-status-icon-success_with_warnings { .ci-status-icon-success_with_warnings {
color: $orange-500; color: $orange-500;
svg {
fill: $orange-500;
}
} }
.ci-status-icon-running { .ci-status-icon-running {
color: $blue-400; color: $blue-400;
svg {
fill: $blue-400;
}
} }
.ci-status-icon-canceled, .ci-status-icon-canceled,
.ci-status-icon-disabled, .ci-status-icon-disabled,
.ci-status-icon-not-found { .ci-status-icon-not-found {
color: $gl-text-color; color: $gl-text-color;
svg {
fill: $gl-text-color;
}
} }
.ci-status-icon-created, .ci-status-icon-created,
.ci-status-icon-skipped { .ci-status-icon-skipped {
color: $gray-darkest; color: $gray-darkest;
svg {
fill: $gray-darkest;
}
} }
.ci-status-icon-manual { .ci-status-icon-manual {
color: $gl-text-color; color: $gl-text-color;
svg {
fill: $gl-text-color;
}
} }
.icon-link { .icon-link {
......
...@@ -27,6 +27,8 @@ ...@@ -27,6 +27,8 @@
} }
svg { svg {
fill: currentColor;
&.s8 { @include svg-size(8px); } &.s8 { @include svg-size(8px); }
&.s12 { @include svg-size(12px); } &.s12 { @include svg-size(12px); }
&.s16 { @include svg-size(16px); } &.s16 { @include svg-size(16px); }
......
...@@ -57,6 +57,7 @@ ...@@ -57,6 +57,7 @@
.md-header { .md-header {
.nav-links { .nav-links {
a { a {
width: 100%;
padding-top: 0; padding-top: 0;
line-height: 19px; line-height: 19px;
...@@ -72,6 +73,28 @@ ...@@ -72,6 +73,28 @@
} }
} }
.md-header-tab {
@media(max-width: $screen-xs-max) {
flex: 1;
width: 100%;
border-bottom: 1px solid $border-color;
text-align: center;
}
}
.md-header-toolbar {
margin-left: auto;
@media(max-width: $screen-xs-max) {
flex: none;
display: flex;
justify-content: center;
width: 100%;
padding-top: $gl-padding-top;
padding-bottom: $gl-padding-top;
}
}
.referenced-users { .referenced-users {
color: $gl-text-color; color: $gl-text-color;
padding-top: 10px; padding-top: 10px;
...@@ -126,16 +149,6 @@ ...@@ -126,16 +149,6 @@
} }
} }
.toolbar-group {
float: left;
margin-right: -5px;
margin-left: $gl-padding;
&:first-child {
margin-left: 0;
}
}
.toolbar-btn { .toolbar-btn {
float: left; float: left;
padding: 0 7px; padding: 0 7px;
...@@ -158,6 +171,16 @@ ...@@ -158,6 +171,16 @@
} }
} }
.toolbar-fullscreen-btn {
margin-left: $gl-padding;
margin-right: -5px;
@media(max-width: $screen-xs-max) {
margin-left: 0;
margin-right: 0;
}
}
.atwho-view { .atwho-view {
overflow-y: auto; overflow-y: auto;
overflow-x: hidden; overflow-x: hidden;
......
...@@ -130,14 +130,6 @@ ...@@ -130,14 +130,6 @@
background-color: $color-light; background-color: $color-light;
color: $color-dark; color: $color-dark;
border-color: $color-dark; border-color: $color-dark;
svg {
fill: $color-dark;
}
}
svg {
fill: $color-main;
} }
} }
......
...@@ -57,15 +57,7 @@ ...@@ -57,15 +57,7 @@
padding: 5px; padding: 5px;
font-size: 36px; font-size: 36px;
svg {
fill: $gl-text-color;
}
&:hover { &:hover {
color: $black; color: $black;
svg {
fill: $black;
}
} }
} }
...@@ -49,6 +49,7 @@ ...@@ -49,6 +49,7 @@
font-size: 12px; font-size: 12px;
border-radius: 0; border-radius: 0;
border: 0; border: 0;
padding: $grid-size;
.bash { .bash {
display: block; display: block;
...@@ -57,14 +58,13 @@ ...@@ -57,14 +58,13 @@
.top-bar { .top-bar {
height: 35px; height: 35px;
display: flex;
justify-content: flex-end;
background: $gray-light; background: $gray-light;
border: 1px solid $border-color; border: 1px solid $border-color;
color: $gl-text-color; color: $gl-text-color;
position: sticky; position: sticky;
position: -webkit-sticky; position: -webkit-sticky;
top: $header-height; top: $header-height;
padding: $grid-size;
&.affix { &.affix {
top: $header-height; top: $header-height;
...@@ -90,9 +90,6 @@ ...@@ -90,9 +90,6 @@
} }
.truncated-info { .truncated-info {
margin: 0 auto;
align-self: center;
.truncated-info-size { .truncated-info-size {
margin: 0 5px; margin: 0 5px;
} }
...@@ -118,7 +115,11 @@ ...@@ -118,7 +115,11 @@
.controllers-buttons { .controllers-buttons {
color: $gl-text-color; color: $gl-text-color;
margin: 0 10px; margin: 0 $grid-size;
&:last-child {
margin-right: 0;
}
} }
.btn-scroll.animate { .btn-scroll.animate {
......
...@@ -628,21 +628,46 @@ ...@@ -628,21 +628,46 @@
} }
.diff-file-changes { .diff-file-changes {
width: 450px; max-width: 560px;
width: 100%;
z-index: 150; z-index: 150;
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
left: $gl-padding; left: $gl-padding;
} }
a { .diff-changed-file {
display: flex;
padding-top: 8px; padding-top: 8px;
padding-bottom: 8px; padding-bottom: 8px;
min-width: 0;
} }
.diff-changed-file { .diff-file-changed-icon {
margin-top: 2px;
}
.diff-changed-file-content {
display: flex; display: flex;
align-items: center; flex-direction: column;
min-width: 0;
}
.diff-changed-file-name,
.diff-changed-file-path {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.diff-changed-file-path {
direction: rtl;
color: $gl-text-color-tertiary;
}
.diff-changed-stats {
margin-left: auto;
white-space: nowrap;
} }
} }
......
.documentation-index {
h1 {
margin: 0;
}
h2 {
font-size: 20px;
}
li {
line-height: 24px;
color: $document-index-color;
a {
margin-right: 3px;
}
}
}
.shortcut-mappings { .shortcut-mappings {
font-size: 12px; font-size: 12px;
color: $help-shortcut-mapping-color; color: $help-shortcut-mapping-color;
......
...@@ -6,28 +6,20 @@ ...@@ -6,28 +6,20 @@
} }
.issuable-warning-icon { .issuable-warning-icon {
color: $orange-600;
background-color: $orange-100; background-color: $orange-100;
border-radius: $border-radius-default; border-radius: $border-radius-default;
padding: 5px;
margin: 0 $btn-side-margin 0 0; margin: 0 $btn-side-margin 0 0;
width: $issuable-warning-size; width: $issuable-warning-size;
height: $issuable-warning-size; height: $issuable-warning-size;
text-align: center; text-align: center;
&:first-of-type { .icon {
margin-right: $issuable-warning-icon-margin; fill: $orange-600;
vertical-align: text-bottom;
} }
}
.sidebar-item-icon { &:first-of-type {
border-radius: $border-radius-default; margin-right: $issuable-warning-icon-margin;
padding: 5px;
margin: 0 3px 0 -4px;
&.is-active {
color: $orange-600;
background-color: $orange-50;
} }
} }
......
...@@ -113,6 +113,8 @@ ...@@ -113,6 +113,8 @@
.icon { .icon {
margin-right: $issuable-warning-icon-margin; margin-right: $issuable-warning-icon-margin;
vertical-align: text-bottom;
fill: $orange-600;
} }
+ .md-area { + .md-area {
...@@ -137,12 +139,24 @@ ...@@ -137,12 +139,24 @@
} }
} }
.sidebar-item-value { .sidebar-item-icon {
.fa { border-radius: $border-radius-default;
background-color: inherit; margin: 0 3px 0 -4px;
vertical-align: middle;
&.is-active {
fill: $orange-600;
} }
} }
.sidebar-collapsed-icon .sidebar-item-icon {
margin: 0;
}
.sidebar-item-value .sidebar-item-icon {
fill: $theme-gray-700;
}
.sidebar-item-warning-message { .sidebar-item-warning-message {
line-height: 1.5; line-height: 1.5;
padding: 16px; padding: 16px;
......
...@@ -547,10 +547,6 @@ ul.notes { ...@@ -547,10 +547,6 @@ ul.notes {
width: 16px; width: 16px;
top: 0; top: 0;
vertical-align: text-top; vertical-align: text-top;
path {
fill: currentColor;
}
} }
.award-control-icon-positive, .award-control-icon-positive,
...@@ -570,10 +566,6 @@ ul.notes { ...@@ -570,10 +566,6 @@ ul.notes {
.link-highlight { .link-highlight {
color: $gl-link-color; color: $gl-link-color;
fill: $gl-link-color; fill: $gl-link-color;
svg {
fill: $gl-link-color;
}
} }
.award-control-icon-neutral { .award-control-icon-neutral {
......
...@@ -55,10 +55,6 @@ ...@@ -55,10 +55,6 @@
&:not(span):hover { &:not(span):hover {
background-color: rgba($gl-text-color-secondary, .07); background-color: rgba($gl-text-color-secondary, .07);
} }
svg {
fill: $gl-text-color-secondary;
}
} }
} }
......
...@@ -11,8 +11,7 @@ class ApplicationController < ActionController::Base ...@@ -11,8 +11,7 @@ class ApplicationController < ActionController::Base
include EnforcesTwoFactorAuthentication include EnforcesTwoFactorAuthentication
include WithPerformanceBar include WithPerformanceBar
before_action :authenticate_user_from_personal_access_token! before_action :authenticate_sessionless_user!
before_action :authenticate_user_from_rss_token!
before_action :authenticate_user! before_action :authenticate_user!
before_action :validate_user_service_ticket! before_action :validate_user_service_ticket!
before_action :check_password_expiration before_action :check_password_expiration
...@@ -97,30 +96,15 @@ class ApplicationController < ActionController::Base ...@@ -97,30 +96,15 @@ class ApplicationController < ActionController::Base
# (e.g. tokens) to authenticate the user, whereas Devise sets current_user # (e.g. tokens) to authenticate the user, whereas Devise sets current_user
def auth_user def auth_user
return current_user if current_user.present? return current_user if current_user.present?
return try(:authenticated_user) return try(:authenticated_user)
end end
def authenticate_user_from_personal_access_token! # This filter handles personal access tokens, and atom requests with rss tokens
token = params[:private_token].presence || request.headers['PRIVATE-TOKEN'].presence def authenticate_sessionless_user!
user = Gitlab::Auth::RequestAuthenticator.new(request).find_sessionless_user
return unless token.present?
user = User.find_by_personal_access_token(token)
sessionless_sign_in(user) sessionless_sign_in(user) if user
end
# This filter handles authentication for atom request with an rss_token
def authenticate_user_from_rss_token!
return unless request.format.atom?
token = params[:rss_token].presence
return unless token.present?
user = User.find_by_rss_token(token)
sessionless_sign_in(user)
end end
def log_exception(exception) def log_exception(exception)
...@@ -212,7 +196,11 @@ class ApplicationController < ActionController::Base ...@@ -212,7 +196,11 @@ class ApplicationController < ActionController::Base
end end
def check_password_expiration def check_password_expiration
if current_user && current_user.password_expires_at && current_user.password_expires_at < Time.now && !current_user.ldap_user? return if session[:impersonator_id] || current_user&.ldap_user?
password_expires_at = current_user&.password_expires_at
if password_expires_at && password_expires_at < Time.now
return redirect_to new_profile_password_path return redirect_to new_profile_password_path
end end
end end
......
...@@ -44,6 +44,7 @@ class AutocompleteController < ApplicationController ...@@ -44,6 +44,7 @@ class AutocompleteController < ApplicationController
if @project.blank? && params[:group_id].present? if @project.blank? && params[:group_id].present?
group = Group.find(params[:group_id]) group = Group.find(params[:group_id])
return render_404 unless can?(current_user, :read_group, group) return render_404 unless can?(current_user, :read_group, group)
group group
end end
end end
...@@ -54,6 +55,7 @@ class AutocompleteController < ApplicationController ...@@ -54,6 +55,7 @@ class AutocompleteController < ApplicationController
if params[:project_id].present? if params[:project_id].present?
project = Project.find(params[:project_id]) project = Project.find(params[:project_id])
return render_404 unless can?(current_user, :read_project, project) return render_404 unless can?(current_user, :read_project, project)
project project
end end
end end
......
...@@ -84,6 +84,7 @@ module Boards ...@@ -84,6 +84,7 @@ module Boards
resource.as_json( resource.as_json(
only: [:id, :iid, :project_id, :title, :confidential, :due_date, :relative_position], only: [:id, :iid, :project_id, :title, :confidential, :due_date, :relative_position],
labels: true, labels: true,
sidebar_endpoints: true,
include: { include: {
project: { only: [:id, :path] }, project: { only: [:id, :path] },
assignees: { only: [:id, :name, :username], methods: [:avatar_url] }, assignees: { only: [:id, :name, :username], methods: [:avatar_url] },
......
...@@ -4,7 +4,7 @@ module NotesActions ...@@ -4,7 +4,7 @@ module NotesActions
included do included do
before_action :set_polling_interval_header, only: [:index] before_action :set_polling_interval_header, only: [:index]
before_action :noteable, only: :index before_action :require_noteable!, only: [:index, :create]
before_action :authorize_admin_note!, only: [:update, :destroy] before_action :authorize_admin_note!, only: [:update, :destroy]
before_action :note_project, only: [:create] before_action :note_project, only: [:create]
end end
...@@ -90,7 +90,7 @@ module NotesActions ...@@ -90,7 +90,7 @@ module NotesActions
if note.persisted? if note.persisted?
attrs[:valid] = true attrs[:valid] = true
if noteable.nil? || noteable.discussions_rendered_on_frontend? if noteable.discussions_rendered_on_frontend?
attrs.merge!(note_serializer.represent(note)) attrs.merge!(note_serializer.represent(note))
else else
attrs.merge!( attrs.merge!(
...@@ -191,7 +191,11 @@ module NotesActions ...@@ -191,7 +191,11 @@ module NotesActions
end end
def noteable def noteable
@noteable ||= notes_finder.target || render_404 @noteable ||= notes_finder.target || @note&.noteable
end
def require_noteable!
render_404 unless noteable
end end
def last_fetched_at def last_fetched_at
......
...@@ -80,7 +80,8 @@ class Groups::MilestonesController < Groups::ApplicationController ...@@ -80,7 +80,8 @@ class Groups::MilestonesController < Groups::ApplicationController
milestones = MilestonesFinder.new(search_params).execute milestones = MilestonesFinder.new(search_params).execute
legacy_milestones = GroupMilestone.build_collection(group, group_projects, params) legacy_milestones = GroupMilestone.build_collection(group, group_projects, params)
milestones + legacy_milestones @sort = params[:sort] || 'due_date_asc'
MilestoneArray.sort(milestones + legacy_milestones, @sort)
end end
def milestone def milestone
......
...@@ -4,6 +4,7 @@ class Import::GitlabProjectsController < Import::BaseController ...@@ -4,6 +4,7 @@ class Import::GitlabProjectsController < Import::BaseController
def new def new
@namespace = Namespace.find(project_params[:namespace_id]) @namespace = Namespace.find(project_params[:namespace_id])
return render_404 unless current_user.can?(:create_projects, @namespace) return render_404 unless current_user.can?(:create_projects, @namespace)
@path = project_params[:path] @path = project_params[:path]
end end
......
...@@ -54,7 +54,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController ...@@ -54,7 +54,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
if current_user if current_user
log_audit_event(current_user, with: :saml) log_audit_event(current_user, with: :saml)
# Update SAML identity if data has changed. # Update SAML identity if data has changed.
identity = current_user.identities.find_by(extern_uid: oauth['uid'], provider: :saml) identity = current_user.identities.with_extern_uid(:saml, oauth['uid']).take
if identity.nil? if identity.nil?
current_user.identities.create(extern_uid: oauth['uid'], provider: :saml) current_user.identities.create(extern_uid: oauth['uid'], provider: :saml)
redirect_to profile_account_path, notice: 'Authentication method updated' redirect_to profile_account_path, notice: 'Authentication method updated'
...@@ -98,7 +98,9 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController ...@@ -98,7 +98,9 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
def handle_omniauth def handle_omniauth
if current_user if current_user
# Add new authentication method # Add new authentication method
current_user.identities.find_or_create_by(extern_uid: oauth['uid'], provider: oauth['provider']) current_user.identities
.with_extern_uid(oauth['provider'], oauth['uid'])
.first_or_create(extern_uid: oauth['uid'])
log_audit_event(current_user, with: oauth['provider']) log_audit_event(current_user, with: oauth['provider'])
redirect_to profile_account_path, notice: 'Authentication method updated' redirect_to profile_account_path, notice: 'Authentication method updated'
else else
......
...@@ -22,12 +22,7 @@ class Projects::CommitController < Projects::ApplicationController ...@@ -22,12 +22,7 @@ class Projects::CommitController < Projects::ApplicationController
apply_diff_view_cookie! apply_diff_view_cookie!
respond_to do |format| respond_to do |format|
format.html do format.html { render }
# n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37599
Gitlab::GitalyClient.allow_n_plus_1_calls do
render
end
end
format.diff { render text: @commit.to_diff } format.diff { render text: @commit.to_diff }
format.patch { render text: @commit.to_patch } format.patch { render text: @commit.to_patch }
end end
...@@ -112,7 +107,7 @@ class Projects::CommitController < Projects::ApplicationController ...@@ -112,7 +107,7 @@ class Projects::CommitController < Projects::ApplicationController
end end
def commit def commit
@noteable = @commit ||= @project.commit(params[:id]) @noteable = @commit ||= @project.commit_by(oid: params[:id])
end end
def define_commit_vars def define_commit_vars
......
...@@ -57,6 +57,7 @@ class Projects::CommitsController < Projects::ApplicationController ...@@ -57,6 +57,7 @@ class Projects::CommitsController < Projects::ApplicationController
@repository.commits(@ref, path: @path, limit: @limit, offset: @offset) @repository.commits(@ref, path: @path, limit: @limit, offset: @offset)
end end
@commits = @commits.with_pipeline_status
@commits = prepare_commits_for_rendering(@commits) @commits = prepare_commits_for_rendering(@commits)
end end
end end
...@@ -12,6 +12,7 @@ class Projects::DeploymentsController < Projects::ApplicationController ...@@ -12,6 +12,7 @@ class Projects::DeploymentsController < Projects::ApplicationController
def metrics def metrics
return render_404 unless deployment.has_metrics? return render_404 unless deployment.has_metrics?
@metrics = deployment.metrics @metrics = deployment.metrics
if @metrics&.any? if @metrics&.any?
render json: @metrics, status: :ok render json: @metrics, status: :ok
......
...@@ -12,6 +12,7 @@ class Projects::GroupLinksController < Projects::ApplicationController ...@@ -12,6 +12,7 @@ class Projects::GroupLinksController < Projects::ApplicationController
if group if group
return render_404 unless can?(current_user, :read_group, group) return render_404 unless can?(current_user, :read_group, group)
Projects::GroupLinks::CreateService.new(project, current_user, group_link_create_params).execute(group) Projects::GroupLinks::CreateService.new(project, current_user, group_link_create_params).execute(group)
else else
flash[:alert] = 'Please select a group.' flash[:alert] = 'Please select a group.'
......
...@@ -171,6 +171,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -171,6 +171,7 @@ class Projects::IssuesController < Projects::ApplicationController
def issue def issue
return @issue if defined?(@issue) return @issue if defined?(@issue)
# The Sortable default scope causes performance issues when used with find_by # The Sortable default scope causes performance issues when used with find_by
@issuable = @noteable = @issue ||= @project.issues.where(iid: params[:id]).reorder(nil).take! @issuable = @noteable = @issue ||= @project.issues.where(iid: params[:id]).reorder(nil).take!
@note = @project.notes.new(noteable: @issuable) @note = @project.notes.new(noteable: @issuable)
......
...@@ -111,6 +111,7 @@ class Projects::LabelsController < Projects::ApplicationController ...@@ -111,6 +111,7 @@ class Projects::LabelsController < Projects::ApplicationController
begin begin
return render_404 unless promote_service.execute(@label) return render_404 unless promote_service.execute(@label)
respond_to do |format| respond_to do |format|
format.html do format.html do
redirect_to(project_labels_path(@project), redirect_to(project_labels_path(@project),
......
...@@ -54,6 +54,7 @@ class Projects::LfsStorageController < Projects::GitHttpClientController ...@@ -54,6 +54,7 @@ class Projects::LfsStorageController < Projects::GitHttpClientController
name = request.headers['X-Gitlab-Lfs-Tmp'] name = request.headers['X-Gitlab-Lfs-Tmp']
return if name.include?('/') return if name.include?('/')
return unless oid.present? && name.start_with?(oid) return unless oid.present? && name.start_with?(oid)
name name
end end
......
...@@ -10,10 +10,7 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic ...@@ -10,10 +10,7 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic
def show def show
@environment = @merge_request.environments_for(current_user).last @environment = @merge_request.environments_for(current_user).last
# n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37431 render json: { html: view_to_html_string("projects/merge_requests/diffs/_diffs") }
Gitlab::GitalyClient.allow_n_plus_1_calls do
render json: { html: view_to_html_string("projects/merge_requests/diffs/_diffs") }
end
end end
def diff_for_path def diff_for_path
......
...@@ -80,7 +80,8 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo ...@@ -80,7 +80,8 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
def commits def commits
# Get commits from repository # Get commits from repository
# or from cache if already merged # or from cache if already merged
@commits = prepare_commits_for_rendering(@merge_request.commits) @commits =
prepare_commits_for_rendering(@merge_request.commits.with_pipeline_status)
render json: { html: view_to_html_string('projects/merge_requests/_commits') } render json: { html: view_to_html_string('projects/merge_requests/_commits') }
end end
......
...@@ -76,6 +76,7 @@ class Projects::NotesController < Projects::ApplicationController ...@@ -76,6 +76,7 @@ class Projects::NotesController < Projects::ApplicationController
def authorize_create_note! def authorize_create_note!
return unless noteable.lockable? return unless noteable.lockable?
access_denied! unless can?(current_user, :create_note, noteable) access_denied! unless can?(current_user, :create_note, noteable)
end end
end end
...@@ -28,6 +28,7 @@ class Projects::WikisController < Projects::ApplicationController ...@@ -28,6 +28,7 @@ class Projects::WikisController < Projects::ApplicationController
) )
else else
return render('empty') unless can?(current_user, :create_wiki, @project) return render('empty') unless can?(current_user, :create_wiki, @project)
@page = WikiPage.new(@project_wiki) @page = WikiPage.new(@project_wiki)
@page.title = params[:id] @page.title = params[:id]
...@@ -74,7 +75,11 @@ class Projects::WikisController < Projects::ApplicationController ...@@ -74,7 +75,11 @@ class Projects::WikisController < Projects::ApplicationController
def history def history
@page = @project_wiki.find_page(params[:id]) @page = @project_wiki.find_page(params[:id])
unless @page if @page
@page_versions = Kaminari.paginate_array(@page.versions(page: params[:page]),
total_count: @page.count_versions)
.page(params[:page])
else
redirect_to( redirect_to(
project_wiki_path(@project, :home), project_wiki_path(@project, :home),
notice: "Page not found" notice: "Page not found"
...@@ -101,7 +106,7 @@ class Projects::WikisController < Projects::ApplicationController ...@@ -101,7 +106,7 @@ class Projects::WikisController < Projects::ApplicationController
# Call #wiki to make sure the Wiki Repo is initialized # Call #wiki to make sure the Wiki Repo is initialized
@project_wiki.wiki @project_wiki.wiki
@sidebar_wiki_entries = WikiPage.group_by_directory(@project_wiki.pages.first(15)) @sidebar_wiki_entries = WikiPage.group_by_directory(@project_wiki.pages(limit: 15))
rescue ProjectWiki::CouldNotCreateWikiError rescue ProjectWiki::CouldNotCreateWikiError
flash[:notice] = "Could not create Wiki Repository at this time. Please try again later." flash[:notice] = "Could not create Wiki Repository at this time. Please try again later."
redirect_to project_path(@project) redirect_to project_path(@project)
......
...@@ -269,6 +269,7 @@ class ProjectsController < Projects::ApplicationController ...@@ -269,6 +269,7 @@ class ProjectsController < Projects::ApplicationController
def render_landing_page def render_landing_page
if can?(current_user, :download_code, @project) if can?(current_user, :download_code, @project)
return render 'projects/no_repo' unless @project.repository_exists? return render 'projects/no_repo' unless @project.repository_exists?
render 'projects/empty' if @project.empty_repo? render 'projects/empty' if @project.empty_repo?
else else
if @project.wiki_enabled? if @project.wiki_enabled?
......
...@@ -20,6 +20,7 @@ class Snippets::NotesController < ApplicationController ...@@ -20,6 +20,7 @@ class Snippets::NotesController < ApplicationController
def snippet def snippet
PersonalSnippet.find_by(id: params[:snippet_id]) PersonalSnippet.find_by(id: params[:snippet_id])
end end
alias_method :noteable, :snippet
def note_params def note_params
super.merge(noteable_id: params[:snippet_id]) super.merge(noteable_id: params[:snippet_id])
......
...@@ -18,6 +18,7 @@ class PersonalAccessTokensFinder ...@@ -18,6 +18,7 @@ class PersonalAccessTokensFinder
def by_user(tokens) def by_user(tokens)
return tokens unless @params[:user] return tokens unless @params[:user]
tokens.where(user: @params[:user]) tokens.where(user: @params[:user])
end end
......
...@@ -30,4 +30,11 @@ module AppearancesHelper ...@@ -30,4 +30,11 @@ module AppearancesHelper
render 'shared/logo.svg' render 'shared/logo.svg'
end end
end end
# Skip the 'GitLab' type logo when custom brand logo is set
def brand_header_logo_type
unless brand_item && brand_item.header_logo?
render 'shared/logo_type.svg'
end
end
end end
...@@ -231,6 +231,15 @@ module ApplicationSettingsHelper ...@@ -231,6 +231,15 @@ module ApplicationSettingsHelper
:sign_in_text, :sign_in_text,
:signup_enabled, :signup_enabled,
:terminal_max_session_time, :terminal_max_session_time,
:throttle_unauthenticated_enabled,
:throttle_unauthenticated_requests_per_period,
:throttle_unauthenticated_period_in_seconds,
:throttle_authenticated_web_enabled,
:throttle_authenticated_web_requests_per_period,
:throttle_authenticated_web_period_in_seconds,
:throttle_authenticated_api_enabled,
:throttle_authenticated_api_requests_per_period,
:throttle_authenticated_api_period_in_seconds,
:two_factor_grace_period, :two_factor_grace_period,
:unique_ips_limit_enabled, :unique_ips_limit_enabled,
:unique_ips_limit_per_user, :unique_ips_limit_per_user,
......
...@@ -6,11 +6,6 @@ ...@@ -6,11 +6,6 @@
# See 'detailed_status?` method and `Gitlab::Ci::Status` module. # See 'detailed_status?` method and `Gitlab::Ci::Status` module.
# #
module CiStatusHelper module CiStatusHelper
def ci_status_path(pipeline)
project = pipeline.project
project_pipeline_path(project, pipeline)
end
def ci_label_for_status(status) def ci_label_for_status(status)
if detailed_status?(status) if detailed_status?(status)
return status.label return status.label
......
...@@ -111,6 +111,7 @@ module DiffHelper ...@@ -111,6 +111,7 @@ module DiffHelper
def diff_file_old_blob_raw_path(diff_file) def diff_file_old_blob_raw_path(diff_file)
sha = diff_file.old_content_sha sha = diff_file.old_content_sha
return unless sha return unless sha
project_raw_path(@project, tree_join(diff_file.old_content_sha, diff_file.old_path)) project_raw_path(@project, tree_join(diff_file.old_content_sha, diff_file.old_path))
end end
...@@ -152,11 +153,11 @@ module DiffHelper ...@@ -152,11 +153,11 @@ module DiffHelper
def diff_file_changed_icon(diff_file) def diff_file_changed_icon(diff_file)
if diff_file.deleted_file? || diff_file.renamed_file? if diff_file.deleted_file? || diff_file.renamed_file?
"minus" "file-deletion"
elsif diff_file.new_file? elsif diff_file.new_file?
"plus" "file-addition"
else else
"adjust" "file-modified"
end end
end end
......
...@@ -24,6 +24,7 @@ module EmailsHelper ...@@ -24,6 +24,7 @@ module EmailsHelper
def action_title(url) def action_title(url)
return unless url return unless url
%w(merge_requests issues commit).each do |action| %w(merge_requests issues commit).each do |action|
if url.split("/").include?(action) if url.split("/").include?(action)
return "View #{action.humanize.singularize}" return "View #{action.humanize.singularize}"
......
...@@ -53,6 +53,7 @@ module MarkupHelper ...@@ -53,6 +53,7 @@ module MarkupHelper
# text, wrapping anything found in the requested link # text, wrapping anything found in the requested link
fragment.children.each do |node| fragment.children.each do |node|
next unless node.text? next unless node.text?
node.replace(link_to(node.text, url, html_options)) node.replace(link_to(node.text, url, html_options))
end end
end end
...@@ -221,7 +222,7 @@ module MarkupHelper ...@@ -221,7 +222,7 @@ module MarkupHelper
data = options[:data].merge({ container: 'body' }) data = options[:data].merge({ container: 'body' })
content_tag :button, content_tag :button,
type: 'button', type: 'button',
class: 'toolbar-btn js-md has-tooltip hidden-xs', class: 'toolbar-btn js-md has-tooltip',
tabindex: -1, tabindex: -1,
data: data, data: data,
title: options[:title], title: options[:title],
......
...@@ -78,6 +78,7 @@ module NotificationsHelper ...@@ -78,6 +78,7 @@ module NotificationsHelper
# Create hidden field to send notification setting source to controller # Create hidden field to send notification setting source to controller
def hidden_setting_source_input(notification_setting) def hidden_setting_source_input(notification_setting)
return unless notification_setting.source_type return unless notification_setting.source_type
hidden_field_tag "#{notification_setting.source_type.downcase}_id", notification_setting.source_id hidden_field_tag "#{notification_setting.source_type.downcase}_id", notification_setting.source_id
end 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.
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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