Commit d7a3180d authored by Shinya Maeda's avatar Shinya Maeda

Merge branch 'master' into build-chunks-on-object-storage

parents 64452959 26c9d716
image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.4.4-golang-1.9-git-2.17-chrome-65.0-node-8.x-yarn-1.2-postgresql-9.6" image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.4.4-golang-1.9-git-2.17-chrome-67.0-node-8.x-yarn-1.2-postgresql-9.6-graphicsmagick-1.3.29"
.dedicated-runner: &dedicated-runner .dedicated-runner: &dedicated-runner
retry: 1 retry: 1
...@@ -394,7 +394,11 @@ compile-assets: ...@@ -394,7 +394,11 @@ compile-assets:
- date - date
- yarn install --frozen-lockfile --cache-folder .yarn-cache - yarn install --frozen-lockfile --cache-folder .yarn-cache
- date - date
- free -m
- bundle exec rake gitlab:assets:compile - bundle exec rake gitlab:assets:compile
variables:
# we override the max_old_space_size to prevent OOM errors
NODE_OPTIONS: --max_old_space_size=3584
artifacts: artifacts:
expire_in: 7d expire_in: 7d
paths: paths:
...@@ -658,10 +662,13 @@ gitlab:assets:compile: ...@@ -658,10 +662,13 @@ gitlab:assets:compile:
SKIP_STORAGE_VALIDATION: "true" SKIP_STORAGE_VALIDATION: "true"
WEBPACK_REPORT: "true" WEBPACK_REPORT: "true"
NO_COMPRESSION: "true" NO_COMPRESSION: "true"
# we override the max_old_space_size to prevent OOM errors
NODE_OPTIONS: --max_old_space_size=3584
script: script:
- date - date
- yarn install --frozen-lockfile --production --cache-folder .yarn-cache - yarn install --frozen-lockfile --production --cache-folder .yarn-cache
- date - date
- free -m
- bundle exec rake gitlab:assets:compile - bundle exec rake gitlab:assets:compile
artifacts: artifacts:
name: webpack-report name: webpack-report
......
...@@ -2,6 +2,10 @@ ...@@ -2,6 +2,10 @@
documentation](doc/development/changelog.md) for instructions on adding your own documentation](doc/development/changelog.md) for instructions on adding your own
entry. entry.
## 10.8.4 (2018-06-06)
- No changes.
## 10.8.3 (2018-05-30) ## 10.8.3 (2018-05-30)
### Fixed (4 changes) ### Fixed (4 changes)
......
...@@ -108,6 +108,7 @@ gem 'hamlit', '~> 2.6.1' ...@@ -108,6 +108,7 @@ gem 'hamlit', '~> 2.6.1'
# Files attachments # Files attachments
gem 'carrierwave', '~> 1.2' gem 'carrierwave', '~> 1.2'
gem 'mini_magick'
# Drag and Drop UI # Drag and Drop UI
gem 'dropzonejs-rails', '~> 0.7.1' gem 'dropzonejs-rails', '~> 0.7.1'
...@@ -133,7 +134,7 @@ gem 'seed-fu', '~> 2.3.7' ...@@ -133,7 +134,7 @@ gem 'seed-fu', '~> 2.3.7'
# Markdown and HTML processing # Markdown and HTML processing
gem 'html-pipeline', '~> 2.7.1' gem 'html-pipeline', '~> 2.7.1'
gem 'deckar01-task_list', '2.0.0' gem 'deckar01-task_list', '2.0.0'
gem 'gitlab-markup', '~> 1.6.2' gem 'gitlab-markup', '~> 1.6.4'
gem 'redcarpet', '~> 3.4' gem 'redcarpet', '~> 3.4'
gem 'commonmarker', '~> 0.17' gem 'commonmarker', '~> 0.17'
gem 'RedCloth', '~> 4.3.2' gem 'RedCloth', '~> 4.3.2'
...@@ -332,7 +333,7 @@ group :development, :test do ...@@ -332,7 +333,7 @@ group :development, :test do
gem 'database_cleaner', '~> 1.5.0' gem 'database_cleaner', '~> 1.5.0'
gem 'factory_bot_rails', '~> 4.8.2' gem 'factory_bot_rails', '~> 4.8.2'
gem 'rspec-rails', '~> 3.6.0' gem 'rspec-rails', '~> 3.7.0'
gem 'rspec-retry', '~> 0.4.5' gem 'rspec-retry', '~> 0.4.5'
gem 'rspec_profiling', '~> 0.0.5' gem 'rspec_profiling', '~> 0.0.5'
gem 'rspec-set', '~> 0.1.3' gem 'rspec-set', '~> 0.1.3'
...@@ -408,18 +409,17 @@ gem 'vmstat', '~> 2.3.0' ...@@ -408,18 +409,17 @@ gem 'vmstat', '~> 2.3.0'
gem 'sys-filesystem', '~> 1.1.6' gem 'sys-filesystem', '~> 1.1.6'
# SSH host key support # SSH host key support
gem 'net-ssh', '~> 4.2.0' gem 'net-ssh', '~> 5.0'
gem 'sshkey', '~> 1.9.0' gem 'sshkey', '~> 1.9.0'
# Required for ED25519 SSH host key support # Required for ED25519 SSH host key support
group :ed25519 do group :ed25519 do
gem 'rbnacl-libsodium' gem 'ed25519', '~> 1.2'
gem 'rbnacl', '~> 4.0'
gem 'bcrypt_pbkdf', '~> 1.0' gem 'bcrypt_pbkdf', '~> 1.0'
end end
# Gitaly GRPC client # Gitaly GRPC client
gem 'gitaly-proto', '~> 0.100.0', require: 'gitaly' gem 'gitaly-proto', '~> 0.101.0', require: 'gitaly'
gem 'grpc', '~> 1.11.0' gem 'grpc', '~> 1.11.0'
# Locked until https://github.com/google/protobuf/issues/4210 is closed # Locked until https://github.com/google/protobuf/issues/4210 is closed
......
...@@ -177,6 +177,7 @@ GEM ...@@ -177,6 +177,7 @@ GEM
json-jwt (~> 1.6) json-jwt (~> 1.6)
dropzonejs-rails (0.7.2) dropzonejs-rails (0.7.2)
rails (> 3.1) rails (> 3.1)
ed25519 (1.2.4)
email_reply_trimmer (0.1.6) email_reply_trimmer (0.1.6)
email_spec (2.2.0) email_spec (2.2.0)
htmlentities (~> 4.3.3) htmlentities (~> 4.3.3)
...@@ -282,7 +283,7 @@ GEM ...@@ -282,7 +283,7 @@ GEM
gettext_i18n_rails (>= 0.7.1) gettext_i18n_rails (>= 0.7.1)
po_to_json (>= 1.0.0) po_to_json (>= 1.0.0)
rails (>= 3.2.0) rails (>= 3.2.0)
gitaly-proto (0.100.0) gitaly-proto (0.101.0)
google-protobuf (~> 3.1) google-protobuf (~> 3.1)
grpc (~> 1.10) grpc (~> 1.10)
github-linguist (5.3.3) github-linguist (5.3.3)
...@@ -311,7 +312,7 @@ GEM ...@@ -311,7 +312,7 @@ GEM
diff-lcs (~> 1.1) diff-lcs (~> 1.1)
mime-types (>= 1.16) mime-types (>= 1.16)
posix-spawn (~> 0.3) posix-spawn (~> 0.3)
gitlab-markup (1.6.3) gitlab-markup (1.6.4)
gitlab-styles (2.3.2) gitlab-styles (2.3.2)
rubocop (~> 0.51) rubocop (~> 0.51)
rubocop-gitlab-security (~> 0.1.0) rubocop-gitlab-security (~> 0.1.0)
...@@ -359,8 +360,8 @@ GEM ...@@ -359,8 +360,8 @@ GEM
grape-entity (0.7.1) grape-entity (0.7.1)
activesupport (>= 4.0) activesupport (>= 4.0)
multi_json (>= 1.3.2) multi_json (>= 1.3.2)
grape-path-helpers (1.0.2) grape-path-helpers (1.0.5)
activesupport (~> 4) activesupport (>= 4, < 5.1)
grape (~> 1.0) grape (~> 1.0)
rake (~> 12) rake (~> 12)
grape_logging (1.7.0) grape_logging (1.7.0)
...@@ -451,7 +452,6 @@ GEM ...@@ -451,7 +452,6 @@ GEM
kgio (2.10.0) kgio (2.10.0)
knapsack (1.16.0) knapsack (1.16.0)
rake rake
timecop (>= 0.1.0)
kubeclient (3.1.0) kubeclient (3.1.0)
http (~> 2.2.2) http (~> 2.2.2)
recursive-open-struct (~> 1.0, >= 1.0.4) recursive-open-struct (~> 1.0, >= 1.0.4)
...@@ -498,6 +498,7 @@ GEM ...@@ -498,6 +498,7 @@ GEM
mime-types-data (~> 3.2015) mime-types-data (~> 3.2015)
mime-types-data (3.2016.0521) mime-types-data (3.2016.0521)
mimemagic (0.3.0) mimemagic (0.3.0)
mini_magick (4.8.0)
mini_mime (1.0.0) mini_mime (1.0.0)
mini_portile2 (2.3.0) mini_portile2 (2.3.0)
minitest (5.7.0) minitest (5.7.0)
...@@ -510,7 +511,7 @@ GEM ...@@ -510,7 +511,7 @@ GEM
mustermann (~> 1.0.0) mustermann (~> 1.0.0)
mysql2 (0.4.10) mysql2 (0.4.10)
net-ldap (0.16.0) net-ldap (0.16.0)
net-ssh (4.2.0) net-ssh (5.0.1)
netrc (0.11.0) netrc (0.11.0)
nokogiri (1.8.2) nokogiri (1.8.2)
mini_portile2 (~> 2.3.0) mini_portile2 (~> 2.3.0)
...@@ -696,10 +697,6 @@ GEM ...@@ -696,10 +697,6 @@ GEM
ffi (>= 0.5.0, < 2) ffi (>= 0.5.0, < 2)
rblineprof (0.3.6) rblineprof (0.3.6)
debugger-ruby_core_source (~> 1.3) debugger-ruby_core_source (~> 1.3)
rbnacl (4.0.2)
ffi
rbnacl-libsodium (1.0.11)
rbnacl (>= 3.0.1)
rdoc (6.0.4) rdoc (6.0.4)
re2 (1.1.1) re2 (1.1.1)
recaptcha (3.0.0) recaptcha (3.0.0)
...@@ -745,36 +742,36 @@ GEM ...@@ -745,36 +742,36 @@ GEM
chunky_png chunky_png
rqrcode-rails3 (0.1.7) rqrcode-rails3 (0.1.7)
rqrcode (>= 0.4.2) rqrcode (>= 0.4.2)
rspec (3.6.0) rspec (3.7.0)
rspec-core (~> 3.6.0) rspec-core (~> 3.7.0)
rspec-expectations (~> 3.6.0) rspec-expectations (~> 3.7.0)
rspec-mocks (~> 3.6.0) rspec-mocks (~> 3.7.0)
rspec-core (3.6.0) rspec-core (3.7.1)
rspec-support (~> 3.6.0) rspec-support (~> 3.7.0)
rspec-expectations (3.6.0) rspec-expectations (3.7.0)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.6.0) rspec-support (~> 3.7.0)
rspec-mocks (3.6.0) rspec-mocks (3.7.0)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.6.0) rspec-support (~> 3.7.0)
rspec-parameterized (0.4.0) rspec-parameterized (0.4.0)
binding_of_caller binding_of_caller
parser parser
proc_to_ast proc_to_ast
rspec (>= 2.13, < 4) rspec (>= 2.13, < 4)
unparser unparser
rspec-rails (3.6.0) rspec-rails (3.7.2)
actionpack (>= 3.0) actionpack (>= 3.0)
activesupport (>= 3.0) activesupport (>= 3.0)
railties (>= 3.0) railties (>= 3.0)
rspec-core (~> 3.6.0) rspec-core (~> 3.7.0)
rspec-expectations (~> 3.6.0) rspec-expectations (~> 3.7.0)
rspec-mocks (~> 3.6.0) rspec-mocks (~> 3.7.0)
rspec-support (~> 3.6.0) rspec-support (~> 3.7.0)
rspec-retry (0.4.5) rspec-retry (0.4.5)
rspec-core rspec-core
rspec-set (0.1.3) rspec-set (0.1.3)
rspec-support (3.6.0) rspec-support (3.7.1)
rspec_profiling (0.0.5) rspec_profiling (0.0.5)
activerecord activerecord
pg pg
...@@ -1016,6 +1013,7 @@ DEPENDENCIES ...@@ -1016,6 +1013,7 @@ DEPENDENCIES
doorkeeper (~> 4.3) doorkeeper (~> 4.3)
doorkeeper-openid_connect (~> 1.3) doorkeeper-openid_connect (~> 1.3)
dropzonejs-rails (~> 0.7.1) dropzonejs-rails (~> 0.7.1)
ed25519 (~> 1.2)
email_reply_trimmer (~> 0.1) email_reply_trimmer (~> 0.1)
email_spec (~> 2.2.0) email_spec (~> 2.2.0)
factory_bot_rails (~> 4.8.2) factory_bot_rails (~> 4.8.2)
...@@ -1041,12 +1039,12 @@ DEPENDENCIES ...@@ -1041,12 +1039,12 @@ 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.3) gettext_i18n_rails_js (~> 1.3)
gitaly-proto (~> 0.100.0) gitaly-proto (~> 0.101.0)
github-linguist (~> 5.3.3) github-linguist (~> 5.3.3)
gitlab-flowdock-git-hook (~> 1.0.1) gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-gollum-lib (~> 4.2) gitlab-gollum-lib (~> 4.2)
gitlab-gollum-rugged_adapter (~> 0.4.4) gitlab-gollum-rugged_adapter (~> 0.4.4)
gitlab-markup (~> 1.6.2) gitlab-markup (~> 1.6.4)
gitlab-styles (~> 2.3) gitlab-styles (~> 2.3)
gitlab_omniauth-ldap (~> 2.0.4) gitlab_omniauth-ldap (~> 2.0.4)
gon (~> 6.2) gon (~> 6.2)
...@@ -1084,11 +1082,12 @@ DEPENDENCIES ...@@ -1084,11 +1082,12 @@ DEPENDENCIES
loofah (~> 2.2) loofah (~> 2.2)
mail_room (~> 0.9.1) mail_room (~> 0.9.1)
method_source (~> 0.8) method_source (~> 0.8)
mini_magick
minitest (~> 5.7.0) minitest (~> 5.7.0)
mousetrap-rails (~> 1.4.6) mousetrap-rails (~> 1.4.6)
mysql2 (~> 0.4.10) mysql2 (~> 0.4.10)
net-ldap net-ldap
net-ssh (~> 4.2.0) net-ssh (~> 5.0)
nokogiri (~> 1.8.2) nokogiri (~> 1.8.2)
oauth2 (~> 1.4) oauth2 (~> 1.4)
octokit (~> 4.9) octokit (~> 4.9)
...@@ -1130,8 +1129,6 @@ DEPENDENCIES ...@@ -1130,8 +1129,6 @@ DEPENDENCIES
rainbow (~> 2.2) rainbow (~> 2.2)
raindrops (~> 0.18) raindrops (~> 0.18)
rblineprof (~> 0.3.6) rblineprof (~> 0.3.6)
rbnacl (~> 4.0)
rbnacl-libsodium
rdoc (~> 6.0) rdoc (~> 6.0)
re2 (~> 1.1.1) re2 (~> 1.1.1)
recaptcha (~> 3.0) recaptcha (~> 3.0)
...@@ -1144,7 +1141,7 @@ DEPENDENCIES ...@@ -1144,7 +1141,7 @@ DEPENDENCIES
rouge (~> 3.1) rouge (~> 3.1)
rqrcode-rails3 (~> 0.1.7) rqrcode-rails3 (~> 0.1.7)
rspec-parameterized rspec-parameterized
rspec-rails (~> 3.6.0) rspec-rails (~> 3.7.0)
rspec-retry (~> 0.4.5) rspec-retry (~> 0.4.5)
rspec-set (~> 0.1.3) rspec-set (~> 0.1.3)
rspec_profiling (~> 0.0.5) rspec_profiling (~> 0.0.5)
......
This diff is collapsed.
...@@ -87,10 +87,46 @@ export default { ...@@ -87,10 +87,46 @@ export default {
mounted() { mounted() {
const options = gl.issueBoards.getBoardSortableDefaultOptions({ const options = gl.issueBoards.getBoardSortableDefaultOptions({
scroll: true, scroll: true,
group: 'issues',
disabled: this.disabled, disabled: this.disabled,
filter: '.board-list-count, .is-disabled', filter: '.board-list-count, .is-disabled',
dataIdAttr: 'data-issue-id', dataIdAttr: 'data-issue-id',
group: {
name: 'issues',
/**
* Dynamically determine between which containers
* items can be moved or copied as
* Assignee lists (EE feature) require this behavior
*/
pull: (to, from, dragEl, e) => {
// As per Sortable's docs, `to` should provide
// reference to exact sortable container on which
// we're trying to drag element, but either it is
// a library's bug or our markup structure is too complex
// that `to` never points to correct container
// See https://github.com/RubaXa/Sortable/issues/1037
//
// So we use `e.target` which is always accurate about
// which element we're currently dragging our card upon
// So from there, we can get reference to actual container
// and thus the container type to enable Copy or Move
if (e.target) {
const containerEl = e.target.closest('.js-board-list') || e.target.querySelector('.js-board-list');
const toBoardType = containerEl.dataset.boardType;
if (toBoardType) {
const fromBoardType = this.list.type;
if ((fromBoardType === 'assignee' && toBoardType === 'label') ||
(fromBoardType === 'label' && toBoardType === 'assignee')) {
return 'clone';
}
}
}
return true;
},
revertClone: true,
},
onStart: (e) => { onStart: (e) => {
const card = this.$refs.issue[e.oldIndex]; const card = this.$refs.issue[e.oldIndex];
...@@ -179,10 +215,11 @@ export default { ...@@ -179,10 +215,11 @@ export default {
:list="list" :list="list"
v-if="list.type !== 'closed' && showIssueForm"/> v-if="list.type !== 'closed' && showIssueForm"/>
<ul <ul
class="board-list" class="board-list js-board-list"
v-show="!loading" v-show="!loading"
ref="list" ref="list"
:data-board="list.id" :data-board="list.id"
:data-board-type="list.type"
:class="{ 'is-smaller': showIssueForm }"> :class="{ 'is-smaller': showIssueForm }">
<board-card <board-card
v-for="(issue, index) in issues" v-for="(issue, index) in issues"
......
...@@ -49,11 +49,12 @@ export default { ...@@ -49,11 +49,12 @@ export default {
this.error = false; this.error = false;
const labels = this.list.label ? [this.list.label] : []; const labels = this.list.label ? [this.list.label] : [];
const assignees = this.list.assignee ? [this.list.assignee] : [];
const issue = new ListIssue({ const issue = new ListIssue({
title: this.title, title: this.title,
labels, labels,
subscribed: true, subscribed: true,
assignees: [], assignees,
project_id: this.selectedProject.id, project_id: this.selectedProject.id,
}); });
...@@ -141,4 +142,3 @@ export default { ...@@ -141,4 +142,3 @@ export default {
</div> </div>
</div> </div>
</template> </template>
...@@ -56,6 +56,7 @@ gl.issueBoards.newListDropdownInit = () => { ...@@ -56,6 +56,7 @@ gl.issueBoards.newListDropdownInit = () => {
filterable: true, filterable: true,
selectable: true, selectable: true,
multiSelect: true, multiSelect: true,
containerSelector: '.js-tab-container-labels .dropdown-page-one .dropdown-content',
clicked (options) { clicked (options) {
const { e } = options; const { e } = options;
const label = options.selectedObj; const label = options.selectedObj;
......
...@@ -7,6 +7,7 @@ import Vue from 'vue'; ...@@ -7,6 +7,7 @@ import Vue from 'vue';
import Flash from '~/flash'; import Flash from '~/flash';
import { __ } from '~/locale'; import { __ } from '~/locale';
import '~/vue_shared/models/label'; import '~/vue_shared/models/label';
import '~/vue_shared/models/assignee';
import FilteredSearchBoards from './filtered_search_boards'; import FilteredSearchBoards from './filtered_search_boards';
import eventHub from './eventhub'; import eventHub from './eventhub';
...@@ -15,7 +16,6 @@ import './models/issue'; ...@@ -15,7 +16,6 @@ import './models/issue';
import './models/list'; import './models/list';
import './models/milestone'; import './models/milestone';
import './models/project'; import './models/project';
import './models/assignee';
import './stores/boards_store'; import './stores/boards_store';
import ModalStore from './stores/modal_store'; import ModalStore from './stores/modal_store';
import BoardService from './services/board_service'; import BoardService from './services/board_service';
......
/* eslint-disable no-unused-vars */
class ListAssignee {
constructor(user, defaultAvatar) {
this.id = user.id;
this.name = user.name;
this.username = user.username;
this.avatar = user.avatar_url || defaultAvatar;
}
}
window.ListAssignee = ListAssignee;
/* eslint-disable space-before-function-paren, no-underscore-dangle, class-methods-use-this, consistent-return, no-shadow, no-param-reassign, max-len, no-unused-vars */ /* eslint-disable space-before-function-paren, no-underscore-dangle, class-methods-use-this, consistent-return, no-shadow, no-param-reassign, max-len, no-unused-vars */
/* global ListIssue */ /* global ListIssue */
/* global ListLabel */
import ListLabel from '~/vue_shared/models/label';
import ListAssignee from '~/vue_shared/models/assignee';
import queryData from '../utils/query_data'; import queryData from '../utils/query_data';
const PER_PAGE = 20; const PER_PAGE = 20;
class List { class List {
constructor (obj, defaultAvatar) { constructor(obj, defaultAvatar) {
this.id = obj.id; this.id = obj.id;
this._uid = this.guid(); this._uid = this.guid();
this.position = obj.position; this.position = obj.position;
...@@ -24,6 +26,9 @@ class List { ...@@ -24,6 +26,9 @@ class List {
if (obj.label) { if (obj.label) {
this.label = new ListLabel(obj.label); this.label = new ListLabel(obj.label);
} else if (obj.user) {
this.assignee = new ListAssignee(obj.user);
this.title = this.assignee.name;
} }
if (this.type !== 'blank' && this.id) { if (this.type !== 'blank' && this.id) {
...@@ -34,14 +39,25 @@ class List { ...@@ -34,14 +39,25 @@ class List {
} }
guid() { guid() {
const s4 = () => Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1); const s4 = () =>
Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
return `${s4()}${s4()}-${s4()}-${s4()}-${s4()}-${s4()}${s4()}${s4()}`; return `${s4()}${s4()}-${s4()}-${s4()}-${s4()}-${s4()}${s4()}${s4()}`;
} }
save () { save() {
const entity = this.label || this.assignee;
let entityType = '';
if (this.label) {
entityType = 'label_id';
} else {
entityType = 'assignee_id';
}
return gl.boardService.createList(this.label.id) return gl.boardService.createList(this.label.id)
.then(res => res.data) .then(res => res.data)
.then((data) => { .then(data => {
this.id = data.id; this.id = data.id;
this.type = data.list_type; this.type = data.list_type;
this.position = data.position; this.position = data.position;
...@@ -50,25 +66,23 @@ class List { ...@@ -50,25 +66,23 @@ class List {
}); });
} }
destroy () { destroy() {
const index = gl.issueBoards.BoardsStore.state.lists.indexOf(this); const index = gl.issueBoards.BoardsStore.state.lists.indexOf(this);
gl.issueBoards.BoardsStore.state.lists.splice(index, 1); gl.issueBoards.BoardsStore.state.lists.splice(index, 1);
gl.issueBoards.BoardsStore.updateNewListDropdown(this.id); gl.issueBoards.BoardsStore.updateNewListDropdown(this.id);
gl.boardService.destroyList(this.id) gl.boardService.destroyList(this.id).catch(() => {
.catch(() => { // TODO: handle request error
// TODO: handle request error });
});
} }
update () { update() {
gl.boardService.updateList(this.id, this.position) gl.boardService.updateList(this.id, this.position).catch(() => {
.catch(() => { // TODO: handle request error
// TODO: handle request error });
});
} }
nextPage () { nextPage() {
if (this.issuesSize > this.issues.length) { if (this.issuesSize > this.issues.length) {
if (this.issues.length / PER_PAGE >= 1) { if (this.issues.length / PER_PAGE >= 1) {
this.page += 1; this.page += 1;
...@@ -78,7 +92,7 @@ class List { ...@@ -78,7 +92,7 @@ class List {
} }
} }
getIssues (emptyIssues = true) { getIssues(emptyIssues = true) {
const data = queryData(gl.issueBoards.BoardsStore.filter.path, { page: this.page }); const data = queryData(gl.issueBoards.BoardsStore.filter.path, { page: this.page });
if (this.label && data.label_name) { if (this.label && data.label_name) {
...@@ -89,7 +103,8 @@ class List { ...@@ -89,7 +103,8 @@ class List {
this.loading = true; this.loading = true;
} }
return gl.boardService.getIssuesForList(this.id, data) return gl.boardService
.getIssuesForList(this.id, data)
.then(res => res.data) .then(res => res.data)
.then((data) => { .then((data) => {
this.loading = false; this.loading = false;
...@@ -103,11 +118,12 @@ class List { ...@@ -103,11 +118,12 @@ class List {
}); });
} }
newIssue (issue) { newIssue(issue) {
this.addIssue(issue, null, 0); this.addIssue(issue, null, 0);
this.issuesSize += 1; this.issuesSize += 1;
return gl.boardService.newIssue(this.id, issue) return gl.boardService
.newIssue(this.id, issue)
.then(res => res.data) .then(res => res.data)
.then((data) => { .then((data) => {
issue.id = data.id; issue.id = data.id;
...@@ -123,13 +139,13 @@ class List { ...@@ -123,13 +139,13 @@ class List {
}); });
} }
createIssues (data) { createIssues(data) {
data.forEach((issueObj) => { data.forEach(issueObj => {
this.addIssue(new ListIssue(issueObj, this.defaultAvatar)); this.addIssue(new ListIssue(issueObj, this.defaultAvatar));
}); });
} }
addIssue (issue, listFrom, newIndex) { addIssue(issue, listFrom, newIndex) {
let moveBeforeId = null; let moveBeforeId = null;
let moveAfterId = null; let moveAfterId = null;
...@@ -152,6 +168,13 @@ class List { ...@@ -152,6 +168,13 @@ class List {
issue.addLabel(this.label); issue.addLabel(this.label);
} }
if (this.assignee) {
if (listFrom && listFrom.type === 'assignee') {
issue.removeAssignee(listFrom.assignee);
}
issue.addAssignee(this.assignee);
}
if (listFrom) { if (listFrom) {
this.issuesSize += 1; this.issuesSize += 1;
...@@ -160,29 +183,29 @@ class List { ...@@ -160,29 +183,29 @@ class List {
} }
} }
moveIssue (issue, oldIndex, newIndex, moveBeforeId, moveAfterId) { moveIssue(issue, oldIndex, newIndex, moveBeforeId, moveAfterId) {
this.issues.splice(oldIndex, 1); this.issues.splice(oldIndex, 1);
this.issues.splice(newIndex, 0, issue); this.issues.splice(newIndex, 0, issue);
gl.boardService.moveIssue(issue.id, null, null, moveBeforeId, moveAfterId) gl.boardService.moveIssue(issue.id, null, null, moveBeforeId, moveAfterId).catch(() => {
.catch(() => { // TODO: handle request error
// TODO: handle request error });
});
} }
updateIssueLabel(issue, listFrom, moveBeforeId, moveAfterId) { updateIssueLabel(issue, listFrom, moveBeforeId, moveAfterId) {
gl.boardService.moveIssue(issue.id, listFrom.id, this.id, moveBeforeId, moveAfterId) gl.boardService
.moveIssue(issue.id, listFrom.id, this.id, moveBeforeId, moveAfterId)
.catch(() => { .catch(() => {
// TODO: handle request error // TODO: handle request error
}); });
} }
findIssue (id) { findIssue(id) {
return this.issues.find(issue => issue.id === id); return this.issues.find(issue => issue.id === id);
} }
removeIssue (removeIssue) { removeIssue(removeIssue) {
this.issues = this.issues.filter((issue) => { this.issues = this.issues.filter(issue => {
const matchesRemove = removeIssue.id === issue.id; const matchesRemove = removeIssue.id === issue.id;
if (matchesRemove) { if (matchesRemove) {
......
...@@ -30,11 +30,13 @@ export default class BoardService { ...@@ -30,11 +30,13 @@ export default class BoardService {
return axios.post(this.listsEndpointGenerate, {}); return axios.post(this.listsEndpointGenerate, {});
} }
createList(labelId) { createList(entityId, entityType) {
const list = {
[entityType]: entityId,
};
return axios.post(this.listsEndpoint, { return axios.post(this.listsEndpoint, {
list: { list,
label_id: labelId,
},
}); });
} }
......
...@@ -103,8 +103,15 @@ gl.issueBoards.BoardsStore = { ...@@ -103,8 +103,15 @@ gl.issueBoards.BoardsStore = {
const listLabels = issueLists.map(listIssue => listIssue.label); const listLabels = issueLists.map(listIssue => listIssue.label);
if (!issueTo) { if (!issueTo) {
// Add to new lists issues if it doesn't already exist // Check if target list assignee is already present in this issue
listTo.addIssue(issue, listFrom, newIndex); if ((listTo.type === 'assignee' && listFrom.type === 'assignee') &&
issue.findAssignee(listTo.assignee)) {
const targetIssue = listTo.findIssue(issue.id);
targetIssue.removeAssignee(listFrom.assignee);
} else {
// Add to new lists issues if it doesn't already exist
listTo.addIssue(issue, listFrom, newIndex);
}
} else { } else {
listTo.updateIssueLabel(issue, listFrom); listTo.updateIssueLabel(issue, listFrom);
issueTo.removeLabel(listFrom.label); issueTo.removeLabel(listFrom.label);
...@@ -115,7 +122,11 @@ gl.issueBoards.BoardsStore = { ...@@ -115,7 +122,11 @@ gl.issueBoards.BoardsStore = {
list.removeIssue(issue); list.removeIssue(issue);
}); });
issue.removeLabels(listLabels); issue.removeLabels(listLabels);
} else { } else if (listTo.type === 'backlog' && listFrom.type === 'assignee') {
issue.removeAssignee(listFrom.assignee);
listFrom.removeIssue(issue);
} else if ((listTo.type !== 'label' && listFrom.type === 'assignee') ||
(listTo.type !== 'assignee' && listFrom.type === 'label')) {
listFrom.removeIssue(issue); listFrom.removeIssue(issue);
} }
}, },
...@@ -126,11 +137,12 @@ gl.issueBoards.BoardsStore = { ...@@ -126,11 +137,12 @@ gl.issueBoards.BoardsStore = {
list.moveIssue(issue, oldIndex, newIndex, beforeId, afterId); list.moveIssue(issue, oldIndex, newIndex, beforeId, afterId);
}, },
findList (key, val, type = 'label') { findList (key, val, type = 'label') {
return this.state.lists.filter((list) => { const filteredList = this.state.lists.filter((list) => {
const byType = type ? list['type'] === type : true; const byType = type ? (list.type === type) || (list.type === 'assignee') : true;
return list[key] === val && byType; return list[key] === val && byType;
})[0]; });
return filteredList[0];
}, },
updateFiltersUrl () { updateFiltersUrl () {
history.pushState(null, null, `?${this.filter.path}`); history.pushState(null, null, `?${this.filter.path}`);
......
...@@ -95,7 +95,7 @@ export default class ClusterStore { ...@@ -95,7 +95,7 @@ export default class ClusterStore {
this.state.applications.jupyter.hostname = this.state.applications.jupyter.hostname =
serverAppEntry.hostname || serverAppEntry.hostname ||
(this.state.applications.ingress.externalIp (this.state.applications.ingress.externalIp
? `jupyter.${this.state.applications.ingress.externalIp}.xip.io` ? `jupyter.${this.state.applications.ingress.externalIp}.nip.io`
: ''); : '');
} }
}); });
......
...@@ -69,9 +69,10 @@ export default () => { ...@@ -69,9 +69,10 @@ export default () => {
gl.diffNotesCompileComponents(); gl.diffNotesCompileComponents();
if (!hasVueMRDiscussionsCookie()) { const resolveCountAppEl = document.querySelector('#resolve-count-app');
if (!hasVueMRDiscussionsCookie() && resolveCountAppEl) {
new Vue({ new Vue({
el: '#resolve-count-app', el: resolveCountAppEl,
components: { components: {
'resolve-count': ResolveCount 'resolve-count': ResolveCount
}, },
......
...@@ -602,7 +602,11 @@ GitLabDropdown = (function() { ...@@ -602,7 +602,11 @@ GitLabDropdown = (function() {
var selector; var selector;
selector = '.dropdown-content'; selector = '.dropdown-content';
if (this.dropdown.find(".dropdown-toggle-page").length) { if (this.dropdown.find(".dropdown-toggle-page").length) {
selector = ".dropdown-page-one .dropdown-content"; if (this.options.containerSelector) {
selector = this.options.containerSelector;
} else {
selector = '.dropdown-page-one .dropdown-content';
}
} }
return $(selector, this.dropdown).empty(); return $(selector, this.dropdown).empty();
......
import $ from 'jquery'; import $ from 'jquery';
import { __ } from '~/locale';
import axios from './lib/utils/axios_utils'; import axios from './lib/utils/axios_utils';
import flash from './flash'; import flash from './flash';
import { __ } from './locale';
const tooltipTitles = {
group: __('Unsubscribe at group level'),
project: __('Unsubscribe at project level'),
};
export default class GroupLabelSubscription { export default class GroupLabelSubscription {
constructor(container) { constructor(container) {
...@@ -35,6 +40,7 @@ export default class GroupLabelSubscription { ...@@ -35,6 +40,7 @@ export default class GroupLabelSubscription {
this.$unsubscribeButtons.attr('data-url', url); this.$unsubscribeButtons.attr('data-url', url);
axios.post(url) axios.post(url)
.then(() => GroupLabelSubscription.setNewTooltip($btn))
.then(() => this.toggleSubscriptionButtons()) .then(() => this.toggleSubscriptionButtons())
.catch(() => flash(__('There was an error when subscribing to this label.'))); .catch(() => flash(__('There was an error when subscribing to this label.')));
} }
...@@ -44,4 +50,14 @@ export default class GroupLabelSubscription { ...@@ -44,4 +50,14 @@ export default class GroupLabelSubscription {
this.$subscribeButtons.toggleClass('hidden'); this.$subscribeButtons.toggleClass('hidden');
this.$unsubscribeButtons.toggleClass('hidden'); this.$unsubscribeButtons.toggleClass('hidden');
} }
static setNewTooltip($button) {
if (!$button.hasClass('js-subscribe-button')) return;
const type = $button.hasClass('js-group-level') ? 'group' : 'project';
const newTitle = tooltipTitles[type];
$('.js-unsubscribe-button', $button.closest('.label-actions-list'))
.tooltip('hide').attr('title', newTitle).tooltip('_fixTitle');
}
} }
...@@ -5,6 +5,7 @@ import Icon from '../../../vue_shared/components/icon.vue'; ...@@ -5,6 +5,7 @@ import Icon from '../../../vue_shared/components/icon.vue';
import { rightSidebarViews } from '../../constants'; import { rightSidebarViews } from '../../constants';
import PipelinesList from '../pipelines/list.vue'; import PipelinesList from '../pipelines/list.vue';
import JobsDetail from '../jobs/detail.vue'; import JobsDetail from '../jobs/detail.vue';
import ResizablePanel from '../resizable_panel.vue';
export default { export default {
directives: { directives: {
...@@ -14,6 +15,7 @@ export default { ...@@ -14,6 +15,7 @@ export default {
Icon, Icon,
PipelinesList, PipelinesList,
JobsDetail, JobsDetail,
ResizablePanel,
}, },
computed: { computed: {
...mapState(['rightPane']), ...mapState(['rightPane']),
...@@ -40,12 +42,16 @@ export default { ...@@ -40,12 +42,16 @@ export default {
<div <div
class="multi-file-commit-panel ide-right-sidebar" class="multi-file-commit-panel ide-right-sidebar"
> >
<div <resizable-panel
class="multi-file-commit-panel-inner"
v-if="rightPane" v-if="rightPane"
class="multi-file-commit-panel-inner"
:collapsible="false"
:initial-width="350"
:min-size="350"
side="right"
> >
<component :is="rightPane" /> <component :is="rightPane" />
</div> </resizable-panel>
<nav class="ide-activity-bar"> <nav class="ide-activity-bar">
<ul class="list-unstyled"> <ul class="list-unstyled">
<li> <li>
......
<script> <script>
/* global monaco */
import { mapState, mapGetters, mapActions } from 'vuex'; import { mapState, mapGetters, mapActions } from 'vuex';
import flash from '~/flash'; import flash from '~/flash';
import ContentViewer from '~/vue_shared/components/content_viewer/content_viewer.vue'; import ContentViewer from '~/vue_shared/components/content_viewer/content_viewer.vue';
import { activityBarViews, viewerTypes } from '../constants'; import { activityBarViews, viewerTypes } from '../constants';
import monacoLoader from '../monaco_loader';
import Editor from '../lib/editor'; import Editor from '../lib/editor';
import ExternalLink from './external_link.vue'; import ExternalLink from './external_link.vue';
...@@ -50,7 +48,7 @@ export default { ...@@ -50,7 +48,7 @@ export default {
// Compare key to allow for files opened in review mode to be cached differently // Compare key to allow for files opened in review mode to be cached differently
if (oldVal.key !== this.file.key) { if (oldVal.key !== this.file.key) {
this.initMonaco(); this.initEditor();
if (this.currentActivityView !== activityBarViews.edit) { if (this.currentActivityView !== activityBarViews.edit) {
this.setFileViewMode({ this.setFileViewMode({
...@@ -84,15 +82,10 @@ export default { ...@@ -84,15 +82,10 @@ export default {
this.editor.dispose(); this.editor.dispose();
}, },
mounted() { mounted() {
if (this.editor && monaco) { if (!this.editor) {
this.initMonaco(); this.editor = Editor.create();
} else {
monacoLoader(['vs/editor/editor.main'], () => {
this.editor = Editor.create(monaco);
this.initMonaco();
});
} }
this.initEditor();
}, },
methods: { methods: {
...mapActions([ ...mapActions([
...@@ -105,7 +98,7 @@ export default { ...@@ -105,7 +98,7 @@ export default {
'updateViewer', 'updateViewer',
'removePendingTab', 'removePendingTab',
]), ]),
initMonaco() { initEditor() {
if (this.shouldHideEditor) return; if (this.shouldHideEditor) return;
this.editor.clearEditor(); this.editor.clearEditor();
...@@ -118,7 +111,7 @@ export default { ...@@ -118,7 +111,7 @@ export default {
this.createEditorInstance(); this.createEditorInstance();
}) })
.catch(err => { .catch(err => {
flash('Error setting up monaco. Please try again.', 'alert', document, null, false, true); flash('Error setting up editor. Please try again.', 'alert', document, null, false, true);
throw err; throw err;
}); });
}, },
......
import { editor as monacoEditor, Uri } from 'monaco-editor';
import Disposable from './disposable'; import Disposable from './disposable';
import eventHub from '../../eventhub'; import eventHub from '../../eventhub';
export default class Model { export default class Model {
constructor(monaco, file, head = null) { constructor(file, head = null) {
this.monaco = monaco;
this.disposable = new Disposable(); this.disposable = new Disposable();
this.file = file; this.file = file;
this.head = head; this.head = head;
this.content = file.content !== '' ? file.content : file.raw; this.content = file.content !== '' ? file.content : file.raw;
this.disposable.add( this.disposable.add(
(this.originalModel = this.monaco.editor.createModel( (this.originalModel = monacoEditor.createModel(
head ? head.content : this.file.raw, head ? head.content : this.file.raw,
undefined, undefined,
new this.monaco.Uri(null, null, `original/${this.path}`), new Uri(false, false, `original/${this.path}`),
)), )),
(this.model = this.monaco.editor.createModel( (this.model = monacoEditor.createModel(
this.content, this.content,
undefined, undefined,
new this.monaco.Uri(null, null, this.path), new Uri(false, false, this.path),
)), )),
); );
if (this.file.mrChange) { if (this.file.mrChange) {
this.disposable.add( this.disposable.add(
(this.baseModel = this.monaco.editor.createModel( (this.baseModel = monacoEditor.createModel(
this.file.baseRaw, this.file.baseRaw,
undefined, undefined,
new this.monaco.Uri(null, null, `target/${this.path}`), new Uri(false, false, `target/${this.path}`),
)), )),
); );
} }
......
...@@ -3,8 +3,7 @@ import Disposable from './disposable'; ...@@ -3,8 +3,7 @@ import Disposable from './disposable';
import Model from './model'; import Model from './model';
export default class ModelManager { export default class ModelManager {
constructor(monaco) { constructor() {
this.monaco = monaco;
this.disposable = new Disposable(); this.disposable = new Disposable();
this.models = new Map(); this.models = new Map();
} }
...@@ -22,7 +21,7 @@ export default class ModelManager { ...@@ -22,7 +21,7 @@ export default class ModelManager {
return this.getModel(file.key); return this.getModel(file.key);
} }
const model = new Model(this.monaco, file, head); const model = new Model(file, head);
this.models.set(model.path, model); this.models.set(model.path, model);
this.disposable.add(model); this.disposable.add(model);
......
/* global monaco */ import { Range } from 'monaco-editor';
import { throttle } from 'underscore'; import { throttle } from 'underscore';
import DirtyDiffWorker from './diff_worker'; import DirtyDiffWorker from './diff_worker';
import Disposable from '../common/disposable'; import Disposable from '../common/disposable';
...@@ -16,7 +16,7 @@ export const getDiffChangeType = change => { ...@@ -16,7 +16,7 @@ export const getDiffChangeType = change => {
}; };
export const getDecorator = change => ({ export const getDecorator = change => ({
range: new monaco.Range(change.lineNumber, 1, change.endLineNumber, 1), range: new Range(change.lineNumber, 1, change.endLineNumber, 1),
options: { options: {
isWholeLine: true, isWholeLine: true,
linesDecorationsClassName: `dirty-diff dirty-diff-${getDiffChangeType(change)}`, linesDecorationsClassName: `dirty-diff dirty-diff-${getDiffChangeType(change)}`,
......
import _ from 'underscore'; import _ from 'underscore';
import { editor as monacoEditor, KeyCode, KeyMod } from 'monaco-editor';
import store from '../stores'; import store from '../stores';
import DecorationsController from './decorations/controller'; import DecorationsController from './decorations/controller';
import DirtyDiffController from './diff/controller'; import DirtyDiffController from './diff/controller';
...@@ -8,6 +9,11 @@ import editorOptions, { defaultEditorOptions } from './editor_options'; ...@@ -8,6 +9,11 @@ import editorOptions, { defaultEditorOptions } from './editor_options';
import gitlabTheme from './themes/gl_theme'; import gitlabTheme from './themes/gl_theme';
import keymap from './keymap.json'; import keymap from './keymap.json';
function setupMonacoTheme() {
monacoEditor.defineTheme(gitlabTheme.themeName, gitlabTheme.monacoTheme);
monacoEditor.setTheme('gitlab');
}
export const clearDomElement = el => { export const clearDomElement = el => {
if (!el || !el.firstChild) return; if (!el || !el.firstChild) return;
...@@ -17,24 +23,22 @@ export const clearDomElement = el => { ...@@ -17,24 +23,22 @@ export const clearDomElement = el => {
}; };
export default class Editor { export default class Editor {
static create(monaco) { static create() {
if (this.editorInstance) return this.editorInstance; if (!this.editorInstance) {
this.editorInstance = new Editor();
this.editorInstance = new Editor(monaco); }
return this.editorInstance; return this.editorInstance;
} }
constructor(monaco) { constructor() {
this.monaco = monaco;
this.currentModel = null; this.currentModel = null;
this.instance = null; this.instance = null;
this.dirtyDiffController = null; this.dirtyDiffController = null;
this.disposable = new Disposable(); this.disposable = new Disposable();
this.modelManager = new ModelManager(this.monaco); this.modelManager = new ModelManager();
this.decorationsController = new DecorationsController(this); this.decorationsController = new DecorationsController(this);
this.setupMonacoTheme(); setupMonacoTheme();
this.debouncedUpdate = _.debounce(() => { this.debouncedUpdate = _.debounce(() => {
this.updateDimensions(); this.updateDimensions();
...@@ -46,7 +50,7 @@ export default class Editor { ...@@ -46,7 +50,7 @@ export default class Editor {
clearDomElement(domElement); clearDomElement(domElement);
this.disposable.add( this.disposable.add(
(this.instance = this.monaco.editor.create(domElement, { (this.instance = monacoEditor.create(domElement, {
...defaultEditorOptions, ...defaultEditorOptions,
})), })),
(this.dirtyDiffController = new DirtyDiffController( (this.dirtyDiffController = new DirtyDiffController(
...@@ -66,7 +70,7 @@ export default class Editor { ...@@ -66,7 +70,7 @@ export default class Editor {
clearDomElement(domElement); clearDomElement(domElement);
this.disposable.add( this.disposable.add(
(this.instance = this.monaco.editor.createDiffEditor(domElement, { (this.instance = monacoEditor.createDiffEditor(domElement, {
...defaultEditorOptions, ...defaultEditorOptions,
quickSuggestions: false, quickSuggestions: false,
occurrencesHighlight: false, occurrencesHighlight: false,
...@@ -122,17 +126,11 @@ export default class Editor { ...@@ -122,17 +126,11 @@ export default class Editor {
modified: model.getModel(), modified: model.getModel(),
}); });
this.monaco.editor.createDiffNavigator(this.instance, { monacoEditor.createDiffNavigator(this.instance, {
alwaysRevealFirst: true, alwaysRevealFirst: true,
}); });
} }
setupMonacoTheme() {
this.monaco.editor.defineTheme(gitlabTheme.themeName, gitlabTheme.monacoTheme);
this.monaco.editor.setTheme('gitlab');
}
clearEditor() { clearEditor() {
if (this.instance) { if (this.instance) {
this.instance.setModel(null); this.instance.setModel(null);
...@@ -200,7 +198,7 @@ export default class Editor { ...@@ -200,7 +198,7 @@ export default class Editor {
const getKeyCode = key => { const getKeyCode = key => {
const monacoKeyMod = key.indexOf('KEY_') === 0; const monacoKeyMod = key.indexOf('KEY_') === 0;
return monacoKeyMod ? this.monaco.KeyCode[key] : this.monaco.KeyMod[key]; return monacoKeyMod ? KeyCode[key] : KeyMod[key];
}; };
keymap.forEach(command => { keymap.forEach(command => {
......
import monacoContext from 'monaco-editor/dev/vs/loader';
monacoContext.require.config({
paths: {
vs: `${__webpack_public_path__}monaco-editor/vs`, // eslint-disable-line camelcase
},
});
// ignore CDN config and use local assets path for service worker which cannot be cross-domain
const relativeRootPath = (gon && gon.relative_url_root) || '';
const monacoPath = `${relativeRootPath}/assets/webpack/monaco-editor/vs`;
window.MonacoEnvironment = { getWorkerUrl: () => `${monacoPath}/base/worker/workerMain.js` };
// eslint-disable-next-line no-underscore-dangle
window.__monaco_context__ = monacoContext;
export default monacoContext.require;
...@@ -31,10 +31,15 @@ export const openMergeRequest = ({ commit, dispatch }, { projectPath, id }) => { ...@@ -31,10 +31,15 @@ export const openMergeRequest = ({ commit, dispatch }, { projectPath, id }) => {
commit(rootTypes.CLEAR_PROJECTS, null, { root: true }); commit(rootTypes.CLEAR_PROJECTS, null, { root: true });
commit(rootTypes.SET_CURRENT_MERGE_REQUEST, `${id}`, { root: true }); commit(rootTypes.SET_CURRENT_MERGE_REQUEST, `${id}`, { root: true });
commit(rootTypes.RESET_OPEN_FILES, null, { root: true }); commit(rootTypes.RESET_OPEN_FILES, null, { root: true });
dispatch('pipelines/stopPipelinePolling', null, { root: true });
dispatch('pipelines/clearEtagPoll', null, { root: true });
dispatch('pipelines/resetLatestPipeline', null, { root: true }); dispatch('pipelines/resetLatestPipeline', null, { root: true });
dispatch('setCurrentBranchId', '', { root: true }); dispatch('setCurrentBranchId', '', { root: true });
dispatch('pipelines/stopPipelinePolling', null, { root: true })
.then(() => {
dispatch('pipelines/clearEtagPoll', null, { root: true });
})
.catch(e => {
throw e;
});
router.push(`/project/${projectPath}/merge_requests/${id}`); router.push(`/project/${projectPath}/merge_requests/${id}`);
}; };
......
...@@ -12,8 +12,12 @@ let eTagPoll; ...@@ -12,8 +12,12 @@ let eTagPoll;
export const clearEtagPoll = () => { export const clearEtagPoll = () => {
eTagPoll = null; eTagPoll = null;
}; };
export const stopPipelinePolling = () => eTagPoll && eTagPoll.stop(); export const stopPipelinePolling = () => {
export const restartPipelinePolling = () => eTagPoll && eTagPoll.restart(); if (eTagPoll) eTagPoll.stop();
};
export const restartPipelinePolling = () => {
if (eTagPoll) eTagPoll.restart();
};
export const requestLatestPipeline = ({ commit }) => commit(types.REQUEST_LATEST_PIPELINE); export const requestLatestPipeline = ({ commit }) => commit(types.REQUEST_LATEST_PIPELINE);
export const receiveLatestPipelineError = ({ commit, dispatch }) => { export const receiveLatestPipelineError = ({ commit, dispatch }) => {
...@@ -51,9 +55,9 @@ export const fetchLatestPipeline = ({ dispatch, rootGetters }) => { ...@@ -51,9 +55,9 @@ export const fetchLatestPipeline = ({ dispatch, rootGetters }) => {
Visibility.change(() => { Visibility.change(() => {
if (!Visibility.hidden()) { if (!Visibility.hidden()) {
eTagPoll.restart(); dispatch('restartPipelinePolling');
} else { } else {
eTagPoll.stop(); dispatch('stopPipelinePolling');
} }
}); });
}; };
......
import $ from 'jquery'; import $ from 'jquery';
import stickyMonitor from './lib/utils/sticky'; import { stickyMonitor } from './lib/utils/sticky';
export default (stickyTop) => { export default (stickyTop) => {
stickyMonitor(document.querySelector('.js-diff-files-changed'), stickyTop); stickyMonitor(document.querySelector('.js-diff-files-changed'), stickyTop);
......
import $ from 'jquery'; import $ from 'jquery';
import _ from 'underscore'; import _ from 'underscore';
import StickyFill from 'stickyfilljs'; import { polyfillSticky } from './lib/utils/sticky';
import axios from './lib/utils/axios_utils'; import axios from './lib/utils/axios_utils';
import { visitUrl } from './lib/utils/url_utility'; import { visitUrl } from './lib/utils/url_utility';
import bp from './breakpoints'; import bp from './breakpoints';
...@@ -70,14 +70,7 @@ export default class Job extends LogOutputBehaviours { ...@@ -70,14 +70,7 @@ export default class Job extends LogOutputBehaviours {
} }
initAffixTopArea() { initAffixTopArea() {
/** polyfillSticky(this.$topBar);
If the browser does not support position sticky, it returns the position as static.
If the browser does support sticky, then we allow the browser to handle it, if not
then we use a polyfill
*/
if (this.$topBar.css('position') !== 'static') return;
StickyFill.add(this.$topBar);
} }
scrollToBottom() { scrollToBottom() {
......
...@@ -13,6 +13,7 @@ export default class LabelManager { ...@@ -13,6 +13,7 @@ export default class LabelManager {
this.otherLabels = otherLabels || $('.js-other-labels'); this.otherLabels = otherLabels || $('.js-other-labels');
this.errorMessage = 'Unable to update label prioritization at this time'; this.errorMessage = 'Unable to update label prioritization at this time';
this.emptyState = document.querySelector('#js-priority-labels-empty-state'); this.emptyState = document.querySelector('#js-priority-labels-empty-state');
this.$badgeItemTemplate = $('#js-badge-item-template');
this.sortable = Sortable.create(this.prioritizedLabels.get(0), { this.sortable = Sortable.create(this.prioritizedLabels.get(0), {
filter: '.empty-message', filter: '.empty-message',
forceFallback: true, forceFallback: true,
...@@ -63,7 +64,11 @@ export default class LabelManager { ...@@ -63,7 +64,11 @@ export default class LabelManager {
$target = this.otherLabels; $target = this.otherLabels;
$from = this.prioritizedLabels; $from = this.prioritizedLabels;
} }
$label.detach().appendTo($target);
const $detachedLabel = $label.detach();
this.toggleLabelPriorityBadge($detachedLabel, action);
$detachedLabel.appendTo($target);
if ($from.find('li').length) { if ($from.find('li').length) {
$from.find('.empty-message').removeClass('hidden'); $from.find('.empty-message').removeClass('hidden');
} }
...@@ -88,6 +93,14 @@ export default class LabelManager { ...@@ -88,6 +93,14 @@ export default class LabelManager {
} }
} }
toggleLabelPriorityBadge($label, action) {
if (action === 'remove') {
$('.js-priority-badge', $label).remove();
} else {
$('.label-links', $label).append(this.$badgeItemTemplate.clone().html());
}
}
onPrioritySortUpdate() { onPrioritySortUpdate() {
this.savePrioritySort() this.savePrioritySort()
.catch(() => flash(this.errorMessage)); .catch(() => flash(this.errorMessage));
......
...@@ -384,6 +384,49 @@ export const backOff = (fn, timeout = 60000) => { ...@@ -384,6 +384,49 @@ export const backOff = (fn, timeout = 60000) => {
}); });
}; };
export const createOverlayIcon = (iconPath, overlayPath) => {
const faviconImage = document.createElement('img');
return new Promise((resolve) => {
faviconImage.onload = () => {
const size = 32;
const canvas = document.createElement('canvas');
canvas.width = size;
canvas.height = size;
const context = canvas.getContext('2d');
context.clearRect(0, 0, size, size);
context.drawImage(
faviconImage, 0, 0, faviconImage.width, faviconImage.height, 0, 0, size, size,
);
const overlayImage = document.createElement('img');
overlayImage.onload = () => {
context.drawImage(
overlayImage, 0, 0, overlayImage.width, overlayImage.height, 0, 0, size, size,
);
const faviconWithOverlayUrl = canvas.toDataURL();
resolve(faviconWithOverlayUrl);
};
overlayImage.src = overlayPath;
};
faviconImage.src = iconPath;
});
};
export const setFaviconOverlay = (overlayPath) => {
const faviconEl = document.getElementById('favicon');
if (!faviconEl) { return null; }
const iconPath = faviconEl.getAttribute('data-original-href');
return createOverlayIcon(iconPath, overlayPath).then(faviconWithOverlayUrl => faviconEl.setAttribute('href', faviconWithOverlayUrl));
};
export const setFavicon = (faviconPath) => { export const setFavicon = (faviconPath) => {
const faviconEl = document.getElementById('favicon'); const faviconEl = document.getElementById('favicon');
if (faviconEl && faviconPath) { if (faviconEl && faviconPath) {
...@@ -393,8 +436,9 @@ export const setFavicon = (faviconPath) => { ...@@ -393,8 +436,9 @@ export const setFavicon = (faviconPath) => {
export const resetFavicon = () => { export const resetFavicon = () => {
const faviconEl = document.getElementById('favicon'); const faviconEl = document.getElementById('favicon');
const originalFavicon = faviconEl ? faviconEl.getAttribute('href') : null;
if (faviconEl) { if (faviconEl) {
const originalFavicon = faviconEl.getAttribute('data-original-href');
faviconEl.setAttribute('href', originalFavicon); faviconEl.setAttribute('href', originalFavicon);
} }
}; };
...@@ -403,10 +447,9 @@ export const setCiStatusFavicon = pageUrl => ...@@ -403,10 +447,9 @@ export const setCiStatusFavicon = pageUrl =>
axios.get(pageUrl) axios.get(pageUrl)
.then(({ data }) => { .then(({ data }) => {
if (data && data.favicon) { if (data && data.favicon) {
setFavicon(data.favicon); return setFaviconOverlay(data.favicon);
} else {
resetFavicon();
} }
return resetFavicon();
}) })
.catch(resetFavicon); .catch(resetFavicon);
......
...@@ -79,37 +79,37 @@ export function getTimeago() { ...@@ -79,37 +79,37 @@ export function getTimeago() {
if (!timeagoInstance) { if (!timeagoInstance) {
const localeRemaining = function getLocaleRemaining(number, index) { const localeRemaining = function getLocaleRemaining(number, index) {
return [ return [
[s__('Timeago|less than a minute ago'), s__('Timeago|right now')], [s__('Timeago|just now'), s__('Timeago|right now')],
[s__('Timeago|less than a minute ago'), s__('Timeago|%s seconds remaining')], [s__('Timeago|%s seconds ago'), s__('Timeago|%s seconds remaining')],
[s__('Timeago|about a minute ago'), s__('Timeago|1 minute remaining')], [s__('Timeago|1 minute ago'), s__('Timeago|1 minute remaining')],
[s__('Timeago|%s minutes ago'), s__('Timeago|%s minutes remaining')], [s__('Timeago|%s minutes ago'), s__('Timeago|%s minutes remaining')],
[s__('Timeago|about an hour ago'), s__('Timeago|1 hour remaining')], [s__('Timeago|1 hour ago'), s__('Timeago|1 hour remaining')],
[s__('Timeago|about %s hours ago'), s__('Timeago|%s hours remaining')], [s__('Timeago|%s hours ago'), s__('Timeago|%s hours remaining')],
[s__('Timeago|a day ago'), s__('Timeago|1 day remaining')], [s__('Timeago|1 day ago'), s__('Timeago|1 day remaining')],
[s__('Timeago|%s days ago'), s__('Timeago|%s days remaining')], [s__('Timeago|%s days ago'), s__('Timeago|%s days remaining')],
[s__('Timeago|a week ago'), s__('Timeago|1 week remaining')], [s__('Timeago|1 week ago'), s__('Timeago|1 week remaining')],
[s__('Timeago|%s weeks ago'), s__('Timeago|%s weeks remaining')], [s__('Timeago|%s weeks ago'), s__('Timeago|%s weeks remaining')],
[s__('Timeago|a month ago'), s__('Timeago|1 month remaining')], [s__('Timeago|1 month ago'), s__('Timeago|1 month remaining')],
[s__('Timeago|%s months ago'), s__('Timeago|%s months remaining')], [s__('Timeago|%s months ago'), s__('Timeago|%s months remaining')],
[s__('Timeago|a year ago'), s__('Timeago|1 year remaining')], [s__('Timeago|1 year ago'), s__('Timeago|1 year remaining')],
[s__('Timeago|%s years ago'), s__('Timeago|%s years remaining')], [s__('Timeago|%s years ago'), s__('Timeago|%s years remaining')],
][index]; ][index];
}; };
const locale = function getLocale(number, index) { const locale = function getLocale(number, index) {
return [ return [
[s__('Timeago|less than a minute ago'), s__('Timeago|right now')], [s__('Timeago|just now'), s__('Timeago|right now')],
[s__('Timeago|less than a minute ago'), s__('Timeago|in %s seconds')], [s__('Timeago|%s seconds ago'), s__('Timeago|in %s seconds')],
[s__('Timeago|about a minute ago'), s__('Timeago|in 1 minute')], [s__('Timeago|1 minute ago'), s__('Timeago|in 1 minute')],
[s__('Timeago|%s minutes ago'), s__('Timeago|in %s minutes')], [s__('Timeago|%s minutes ago'), s__('Timeago|in %s minutes')],
[s__('Timeago|about an hour ago'), s__('Timeago|in 1 hour')], [s__('Timeago|1 hour ago'), s__('Timeago|in 1 hour')],
[s__('Timeago|about %s hours ago'), s__('Timeago|in %s hours')], [s__('Timeago|%s hours ago'), s__('Timeago|in %s hours')],
[s__('Timeago|a day ago'), s__('Timeago|in 1 day')], [s__('Timeago|1 day ago'), s__('Timeago|in 1 day')],
[s__('Timeago|%s days ago'), s__('Timeago|in %s days')], [s__('Timeago|%s days ago'), s__('Timeago|in %s days')],
[s__('Timeago|a week ago'), s__('Timeago|in 1 week')], [s__('Timeago|1 week ago'), s__('Timeago|in 1 week')],
[s__('Timeago|%s weeks ago'), s__('Timeago|in %s weeks')], [s__('Timeago|%s weeks ago'), s__('Timeago|in %s weeks')],
[s__('Timeago|a month ago'), s__('Timeago|in 1 month')], [s__('Timeago|1 month ago'), s__('Timeago|in 1 month')],
[s__('Timeago|%s months ago'), s__('Timeago|in %s months')], [s__('Timeago|%s months ago'), s__('Timeago|in %s months')],
[s__('Timeago|a year ago'), s__('Timeago|in 1 year')], [s__('Timeago|1 year ago'), s__('Timeago|in 1 year')],
[s__('Timeago|%s years ago'), s__('Timeago|in %s years')], [s__('Timeago|%s years ago'), s__('Timeago|in %s years')],
][index]; ][index];
}; };
...@@ -269,6 +269,17 @@ export const totalDaysInMonth = date => { ...@@ -269,6 +269,17 @@ export const totalDaysInMonth = date => {
return new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate(); return new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate();
}; };
/**
* Returns number of days in a quarter from provided
* months array.
*
* @param {Array} quarter
*/
export const totalDaysInQuarter = quarter => quarter.reduce(
(acc, month) => acc + totalDaysInMonth(month),
0,
);
/** /**
* Returns list of Dates referring to Sundays of the month * Returns list of Dates referring to Sundays of the month
* based on provided date * based on provided date
...@@ -309,42 +320,27 @@ export const getSundays = date => { ...@@ -309,42 +320,27 @@ export const getSundays = date => {
}; };
/** /**
* Returns list of Dates representing a timeframe of Months from month of provided date (inclusive) * Returns list of Dates representing a timeframe of months from startDate and length
* up to provided length
*
* For eg;
* If current month is January 2018 and `length` provided is `6`
* Then this method will return list of Date objects as follows;
*
* [ October 2017, November 2017, December 2017, January 2018, February 2018, March 2018 ]
*
* If current month is March 2018 and `length` provided is `3`
* Then this method will return list of Date objects as follows;
*
* [ February 2018, March 2018, April 2018 ]
* *
* @param {Date} startDate
* @param {Number} length * @param {Number} length
* @param {Date} date
*/ */
export const getTimeframeWindow = (length, date) => { export const getTimeframeWindowFrom = (startDate, length) => {
if (!length) { if (!(startDate instanceof Date) || !length) {
return []; return [];
} }
const currentDate = date instanceof Date ? date : new Date(); // Iterate and set date for the size of length
const currentMonthIndex = Math.floor(length / 2);
const timeframe = [];
// Move date object backward to the first month of timeframe
currentDate.setDate(1);
currentDate.setMonth(currentDate.getMonth() - currentMonthIndex);
// Iterate and update date for the size of length
// and push date reference to timeframe list // and push date reference to timeframe list
for (let i = 0; i < length; i += 1) { const timeframe = new Array(length)
timeframe.push(new Date(currentDate.getTime())); .fill()
currentDate.setMonth(currentDate.getMonth() + 1); .map(
} (val, i) => new Date(
startDate.getFullYear(),
startDate.getMonth() + i,
1,
),
);
// Change date of last timeframe item to last date of the month // Change date of last timeframe item to last date of the month
timeframe[length - 1].setDate(totalDaysInMonth(timeframe[length - 1])); timeframe[length - 1].setDate(totalDaysInMonth(timeframe[length - 1]));
...@@ -352,6 +348,29 @@ export const getTimeframeWindow = (length, date) => { ...@@ -352,6 +348,29 @@ export const getTimeframeWindow = (length, date) => {
return timeframe; return timeframe;
}; };
/**
* Returns count of day within current quarter from provided date
* and array of months for the quarter
*
* Eg;
* If date is 15 Feb 2018
* and quarter is [Jan, Feb, Mar]
*
* Then 15th Feb is 46th day of the quarter
* Where 31 (days in Jan) + 15 (date of Feb).
*
* @param {Date} date
* @param {Array} quarter
*/
export const dayInQuarter = (date, quarter) => quarter.reduce((acc, month) => {
if (date.getMonth() > month.getMonth()) {
return acc + totalDaysInMonth(month);
} else if (date.getMonth() === month.getMonth()) {
return acc + date.getDate();
}
return acc + 0;
}, 0);
window.gl = window.gl || {}; window.gl = window.gl || {};
window.gl.utils = { window.gl.utils = {
...(window.gl.utils || {}), ...(window.gl.utils || {}),
......
...@@ -8,4 +8,5 @@ export default { ...@@ -8,4 +8,5 @@ export default {
OK: 200, OK: 200,
MULTIPLE_CHOICES: 300, MULTIPLE_CHOICES: 300,
BAD_REQUEST: 400, BAD_REQUEST: 400,
NOT_FOUND: 404,
}; };
import StickyFill from 'stickyfilljs';
export const createPlaceholder = () => { export const createPlaceholder = () => {
const placeholder = document.createElement('div'); const placeholder = document.createElement('div');
placeholder.classList.add('sticky-placeholder'); placeholder.classList.add('sticky-placeholder');
...@@ -28,7 +30,16 @@ export const isSticky = (el, scrollY, stickyTop, insertPlaceholder) => { ...@@ -28,7 +30,16 @@ export const isSticky = (el, scrollY, stickyTop, insertPlaceholder) => {
} }
}; };
export default (el, stickyTop, insertPlaceholder = true) => { /**
* Create a listener that will toggle a 'is-stuck' class, based on the current scroll position.
*
* - If the current environment does not support `position: sticky`, do nothing.
*
* @param {HTMLElement} el The `position: sticky` element.
* @param {Number} stickyTop Used to determine when an element is stuck.
* @param {Boolean} insertPlaceholder Should a placeholder element be created when element is stuck?
*/
export const stickyMonitor = (el, stickyTop, insertPlaceholder = true) => {
if (!el) return; if (!el) return;
if (typeof CSS === 'undefined' || !(CSS.supports('(position: -webkit-sticky) or (position: sticky)'))) return; if (typeof CSS === 'undefined' || !(CSS.supports('(position: -webkit-sticky) or (position: sticky)'))) return;
...@@ -37,3 +48,13 @@ export default (el, stickyTop, insertPlaceholder = true) => { ...@@ -37,3 +48,13 @@ export default (el, stickyTop, insertPlaceholder = true) => {
passive: true, passive: true,
}); });
}; };
/**
* Polyfill the `position: sticky` behavior.
*
* - If the current environment supports `position: sticky`, do nothing.
* - Can receive an iterable element list (NodeList, jQuery collection, etc.) or single HTMLElement.
*/
export const polyfillSticky = (el) => {
StickyFill.add(el);
};
...@@ -85,9 +85,9 @@ export function redirectTo(url) { ...@@ -85,9 +85,9 @@ export function redirectTo(url) {
} }
export function webIDEUrl(route = undefined) { export function webIDEUrl(route = undefined) {
let returnUrl = `${gon.relative_url_root}/-/ide/`; let returnUrl = `${gon.relative_url_root || ''}/-/ide/`;
if (route) { if (route) {
returnUrl += `project${route}`; returnUrl += `project${route.replace(new RegExp(`^${gon.relative_url_root || ''}`), '')}`;
} }
return returnUrl; return returnUrl;
} }
...@@ -144,6 +144,7 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -144,6 +144,7 @@ document.addEventListener('DOMContentLoaded', () => {
$body.tooltip({ $body.tooltip({
selector: '.has-tooltip, [data-toggle="tooltip"]', selector: '.has-tooltip, [data-toggle="tooltip"]',
trigger: 'hover', trigger: 'hover',
boundary: 'viewport',
placement(tip, el) { placement(tip, el) {
return $(el).data('placement') || 'bottom'; return $(el).data('placement') || 'bottom';
}, },
......
...@@ -22,4 +22,18 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -22,4 +22,18 @@ document.addEventListener('DOMContentLoaded', () => {
errorBox: variableListEl.querySelector('.js-ci-variable-error-box'), errorBox: variableListEl.querySelector('.js-ci-variable-error-box'),
saveEndpoint: variableListEl.dataset.saveEndpoint, saveEndpoint: variableListEl.dataset.saveEndpoint,
}); });
// hide extra auto devops settings based on data-attributes
const autoDevOpsSettings = document.querySelector('.js-auto-devops-settings');
const autoDevOpsExtraSettings = document.querySelector('.js-extra-settings');
autoDevOpsSettings.addEventListener('click', event => {
const target = event.target;
if (target.classList.contains('js-toggle-extra-settings')) {
autoDevOpsExtraSettings.classList.toggle(
'hidden',
!!(target.dataset && target.dataset.hideExtraSettings),
);
}
});
}); });
...@@ -3,6 +3,17 @@ import { __ } from './locale'; ...@@ -3,6 +3,17 @@ import { __ } from './locale';
import axios from './lib/utils/axios_utils'; import axios from './lib/utils/axios_utils';
import flash from './flash'; import flash from './flash';
const tooltipTitles = {
group: {
subscribed: __('Unsubscribe at group level'),
unsubscribed: __('Subscribe at group level'),
},
project: {
subscribed: __('Unsubscribe at project level'),
unsubscribed: __('Subscribe at project level'),
},
};
export default class ProjectLabelSubscription { export default class ProjectLabelSubscription {
constructor(container) { constructor(container) {
this.$container = $(container); this.$container = $(container);
...@@ -15,12 +26,10 @@ export default class ProjectLabelSubscription { ...@@ -15,12 +26,10 @@ export default class ProjectLabelSubscription {
event.preventDefault(); event.preventDefault();
const $btn = $(event.currentTarget); const $btn = $(event.currentTarget);
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');
axios.post(url).then(() => { axios.post(url).then(() => {
let newStatus; let newStatus;
...@@ -32,21 +41,28 @@ export default class ProjectLabelSubscription { ...@@ -32,21 +41,28 @@ export default class ProjectLabelSubscription {
[newStatus, newAction] = ['unsubscribed', 'Subscribe']; [newStatus, newAction] = ['unsubscribed', 'Subscribe'];
} }
$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((i, button) => {
const $button = $(button); const $button = $(button);
const originalTitle = $button.attr('data-original-title');
if ($button.attr('data-original-title')) { if (originalTitle) {
$button.tooltip('hide').attr('data-original-title', newAction).tooltip('_fixTitle'); ProjectLabelSubscription.setNewTitle($button, originalTitle, newStatus, newAction);
} }
return button; return button;
}); });
}).catch(() => flash(__('There was an error subscribing to this label.'))); }).catch(() => flash(__('There was an error subscribing to this label.')));
} }
static setNewTitle($button, originalTitle, newStatus) {
const type = /group/.test(originalTitle) ? 'group' : 'project';
const newTitle = tooltipTitles[type][newStatus];
$button.attr('title', newTitle).tooltip('_fixTitle');
}
} }
...@@ -89,14 +89,13 @@ export default { ...@@ -89,14 +89,13 @@ export default {
<div> <div>
<div <div
class="js-gcp-machine-type-dropdown dropdown" class="js-gcp-machine-type-dropdown dropdown"
:class="{ 'gl-show-field-errors': hasErrors }"
> >
<dropdown-hidden-input <dropdown-hidden-input
:name="fieldName" :name="fieldName"
:value="selectedMachineType" :value="selectedMachineType"
/> />
<dropdown-button <dropdown-button
:class="{ 'gl-field-error-outline': hasErrors }" :class="{ 'border-danger': hasErrors }"
:is-disabled="isDisabled" :is-disabled="isDisabled"
:is-loading="isLoading" :is-loading="isLoading"
:toggle-text="toggleText" :toggle-text="toggleText"
...@@ -132,8 +131,11 @@ export default { ...@@ -132,8 +131,11 @@ export default {
</div> </div>
</div> </div>
<span <span
class="form-text text-muted" class="form-text"
:class="{ 'gl-field-error': hasErrors }" :class="{
'text-danger': hasErrors,
'text-muted': !hasErrors
}"
v-if="hasErrors" v-if="hasErrors"
> >
{{ errorMessage }} {{ errorMessage }}
......
...@@ -147,7 +147,6 @@ export default { ...@@ -147,7 +147,6 @@ export default {
<div> <div>
<div <div
class="js-gcp-project-id-dropdown dropdown" class="js-gcp-project-id-dropdown dropdown"
:class="{ 'gl-show-field-errors': hasErrors }"
> >
<dropdown-hidden-input <dropdown-hidden-input
:name="fieldName" :name="fieldName"
...@@ -155,7 +154,7 @@ export default { ...@@ -155,7 +154,7 @@ export default {
/> />
<dropdown-button <dropdown-button
:class="{ :class="{
'gl-field-error-outline': hasErrors, 'border-danger': hasErrors,
'read-only': hasOneProject 'read-only': hasOneProject
}" }"
:is-disabled="isDisabled" :is-disabled="isDisabled"
...@@ -193,8 +192,11 @@ export default { ...@@ -193,8 +192,11 @@ export default {
</div> </div>
</div> </div>
<span <span
class="form-text text-muted" class="form-text"
:class="{ 'gl-field-error': hasErrors }" :class="{
'text-danger': hasErrors,
'text-muted': !hasErrors
}"
v-html="helpText" v-html="helpText"
></span> ></span>
</div> </div>
......
...@@ -63,14 +63,13 @@ export default { ...@@ -63,14 +63,13 @@ export default {
<div> <div>
<div <div
class="js-gcp-zone-dropdown dropdown" class="js-gcp-zone-dropdown dropdown"
:class="{ 'gl-show-field-errors': hasErrors }"
> >
<dropdown-hidden-input <dropdown-hidden-input
:name="fieldName" :name="fieldName"
:value="selectedZone" :value="selectedZone"
/> />
<dropdown-button <dropdown-button
:class="{ 'gl-field-error-outline': hasErrors }" :class="{ 'border-danger': hasErrors }"
:is-disabled="isDisabled" :is-disabled="isDisabled"
:is-loading="isLoading" :is-loading="isLoading"
:toggle-text="toggleText" :toggle-text="toggleText"
...@@ -106,8 +105,11 @@ export default { ...@@ -106,8 +105,11 @@ export default {
</div> </div>
</div> </div>
<span <span
class="form-text text-muted" class="form-text"
:class="{ 'gl-field-error': hasErrors }" :class="{
'text-danger': hasErrors,
'text-muted': !hasErrors
}"
v-if="hasErrors" v-if="hasErrors"
> >
{{ errorMessage }} {{ errorMessage }}
......
<script> <script>
import { parseSeconds, stringifyTime } from '../../../lib/utils/pretty_time'; import { parseSeconds, stringifyTime } from '../../../lib/utils/pretty_time';
import tooltip from '../../../vue_shared/directives/tooltip';
export default { export default {
name: 'TimeTrackingComparisonPane', name: 'TimeTrackingComparisonPane',
directives: {
tooltip,
},
props: { props: {
timeSpent: { timeSpent: {
type: Number, type: Number,
...@@ -51,17 +55,12 @@ export default { ...@@ -51,17 +55,12 @@ export default {
<div class="time-tracking-comparison-pane"> <div class="time-tracking-comparison-pane">
<div <div
class="compare-meter" class="compare-meter"
data-toggle="tooltip"
data-placement="top"
role="timeRemainingDisplay"
:aria-valuenow="timeRemainingTooltip"
:title="timeRemainingTooltip" :title="timeRemainingTooltip"
:data-original-title="timeRemainingTooltip" v-tooltip
:class="timeRemainingStatusClass" :class="timeRemainingStatusClass"
> >
<div <div
class="meter-container" class="meter-container"
role="timeSpentPercent"
:aria-valuenow="timeRemainingPercent" :aria-valuenow="timeRemainingPercent"
> >
<div <div
......
<script> <script>
import tooltip from '~/vue_shared/directives/tooltip';
import MrWidgetAuthor from './mr_widget_author.vue'; import MrWidgetAuthor from './mr_widget_author.vue';
export default { export default {
name: 'MRWidgetAuthorTime', name: 'MrWidgetAuthorTime',
components: { components: {
MrWidgetAuthor, MrWidgetAuthor,
}, },
directives: {
tooltip,
},
props: { props: {
actionText: { actionText: {
type: String, type: String,
...@@ -32,8 +36,7 @@ ...@@ -32,8 +36,7 @@
<mr-widget-author :author="author" /> <mr-widget-author :author="author" />
<time <time
:title="dateTitle" :title="dateTitle"
data-toggle="tooltip" v-tooltip
data-placement="top"
data-container="body" data-container="body"
> >
{{ dateReadable }} {{ dateReadable }}
......
<script> <script>
import mrWidgetAuthorTime from '../../components/mr_widget_author_time.vue'; import MrWidgetAuthorTime from '../../components/mr_widget_author_time.vue';
import statusIcon from '../mr_widget_status_icon.vue'; import statusIcon from '../mr_widget_status_icon.vue';
export default { export default {
name: 'MRWidgetClosed', name: 'MRWidgetClosed',
components: { components: {
mrWidgetAuthorTime, MrWidgetAuthorTime,
statusIcon, statusIcon,
}, },
props: { props: {
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
import loadingIcon from '~/vue_shared/components/loading_icon.vue'; import loadingIcon from '~/vue_shared/components/loading_icon.vue';
import { s__, __ } from '~/locale'; import { s__, __ } from '~/locale';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import mrWidgetAuthorTime from '../../components/mr_widget_author_time.vue'; import MrWidgetAuthorTime from '../../components/mr_widget_author_time.vue';
import statusIcon from '../mr_widget_status_icon.vue'; import statusIcon from '../mr_widget_status_icon.vue';
import eventHub from '../../event_hub'; import eventHub from '../../event_hub';
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
tooltip, tooltip,
}, },
components: { components: {
mrWidgetAuthorTime, MrWidgetAuthorTime,
loadingIcon, loadingIcon,
statusIcon, statusIcon,
ClipboardButton, ClipboardButton,
......
...@@ -36,7 +36,7 @@ import { ...@@ -36,7 +36,7 @@ import {
notify, notify,
SourceBranchRemovalStatus, SourceBranchRemovalStatus,
} from './dependencies'; } from './dependencies';
import { setFavicon } from '../lib/utils/common_utils'; import { setFaviconOverlay } from '../lib/utils/common_utils';
export default { export default {
el: '#js-vue-mr-widget', el: '#js-vue-mr-widget',
...@@ -159,8 +159,9 @@ export default { ...@@ -159,8 +159,9 @@ export default {
}, },
setFaviconHelper() { setFaviconHelper() {
if (this.mr.ciStatusFaviconPath) { if (this.mr.ciStatusFaviconPath) {
setFavicon(this.mr.ciStatusFaviconPath); return setFaviconOverlay(this.mr.ciStatusFaviconPath);
} }
return Promise.resolve();
}, },
fetchDeployments() { fetchDeployments() {
return this.service.fetchDeployments() return this.service.fetchDeployments()
......
...@@ -513,7 +513,7 @@ const fileNameIcons = { ...@@ -513,7 +513,7 @@ const fileNameIcons = {
'credits.md': 'credits', 'credits.md': 'credits',
'credits.md.rendered': 'credits', 'credits.md.rendered': 'credits',
'.flowconfig': 'flow', '.flowconfig': 'flow',
'favicon.ico': 'favicon', 'favicon.png': 'favicon',
'karma.conf.js': 'karma', 'karma.conf.js': 'karma',
'karma.conf.ts': 'karma', 'karma.conf.ts': 'karma',
'karma.conf.coffee': 'karma', 'karma.conf.coffee': 'karma',
......
export default class ListAssignee {
constructor(obj, defaultAvatar) {
this.id = obj.id;
this.name = obj.name;
this.username = obj.username;
this.avatar = obj.avatar_url || obj.avatar || defaultAvatar;
this.path = obj.path;
this.state = obj.state;
this.webUrl = obj.web_url || obj.webUrl;
}
}
window.ListAssignee = ListAssignee;
...@@ -89,6 +89,10 @@ a { ...@@ -89,6 +89,10 @@ a {
color: $gl-link-color; color: $gl-link-color;
} }
hr {
overflow: hidden;
}
.form-group.row .col-form-label { .form-group.row .col-form-label {
// Bootstrap 4 aligns labels to the left // Bootstrap 4 aligns labels to the left
// for horizontal forms // for horizontal forms
...@@ -107,7 +111,7 @@ code { ...@@ -107,7 +111,7 @@ code {
background-color: $red-100; background-color: $red-100;
border-radius: 3px; border-radius: 3px;
.code & { .code > & {
background-color: inherit; background-color: inherit;
padding: unset; padding: unset;
} }
...@@ -118,10 +122,6 @@ code { ...@@ -118,10 +122,6 @@ code {
} }
} }
.code {
padding: 9.5px;
}
table { table {
// Remove any table border lines // Remove any table border lines
border-spacing: 0; border-spacing: 0;
...@@ -213,6 +213,10 @@ table { ...@@ -213,6 +213,10 @@ table {
border-bottom: 1px solid $well-inner-border; border-bottom: 1px solid $well-inner-border;
} }
} }
.badge.badge-gray {
background-color: $well-expand-item;
}
} }
.card { .card {
...@@ -233,6 +237,13 @@ table { ...@@ -233,6 +237,13 @@ table {
} }
} }
.card-header {
h3.card-title,
h4.card-title {
margin-top: 0;
}
}
.nav-tabs { .nav-tabs {
// Override bootstrap's default border // Override bootstrap's default border
border-bottom: 0; border-bottom: 0;
...@@ -251,3 +262,33 @@ table { ...@@ -251,3 +262,33 @@ table {
pre code { pre code {
white-space: pre-wrap; white-space: pre-wrap;
} }
.alert-danger {
background-color: $red-500;
border-color: $red-500;
}
.alert-warning,
.alert-danger,
.flash-notice {
border-radius: 0;
color: $white-light;
h4,
a,
.alert-link {
color: $white-light;
}
}
input[type=color].form-control {
height: $input-height;
}
.toggle-sidebar-button {
.collapse-text,
.icon-angle-double-left,
.icon-angle-double-right {
color: $gl-text-color-secondary;
}
}
...@@ -497,6 +497,10 @@ fieldset[disabled] .btn, ...@@ -497,6 +497,10 @@ fieldset[disabled] .btn,
} }
} }
[readonly] {
cursor: default;
}
.btn-no-padding { .btn-no-padding {
padding: 0; padding: 0;
} }
...@@ -305,14 +305,6 @@ img.emoji { ...@@ -305,14 +305,6 @@ img.emoji {
margin-bottom: 10px; margin-bottom: 10px;
} }
.btn-sign-in {
text-shadow: none;
@include media-breakpoint-up(sm) {
margin-top: 8px;
}
}
.side-filters { .side-filters {
fieldset { fieldset {
margin-bottom: 15px; margin-bottom: 15px;
......
...@@ -299,6 +299,7 @@ ...@@ -299,6 +299,7 @@
height: 14px; height: 14px;
width: 14px; width: 14px;
vertical-align: middle; vertical-align: middle;
margin-bottom: 4px;
} }
.dropdown-toggle-text { .dropdown-toggle-text {
......
...@@ -170,7 +170,7 @@ label { ...@@ -170,7 +170,7 @@ label {
} }
.form-control::-webkit-input-placeholder { .form-control::-webkit-input-placeholder {
color: $gl-text-color-secondary; color: $placeholder-text-color;
} }
.input-group { .input-group {
......
...@@ -3,26 +3,26 @@ ...@@ -3,26 +3,26 @@
*/ */
@mixin gitlab-theme( @mixin gitlab-theme(
$color-100, $location-badge-color,
$color-200, $search-and-nav-links,
$color-500, $active-tab-border,
$color-700, $border-and-box-shadow,
$color-800, $sidebar-text,
$color-900, $nav-svg-color,
$color-alternate $color-alternate
) { ) {
// Header // Header
.navbar-gitlab { .navbar-gitlab {
background-color: $color-900; background-color: $nav-svg-color;
.navbar-collapse { .navbar-collapse {
color: $color-200; color: $search-and-nav-links;
} }
.container-fluid { .container-fluid {
.navbar-toggler { .navbar-toggler {
border-left: 1px solid lighten($color-700, 10%); border-left: 1px solid lighten($border-and-box-shadow, 10%);
} }
} }
...@@ -31,40 +31,40 @@ ...@@ -31,40 +31,40 @@
> li { > li {
> a:hover, > a:hover,
> a:focus { > a:focus {
background-color: rgba($color-200, 0.2); background-color: rgba($search-and-nav-links, 0.2);
} }
&.active > a, &.active > a,
&.dropdown.show > a { &.dropdown.show > a {
color: $color-900; color: $nav-svg-color;
background-color: $color-alternate; background-color: $color-alternate;
} }
&.line-separator { &.line-separator {
border-left: 1px solid rgba($color-200, 0.2); border-left: 1px solid rgba($search-and-nav-links, 0.2);
} }
} }
} }
.navbar-sub-nav { .navbar-sub-nav {
color: $color-200; color: $search-and-nav-links;
} }
.nav { .nav {
> li { > li {
color: $color-200; color: $search-and-nav-links;
> a { > a {
&.header-user-dropdown-toggle { &.header-user-dropdown-toggle {
.header-user-avatar { .header-user-avatar {
border-color: $color-200; border-color: $search-and-nav-links;
} }
} }
&:hover, &:hover,
&:focus { &:focus {
@include media-breakpoint-up(sm) { @include media-breakpoint-up(sm) {
background-color: rgba($color-200, 0.2); background-color: rgba($search-and-nav-links, 0.2);
} }
svg { svg {
...@@ -75,12 +75,12 @@ ...@@ -75,12 +75,12 @@
&.active > a, &.active > a,
&.dropdown.show > a { &.dropdown.show > a {
color: $color-900; color: $nav-svg-color;
background-color: $color-alternate; background-color: $color-alternate;
&:hover { &:hover {
svg { svg {
fill: $color-900; fill: $nav-svg-color;
} }
} }
} }
...@@ -88,7 +88,7 @@ ...@@ -88,7 +88,7 @@
.impersonated-user, .impersonated-user,
.impersonated-user:hover { .impersonated-user:hover {
svg { svg {
fill: $color-900; fill: $nav-svg-color;
} }
} }
} }
...@@ -99,34 +99,34 @@ ...@@ -99,34 +99,34 @@
> a { > a {
&:hover, &:hover,
&:focus { &:focus {
background-color: rgba($color-200, 0.2); background-color: rgba($search-and-nav-links, 0.2);
} }
} }
} }
.search { .search {
form { form {
background-color: rgba($color-200, 0.2); background-color: rgba($search-and-nav-links, 0.2);
&:hover { &:hover {
background-color: rgba($color-200, 0.3); background-color: rgba($search-and-nav-links, 0.3);
} }
} }
.location-badge { .location-badge {
color: $color-100; color: $location-badge-color;
background-color: rgba($color-200, 0.1); background-color: rgba($search-and-nav-links, 0.1);
border-right: 1px solid $color-800; border-right: 1px solid $sidebar-text;
} }
.search-input::placeholder { .search-input::placeholder {
color: rgba($color-200, 0.8); color: rgba($search-and-nav-links, 0.8);
} }
.search-input-wrap { .search-input-wrap {
.search-icon, .search-icon,
.clear-icon { .clear-icon {
fill: rgba($color-200, 0.8); fill: rgba($search-and-nav-links, 0.8);
} }
} }
...@@ -141,38 +141,34 @@ ...@@ -141,38 +141,34 @@
.search-input-wrap { .search-input-wrap {
.search-icon { .search-icon {
fill: rgba($color-200, 0.8); fill: rgba($search-and-nav-links, 0.8);
} }
} }
} }
} }
.btn-sign-in {
background-color: $color-100;
color: $color-900;
}
// Sidebar // Sidebar
.nav-sidebar li.active { .nav-sidebar li.active {
box-shadow: inset 4px 0 0 $color-700; box-shadow: inset 4px 0 0 $border-and-box-shadow;
> a { > a {
color: $color-800; color: $sidebar-text;
} }
svg { svg {
fill: $color-800; fill: $sidebar-text;
} }
} }
.sidebar-top-level-items > li.active .badge.badge-pill { .sidebar-top-level-items > li.active .badge.badge-pill {
color: $color-800; color: $sidebar-text;
} }
.nav-links li { .nav-links li {
&.active a, &.active a,
a.active { a.active {
border-bottom: 2px solid $color-500; border-bottom: 2px solid $active-tab-border;
.badge.badge-pill { .badge.badge-pill {
font-weight: $gl-font-weight-bold; font-weight: $gl-font-weight-bold;
...@@ -181,27 +177,27 @@ ...@@ -181,27 +177,27 @@
} }
.branch-header-title { .branch-header-title {
color: $color-700; color: $border-and-box-shadow;
} }
.ide-file-list .file.file-active { .ide-file-list .file.file-active {
color: $color-700; color: $border-and-box-shadow;
} }
.ide-sidebar-link { .ide-sidebar-link {
&.active { &.active {
color: $color-700; color: $border-and-box-shadow;
box-shadow: inset 3px 0 $color-700; box-shadow: inset 3px 0 $border-and-box-shadow;
&.is-right { &.is-right {
box-shadow: inset -3px 0 $color-700; box-shadow: inset -3px 0 $border-and-box-shadow;
} }
} }
} }
} }
body { body {
&.ui_indigo { &.ui-indigo {
@include gitlab-theme( @include gitlab-theme(
$indigo-100, $indigo-100,
$indigo-200, $indigo-200,
...@@ -213,19 +209,19 @@ body { ...@@ -213,19 +209,19 @@ body {
); );
} }
&.ui_dark { &.ui-light-indigo {
@include gitlab-theme( @include gitlab-theme(
$theme-gray-100, $indigo-100,
$theme-gray-200, $indigo-200,
$theme-gray-500, $indigo-500,
$theme-gray-700, $indigo-500,
$theme-gray-800, $indigo-700,
$theme-gray-900, $indigo-700,
$white-light $white-light
); );
} }
&.ui_blue { &.ui-blue {
@include gitlab-theme( @include gitlab-theme(
$theme-blue-100, $theme-blue-100,
$theme-blue-200, $theme-blue-200,
...@@ -237,7 +233,19 @@ body { ...@@ -237,7 +233,19 @@ body {
); );
} }
&.ui_green { &.ui-light-blue {
@include gitlab-theme(
$theme-light-blue-100,
$theme-light-blue-200,
$theme-light-blue-500,
$theme-light-blue-500,
$theme-light-blue-700,
$theme-light-blue-700,
$white-light
);
}
&.ui-green {
@include gitlab-theme( @include gitlab-theme(
$theme-green-100, $theme-green-100,
$theme-green-200, $theme-green-200,
...@@ -249,7 +257,55 @@ body { ...@@ -249,7 +257,55 @@ body {
); );
} }
&.ui_light { &.ui-light-green {
@include gitlab-theme(
$theme-green-100,
$theme-green-200,
$theme-green-500,
$theme-green-500,
$theme-light-green-700,
$theme-light-green-700,
$white-light
);
}
&.ui-red {
@include gitlab-theme(
$theme-red-100,
$theme-red-200,
$theme-red-500,
$theme-red-700,
$theme-red-800,
$theme-red-900,
$white-light
);
}
&.ui-light-red {
@include gitlab-theme(
$theme-light-red-100,
$theme-light-red-200,
$theme-light-red-500,
$theme-light-red-500,
$theme-light-red-700,
$theme-light-red-700,
$white-light
);
}
&.ui-dark {
@include gitlab-theme(
$theme-gray-100,
$theme-gray-200,
$theme-gray-500,
$theme-gray-700,
$theme-gray-800,
$theme-gray-900,
$white-light
);
}
&.ui-light {
@include gitlab-theme( @include gitlab-theme(
$theme-gray-900, $theme-gray-900,
$theme-gray-700, $theme-gray-700,
......
...@@ -139,6 +139,8 @@ ...@@ -139,6 +139,8 @@
} }
.nav { .nav {
flex-wrap: nowrap;
> li:not(.d-none) a { > li:not(.d-none) a {
@include media-breakpoint-down(xs) { @include media-breakpoint-down(xs) {
margin-left: 0; margin-left: 0;
...@@ -158,11 +160,12 @@ ...@@ -158,11 +160,12 @@
} }
.navbar-toggler { .navbar-toggler {
position: relative;
right: -10px; right: -10px;
border-radius: 0; border-radius: 0;
min-width: 45px; min-width: 45px;
padding: 0; padding: 0;
margin-right: -7px; margin: $gl-padding-8 -7px $gl-padding-8 0;
font-size: 14px; font-size: 14px;
text-align: center; text-align: center;
color: currentColor; color: currentColor;
...@@ -186,6 +189,7 @@ ...@@ -186,6 +189,7 @@
display: -webkit-flex; display: -webkit-flex;
display: flex; display: flex;
padding-right: 10px; padding-right: 10px;
flex-direction: row;
} }
li { li {
...@@ -290,6 +294,10 @@ ...@@ -290,6 +294,10 @@
margin: 8px; margin: 8px;
} }
} }
.dropdown-menu {
position: absolute;
}
} }
.navbar-sub-nav { .navbar-sub-nav {
...@@ -437,12 +445,18 @@ ...@@ -437,12 +445,18 @@
} }
.btn-sign-in { .btn-sign-in {
margin-top: 3px; background-color: $indigo-100;
color: $indigo-900;
font-weight: $gl-font-weight-bold; font-weight: $gl-font-weight-bold;
line-height: 18px;
&:hover { &:hover {
background-color: $white-light; background-color: $white-light;
} }
@include media-breakpoint-down(xs) {
margin-top: $gl-padding-4;
}
} }
.navbar-nav { .navbar-nav {
......
...@@ -4,3 +4,11 @@ ...@@ -4,3 +4,11 @@
text-decoration: none; text-decoration: none;
} }
} }
.page-item {
&.active {
.page-link {
z-index: 3;
}
}
}
.table-holder { .table-holder {
margin: 0; margin: 0;
overflow: auto;
} }
table { table {
...@@ -38,6 +39,11 @@ table { ...@@ -38,6 +39,11 @@ table {
&.wide { &.wide {
width: 55%; width: 55%;
} }
&.table-th-transparent {
background: none;
color: $gl-text-color-secondary;
}
} }
td { td {
...@@ -45,9 +51,91 @@ table { ...@@ -45,9 +51,91 @@ table {
} }
} }
} }
&.responsive-table {
@include media-breakpoint-down(sm) {
thead {
display: none;
}
table,
tbody,
td {
display: block;
}
td {
color: $gl-text-color-secondary;
}
tbody td.responsive-table-cell {
padding: $gl-padding 0;
width: 100%;
display: flex;
text-align: right;
align-items: center;
justify-content: space-between;
&[data-column]::before {
content: attr(data-column);
display: block;
text-align: left;
padding-right: $gl-padding;
color: $gl-text-color-secondary;
}
&:not([data-column]) {
flex-direction: row-reverse;
}
}
tr.responsive-table-border-start,
tr.responsive-table-border-end {
display: block;
border: solid $gl-text-color-quaternary;
padding-left: 0;
padding-right: 0;
> td {
border-color: $gl-text-color-quaternary;
&,
&:last-child {
padding-left: $gl-padding;
padding-right: $gl-padding;
}
}
}
tr.responsive-table-border-start {
border-width: 1px 1px 0;
border-radius: $border-radius-default $border-radius-default 0 0;
padding-top: 0;
padding-bottom: 0;
> td:first-child {
border-top: 0; // always have the <table> top border
}
> td:last-child {
border-bottom: 1px solid $gl-text-color-quaternary;
}
}
tr.responsive-table-border-end {
border-width: 0 1px 1px;
border-radius: 0 0 $border-radius-default $border-radius-default;
margin-bottom: 2 * $gl-padding;
> :last-child {
border-bottom: 0;
}
}
}
}
} }
.responsive-table { .responsive-table:not(table) {
@include media-breakpoint-down(sm) { @include media-breakpoint-down(sm) {
th { th {
width: 100%; width: 100%;
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
padding: 0; padding: 0;
&::before { &::before {
@include notes-media('max', map-get($grid-breakpoints, xs)) { @include notes-media('max', map-get($grid-breakpoints, sm)) {
background: none; background: none;
} }
} }
...@@ -34,7 +34,7 @@ ...@@ -34,7 +34,7 @@
.timeline-entry-inner { .timeline-entry-inner {
position: relative; position: relative;
@include notes-media('max', map-get($grid-breakpoints, xs)) { @include notes-media('max', map-get($grid-breakpoints, sm)) {
.timeline-icon { .timeline-icon {
display: none; display: none;
} }
......
...@@ -99,7 +99,7 @@ $theme-gray-200: #dfdfdf; ...@@ -99,7 +99,7 @@ $theme-gray-200: #dfdfdf;
$theme-gray-300: #cccccc; $theme-gray-300: #cccccc;
$theme-gray-400: #bababa; $theme-gray-400: #bababa;
$theme-gray-500: #a7a7a7; $theme-gray-500: #a7a7a7;
$theme-gray-600: #949494; $theme-gray-600: #919191;
$theme-gray-700: #707070; $theme-gray-700: #707070;
$theme-gray-800: #4f4f4f; $theme-gray-800: #4f4f4f;
$theme-gray-900: #2e2e2e; $theme-gray-900: #2e2e2e;
...@@ -117,6 +117,15 @@ $theme-blue-800: #25496e; ...@@ -117,6 +117,15 @@ $theme-blue-800: #25496e;
$theme-blue-900: #1a3652; $theme-blue-900: #1a3652;
$theme-blue-950: #0f2235; $theme-blue-950: #0f2235;
$theme-light-blue-50: #f2f7fc;
$theme-light-blue-100: #ebf1f7;
$theme-light-blue-200: #c9dcf2;
$theme-light-blue-300: #83abd4;
$theme-light-blue-400: #4d86bf;
$theme-light-blue-500: #367cc2;
$theme-light-blue-600: #3771ab;
$theme-light-blue-700: #2261a1;
$theme-green-50: #f2faf6; $theme-green-50: #f2faf6;
$theme-green-100: #e4f3ea; $theme-green-100: #e4f3ea;
$theme-green-200: #c0dfcd; $theme-green-200: #c0dfcd;
...@@ -129,6 +138,29 @@ $theme-green-800: #145d33; ...@@ -129,6 +138,29 @@ $theme-green-800: #145d33;
$theme-green-900: #0d4524; $theme-green-900: #0d4524;
$theme-green-950: #072d16; $theme-green-950: #072d16;
$theme-light-green-700: #156b39;
$theme-red-50: #fcf4f2;
$theme-red-100: #fae9e6;
$theme-red-200: #ebcac5;
$theme-red-300: #d99b91;
$theme-red-400: #b0655a;
$theme-red-500: #ad4a3b;
$theme-red-600: #9e4133;
$theme-red-700: #912f20;
$theme-red-800: #78291d;
$theme-red-900: #691a16;
$theme-red-950: #36140f;
$theme-light-red-50: #fff6f5;
$theme-light-red-100: #fae2de;
$theme-light-red-200: #f7d5d0;
$theme-light-red-300: #d9796a;
$theme-light-red-400: #cf604e;
$theme-light-red-500: #c24b38;
$theme-light-red-600: #b03927;
$theme-light-red-700: #a62e21;
$black: #000; $black: #000;
$black-transparent: rgba(0, 0, 0, 0.3); $black-transparent: rgba(0, 0, 0, 0.3);
$almost-black: #242424; $almost-black: #242424;
...@@ -141,11 +173,6 @@ $border-gray-normal: darken($gray-normal, $darken-border-factor); ...@@ -141,11 +173,6 @@ $border-gray-normal: darken($gray-normal, $darken-border-factor);
$border-gray-normal-dashed: darken($gray-normal, $darken-border-dashed-factor); $border-gray-normal-dashed: darken($gray-normal, $darken-border-dashed-factor);
$border-gray-dark: darken($white-normal, $darken-border-factor); $border-gray-dark: darken($white-normal, $darken-border-factor);
/*
* Override Bootstrap 4 variables
*/
$secondary: $gray-light;
/* /*
* UI elements * UI elements
*/ */
...@@ -164,7 +191,7 @@ $gl-font-weight-normal: 400; ...@@ -164,7 +191,7 @@ $gl-font-weight-normal: 400;
$gl-font-weight-bold: 600; $gl-font-weight-bold: 600;
$gl-text-color: #2e2e2e; $gl-text-color: #2e2e2e;
$gl-text-color-secondary: #707070; $gl-text-color-secondary: #707070;
$gl-text-color-tertiary: #949494; $gl-text-color-tertiary: #919191;
$gl-text-color-quaternary: #d6d6d6; $gl-text-color-quaternary: #d6d6d6;
$gl-text-color-inverted: rgba(255, 255, 255, 1); $gl-text-color-inverted: rgba(255, 255, 255, 1);
$gl-text-color-secondary-inverted: rgba(255, 255, 255, 0.85); $gl-text-color-secondary-inverted: rgba(255, 255, 255, 0.85);
...@@ -407,6 +434,22 @@ $gl-btn-horz-padding: 12px; ...@@ -407,6 +434,22 @@ $gl-btn-horz-padding: 12px;
$badge-bg: rgba(0, 0, 0, 0.07); $badge-bg: rgba(0, 0, 0, 0.07);
$badge-color: $gl-text-color-secondary; $badge-color: $gl-text-color-secondary;
/*
* Pagination
*/
$pagination-padding-y: 6px;
$pagination-padding-x: 16px;
$pagination-line-height: 20px;
$pagination-border-color: $border-color;
$pagination-active-bg: $blue-600;
$pagination-active-border-color: $blue-600;
$pagination-hover-bg: $blue-50;
$pagination-hover-border-color: $border-color;
$pagination-hover-color: $gl-text-color;
$pagination-disabled-color: #cdcdcd;
$pagination-disabled-bg: $gray-light;
$pagination-disabled-border-color: $border-color;
/* /*
* Status icons * Status icons
*/ */
...@@ -776,3 +819,16 @@ $modal-body-height: 134px; ...@@ -776,3 +819,16 @@ $modal-body-height: 134px;
Prometheus Prometheus
*/ */
$prometheus-table-row-highlight-color: $theme-gray-100; $prometheus-table-row-highlight-color: $theme-gray-100;
$priority-label-empty-state-width: 114px;
/*
* Override Bootstrap 4 variables
*/
$secondary: $gray-light;
$input-disabled-bg: $gray-light;
$input-border-color: $theme-gray-200;
$input-color: $gl-text-color;
$font-family-sans-serif: $regular_font;
$font-family-monospace: $monospace_font;
...@@ -282,9 +282,6 @@ ...@@ -282,9 +282,6 @@
box-shadow: 0 1px 2px $issue-boards-card-shadow; box-shadow: 0 1px 2px $issue-boards-card-shadow;
list-style: none; list-style: none;
// as a fallback, hide overflow content so that dragging and dropping still works
overflow: hidden;
&:not(:last-child) { &:not(:last-child) {
margin-bottom: 5px; margin-bottom: 5px;
} }
......
...@@ -12,26 +12,22 @@ ...@@ -12,26 +12,22 @@
@keyframes blinking-dots { @keyframes blinking-dots {
0% { 0% {
background-color: rgba($white-light, 1); background-color: rgba($white-light, 1);
box-shadow: 12px 0 0 0 rgba($white-light, 0.2), box-shadow: 12px 0 0 0 rgba($white-light, 0.2), 24px 0 0 0 rgba($white-light, 0.2);
24px 0 0 0 rgba($white-light, 0.2);
} }
25% { 25% {
background-color: rgba($white-light, 0.4); background-color: rgba($white-light, 0.4);
box-shadow: 12px 0 0 0 rgba($white-light, 2), box-shadow: 12px 0 0 0 rgba($white-light, 2), 24px 0 0 0 rgba($white-light, 0.2);
24px 0 0 0 rgba($white-light, 0.2);
} }
75% { 75% {
background-color: rgba($white-light, 0.4); background-color: rgba($white-light, 0.4);
box-shadow: 12px 0 0 0 rgba($white-light, 0.2), box-shadow: 12px 0 0 0 rgba($white-light, 0.2), 24px 0 0 0 rgba($white-light, 1);
24px 0 0 0 rgba($white-light, 1);
} }
100% { 100% {
background-color: rgba($white-light, 1); background-color: rgba($white-light, 1);
box-shadow: 12px 0 0 0 rgba($white-light, 0.2), box-shadow: 12px 0 0 0 rgba($white-light, 0.2), 24px 0 0 0 rgba($white-light, 0.2);
24px 0 0 0 rgba($white-light, 0.2);
} }
} }
...@@ -71,6 +67,10 @@ ...@@ -71,6 +67,10 @@
.bash { .bash {
display: block; display: block;
} }
&.build-trace-rounded {
border-radius: $border-radius-base;
}
} }
.top-bar { .top-bar {
......
...@@ -57,69 +57,8 @@ ...@@ -57,69 +57,8 @@
border-bottom-left-radius: $border-radius-base; border-bottom-left-radius: $border-radius-base;
} }
.label-row {
.label-name {
display: inline-block;
margin-bottom: 10px;
@include media-breakpoint-up(sm) {
width: 200px;
margin-left: $gl-padding * 2;
margin-bottom: 0;
}
.badge {
overflow: hidden;
text-overflow: ellipsis;
max-width: 100%;
}
}
.label-type {
display: block;
margin-bottom: 10px;
margin-left: 50px;
@include media-breakpoint-up(sm) {
display: inline-block;
width: 100px;
margin-left: 10px;
margin-bottom: 0;
vertical-align: top;
}
}
.label-description {
display: block;
margin-bottom: 10px;
.description-text {
margin-bottom: $gl-padding;
}
a {
color: $blue-600;
}
@include media-breakpoint-up(sm) {
display: inline-block;
max-width: 50%;
margin-left: 10px;
margin-bottom: 0;
vertical-align: top;
}
}
.badge {
padding: 4px $grid-size;
font-size: $label-font-size;
position: relative;
top: ($grid-size / 2);
}
}
.color-label { .color-label {
padding: 0 $grid-size; padding: $gl-padding-4 $grid-size;
line-height: 16px; line-height: 16px;
border-radius: $label-border-radius; border-radius: $label-border-radius;
color: $white-light; color: $white-light;
...@@ -133,26 +72,29 @@ ...@@ -133,26 +72,29 @@
} }
.manage-labels-list { .manage-labels-list {
@media(min-width: map-get($grid-breakpoints, md)) {
&.content-list li {
padding: $gl-padding 0;
}
}
> li:not(.empty-message):not(.is-not-draggable) { > li:not(.empty-message):not(.is-not-draggable) {
background-color: $white-light; background-color: $white-light;
cursor: move; margin-bottom: 5px;
cursor: -webkit-grab; display: flex;
cursor: -moz-grab; justify-content: space-between;
padding: $gl-padding;
&:active { border-radius: $border-radius-default;
cursor: -webkit-grabbing;
cursor: -moz-grabbing;
}
&.sortable-ghost { &.sortable-ghost {
opacity: 0.3; opacity: 0.3;
} }
.prioritized-labels & {
box-shadow: 0 1px 2px $issue-boards-card-shadow;
cursor: move;
cursor: -webkit-grab;
cursor: -moz-grab;
&:active {
cursor: -webkit-grabbing;
cursor: -moz-grabbing;
}
}
} }
.btn-action { .btn-action {
...@@ -170,36 +112,11 @@ ...@@ -170,36 +112,11 @@
} }
} }
} }
.dropdown {
@include media-breakpoint-up(sm) {
float: right;
}
}
@include media-breakpoint-down(xs) {
.dropdown-menu {
min-width: 100%;
}
}
}
.draggable-handler {
display: inline-block;
vertical-align: top;
margin: 5px 0;
opacity: 0;
transition: opacity .3s;
color: $gray-darkest;
} }
.prioritized-labels { .prioritized-labels {
margin-bottom: 30px; margin-bottom: 30px;
h5 {
font-size: $gl-font-size;
}
.add-priority { .add-priority {
display: none; display: none;
color: $gray-light; color: $gray-light;
...@@ -214,31 +131,11 @@ ...@@ -214,31 +131,11 @@
} }
.other-labels { .other-labels {
h5 {
font-size: $gl-font-size;
}
.remove-priority { .remove-priority {
display: none; display: none;
} }
} }
.toggle-priority {
display: inline-block;
vertical-align: top;
button {
border-color: transparent;
padding: 5px 8px;
vertical-align: top;
font-size: 14px;
&:hover {
border-color: transparent;
}
}
}
.filtered-labels { .filtered-labels {
font-size: 0; font-size: 0;
padding: 12px 16px; padding: 12px 16px;
...@@ -292,10 +189,8 @@ ...@@ -292,10 +189,8 @@
} }
.label-subscribe-button { .label-subscribe-button {
@media(min-width: map-get($grid-breakpoints, md)) { width: 105px;
min-width: 105px; font-weight: 200;
margin-left: $gl-padding;
}
.label-subscribe-button-icon { .label-subscribe-button-icon {
&[disabled] { &[disabled] {
...@@ -332,3 +227,95 @@ ...@@ -332,3 +227,95 @@
font-size: $label-font-size; font-size: $label-font-size;
} }
} }
.labels-container {
background-color: $gray-light;
border-radius: $border-radius-default;
padding: $gl-padding $gl-padding-8;
}
.label-actions-list {
list-style: none;
flex-shrink: 0;
padding: 0;
}
.label-badge {
color: $theme-gray-900;
font-weight: $gl-font-weight-normal;
padding: $gl-padding-4 $gl-padding-8;
border-radius: $border-radius-default;
font-size: $label-font-size;
}
.label-badge-blue {
background-color: $theme-blue-100;
}
.label-badge-gray {
background-color: $theme-gray-100;
}
.label-links {
list-style: none;
padding: 0;
white-space: nowrap;
}
.label-link-item {
padding: 0;
}
.label-list-item {
.content-list &::before,
.content-list &::after {
content: none;
}
.label-name {
width: 150px;
flex-shrink: 0;
.label {
overflow: hidden;
text-overflow: ellipsis;
max-width: 100%;
}
}
.label-description {
flex-grow: 1;
a {
color: $blue-600;
}
}
.label {
padding: 4px $grid-size;
font-size: $label-font-size;
position: relative;
top: $gl-padding-4;
}
.label-action {
color: $theme-gray-800;
cursor: pointer;
svg {
fill: $theme-gray-800;
}
&:hover {
color: $blue-600;
svg {
fill: $blue-600;
}
}
}
}
.priority-labels-empty-state .svg-content img {
max-width: $priority-label-empty-state-width;
}
...@@ -321,18 +321,17 @@ ...@@ -321,18 +321,17 @@
} }
.build-failures { .build-failures {
th {
border-top: 0;
}
.build-state { .build-state {
padding: 20px 2px; padding: 20px 2px;
.build-name { .build-name {
float: right;
font-weight: $gl-font-weight-normal; font-weight: $gl-font-weight-normal;
} }
.ci-status-icon-failed svg {
vertical-align: middle;
}
.stage { .stage {
color: $gl-text-color-secondary; color: $gl-text-color-secondary;
font-weight: $gl-font-weight-normal; font-weight: $gl-font-weight-normal;
...@@ -344,6 +343,81 @@ ...@@ -344,6 +343,81 @@
border: 0; border: 0;
line-height: initial; line-height: initial;
} }
.build-trace-row td {
border-top: 0;
border-bottom-width: 1px;
border-bottom-style: solid;
padding-top: 0;
}
.build-trace {
width: 100%;
text-align: left;
margin-top: $gl-padding;
}
.build-name {
width: 196px;
a {
font-weight: $gl-font-weight-bold;
color: $gl-text-color;
text-decoration: none;
&:focus,
&:hover {
text-decoration: underline;
}
}
}
.build-actions {
width: 70px;
text-align: right;
}
.build-stage {
width: 140px;
}
.ci-status-icon-failed {
padding: 10px 0 10px 12px;
width: 12px + 24px; // padding-left + svg width
}
.build-icon svg {
width: 24px;
height: 24px;
vertical-align: middle;
}
.build-state,
.build-trace-row {
> td:last-child {
padding-right: 0;
}
}
@include media-breakpoint-down(sm) {
td:empty {
display: none;
}
.ci-table {
margin-top: 2 * $gl-padding;
}
.build-trace-container {
padding-top: $gl-padding;
padding-bottom: $gl-padding;
}
.build-trace {
margin-bottom: 0;
margin-top: 0;
}
}
} }
.pipeline-tab-content { .pipeline-tab-content {
...@@ -929,7 +1003,7 @@ button.mini-pipeline-graph-dropdown-toggle { ...@@ -929,7 +1003,7 @@ button.mini-pipeline-graph-dropdown-toggle {
&.dropdown-menu { &.dropdown-menu {
transform: translate(-80%, 0); transform: translate(-80%, 0);
@media(min-width: map-get($grid-breakpoints, md)) { @media (min-width: map-get($grid-breakpoints, md)) {
transform: translate(-50%, 0); transform: translate(-50%, 0);
right: auto; right: auto;
left: 50%; left: 50%;
......
@mixin application-theme-preview($color-1, $color-2, $color-3, $color-4) {
.one {
background-color: $color-1;
border-top-left-radius: $border-radius-default;
}
.two {
background-color: $color-2;
border-top-right-radius: $border-radius-default;
}
.three {
background-color: $color-3;
border-bottom-left-radius: $border-radius-default;
}
.four {
background-color: $color-4;
border-bottom-right-radius: $border-radius-default;
}
}
.multi-file-editor-options { .multi-file-editor-options {
label { label {
margin-right: 20px; margin-right: 20px;
...@@ -38,44 +16,61 @@ ...@@ -38,44 +16,61 @@
.application-theme { .application-theme {
label { label {
margin-right: 20px; margin: 0 $gl-padding $gl-padding 0;
text-align: center; text-align: center;
} }
.preview { .preview {
font-size: 0; font-size: 0;
margin-bottom: 10px; height: 48px;
border-radius: 4px;
min-width: 135px;
margin-bottom: $gl-padding-8;
&.ui-indigo {
background-color: $indigo-900;
}
&.ui-light-indigo {
background-color: $indigo-700;
}
&.indigo { &.ui-blue {
@include application-theme-preview($indigo-900, $indigo-700, $indigo-800, $indigo-500); background-color: $theme-blue-900;
} }
&.dark { &.ui-light-blue {
@include application-theme-preview($theme-gray-900, $theme-gray-700, $theme-gray-800, $theme-gray-600); background-color: $theme-light-blue-700;
} }
&.light { &.ui-green {
@include application-theme-preview($theme-gray-600, $theme-gray-200, $theme-gray-400, $theme-gray-100); background-color: $theme-green-900;
} }
&.blue { &.ui-light-green {
@include application-theme-preview($theme-blue-900, $theme-blue-700, $theme-blue-800, $theme-blue-500); background-color: $theme-light-green-700;
} }
&.green { &.ui-red {
@include application-theme-preview($theme-green-900, $theme-green-700, $theme-green-800, $theme-green-500); background-color: $theme-red-900;
}
&.ui-light-red {
background-color: $theme-light-red-700;
}
&.ui-dark {
background-color: $theme-gray-900;
}
&.ui-light {
background-color: $theme-gray-200;
} }
} }
.preview-row { .preview-row {
display: block; display: block;
} }
.quadrant {
display: inline-block;
height: 50px;
width: 80px;
}
} }
.syntax-theme { .syntax-theme {
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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