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
retry: 1
......@@ -394,7 +394,11 @@ compile-assets:
- date
- yarn install --frozen-lockfile --cache-folder .yarn-cache
- date
- free -m
- 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:
expire_in: 7d
paths:
......@@ -658,10 +662,13 @@ gitlab:assets:compile:
SKIP_STORAGE_VALIDATION: "true"
WEBPACK_REPORT: "true"
NO_COMPRESSION: "true"
# we override the max_old_space_size to prevent OOM errors
NODE_OPTIONS: --max_old_space_size=3584
script:
- date
- yarn install --frozen-lockfile --production --cache-folder .yarn-cache
- date
- free -m
- bundle exec rake gitlab:assets:compile
artifacts:
name: webpack-report
......
......@@ -2,6 +2,10 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
## 10.8.4 (2018-06-06)
- No changes.
## 10.8.3 (2018-05-30)
### Fixed (4 changes)
......
......@@ -108,6 +108,7 @@ gem 'hamlit', '~> 2.6.1'
# Files attachments
gem 'carrierwave', '~> 1.2'
gem 'mini_magick'
# Drag and Drop UI
gem 'dropzonejs-rails', '~> 0.7.1'
......@@ -133,7 +134,7 @@ gem 'seed-fu', '~> 2.3.7'
# Markdown and HTML processing
gem 'html-pipeline', '~> 2.7.1'
gem 'deckar01-task_list', '2.0.0'
gem 'gitlab-markup', '~> 1.6.2'
gem 'gitlab-markup', '~> 1.6.4'
gem 'redcarpet', '~> 3.4'
gem 'commonmarker', '~> 0.17'
gem 'RedCloth', '~> 4.3.2'
......@@ -332,7 +333,7 @@ group :development, :test do
gem 'database_cleaner', '~> 1.5.0'
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_profiling', '~> 0.0.5'
gem 'rspec-set', '~> 0.1.3'
......@@ -408,18 +409,17 @@ gem 'vmstat', '~> 2.3.0'
gem 'sys-filesystem', '~> 1.1.6'
# SSH host key support
gem 'net-ssh', '~> 4.2.0'
gem 'net-ssh', '~> 5.0'
gem 'sshkey', '~> 1.9.0'
# Required for ED25519 SSH host key support
group :ed25519 do
gem 'rbnacl-libsodium'
gem 'rbnacl', '~> 4.0'
gem 'ed25519', '~> 1.2'
gem 'bcrypt_pbkdf', '~> 1.0'
end
# Gitaly GRPC client
gem 'gitaly-proto', '~> 0.100.0', require: 'gitaly'
gem 'gitaly-proto', '~> 0.101.0', require: 'gitaly'
gem 'grpc', '~> 1.11.0'
# Locked until https://github.com/google/protobuf/issues/4210 is closed
......
......@@ -177,6 +177,7 @@ GEM
json-jwt (~> 1.6)
dropzonejs-rails (0.7.2)
rails (> 3.1)
ed25519 (1.2.4)
email_reply_trimmer (0.1.6)
email_spec (2.2.0)
htmlentities (~> 4.3.3)
......@@ -282,7 +283,7 @@ GEM
gettext_i18n_rails (>= 0.7.1)
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
gitaly-proto (0.100.0)
gitaly-proto (0.101.0)
google-protobuf (~> 3.1)
grpc (~> 1.10)
github-linguist (5.3.3)
......@@ -311,7 +312,7 @@ GEM
diff-lcs (~> 1.1)
mime-types (>= 1.16)
posix-spawn (~> 0.3)
gitlab-markup (1.6.3)
gitlab-markup (1.6.4)
gitlab-styles (2.3.2)
rubocop (~> 0.51)
rubocop-gitlab-security (~> 0.1.0)
......@@ -359,8 +360,8 @@ GEM
grape-entity (0.7.1)
activesupport (>= 4.0)
multi_json (>= 1.3.2)
grape-path-helpers (1.0.2)
activesupport (~> 4)
grape-path-helpers (1.0.5)
activesupport (>= 4, < 5.1)
grape (~> 1.0)
rake (~> 12)
grape_logging (1.7.0)
......@@ -451,7 +452,6 @@ GEM
kgio (2.10.0)
knapsack (1.16.0)
rake
timecop (>= 0.1.0)
kubeclient (3.1.0)
http (~> 2.2.2)
recursive-open-struct (~> 1.0, >= 1.0.4)
......@@ -498,6 +498,7 @@ GEM
mime-types-data (~> 3.2015)
mime-types-data (3.2016.0521)
mimemagic (0.3.0)
mini_magick (4.8.0)
mini_mime (1.0.0)
mini_portile2 (2.3.0)
minitest (5.7.0)
......@@ -510,7 +511,7 @@ GEM
mustermann (~> 1.0.0)
mysql2 (0.4.10)
net-ldap (0.16.0)
net-ssh (4.2.0)
net-ssh (5.0.1)
netrc (0.11.0)
nokogiri (1.8.2)
mini_portile2 (~> 2.3.0)
......@@ -696,10 +697,6 @@ GEM
ffi (>= 0.5.0, < 2)
rblineprof (0.3.6)
debugger-ruby_core_source (~> 1.3)
rbnacl (4.0.2)
ffi
rbnacl-libsodium (1.0.11)
rbnacl (>= 3.0.1)
rdoc (6.0.4)
re2 (1.1.1)
recaptcha (3.0.0)
......@@ -745,36 +742,36 @@ GEM
chunky_png
rqrcode-rails3 (0.1.7)
rqrcode (>= 0.4.2)
rspec (3.6.0)
rspec-core (~> 3.6.0)
rspec-expectations (~> 3.6.0)
rspec-mocks (~> 3.6.0)
rspec-core (3.6.0)
rspec-support (~> 3.6.0)
rspec-expectations (3.6.0)
rspec (3.7.0)
rspec-core (~> 3.7.0)
rspec-expectations (~> 3.7.0)
rspec-mocks (~> 3.7.0)
rspec-core (3.7.1)
rspec-support (~> 3.7.0)
rspec-expectations (3.7.0)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.6.0)
rspec-mocks (3.6.0)
rspec-support (~> 3.7.0)
rspec-mocks (3.7.0)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.6.0)
rspec-support (~> 3.7.0)
rspec-parameterized (0.4.0)
binding_of_caller
parser
proc_to_ast
rspec (>= 2.13, < 4)
unparser
rspec-rails (3.6.0)
rspec-rails (3.7.2)
actionpack (>= 3.0)
activesupport (>= 3.0)
railties (>= 3.0)
rspec-core (~> 3.6.0)
rspec-expectations (~> 3.6.0)
rspec-mocks (~> 3.6.0)
rspec-support (~> 3.6.0)
rspec-core (~> 3.7.0)
rspec-expectations (~> 3.7.0)
rspec-mocks (~> 3.7.0)
rspec-support (~> 3.7.0)
rspec-retry (0.4.5)
rspec-core
rspec-set (0.1.3)
rspec-support (3.6.0)
rspec-support (3.7.1)
rspec_profiling (0.0.5)
activerecord
pg
......@@ -1016,6 +1013,7 @@ DEPENDENCIES
doorkeeper (~> 4.3)
doorkeeper-openid_connect (~> 1.3)
dropzonejs-rails (~> 0.7.1)
ed25519 (~> 1.2)
email_reply_trimmer (~> 0.1)
email_spec (~> 2.2.0)
factory_bot_rails (~> 4.8.2)
......@@ -1041,12 +1039,12 @@ DEPENDENCIES
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3)
gitaly-proto (~> 0.100.0)
gitaly-proto (~> 0.101.0)
github-linguist (~> 5.3.3)
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-gollum-lib (~> 4.2)
gitlab-gollum-rugged_adapter (~> 0.4.4)
gitlab-markup (~> 1.6.2)
gitlab-markup (~> 1.6.4)
gitlab-styles (~> 2.3)
gitlab_omniauth-ldap (~> 2.0.4)
gon (~> 6.2)
......@@ -1084,11 +1082,12 @@ DEPENDENCIES
loofah (~> 2.2)
mail_room (~> 0.9.1)
method_source (~> 0.8)
mini_magick
minitest (~> 5.7.0)
mousetrap-rails (~> 1.4.6)
mysql2 (~> 0.4.10)
net-ldap
net-ssh (~> 4.2.0)
net-ssh (~> 5.0)
nokogiri (~> 1.8.2)
oauth2 (~> 1.4)
octokit (~> 4.9)
......@@ -1130,8 +1129,6 @@ DEPENDENCIES
rainbow (~> 2.2)
raindrops (~> 0.18)
rblineprof (~> 0.3.6)
rbnacl (~> 4.0)
rbnacl-libsodium
rdoc (~> 6.0)
re2 (~> 1.1.1)
recaptcha (~> 3.0)
......@@ -1144,7 +1141,7 @@ DEPENDENCIES
rouge (~> 3.1)
rqrcode-rails3 (~> 0.1.7)
rspec-parameterized
rspec-rails (~> 3.6.0)
rspec-rails (~> 3.7.0)
rspec-retry (~> 0.4.5)
rspec-set (~> 0.1.3)
rspec_profiling (~> 0.0.5)
......
This diff is collapsed.
......@@ -87,10 +87,46 @@ export default {
mounted() {
const options = gl.issueBoards.getBoardSortableDefaultOptions({
scroll: true,
group: 'issues',
disabled: this.disabled,
filter: '.board-list-count, .is-disabled',
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) => {
const card = this.$refs.issue[e.oldIndex];
......@@ -179,10 +215,11 @@ export default {
:list="list"
v-if="list.type !== 'closed' && showIssueForm"/>
<ul
class="board-list"
class="board-list js-board-list"
v-show="!loading"
ref="list"
:data-board="list.id"
:data-board-type="list.type"
:class="{ 'is-smaller': showIssueForm }">
<board-card
v-for="(issue, index) in issues"
......
......@@ -49,11 +49,12 @@ export default {
this.error = false;
const labels = this.list.label ? [this.list.label] : [];
const assignees = this.list.assignee ? [this.list.assignee] : [];
const issue = new ListIssue({
title: this.title,
labels,
subscribed: true,
assignees: [],
assignees,
project_id: this.selectedProject.id,
});
......@@ -141,4 +142,3 @@ export default {
</div>
</div>
</template>
......@@ -56,6 +56,7 @@ gl.issueBoards.newListDropdownInit = () => {
filterable: true,
selectable: true,
multiSelect: true,
containerSelector: '.js-tab-container-labels .dropdown-page-one .dropdown-content',
clicked (options) {
const { e } = options;
const label = options.selectedObj;
......
......@@ -7,6 +7,7 @@ import Vue from 'vue';
import Flash from '~/flash';
import { __ } from '~/locale';
import '~/vue_shared/models/label';
import '~/vue_shared/models/assignee';
import FilteredSearchBoards from './filtered_search_boards';
import eventHub from './eventhub';
......@@ -15,7 +16,6 @@ import './models/issue';
import './models/list';
import './models/milestone';
import './models/project';
import './models/assignee';
import './stores/boards_store';
import ModalStore from './stores/modal_store';
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 */
/* global ListIssue */
/* global ListLabel */
import ListLabel from '~/vue_shared/models/label';
import ListAssignee from '~/vue_shared/models/assignee';
import queryData from '../utils/query_data';
const PER_PAGE = 20;
class List {
constructor (obj, defaultAvatar) {
constructor(obj, defaultAvatar) {
this.id = obj.id;
this._uid = this.guid();
this.position = obj.position;
......@@ -24,6 +26,9 @@ class List {
if (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) {
......@@ -34,14 +39,25 @@ class List {
}
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()}`;
}
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)
.then(res => res.data)
.then((data) => {
.then(data => {
this.id = data.id;
this.type = data.list_type;
this.position = data.position;
......@@ -50,25 +66,23 @@ class List {
});
}
destroy () {
destroy() {
const index = gl.issueBoards.BoardsStore.state.lists.indexOf(this);
gl.issueBoards.BoardsStore.state.lists.splice(index, 1);
gl.issueBoards.BoardsStore.updateNewListDropdown(this.id);
gl.boardService.destroyList(this.id)
.catch(() => {
// TODO: handle request error
});
gl.boardService.destroyList(this.id).catch(() => {
// TODO: handle request error
});
}
update () {
gl.boardService.updateList(this.id, this.position)
.catch(() => {
// TODO: handle request error
});
update() {
gl.boardService.updateList(this.id, this.position).catch(() => {
// TODO: handle request error
});
}
nextPage () {
nextPage() {
if (this.issuesSize > this.issues.length) {
if (this.issues.length / PER_PAGE >= 1) {
this.page += 1;
......@@ -78,7 +92,7 @@ class List {
}
}
getIssues (emptyIssues = true) {
getIssues(emptyIssues = true) {
const data = queryData(gl.issueBoards.BoardsStore.filter.path, { page: this.page });
if (this.label && data.label_name) {
......@@ -89,7 +103,8 @@ class List {
this.loading = true;
}
return gl.boardService.getIssuesForList(this.id, data)
return gl.boardService
.getIssuesForList(this.id, data)
.then(res => res.data)
.then((data) => {
this.loading = false;
......@@ -103,11 +118,12 @@ class List {
});
}
newIssue (issue) {
newIssue(issue) {
this.addIssue(issue, null, 0);
this.issuesSize += 1;
return gl.boardService.newIssue(this.id, issue)
return gl.boardService
.newIssue(this.id, issue)
.then(res => res.data)
.then((data) => {
issue.id = data.id;
......@@ -123,13 +139,13 @@ class List {
});
}
createIssues (data) {
data.forEach((issueObj) => {
createIssues(data) {
data.forEach(issueObj => {
this.addIssue(new ListIssue(issueObj, this.defaultAvatar));
});
}
addIssue (issue, listFrom, newIndex) {
addIssue(issue, listFrom, newIndex) {
let moveBeforeId = null;
let moveAfterId = null;
......@@ -152,6 +168,13 @@ class List {
issue.addLabel(this.label);
}
if (this.assignee) {
if (listFrom && listFrom.type === 'assignee') {
issue.removeAssignee(listFrom.assignee);
}
issue.addAssignee(this.assignee);
}
if (listFrom) {
this.issuesSize += 1;
......@@ -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(newIndex, 0, issue);
gl.boardService.moveIssue(issue.id, null, null, moveBeforeId, moveAfterId)
.catch(() => {
// TODO: handle request error
});
gl.boardService.moveIssue(issue.id, null, null, moveBeforeId, moveAfterId).catch(() => {
// TODO: handle request error
});
}
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(() => {
// TODO: handle request error
});
}
findIssue (id) {
findIssue(id) {
return this.issues.find(issue => issue.id === id);
}
removeIssue (removeIssue) {
this.issues = this.issues.filter((issue) => {
removeIssue(removeIssue) {
this.issues = this.issues.filter(issue => {
const matchesRemove = removeIssue.id === issue.id;
if (matchesRemove) {
......
......@@ -30,11 +30,13 @@ export default class BoardService {
return axios.post(this.listsEndpointGenerate, {});
}
createList(labelId) {
createList(entityId, entityType) {
const list = {
[entityType]: entityId,
};
return axios.post(this.listsEndpoint, {
list: {
label_id: labelId,
},
list,
});
}
......
......@@ -103,8 +103,15 @@ gl.issueBoards.BoardsStore = {
const listLabels = issueLists.map(listIssue => listIssue.label);
if (!issueTo) {
// Add to new lists issues if it doesn't already exist
listTo.addIssue(issue, listFrom, newIndex);
// Check if target list assignee is already present in this issue
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 {
listTo.updateIssueLabel(issue, listFrom);
issueTo.removeLabel(listFrom.label);
......@@ -115,7 +122,11 @@ gl.issueBoards.BoardsStore = {
list.removeIssue(issue);
});
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);
}
},
......@@ -126,11 +137,12 @@ gl.issueBoards.BoardsStore = {
list.moveIssue(issue, oldIndex, newIndex, beforeId, afterId);
},
findList (key, val, type = 'label') {
return this.state.lists.filter((list) => {
const byType = type ? list['type'] === type : true;
const filteredList = this.state.lists.filter((list) => {
const byType = type ? (list.type === type) || (list.type === 'assignee') : true;
return list[key] === val && byType;
})[0];
});
return filteredList[0];
},
updateFiltersUrl () {
history.pushState(null, null, `?${this.filter.path}`);
......
......@@ -95,7 +95,7 @@ export default class ClusterStore {
this.state.applications.jupyter.hostname =
serverAppEntry.hostname ||
(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 () => {
gl.diffNotesCompileComponents();
if (!hasVueMRDiscussionsCookie()) {
const resolveCountAppEl = document.querySelector('#resolve-count-app');
if (!hasVueMRDiscussionsCookie() && resolveCountAppEl) {
new Vue({
el: '#resolve-count-app',
el: resolveCountAppEl,
components: {
'resolve-count': ResolveCount
},
......
......@@ -602,7 +602,11 @@ GitLabDropdown = (function() {
var selector;
selector = '.dropdown-content';
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();
......
import $ from 'jquery';
import { __ } from '~/locale';
import axios from './lib/utils/axios_utils';
import flash from './flash';
import { __ } from './locale';
const tooltipTitles = {
group: __('Unsubscribe at group level'),
project: __('Unsubscribe at project level'),
};
export default class GroupLabelSubscription {
constructor(container) {
......@@ -35,6 +40,7 @@ export default class GroupLabelSubscription {
this.$unsubscribeButtons.attr('data-url', url);
axios.post(url)
.then(() => GroupLabelSubscription.setNewTooltip($btn))
.then(() => this.toggleSubscriptionButtons())
.catch(() => flash(__('There was an error when subscribing to this label.')));
}
......@@ -44,4 +50,14 @@ export default class GroupLabelSubscription {
this.$subscribeButtons.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';
import { rightSidebarViews } from '../../constants';
import PipelinesList from '../pipelines/list.vue';
import JobsDetail from '../jobs/detail.vue';
import ResizablePanel from '../resizable_panel.vue';
export default {
directives: {
......@@ -14,6 +15,7 @@ export default {
Icon,
PipelinesList,
JobsDetail,
ResizablePanel,
},
computed: {
...mapState(['rightPane']),
......@@ -40,12 +42,16 @@ export default {
<div
class="multi-file-commit-panel ide-right-sidebar"
>
<div
class="multi-file-commit-panel-inner"
<resizable-panel
v-if="rightPane"
class="multi-file-commit-panel-inner"
:collapsible="false"
:initial-width="350"
:min-size="350"
side="right"
>
<component :is="rightPane" />
</div>
</resizable-panel>
<nav class="ide-activity-bar">
<ul class="list-unstyled">
<li>
......
<script>
/* global monaco */
import { mapState, mapGetters, mapActions } from 'vuex';
import flash from '~/flash';
import ContentViewer from '~/vue_shared/components/content_viewer/content_viewer.vue';
import { activityBarViews, viewerTypes } from '../constants';
import monacoLoader from '../monaco_loader';
import Editor from '../lib/editor';
import ExternalLink from './external_link.vue';
......@@ -50,7 +48,7 @@ export default {
// Compare key to allow for files opened in review mode to be cached differently
if (oldVal.key !== this.file.key) {
this.initMonaco();
this.initEditor();
if (this.currentActivityView !== activityBarViews.edit) {
this.setFileViewMode({
......@@ -84,15 +82,10 @@ export default {
this.editor.dispose();
},
mounted() {
if (this.editor && monaco) {
this.initMonaco();
} else {
monacoLoader(['vs/editor/editor.main'], () => {
this.editor = Editor.create(monaco);
this.initMonaco();
});
if (!this.editor) {
this.editor = Editor.create();
}
this.initEditor();
},
methods: {
...mapActions([
......@@ -105,7 +98,7 @@ export default {
'updateViewer',
'removePendingTab',
]),
initMonaco() {
initEditor() {
if (this.shouldHideEditor) return;
this.editor.clearEditor();
......@@ -118,7 +111,7 @@ export default {
this.createEditorInstance();
})
.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;
});
},
......
import { editor as monacoEditor, Uri } from 'monaco-editor';
import Disposable from './disposable';
import eventHub from '../../eventhub';
export default class Model {
constructor(monaco, file, head = null) {
this.monaco = monaco;
constructor(file, head = null) {
this.disposable = new Disposable();
this.file = file;
this.head = head;
this.content = file.content !== '' ? file.content : file.raw;
this.disposable.add(
(this.originalModel = this.monaco.editor.createModel(
(this.originalModel = monacoEditor.createModel(
head ? head.content : this.file.raw,
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,
undefined,
new this.monaco.Uri(null, null, this.path),
new Uri(false, false, this.path),
)),
);
if (this.file.mrChange) {
this.disposable.add(
(this.baseModel = this.monaco.editor.createModel(
(this.baseModel = monacoEditor.createModel(
this.file.baseRaw,
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';
import Model from './model';
export default class ModelManager {
constructor(monaco) {
this.monaco = monaco;
constructor() {
this.disposable = new Disposable();
this.models = new Map();
}
......@@ -22,7 +21,7 @@ export default class ModelManager {
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.disposable.add(model);
......
/* global monaco */
import { Range } from 'monaco-editor';
import { throttle } from 'underscore';
import DirtyDiffWorker from './diff_worker';
import Disposable from '../common/disposable';
......@@ -16,7 +16,7 @@ export const getDiffChangeType = 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: {
isWholeLine: true,
linesDecorationsClassName: `dirty-diff dirty-diff-${getDiffChangeType(change)}`,
......
import _ from 'underscore';
import { editor as monacoEditor, KeyCode, KeyMod } from 'monaco-editor';
import store from '../stores';
import DecorationsController from './decorations/controller';
import DirtyDiffController from './diff/controller';
......@@ -8,6 +9,11 @@ import editorOptions, { defaultEditorOptions } from './editor_options';
import gitlabTheme from './themes/gl_theme';
import keymap from './keymap.json';
function setupMonacoTheme() {
monacoEditor.defineTheme(gitlabTheme.themeName, gitlabTheme.monacoTheme);
monacoEditor.setTheme('gitlab');
}
export const clearDomElement = el => {
if (!el || !el.firstChild) return;
......@@ -17,24 +23,22 @@ export const clearDomElement = el => {
};
export default class Editor {
static create(monaco) {
if (this.editorInstance) return this.editorInstance;
this.editorInstance = new Editor(monaco);
static create() {
if (!this.editorInstance) {
this.editorInstance = new Editor();
}
return this.editorInstance;
}
constructor(monaco) {
this.monaco = monaco;
constructor() {
this.currentModel = null;
this.instance = null;
this.dirtyDiffController = null;
this.disposable = new Disposable();
this.modelManager = new ModelManager(this.monaco);
this.modelManager = new ModelManager();
this.decorationsController = new DecorationsController(this);
this.setupMonacoTheme();
setupMonacoTheme();
this.debouncedUpdate = _.debounce(() => {
this.updateDimensions();
......@@ -46,7 +50,7 @@ export default class Editor {
clearDomElement(domElement);
this.disposable.add(
(this.instance = this.monaco.editor.create(domElement, {
(this.instance = monacoEditor.create(domElement, {
...defaultEditorOptions,
})),
(this.dirtyDiffController = new DirtyDiffController(
......@@ -66,7 +70,7 @@ export default class Editor {
clearDomElement(domElement);
this.disposable.add(
(this.instance = this.monaco.editor.createDiffEditor(domElement, {
(this.instance = monacoEditor.createDiffEditor(domElement, {
...defaultEditorOptions,
quickSuggestions: false,
occurrencesHighlight: false,
......@@ -122,17 +126,11 @@ export default class Editor {
modified: model.getModel(),
});
this.monaco.editor.createDiffNavigator(this.instance, {
monacoEditor.createDiffNavigator(this.instance, {
alwaysRevealFirst: true,
});
}
setupMonacoTheme() {
this.monaco.editor.defineTheme(gitlabTheme.themeName, gitlabTheme.monacoTheme);
this.monaco.editor.setTheme('gitlab');
}
clearEditor() {
if (this.instance) {
this.instance.setModel(null);
......@@ -200,7 +198,7 @@ export default class Editor {
const getKeyCode = key => {
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 => {
......
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 }) => {
commit(rootTypes.CLEAR_PROJECTS, null, { root: true });
commit(rootTypes.SET_CURRENT_MERGE_REQUEST, `${id}`, { 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('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}`);
};
......
......@@ -12,8 +12,12 @@ let eTagPoll;
export const clearEtagPoll = () => {
eTagPoll = null;
};
export const stopPipelinePolling = () => eTagPoll && eTagPoll.stop();
export const restartPipelinePolling = () => eTagPoll && eTagPoll.restart();
export const stopPipelinePolling = () => {
if (eTagPoll) eTagPoll.stop();
};
export const restartPipelinePolling = () => {
if (eTagPoll) eTagPoll.restart();
};
export const requestLatestPipeline = ({ commit }) => commit(types.REQUEST_LATEST_PIPELINE);
export const receiveLatestPipelineError = ({ commit, dispatch }) => {
......@@ -51,9 +55,9 @@ export const fetchLatestPipeline = ({ dispatch, rootGetters }) => {
Visibility.change(() => {
if (!Visibility.hidden()) {
eTagPoll.restart();
dispatch('restartPipelinePolling');
} else {
eTagPoll.stop();
dispatch('stopPipelinePolling');
}
});
};
......
import $ from 'jquery';
import stickyMonitor from './lib/utils/sticky';
import { stickyMonitor } from './lib/utils/sticky';
export default (stickyTop) => {
stickyMonitor(document.querySelector('.js-diff-files-changed'), stickyTop);
......
import $ from 'jquery';
import _ from 'underscore';
import StickyFill from 'stickyfilljs';
import { polyfillSticky } from './lib/utils/sticky';
import axios from './lib/utils/axios_utils';
import { visitUrl } from './lib/utils/url_utility';
import bp from './breakpoints';
......@@ -70,14 +70,7 @@ export default class Job extends LogOutputBehaviours {
}
initAffixTopArea() {
/**
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);
polyfillSticky(this.$topBar);
}
scrollToBottom() {
......
......@@ -13,6 +13,7 @@ export default class LabelManager {
this.otherLabels = otherLabels || $('.js-other-labels');
this.errorMessage = 'Unable to update label prioritization at this time';
this.emptyState = document.querySelector('#js-priority-labels-empty-state');
this.$badgeItemTemplate = $('#js-badge-item-template');
this.sortable = Sortable.create(this.prioritizedLabels.get(0), {
filter: '.empty-message',
forceFallback: true,
......@@ -63,7 +64,11 @@ export default class LabelManager {
$target = this.otherLabels;
$from = this.prioritizedLabels;
}
$label.detach().appendTo($target);
const $detachedLabel = $label.detach();
this.toggleLabelPriorityBadge($detachedLabel, action);
$detachedLabel.appendTo($target);
if ($from.find('li').length) {
$from.find('.empty-message').removeClass('hidden');
}
......@@ -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() {
this.savePrioritySort()
.catch(() => flash(this.errorMessage));
......
......@@ -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) => {
const faviconEl = document.getElementById('favicon');
if (faviconEl && faviconPath) {
......@@ -393,8 +436,9 @@ export const setFavicon = (faviconPath) => {
export const resetFavicon = () => {
const faviconEl = document.getElementById('favicon');
const originalFavicon = faviconEl ? faviconEl.getAttribute('href') : null;
if (faviconEl) {
const originalFavicon = faviconEl.getAttribute('data-original-href');
faviconEl.setAttribute('href', originalFavicon);
}
};
......@@ -403,10 +447,9 @@ export const setCiStatusFavicon = pageUrl =>
axios.get(pageUrl)
.then(({ data }) => {
if (data && data.favicon) {
setFavicon(data.favicon);
} else {
resetFavicon();
return setFaviconOverlay(data.favicon);
}
return resetFavicon();
})
.catch(resetFavicon);
......
......@@ -79,37 +79,37 @@ export function getTimeago() {
if (!timeagoInstance) {
const localeRemaining = function getLocaleRemaining(number, index) {
return [
[s__('Timeago|less than a minute ago'), s__('Timeago|right now')],
[s__('Timeago|less than a minute ago'), s__('Timeago|%s seconds remaining')],
[s__('Timeago|about a minute ago'), s__('Timeago|1 minute remaining')],
[s__('Timeago|just now'), s__('Timeago|right now')],
[s__('Timeago|%s seconds ago'), s__('Timeago|%s seconds remaining')],
[s__('Timeago|1 minute ago'), s__('Timeago|1 minute remaining')],
[s__('Timeago|%s minutes ago'), s__('Timeago|%s minutes remaining')],
[s__('Timeago|about an hour ago'), s__('Timeago|1 hour remaining')],
[s__('Timeago|about %s hours ago'), s__('Timeago|%s hours remaining')],
[s__('Timeago|a day ago'), s__('Timeago|1 day remaining')],
[s__('Timeago|1 hour ago'), s__('Timeago|1 hour remaining')],
[s__('Timeago|%s hours ago'), s__('Timeago|%s hours remaining')],
[s__('Timeago|1 day ago'), s__('Timeago|1 day 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|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|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')],
][index];
};
const locale = function getLocale(number, index) {
return [
[s__('Timeago|less than a minute ago'), s__('Timeago|right now')],
[s__('Timeago|less than a minute ago'), s__('Timeago|in %s seconds')],
[s__('Timeago|about a minute ago'), s__('Timeago|in 1 minute')],
[s__('Timeago|just now'), s__('Timeago|right now')],
[s__('Timeago|%s seconds ago'), s__('Timeago|in %s seconds')],
[s__('Timeago|1 minute ago'), s__('Timeago|in 1 minute')],
[s__('Timeago|%s minutes ago'), s__('Timeago|in %s minutes')],
[s__('Timeago|about an hour ago'), s__('Timeago|in 1 hour')],
[s__('Timeago|about %s hours ago'), s__('Timeago|in %s hours')],
[s__('Timeago|a day ago'), s__('Timeago|in 1 day')],
[s__('Timeago|1 hour ago'), s__('Timeago|in 1 hour')],
[s__('Timeago|%s hours ago'), s__('Timeago|in %s hours')],
[s__('Timeago|1 day ago'), s__('Timeago|in 1 day')],
[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|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|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')],
][index];
};
......@@ -269,6 +269,17 @@ export const totalDaysInMonth = date => {
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
* based on provided date
......@@ -309,42 +320,27 @@ export const getSundays = date => {
};
/**
* Returns list of Dates representing a timeframe of Months from month of provided date (inclusive)
* 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 ]
* Returns list of Dates representing a timeframe of months from startDate and length
*
* @param {Date} startDate
* @param {Number} length
* @param {Date} date
*/
export const getTimeframeWindow = (length, date) => {
if (!length) {
export const getTimeframeWindowFrom = (startDate, length) => {
if (!(startDate instanceof Date) || !length) {
return [];
}
const currentDate = date instanceof Date ? date : new Date();
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
// Iterate and set date for the size of length
// and push date reference to timeframe list
for (let i = 0; i < length; i += 1) {
timeframe.push(new Date(currentDate.getTime()));
currentDate.setMonth(currentDate.getMonth() + 1);
}
const timeframe = new Array(length)
.fill()
.map(
(val, i) => new Date(
startDate.getFullYear(),
startDate.getMonth() + i,
1,
),
);
// Change date of last timeframe item to last date of the month
timeframe[length - 1].setDate(totalDaysInMonth(timeframe[length - 1]));
......@@ -352,6 +348,29 @@ export const getTimeframeWindow = (length, date) => {
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.utils = {
...(window.gl.utils || {}),
......
......@@ -8,4 +8,5 @@ export default {
OK: 200,
MULTIPLE_CHOICES: 300,
BAD_REQUEST: 400,
NOT_FOUND: 404,
};
import StickyFill from 'stickyfilljs';
export const createPlaceholder = () => {
const placeholder = document.createElement('div');
placeholder.classList.add('sticky-placeholder');
......@@ -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 (typeof CSS === 'undefined' || !(CSS.supports('(position: -webkit-sticky) or (position: sticky)'))) return;
......@@ -37,3 +48,13 @@ export default (el, stickyTop, insertPlaceholder = 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) {
}
export function webIDEUrl(route = undefined) {
let returnUrl = `${gon.relative_url_root}/-/ide/`;
let returnUrl = `${gon.relative_url_root || ''}/-/ide/`;
if (route) {
returnUrl += `project${route}`;
returnUrl += `project${route.replace(new RegExp(`^${gon.relative_url_root || ''}`), '')}`;
}
return returnUrl;
}
......@@ -144,6 +144,7 @@ document.addEventListener('DOMContentLoaded', () => {
$body.tooltip({
selector: '.has-tooltip, [data-toggle="tooltip"]',
trigger: 'hover',
boundary: 'viewport',
placement(tip, el) {
return $(el).data('placement') || 'bottom';
},
......
......@@ -22,4 +22,18 @@ document.addEventListener('DOMContentLoaded', () => {
errorBox: variableListEl.querySelector('.js-ci-variable-error-box'),
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';
import axios from './lib/utils/axios_utils';
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 {
constructor(container) {
this.$container = $(container);
......@@ -15,12 +26,10 @@ export default class ProjectLabelSubscription {
event.preventDefault();
const $btn = $(event.currentTarget);
const $span = $btn.find('span');
const url = $btn.attr('data-url');
const oldStatus = $btn.attr('data-status');
$btn.addClass('disabled');
$span.toggleClass('hidden');
axios.post(url).then(() => {
let newStatus;
......@@ -32,21 +41,28 @@ export default class ProjectLabelSubscription {
[newStatus, newAction] = ['unsubscribed', 'Subscribe'];
}
$span.toggleClass('hidden');
$btn.removeClass('disabled');
this.$buttons.attr('data-status', newStatus);
this.$buttons.find('> span').text(newAction);
this.$buttons.map((button) => {
this.$buttons.map((i, button) => {
const $button = $(button);
const originalTitle = $button.attr('data-original-title');
if ($button.attr('data-original-title')) {
$button.tooltip('hide').attr('data-original-title', newAction).tooltip('_fixTitle');
if (originalTitle) {
ProjectLabelSubscription.setNewTitle($button, originalTitle, newStatus, newAction);
}
return button;
});
}).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 {
<div>
<div
class="js-gcp-machine-type-dropdown dropdown"
:class="{ 'gl-show-field-errors': hasErrors }"
>
<dropdown-hidden-input
:name="fieldName"
:value="selectedMachineType"
/>
<dropdown-button
:class="{ 'gl-field-error-outline': hasErrors }"
:class="{ 'border-danger': hasErrors }"
:is-disabled="isDisabled"
:is-loading="isLoading"
:toggle-text="toggleText"
......@@ -132,8 +131,11 @@ export default {
</div>
</div>
<span
class="form-text text-muted"
:class="{ 'gl-field-error': hasErrors }"
class="form-text"
:class="{
'text-danger': hasErrors,
'text-muted': !hasErrors
}"
v-if="hasErrors"
>
{{ errorMessage }}
......
......@@ -147,7 +147,6 @@ export default {
<div>
<div
class="js-gcp-project-id-dropdown dropdown"
:class="{ 'gl-show-field-errors': hasErrors }"
>
<dropdown-hidden-input
:name="fieldName"
......@@ -155,7 +154,7 @@ export default {
/>
<dropdown-button
:class="{
'gl-field-error-outline': hasErrors,
'border-danger': hasErrors,
'read-only': hasOneProject
}"
:is-disabled="isDisabled"
......@@ -193,8 +192,11 @@ export default {
</div>
</div>
<span
class="form-text text-muted"
:class="{ 'gl-field-error': hasErrors }"
class="form-text"
:class="{
'text-danger': hasErrors,
'text-muted': !hasErrors
}"
v-html="helpText"
></span>
</div>
......
......@@ -63,14 +63,13 @@ export default {
<div>
<div
class="js-gcp-zone-dropdown dropdown"
:class="{ 'gl-show-field-errors': hasErrors }"
>
<dropdown-hidden-input
:name="fieldName"
:value="selectedZone"
/>
<dropdown-button
:class="{ 'gl-field-error-outline': hasErrors }"
:class="{ 'border-danger': hasErrors }"
:is-disabled="isDisabled"
:is-loading="isLoading"
:toggle-text="toggleText"
......@@ -106,8 +105,11 @@ export default {
</div>
</div>
<span
class="form-text text-muted"
:class="{ 'gl-field-error': hasErrors }"
class="form-text"
:class="{
'text-danger': hasErrors,
'text-muted': !hasErrors
}"
v-if="hasErrors"
>
{{ errorMessage }}
......
<script>
import { parseSeconds, stringifyTime } from '../../../lib/utils/pretty_time';
import tooltip from '../../../vue_shared/directives/tooltip';
export default {
name: 'TimeTrackingComparisonPane',
directives: {
tooltip,
},
props: {
timeSpent: {
type: Number,
......@@ -51,17 +55,12 @@ export default {
<div class="time-tracking-comparison-pane">
<div
class="compare-meter"
data-toggle="tooltip"
data-placement="top"
role="timeRemainingDisplay"
:aria-valuenow="timeRemainingTooltip"
:title="timeRemainingTooltip"
:data-original-title="timeRemainingTooltip"
v-tooltip
:class="timeRemainingStatusClass"
>
<div
class="meter-container"
role="timeSpentPercent"
:aria-valuenow="timeRemainingPercent"
>
<div
......
<script>
import tooltip from '~/vue_shared/directives/tooltip';
import MrWidgetAuthor from './mr_widget_author.vue';
export default {
name: 'MRWidgetAuthorTime',
name: 'MrWidgetAuthorTime',
components: {
MrWidgetAuthor,
},
directives: {
tooltip,
},
props: {
actionText: {
type: String,
......@@ -32,8 +36,7 @@
<mr-widget-author :author="author" />
<time
:title="dateTitle"
data-toggle="tooltip"
data-placement="top"
v-tooltip
data-container="body"
>
{{ dateReadable }}
......
<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';
export default {
name: 'MRWidgetClosed',
components: {
mrWidgetAuthorTime,
MrWidgetAuthorTime,
statusIcon,
},
props: {
......
......@@ -4,7 +4,7 @@
import loadingIcon from '~/vue_shared/components/loading_icon.vue';
import { s__, __ } from '~/locale';
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 eventHub from '../../event_hub';
......@@ -14,7 +14,7 @@
tooltip,
},
components: {
mrWidgetAuthorTime,
MrWidgetAuthorTime,
loadingIcon,
statusIcon,
ClipboardButton,
......
......@@ -36,7 +36,7 @@ import {
notify,
SourceBranchRemovalStatus,
} from './dependencies';
import { setFavicon } from '../lib/utils/common_utils';
import { setFaviconOverlay } from '../lib/utils/common_utils';
export default {
el: '#js-vue-mr-widget',
......@@ -159,8 +159,9 @@ export default {
},
setFaviconHelper() {
if (this.mr.ciStatusFaviconPath) {
setFavicon(this.mr.ciStatusFaviconPath);
return setFaviconOverlay(this.mr.ciStatusFaviconPath);
}
return Promise.resolve();
},
fetchDeployments() {
return this.service.fetchDeployments()
......
......@@ -513,7 +513,7 @@ const fileNameIcons = {
'credits.md': 'credits',
'credits.md.rendered': 'credits',
'.flowconfig': 'flow',
'favicon.ico': 'favicon',
'favicon.png': 'favicon',
'karma.conf.js': 'karma',
'karma.conf.ts': '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 {
color: $gl-link-color;
}
hr {
overflow: hidden;
}
.form-group.row .col-form-label {
// Bootstrap 4 aligns labels to the left
// for horizontal forms
......@@ -107,7 +111,7 @@ code {
background-color: $red-100;
border-radius: 3px;
.code & {
.code > & {
background-color: inherit;
padding: unset;
}
......@@ -118,10 +122,6 @@ code {
}
}
.code {
padding: 9.5px;
}
table {
// Remove any table border lines
border-spacing: 0;
......@@ -213,6 +213,10 @@ table {
border-bottom: 1px solid $well-inner-border;
}
}
.badge.badge-gray {
background-color: $well-expand-item;
}
}
.card {
......@@ -233,6 +237,13 @@ table {
}
}
.card-header {
h3.card-title,
h4.card-title {
margin-top: 0;
}
}
.nav-tabs {
// Override bootstrap's default border
border-bottom: 0;
......@@ -251,3 +262,33 @@ table {
pre code {
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,
}
}
[readonly] {
cursor: default;
}
.btn-no-padding {
padding: 0;
}
......@@ -305,14 +305,6 @@ img.emoji {
margin-bottom: 10px;
}
.btn-sign-in {
text-shadow: none;
@include media-breakpoint-up(sm) {
margin-top: 8px;
}
}
.side-filters {
fieldset {
margin-bottom: 15px;
......
......@@ -299,6 +299,7 @@
height: 14px;
width: 14px;
vertical-align: middle;
margin-bottom: 4px;
}
.dropdown-toggle-text {
......
......@@ -170,7 +170,7 @@ label {
}
.form-control::-webkit-input-placeholder {
color: $gl-text-color-secondary;
color: $placeholder-text-color;
}
.input-group {
......
......@@ -3,26 +3,26 @@
*/
@mixin gitlab-theme(
$color-100,
$color-200,
$color-500,
$color-700,
$color-800,
$color-900,
$location-badge-color,
$search-and-nav-links,
$active-tab-border,
$border-and-box-shadow,
$sidebar-text,
$nav-svg-color,
$color-alternate
) {
// Header
.navbar-gitlab {
background-color: $color-900;
background-color: $nav-svg-color;
.navbar-collapse {
color: $color-200;
color: $search-and-nav-links;
}
.container-fluid {
.navbar-toggler {
border-left: 1px solid lighten($color-700, 10%);
border-left: 1px solid lighten($border-and-box-shadow, 10%);
}
}
......@@ -31,40 +31,40 @@
> li {
> a:hover,
> a:focus {
background-color: rgba($color-200, 0.2);
background-color: rgba($search-and-nav-links, 0.2);
}
&.active > a,
&.dropdown.show > a {
color: $color-900;
color: $nav-svg-color;
background-color: $color-alternate;
}
&.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 {
color: $color-200;
color: $search-and-nav-links;
}
.nav {
> li {
color: $color-200;
color: $search-and-nav-links;
> a {
&.header-user-dropdown-toggle {
.header-user-avatar {
border-color: $color-200;
border-color: $search-and-nav-links;
}
}
&:hover,
&:focus {
@include media-breakpoint-up(sm) {
background-color: rgba($color-200, 0.2);
background-color: rgba($search-and-nav-links, 0.2);
}
svg {
......@@ -75,12 +75,12 @@
&.active > a,
&.dropdown.show > a {
color: $color-900;
color: $nav-svg-color;
background-color: $color-alternate;
&:hover {
svg {
fill: $color-900;
fill: $nav-svg-color;
}
}
}
......@@ -88,7 +88,7 @@
.impersonated-user,
.impersonated-user:hover {
svg {
fill: $color-900;
fill: $nav-svg-color;
}
}
}
......@@ -99,34 +99,34 @@
> a {
&:hover,
&:focus {
background-color: rgba($color-200, 0.2);
background-color: rgba($search-and-nav-links, 0.2);
}
}
}
.search {
form {
background-color: rgba($color-200, 0.2);
background-color: rgba($search-and-nav-links, 0.2);
&:hover {
background-color: rgba($color-200, 0.3);
background-color: rgba($search-and-nav-links, 0.3);
}
}
.location-badge {
color: $color-100;
background-color: rgba($color-200, 0.1);
border-right: 1px solid $color-800;
color: $location-badge-color;
background-color: rgba($search-and-nav-links, 0.1);
border-right: 1px solid $sidebar-text;
}
.search-input::placeholder {
color: rgba($color-200, 0.8);
color: rgba($search-and-nav-links, 0.8);
}
.search-input-wrap {
.search-icon,
.clear-icon {
fill: rgba($color-200, 0.8);
fill: rgba($search-and-nav-links, 0.8);
}
}
......@@ -141,38 +141,34 @@
.search-input-wrap {
.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
.nav-sidebar li.active {
box-shadow: inset 4px 0 0 $color-700;
box-shadow: inset 4px 0 0 $border-and-box-shadow;
> a {
color: $color-800;
color: $sidebar-text;
}
svg {
fill: $color-800;
fill: $sidebar-text;
}
}
.sidebar-top-level-items > li.active .badge.badge-pill {
color: $color-800;
color: $sidebar-text;
}
.nav-links li {
&.active a,
a.active {
border-bottom: 2px solid $color-500;
border-bottom: 2px solid $active-tab-border;
.badge.badge-pill {
font-weight: $gl-font-weight-bold;
......@@ -181,27 +177,27 @@
}
.branch-header-title {
color: $color-700;
color: $border-and-box-shadow;
}
.ide-file-list .file.file-active {
color: $color-700;
color: $border-and-box-shadow;
}
.ide-sidebar-link {
&.active {
color: $color-700;
box-shadow: inset 3px 0 $color-700;
color: $border-and-box-shadow;
box-shadow: inset 3px 0 $border-and-box-shadow;
&.is-right {
box-shadow: inset -3px 0 $color-700;
box-shadow: inset -3px 0 $border-and-box-shadow;
}
}
}
}
body {
&.ui_indigo {
&.ui-indigo {
@include gitlab-theme(
$indigo-100,
$indigo-200,
......@@ -213,19 +209,19 @@ body {
);
}
&.ui_dark {
&.ui-light-indigo {
@include gitlab-theme(
$theme-gray-100,
$theme-gray-200,
$theme-gray-500,
$theme-gray-700,
$theme-gray-800,
$theme-gray-900,
$indigo-100,
$indigo-200,
$indigo-500,
$indigo-500,
$indigo-700,
$indigo-700,
$white-light
);
}
&.ui_blue {
&.ui-blue {
@include gitlab-theme(
$theme-blue-100,
$theme-blue-200,
......@@ -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(
$theme-green-100,
$theme-green-200,
......@@ -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(
$theme-gray-900,
$theme-gray-700,
......
......@@ -139,6 +139,8 @@
}
.nav {
flex-wrap: nowrap;
> li:not(.d-none) a {
@include media-breakpoint-down(xs) {
margin-left: 0;
......@@ -158,11 +160,12 @@
}
.navbar-toggler {
position: relative;
right: -10px;
border-radius: 0;
min-width: 45px;
padding: 0;
margin-right: -7px;
margin: $gl-padding-8 -7px $gl-padding-8 0;
font-size: 14px;
text-align: center;
color: currentColor;
......@@ -186,6 +189,7 @@
display: -webkit-flex;
display: flex;
padding-right: 10px;
flex-direction: row;
}
li {
......@@ -290,6 +294,10 @@
margin: 8px;
}
}
.dropdown-menu {
position: absolute;
}
}
.navbar-sub-nav {
......@@ -437,12 +445,18 @@
}
.btn-sign-in {
margin-top: 3px;
background-color: $indigo-100;
color: $indigo-900;
font-weight: $gl-font-weight-bold;
line-height: 18px;
&:hover {
background-color: $white-light;
}
@include media-breakpoint-down(xs) {
margin-top: $gl-padding-4;
}
}
.navbar-nav {
......
......@@ -4,3 +4,11 @@
text-decoration: none;
}
}
.page-item {
&.active {
.page-link {
z-index: 3;
}
}
}
.table-holder {
margin: 0;
overflow: auto;
}
table {
......@@ -38,6 +39,11 @@ table {
&.wide {
width: 55%;
}
&.table-th-transparent {
background: none;
color: $gl-text-color-secondary;
}
}
td {
......@@ -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) {
th {
width: 100%;
......
......@@ -4,7 +4,7 @@
padding: 0;
&::before {
@include notes-media('max', map-get($grid-breakpoints, xs)) {
@include notes-media('max', map-get($grid-breakpoints, sm)) {
background: none;
}
}
......@@ -34,7 +34,7 @@
.timeline-entry-inner {
position: relative;
@include notes-media('max', map-get($grid-breakpoints, xs)) {
@include notes-media('max', map-get($grid-breakpoints, sm)) {
.timeline-icon {
display: none;
}
......
......@@ -99,7 +99,7 @@ $theme-gray-200: #dfdfdf;
$theme-gray-300: #cccccc;
$theme-gray-400: #bababa;
$theme-gray-500: #a7a7a7;
$theme-gray-600: #949494;
$theme-gray-600: #919191;
$theme-gray-700: #707070;
$theme-gray-800: #4f4f4f;
$theme-gray-900: #2e2e2e;
......@@ -117,6 +117,15 @@ $theme-blue-800: #25496e;
$theme-blue-900: #1a3652;
$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-100: #e4f3ea;
$theme-green-200: #c0dfcd;
......@@ -129,6 +138,29 @@ $theme-green-800: #145d33;
$theme-green-900: #0d4524;
$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-transparent: rgba(0, 0, 0, 0.3);
$almost-black: #242424;
......@@ -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-dark: darken($white-normal, $darken-border-factor);
/*
* Override Bootstrap 4 variables
*/
$secondary: $gray-light;
/*
* UI elements
*/
......@@ -164,7 +191,7 @@ $gl-font-weight-normal: 400;
$gl-font-weight-bold: 600;
$gl-text-color: #2e2e2e;
$gl-text-color-secondary: #707070;
$gl-text-color-tertiary: #949494;
$gl-text-color-tertiary: #919191;
$gl-text-color-quaternary: #d6d6d6;
$gl-text-color-inverted: rgba(255, 255, 255, 1);
$gl-text-color-secondary-inverted: rgba(255, 255, 255, 0.85);
......@@ -407,6 +434,22 @@ $gl-btn-horz-padding: 12px;
$badge-bg: rgba(0, 0, 0, 0.07);
$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
*/
......@@ -776,3 +819,16 @@ $modal-body-height: 134px;
Prometheus
*/
$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 @@
box-shadow: 0 1px 2px $issue-boards-card-shadow;
list-style: none;
// as a fallback, hide overflow content so that dragging and dropping still works
overflow: hidden;
&:not(:last-child) {
margin-bottom: 5px;
}
......
......@@ -12,26 +12,22 @@
@keyframes blinking-dots {
0% {
background-color: rgba($white-light, 1);
box-shadow: 12px 0 0 0 rgba($white-light, 0.2),
24px 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);
}
25% {
background-color: rgba($white-light, 0.4);
box-shadow: 12px 0 0 0 rgba($white-light, 2),
24px 0 0 0 rgba($white-light, 0.2);
box-shadow: 12px 0 0 0 rgba($white-light, 2), 24px 0 0 0 rgba($white-light, 0.2);
}
75% {
background-color: rgba($white-light, 0.4);
box-shadow: 12px 0 0 0 rgba($white-light, 0.2),
24px 0 0 0 rgba($white-light, 1);
box-shadow: 12px 0 0 0 rgba($white-light, 0.2), 24px 0 0 0 rgba($white-light, 1);
}
100% {
background-color: rgba($white-light, 1);
box-shadow: 12px 0 0 0 rgba($white-light, 0.2),
24px 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);
}
}
......@@ -71,6 +67,10 @@
.bash {
display: block;
}
&.build-trace-rounded {
border-radius: $border-radius-base;
}
}
.top-bar {
......
......@@ -57,69 +57,8 @@
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 {
padding: 0 $grid-size;
padding: $gl-padding-4 $grid-size;
line-height: 16px;
border-radius: $label-border-radius;
color: $white-light;
......@@ -133,26 +72,29 @@
}
.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) {
background-color: $white-light;
cursor: move;
cursor: -webkit-grab;
cursor: -moz-grab;
&:active {
cursor: -webkit-grabbing;
cursor: -moz-grabbing;
}
margin-bottom: 5px;
display: flex;
justify-content: space-between;
padding: $gl-padding;
border-radius: $border-radius-default;
&.sortable-ghost {
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 {
......@@ -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 {
margin-bottom: 30px;
h5 {
font-size: $gl-font-size;
}
.add-priority {
display: none;
color: $gray-light;
......@@ -214,31 +131,11 @@
}
.other-labels {
h5 {
font-size: $gl-font-size;
}
.remove-priority {
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 {
font-size: 0;
padding: 12px 16px;
......@@ -292,10 +189,8 @@
}
.label-subscribe-button {
@media(min-width: map-get($grid-breakpoints, md)) {
min-width: 105px;
margin-left: $gl-padding;
}
width: 105px;
font-weight: 200;
.label-subscribe-button-icon {
&[disabled] {
......@@ -332,3 +227,95 @@
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 @@
}
.build-failures {
th {
border-top: 0;
}
.build-state {
padding: 20px 2px;
.build-name {
float: right;
font-weight: $gl-font-weight-normal;
}
.ci-status-icon-failed svg {
vertical-align: middle;
}
.stage {
color: $gl-text-color-secondary;
font-weight: $gl-font-weight-normal;
......@@ -344,6 +343,81 @@
border: 0;
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 {
......@@ -929,7 +1003,7 @@ button.mini-pipeline-graph-dropdown-toggle {
&.dropdown-menu {
transform: translate(-80%, 0);
@media(min-width: map-get($grid-breakpoints, md)) {
@media (min-width: map-get($grid-breakpoints, md)) {
transform: translate(-50%, 0);
right: auto;
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 {
label {
margin-right: 20px;
......@@ -38,44 +16,61 @@
.application-theme {
label {
margin-right: 20px;
margin: 0 $gl-padding $gl-padding 0;
text-align: center;
}
.preview {
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 {
@include application-theme-preview($indigo-900, $indigo-700, $indigo-800, $indigo-500);
&.ui-blue {
background-color: $theme-blue-900;
}
&.dark {
@include application-theme-preview($theme-gray-900, $theme-gray-700, $theme-gray-800, $theme-gray-600);
&.ui-light-blue {
background-color: $theme-light-blue-700;
}
&.light {
@include application-theme-preview($theme-gray-600, $theme-gray-200, $theme-gray-400, $theme-gray-100);
&.ui-green {
background-color: $theme-green-900;
}
&.blue {
@include application-theme-preview($theme-blue-900, $theme-blue-700, $theme-blue-800, $theme-blue-500);
&.ui-light-green {
background-color: $theme-light-green-700;
}
&.green {
@include application-theme-preview($theme-green-900, $theme-green-700, $theme-green-800, $theme-green-500);
&.ui-red {
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 {
display: block;
}
.quadrant {
display: inline-block;
height: 50px;
width: 80px;
}
}
.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