Commit 2409acfa authored by Robert Speicher's avatar Robert Speicher

Merge branch 'master' into 'group-sort-dropdown-blank'

# Conflicts:
#   spec/features/dashboard/group_spec.rb
parents 0f366c74 f277fa14
...@@ -270,6 +270,7 @@ flaky-examples-check: ...@@ -270,6 +270,7 @@ flaky-examples-check:
NEW_FLAKY_SPECS_REPORT: rspec_flaky/report-new.json NEW_FLAKY_SPECS_REPORT: rspec_flaky/report-new.json
stage: post-test stage: post-test
allow_failure: yes allow_failure: yes
retry: 0
only: only:
- branches - branches
except: except:
...@@ -429,6 +430,7 @@ ee_compat_check: ...@@ -429,6 +430,7 @@ ee_compat_check:
- branches@gitlab-org/gitlab-ee - branches@gitlab-org/gitlab-ee
- branches@gitlab/gitlab-ee - branches@gitlab/gitlab-ee
allow_failure: yes allow_failure: yes
retry: 0
cache: cache:
key: "ee_compat_check_repo" key: "ee_compat_check_repo"
paths: paths:
......
<svg width="24" height="30" viewBox="0 0 24 30" xmlns="http://www.w3.org/2000/svg"><title>cursor</title><g fill="none" fill-rule="evenodd"><path d="M24 12.105c0 6.686-5.74 11.58-12 17.895C5.74 23.684 0 18.79 0 12.105 0 5.42 5.373 0 12 0s12 5.42 12 12.105z" fill="#1F78D1" fill-rule="nonzero"/><path d="M15.28 25.249c1.458-1.475 2.539-2.635 3.474-3.747 2.851-3.394 4.203-6.265 4.203-9.397 0-6.111-4.908-11.062-10.957-11.062-6.05 0-10.957 4.951-10.957 11.062 0 3.132 1.352 6.003 4.203 9.397.935 1.112 2.016 2.272 3.474 3.747.511.517 2.216 2.213 3.28 3.275 1.064-1.062 2.769-2.758 3.28-3.275z" fill="#FFF"/><path d="M14.551 8.256A6.874 6.874 0 0 0 12 7.787c-.91 0-1.763.156-2.558.469-.79.308-1.42.725-1.888 1.252-.465.527-.697 1.096-.697 1.708 0 .5.159.977.476 1.433.321.45.772.841 1.352 1.172l.583.334-.181.643c-.107.407-.263.79-.469 1.152a6.604 6.604 0 0 0 1.842-1.145l.288-.254.381.04c.309.035.599.053.871.053.91 0 1.761-.154 2.551-.462.795-.312 1.424-.732 1.889-1.259.468-.526.703-1.096.703-1.707 0-.612-.235-1.181-.703-1.708-.465-.527-1.094-.944-1.889-1.252zm2.645.81c.536.656.804 1.373.804 2.15 0 .776-.268 1.495-.804 2.156-.535.656-1.263 1.176-2.183 1.56-.92.38-1.924.57-3.013.57a9.16 9.16 0 0 1-.971-.054 7.32 7.32 0 0 1-3.08 1.62 5.044 5.044 0 0 1-.764.148h-.033a.26.26 0 0 1-.181-.074.324.324 0 0 1-.107-.18v-.007c-.014-.018-.016-.045-.007-.08.014-.037.018-.059.014-.068 0-.009.01-.031.033-.067a.645.645 0 0 0 .04-.06 1.73 1.73 0 0 0 .047-.054l.054-.06a53.034 53.034 0 0 1 .435-.489c.049-.049.118-.136.207-.26.094-.126.168-.24.221-.342.054-.103.114-.235.181-.395.067-.161.125-.33.174-.51-.7-.397-1.254-.888-1.66-1.473A3.261 3.261 0 0 1 6 11.216c0-.777.268-1.494.804-2.15.535-.66 1.263-1.18 2.183-1.56.92-.384 1.924-.576 3.013-.576 1.09 0 2.094.192 3.013.576.92.38 1.648.9 2.183 1.56z" fill="#1F78D1" fill-rule="nonzero"/></g></svg>
<svg width="48" height="60" viewBox="0 0 48 60" xmlns="http://www.w3.org/2000/svg"><title>cursor_2x</title><g fill="none" fill-rule="evenodd"><path d="M48 24.21C48 37.583 36.522 47.369 24 60 11.478 47.368 0 37.582 0 24.21 0 10.84 10.745 0 24 0s24 10.84 24 24.21z" fill="#1F78D1" fill-rule="nonzero"/><path d="M30.56 50.497c2.915-2.95 5.078-5.268 6.947-7.493 5.703-6.788 8.406-12.53 8.406-18.793 0-12.223-9.815-22.124-21.913-22.124S2.087 11.988 2.087 24.211c0 6.263 2.703 12.005 8.406 18.793 1.87 2.225 4.032 4.544 6.947 7.493 1.022 1.035 4.432 4.426 6.56 6.55 2.128-2.124 5.538-5.515 6.56-6.55z" fill="#FFF"/><path d="M29.103 16.512c-1.58-.625-3.282-.938-5.103-.938-1.821 0-3.527.313-5.116.938-1.58.616-2.84 1.45-3.777 2.504-.928 1.054-1.393 2.192-1.393 3.415 0 1 .317 1.956.951 2.866.643.902 1.545 1.684 2.706 2.344l1.165.67-.362 1.286a9.603 9.603 0 0 1-.937 2.303 13.208 13.208 0 0 0 3.683-2.29l.576-.509.763.08c.616.072 1.196.108 1.741.108 1.821 0 3.522-.308 5.103-.925 1.589-.625 2.848-1.464 3.776-2.517.938-1.054 1.407-2.192 1.407-3.416 0-1.223-.469-2.361-1.407-3.415-.928-1.053-2.187-1.888-3.776-2.504zm5.29 1.62c1.071 1.313 1.607 2.746 1.607 4.3 0 1.553-.536 2.99-1.607 4.312-1.072 1.312-2.527 2.353-4.366 3.12-1.84.76-3.848 1.139-6.027 1.139a18.32 18.32 0 0 1-1.942-.107c-1.768 1.562-3.821 2.643-6.16 3.24-.438.126-.947.224-1.527.295h-.067a.521.521 0 0 1-.362-.147.649.649 0 0 1-.214-.362v-.013c-.027-.036-.032-.09-.014-.16.027-.072.036-.117.027-.135 0-.017.022-.062.067-.133a1.29 1.29 0 0 0 .08-.121c.01-.009.04-.045.094-.107a106.068 106.068 0 0 1 .522-.59c.215-.232.367-.401.456-.508.098-.099.236-.273.415-.523.188-.25.335-.477.442-.683.107-.205.228-.468.362-.79.134-.321.25-.66.348-1.018-1.402-.794-2.51-1.777-3.322-2.946C12.402 25.025 12 23.77 12 22.43c0-1.553.536-2.986 1.607-4.299 1.072-1.321 2.527-2.361 4.366-3.12 1.84-.768 3.848-1.152 6.027-1.152 2.179 0 4.188.384 6.027 1.152 1.84.759 3.294 1.799 4.366 3.12z" fill="#1F78D1" fill-rule="nonzero"/></g></svg>
{"iconCount":135,"spriteSize":58718,"icons":["abuse","account","admin","angle-double-left","angle-double-right","angle-down","angle-left","angle-right","angle-up","appearance","applications","approval","arrow-right","assignee","bold","book","branch","calendar","cancel","chevron-down","chevron-left","chevron-right","chevron-up","clock","close","code","comment-dots","comment-next","comment","comments","commit","credit-card","disk","doc_code","doc_image","doc_text","download","duplicate","earth","eye-slash","eye","file-additions","file-deletion","file-modified","filter","folder","fork","geo-nodes","git-merge","group","history","home","hook","issue-block","issue-child","issue-close","issue-duplicate","issue-new","issue-open-m","issue-open","issue-parent","issues","key-2","key","label","labels","leave","level-up","license","link","list-bulleted","list-numbered","location-dot","location","lock-open","lock","log","mail","merge-request-close","messages","mobile-issue-close","monitor","more","notifications-off","notifications","overview","pencil","pipeline","play","plus-square-o","plus-square","plus","preferences","profile","project","push-rules","question-o","question","quote","redo","remove","repeat","retry","scale","screen-full","screen-normal","search","settings","shield","slight-frown","slight-smile","smile","smiley","snippet","spam","star-o","star","stop","talic","task-done","template","thump-down","thump-up","timer","todo-add","todo-done","token","unapproval","unassignee","unlink","user","users","volume-up","warning","work"]} {"iconCount":164,"spriteSize":72823,"icons":["abuse","account","admin","angle-double-left","angle-double-right","angle-down","angle-left","angle-right","angle-up","appearance","applications","approval","arrow-right","assignee","bold","book","branch","calendar","cancel","chevron-down","chevron-left","chevron-right","chevron-up","clock","close","code","collapse","comment-dots","comment-next","comment","comments","commit","credit-card","dashboard","disk","doc_code","doc_image","doc_text","download","duplicate","earth","eye-slash","eye","file-additions","file-deletion","file-modified","filter","folder","fork","geo-nodes","git-merge","group","history","home","hook","image-comment-dark","import","issue-block","issue-child","issue-close","issue-duplicate","issue-new","issue-open-m","issue-open","issue-parent","issues","key-2","key","label","labels","leave","level-up","license","link","list-bulleted","list-numbered","location-dot","location","lock-open","lock","log","mail","menu","merge-request-close","messages","mobile-issue-close","monitor","more","notifications-off","notifications","overview","pencil","pipeline","play","plus-square-o","plus-square","plus","preferences","profile","project","push-rules","question-o","question","quote","redo","remove","repeat","retry","scale","screen-full","screen-normal","scroll_down","scroll_up","search","settings","shield","slight-frown","slight-smile","smile","smiley","snippet","spam","star-o","star","status_canceled_borderless","status_canceled","status_closed","status_created_borderless","status_created","status_failed_borderless","status_failed","status_manual_borderless","status_manual","status_notfound_borderless","status_open","status_pending_borderless","status_pending","status_running_borderless","status_running","status_skipped_borderless","status_skipped","status_success_borderless","status_success_solid","status_success","status_warning_borderless","status_warning","stop","talic","task-done","template","thump-down","thump-up","timer","todo-add","todo-done","token","unapproval","unassignee","unlink","user","users","volume-up","warning","work"]}
\ No newline at end of file \ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
const MAX_MESSAGE_LENGTH = 500; const MAX_MESSAGE_LENGTH = 500;
const MESSAGE_CELL_SELECTOR = '.abuse-reports .message'; const MESSAGE_CELL_SELECTOR = '.abuse-reports .message';
class AbuseReports { export default class AbuseReports {
constructor() { constructor() {
$(MESSAGE_CELL_SELECTOR).each(this.truncateLongMessage); $(MESSAGE_CELL_SELECTOR).each(this.truncateLongMessage);
$(document) $(document)
...@@ -32,6 +32,3 @@ class AbuseReports { ...@@ -32,6 +32,3 @@ class AbuseReports {
} }
} }
} }
window.gl = window.gl || {};
window.gl.AbuseReports = AbuseReports;
class AjaxLoadingSpinner { export default class AjaxLoadingSpinner {
static init() { static init() {
const $elements = $('.js-ajax-loading-spinner'); const $elements = $('.js-ajax-loading-spinner');
...@@ -30,6 +30,3 @@ class AjaxLoadingSpinner { ...@@ -30,6 +30,3 @@ class AjaxLoadingSpinner {
classList.toggle('fa-spin'); classList.toggle('fa-spin');
} }
} }
window.gl = window.gl || {};
gl.AjaxLoadingSpinner = AjaxLoadingSpinner;
/* eslint-disable comma-dangle, func-names, no-new, space-before-function-paren, one-var, /* eslint-disable func-names, no-new, space-before-function-paren, one-var,
promise/catch-or-return */ promise/catch-or-return */
import _ from 'underscore'; import _ from 'underscore';
import CreateLabelDropdown from '../../create_label';
window.gl = window.gl || {}; window.gl = window.gl || {};
window.gl.issueBoards = window.gl.issueBoards || {}; window.gl.issueBoards = window.gl.issueBoards || {};
...@@ -15,15 +16,15 @@ $(document).off('created.label').on('created.label', (e, label) => { ...@@ -15,15 +16,15 @@ $(document).off('created.label').on('created.label', (e, label) => {
label: { label: {
id: label.id, id: label.id,
title: label.title, title: label.title,
color: label.color color: label.color,
} },
}); });
}); });
gl.issueBoards.newListDropdownInit = () => { gl.issueBoards.newListDropdownInit = () => {
$('.js-new-board-list').each(function () { $('.js-new-board-list').each(function () {
const $this = $(this); const $this = $(this);
new gl.CreateLabelDropdown($this.closest('.dropdown').find('.dropdown-new-label'), $this.data('namespace-path'), $this.data('project-path')); new CreateLabelDropdown($this.closest('.dropdown').find('.dropdown-new-label'), $this.data('namespace-path'), $this.data('project-path'));
$this.glDropdown({ $this.glDropdown({
data(term, callback) { data(term, callback) {
...@@ -38,17 +39,17 @@ gl.issueBoards.newListDropdownInit = () => { ...@@ -38,17 +39,17 @@ gl.issueBoards.newListDropdownInit = () => {
const $a = $('<a />', { const $a = $('<a />', {
class: (active ? `is-active js-board-list-${active.id}` : ''), class: (active ? `is-active js-board-list-${active.id}` : ''),
text: label.title, text: label.title,
href: '#' href: '#',
}); });
const $labelColor = $('<span />', { const $labelColor = $('<span />', {
class: 'dropdown-label-box', class: 'dropdown-label-box',
style: `background-color: ${label.color}` style: `background-color: ${label.color}`,
}); });
return $li.append($a.prepend($labelColor)); return $li.append($a.prepend($labelColor));
}, },
search: { search: {
fields: ['title'] fields: ['title'],
}, },
filterable: true, filterable: true,
selectable: true, selectable: true,
...@@ -66,13 +67,13 @@ gl.issueBoards.newListDropdownInit = () => { ...@@ -66,13 +67,13 @@ gl.issueBoards.newListDropdownInit = () => {
label: { label: {
id: label.id, id: label.id,
title: label.title, title: label.title,
color: label.color color: label.color,
} },
}); });
Store.state.lists = _.sortBy(Store.state.lists, 'position'); Store.state.lists = _.sortBy(Store.state.lists, 'position');
} }
} },
}); });
}); });
}; };
/* globals Flash */
import Visibility from 'visibilityjs';
import axios from 'axios';
import Poll from './lib/utils/poll';
import { s__ } from './locale';
import './flash';
/**
* Cluster page has 2 separate parts:
* Toggle button
*
* - Polling status while creating or scheduled
* -- Update status area with the response result
*/
class ClusterService {
constructor(options = {}) {
this.options = options;
}
fetchData() {
return axios.get(this.options.endpoint);
}
}
export default class Clusters {
constructor() {
const dataset = document.querySelector('.js-edit-cluster-form').dataset;
this.state = {
statusPath: dataset.statusPath,
clusterStatus: dataset.clusterStatus,
clusterStatusReason: dataset.clusterStatusReason,
toggleStatus: dataset.toggleStatus,
};
this.service = new ClusterService({ endpoint: this.state.statusPath });
this.toggleButton = document.querySelector('.js-toggle-cluster');
this.toggleInput = document.querySelector('.js-toggle-input');
this.errorContainer = document.querySelector('.js-cluster-error');
this.successContainer = document.querySelector('.js-cluster-success');
this.creatingContainer = document.querySelector('.js-cluster-creating');
this.errorReasonContainer = this.errorContainer.querySelector('.js-error-reason');
this.toggleButton.addEventListener('click', this.toggle.bind(this));
if (this.state.clusterStatus !== 'created') {
this.updateContainer(this.state.clusterStatus, this.state.clusterStatusReason);
}
if (this.state.statusPath) {
this.initPolling();
}
}
toggle() {
this.toggleButton.classList.toggle('checked');
this.toggleInput.setAttribute('value', this.toggleButton.classList.contains('checked').toString());
}
initPolling() {
this.poll = new Poll({
resource: this.service,
method: 'fetchData',
successCallback: (data) => {
const { status, status_reason } = data.data;
this.updateContainer(status, status_reason);
},
errorCallback: () => {
Flash(s__('ClusterIntegration|Something went wrong on our end.'));
},
});
if (!Visibility.hidden()) {
this.poll.makeRequest();
} else {
this.service.fetchData();
}
Visibility.change(() => {
if (!Visibility.hidden()) {
this.poll.restart();
} else {
this.poll.stop();
}
});
}
hideAll() {
this.errorContainer.classList.add('hidden');
this.successContainer.classList.add('hidden');
this.creatingContainer.classList.add('hidden');
}
updateContainer(status, error) {
this.hideAll();
switch (status) {
case 'created':
this.successContainer.classList.remove('hidden');
break;
case 'errored':
this.errorContainer.classList.remove('hidden');
this.errorReasonContainer.textContent = error;
break;
case 'scheduled':
case 'creating':
this.creatingContainer.classList.remove('hidden');
break;
default:
this.hideAll();
}
}
}
/* eslint-disable func-names, space-before-function-paren, wrap-iife */
/* global CommitFile */
window.Commit = (function() {
function Commit() {
$('.files .diff-file').each(function() {
return new CommitFile(this);
});
}
return Commit;
})();
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-new */
/* global ImageFile */
(function() {
this.CommitFile = (function() {
function CommitFile(file) {
if ($('.image', file).length) {
new gl.ImageFile(file);
}
}
return CommitFile;
})();
}).call(window);
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-use-before-define, prefer-arrow-callback, no-else-return, consistent-return, prefer-template, quotes, one-var, one-var-declaration-per-line, no-unused-vars, no-return-assign, comma-dangle, quote-props, no-unused-expressions, no-sequences, object-shorthand, max-len */ /* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-use-before-define, prefer-arrow-callback, no-else-return, consistent-return, prefer-template, quotes, one-var, one-var-declaration-per-line, no-unused-vars, no-return-assign, comma-dangle, quote-props, no-unused-expressions, no-sequences, object-shorthand, max-len */
import 'vendor/jquery.waitforimages';
(function() { (function() {
gl.ImageFile = (function() { gl.ImageFile = (function() {
var prepareFrames; var prepareFrames;
...@@ -17,15 +19,10 @@ ...@@ -17,15 +19,10 @@
// Load two-up view after images are loaded // Load two-up view after images are loaded
// so that we can display the correct width and height information // so that we can display the correct width and height information
const images = $('.two-up.view img', _this.file); const $images = $('.two-up.view img', _this.file);
let loadedCount = 0;
images.on('load', () => {
loadedCount += 1;
if (loadedCount === images.length) { $images.waitForImages(function() {
_this.initView('two-up'); _this.initView('two-up');
}
}); });
}); });
}; };
......
/* eslint-disable func-names, space-before-function-paren, wrap-iife, quotes, consistent-return, no-return-assign, no-param-reassign, one-var, no-var, one-var-declaration-per-line, no-unused-vars, prefer-template, object-shorthand, comma-dangle, max-len, prefer-arrow-callback */ /* eslint-disable func-names, wrap-iife, consistent-return,
no-return-assign, no-param-reassign, one-var-declaration-per-line, no-unused-vars,
prefer-template, object-shorthand, prefer-arrow-callback */
/* global Pager */ /* global Pager */
window.CommitsList = (function() { export default (function () {
var CommitsList = {}; const CommitsList = {};
CommitsList.timer = null; CommitsList.timer = null;
CommitsList.init = function(limit) { CommitsList.init = function (limit) {
this.$contentList = $('.content_list'); this.$contentList = $('.content_list');
$("body").on("click", ".day-commits-table li.commit", function(e) { $('body').on('click', '.day-commits-table li.commit', function (e) {
if (e.target.nodeName !== "A") { if (e.target.nodeName !== 'A') {
location.href = $(this).attr("url"); location.href = $(this).attr('url');
e.stopPropagation(); e.stopPropagation();
return false; return false;
} }
...@@ -19,48 +21,47 @@ window.CommitsList = (function() { ...@@ -19,48 +21,47 @@ window.CommitsList = (function() {
Pager.init(parseInt(limit, 10), false, false, this.processCommits); Pager.init(parseInt(limit, 10), false, false, this.processCommits);
this.content = $("#commits-list"); this.content = $('#commits-list');
this.searchField = $("#commits-search"); this.searchField = $('#commits-search');
this.lastSearch = this.searchField.val(); this.lastSearch = this.searchField.val();
return this.initSearch(); return this.initSearch();
}; };
CommitsList.initSearch = function() { CommitsList.initSearch = function () {
this.timer = null; this.timer = null;
return this.searchField.keyup((function(_this) { return this.searchField.keyup((function (_this) {
return function() { return function () {
clearTimeout(_this.timer); clearTimeout(_this.timer);
return _this.timer = setTimeout(_this.filterResults, 500); return _this.timer = setTimeout(_this.filterResults, 500);
}; };
})(this)); })(this));
}; };
CommitsList.filterResults = function() { CommitsList.filterResults = function () {
var commitsUrl, form, search; const form = $('.commits-search-form');
form = $(".commits-search-form"); const search = CommitsList.searchField.val();
search = CommitsList.searchField.val();
if (search === CommitsList.lastSearch) return; if (search === CommitsList.lastSearch) return;
commitsUrl = form.attr("action") + '?' + form.serialize(); const commitsUrl = form.attr('action') + '?' + form.serialize();
CommitsList.content.fadeTo('fast', 0.5); CommitsList.content.fadeTo('fast', 0.5);
return $.ajax({ return $.ajax({
type: "GET", type: 'GET',
url: form.attr("action"), url: form.attr('action'),
data: form.serialize(), data: form.serialize(),
complete: function() { complete: function () {
return CommitsList.content.fadeTo('fast', 1.0); return CommitsList.content.fadeTo('fast', 1.0);
}, },
success: function(data) { success: function (data) {
CommitsList.lastSearch = search; CommitsList.lastSearch = search;
CommitsList.content.html(data.html); CommitsList.content.html(data.html);
return history.replaceState({ return history.replaceState({
page: commitsUrl page: commitsUrl,
// Change url so if user reload a page - search results are saved // Change url so if user reload a page - search results are saved
}, document.title, commitsUrl); }, document.title, commitsUrl);
}, },
error: function() { error: function () {
CommitsList.lastSearch = null; CommitsList.lastSearch = null;
}, },
dataType: "json" dataType: 'json',
}); });
}; };
...@@ -81,7 +82,7 @@ window.CommitsList = (function() { ...@@ -81,7 +82,7 @@ window.CommitsList = (function() {
commitsCount = $commitsHeadersLast.nextUntil('li.js-commit-header').find('li.commit').length; commitsCount = $commitsHeadersLast.nextUntil('li.js-commit-header').find('li.commit').length;
// Remove duplicate of commits header. // Remove duplicate of commits header.
processedData = $processedData.not(`li.js-commit-header[data-day="${loadedShownDayFirst}"]`); processedData = $processedData.not(`li.js-commit-header[data-day='${loadedShownDayFirst}']`);
// Update commits count in the previous commits header. // Update commits count in the previous commits header.
commitsCount += Number($(processedData).nextUntil('li.js-commit-header').first().find('li.commit').length); commitsCount += Number($(processedData).nextUntil('li.js-commit-header').first().find('li.commit').length);
......
/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, comma-dangle, prefer-template, quotes, no-param-reassign, wrap-iife, max-len */ /* eslint-disable func-names, prefer-arrow-callback */
import Api from './api'; import Api from './api';
class CreateLabelDropdown { export default class CreateLabelDropdown {
constructor ($el, namespacePath, projectPath) { constructor($el, namespacePath, projectPath) {
this.$el = $el; this.$el = $el;
this.namespacePath = namespacePath; this.namespacePath = namespacePath;
this.projectPath = projectPath; this.projectPath = projectPath;
...@@ -22,7 +22,7 @@ class CreateLabelDropdown { ...@@ -22,7 +22,7 @@ class CreateLabelDropdown {
this.addBinding(); this.addBinding();
} }
cleanBinding () { cleanBinding() {
this.$colorSuggestions.off('click'); this.$colorSuggestions.off('click');
this.$newLabelField.off('keyup change'); this.$newLabelField.off('keyup change');
this.$newColorField.off('keyup change'); this.$newColorField.off('keyup change');
...@@ -31,7 +31,7 @@ class CreateLabelDropdown { ...@@ -31,7 +31,7 @@ class CreateLabelDropdown {
this.$newLabelCreateButton.off('click'); this.$newLabelCreateButton.off('click');
} }
addBinding () { addBinding() {
const self = this; const self = this;
this.$colorSuggestions.on('click', function (e) { this.$colorSuggestions.on('click', function (e) {
...@@ -44,7 +44,7 @@ class CreateLabelDropdown { ...@@ -44,7 +44,7 @@ class CreateLabelDropdown {
this.$dropdownBack.on('click', this.resetForm.bind(this)); this.$dropdownBack.on('click', this.resetForm.bind(this));
this.$cancelButton.on('click', function(e) { this.$cancelButton.on('click', function (e) {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
...@@ -55,7 +55,7 @@ class CreateLabelDropdown { ...@@ -55,7 +55,7 @@ class CreateLabelDropdown {
this.$newLabelCreateButton.on('click', this.saveLabel.bind(this)); this.$newLabelCreateButton.on('click', this.saveLabel.bind(this));
} }
addColorValue (e, $this) { addColorValue(e, $this) {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
...@@ -66,7 +66,7 @@ class CreateLabelDropdown { ...@@ -66,7 +66,7 @@ class CreateLabelDropdown {
.addClass('is-active'); .addClass('is-active');
} }
enableLabelCreateButton () { enableLabelCreateButton() {
if (this.$newLabelField.val() !== '' && this.$newColorField.val() !== '') { if (this.$newLabelField.val() !== '' && this.$newColorField.val() !== '') {
this.$newLabelError.hide(); this.$newLabelError.hide();
this.$newLabelCreateButton.enable(); this.$newLabelCreateButton.enable();
...@@ -75,7 +75,7 @@ class CreateLabelDropdown { ...@@ -75,7 +75,7 @@ class CreateLabelDropdown {
} }
} }
resetForm () { resetForm() {
this.$newLabelField this.$newLabelField
.val('') .val('')
.trigger('change'); .trigger('change');
...@@ -90,13 +90,13 @@ class CreateLabelDropdown { ...@@ -90,13 +90,13 @@ class CreateLabelDropdown {
.removeClass('is-active'); .removeClass('is-active');
} }
saveLabel (e) { saveLabel(e) {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
Api.newLabel(this.namespacePath, this.projectPath, { Api.newLabel(this.namespacePath, this.projectPath, {
title: this.$newLabelField.val(), title: this.$newLabelField.val(),
color: this.$newColorField.val() color: this.$newColorField.val(),
}, (label) => { }, (label) => {
this.$newLabelCreateButton.enable(); this.$newLabelCreateButton.enable();
...@@ -107,8 +107,8 @@ class CreateLabelDropdown { ...@@ -107,8 +107,8 @@ class CreateLabelDropdown {
errors = label.message; errors = label.message;
} else { } else {
errors = Object.keys(label.message).map(key => errors = Object.keys(label.message).map(key =>
`${gl.text.humanize(key)} ${label.message[key].join(', ')}` `${gl.text.humanize(key)} ${label.message[key].join(', ')}`,
).join("<br/>"); ).join('<br/>');
} }
this.$newLabelError this.$newLabelError
...@@ -122,6 +122,3 @@ class CreateLabelDropdown { ...@@ -122,6 +122,3 @@ class CreateLabelDropdown {
}); });
} }
} }
window.gl = window.gl || {};
gl.CreateLabelDropdown = CreateLabelDropdown;
<script>
import iconCycleAnalyticsSplash from 'icons/_icon_cycle_analytics_splash.svg';
export default {
props: {
documentationLink: {
type: String,
required: true,
},
},
computed: {
iconCycleAnalyticsSplash() {
return iconCycleAnalyticsSplash;
},
},
methods: {
dismissOverviewDialog() {
this.$emit('dismiss-overview-dialog');
},
},
};
</script>
<template>
<div class="landing content-block">
<button
class="js-ca-dismiss-button dismiss-button"
type="button"
:aria-label="__('Dismiss Cycle Analytics introduction box')"
@click="dismissOverviewDialog">
<i
class="fa fa-times"
aria-hidden="true">
</i>
</button>
<div class="svg-container" v-html="iconCycleAnalyticsSplash">
</div>
<div class="inner-content">
<h4>
{{__('Introducing Cycle Analytics')}}
</h4>
<p>
{{ __('Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.') }}
</p>
<p>
<a
:href="documentationLink"
target="_blank"
rel="nofollow"
class="btn">
{{__('Read more')}}
</a>
</p>
</div>
</div>
</template>
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
import Vue from 'vue'; import Vue from 'vue';
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
import Translate from '../vue_shared/translate'; import Translate from '../vue_shared/translate';
import banner from './components/banner.vue';
import stageCodeComponent from './components/stage_code_component.vue'; import stageCodeComponent from './components/stage_code_component.vue';
import stagePlanComponent from './components/stage_plan_component.vue'; import stagePlanComponent from './components/stage_plan_component.vue';
import stageComponent from './components/stage_component.vue'; import stageComponent from './components/stage_component.vue';
...@@ -44,6 +45,7 @@ $(() => { ...@@ -44,6 +45,7 @@ $(() => {
}, },
}, },
components: { components: {
banner,
'stage-issue-component': stageComponent, 'stage-issue-component': stageComponent,
'stage-plan-component': stagePlanComponent, 'stage-plan-component': stagePlanComponent,
'stage-code-component': stageCodeComponent, 'stage-code-component': stageCodeComponent,
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
import './lib/utils/url_utility'; import './lib/utils/url_utility';
import FilesCommentButton from './files_comment_button'; import FilesCommentButton from './files_comment_button';
import SingleFileDiff from './single_file_diff'; import SingleFileDiff from './single_file_diff';
import imageDiffHelper from './image_diff/helpers/index';
const UNFOLD_COUNT = 20; const UNFOLD_COUNT = 20;
let isBound = false; let isBound = false;
...@@ -17,9 +18,12 @@ class Diff { ...@@ -17,9 +18,12 @@ class Diff {
} }
}); });
FilesCommentButton.init($diffFile); const tab = document.getElementById('diffs');
if (!tab || (tab && tab.dataset && tab.dataset.isLocked !== '')) FilesCommentButton.init($diffFile);
$diffFile.each((index, file) => new gl.ImageFile(file)); const firstFile = $('.files').first().get(0);
const canCreateNote = firstFile && firstFile.hasAttribute('data-can-create-note');
$diffFile.each((index, file) => imageDiffHelper.initImageDiff(file, canCreateNote));
if (!isBound) { if (!isBound) {
$(document) $(document)
......
...@@ -171,7 +171,14 @@ const JumpToDiscussion = Vue.extend({ ...@@ -171,7 +171,14 @@ const JumpToDiscussion = Vue.extend({
// When jumping between unresolved discussions on the diffs tab, we show them. // When jumping between unresolved discussions on the diffs tab, we show them.
$target.closest(".content").show(); $target.closest(".content").show();
$target = $target.closest("tr.notes_holder"); const $notesHolder = $target.closest("tr.notes_holder");
// Image diff discussions does not use notes_holder
// so we should keep original $target value in those cases
if ($notesHolder.length > 0) {
$target = $notesHolder;
}
$target.show(); $target.show();
// If we are on the diffs tab, we don't scroll to the discussion itself, but to // If we are on the diffs tab, we don't scroll to the discussion itself, but to
......
...@@ -7,8 +7,6 @@ ...@@ -7,8 +7,6 @@
/* global IssuableForm */ /* global IssuableForm */
/* global LabelsSelect */ /* global LabelsSelect */
/* global MilestoneSelect */ /* global MilestoneSelect */
/* global Commit */
/* global CommitsList */
/* global NewBranchForm */ /* global NewBranchForm */
/* global NotificationsForm */ /* global NotificationsForm */
/* global NotificationsDropdown */ /* global NotificationsDropdown */
...@@ -36,6 +34,7 @@ ...@@ -36,6 +34,7 @@
/* global Sidebar */ /* global Sidebar */
/* global ShortcutsWiki */ /* global ShortcutsWiki */
import CommitsList from './commits';
import Issue from './issue'; import Issue from './issue';
import BindInOut from './behaviors/bind_in_out'; import BindInOut from './behaviors/bind_in_out';
import DeleteModal from './branches/branches_delete_modal'; import DeleteModal from './branches/branches_delete_modal';
...@@ -76,7 +75,9 @@ import initProjectVisibilitySelector from './project_visibility'; ...@@ -76,7 +75,9 @@ import initProjectVisibilitySelector from './project_visibility';
import GpgBadges from './gpg_badges'; import GpgBadges from './gpg_badges';
import UserFeatureHelper from './helpers/user_feature_helper'; import UserFeatureHelper from './helpers/user_feature_helper';
import initChangesDropdown from './init_changes_dropdown'; import initChangesDropdown from './init_changes_dropdown';
import AbuseReports from './abuse_reports';
import { ajaxGet, convertPermissionToBoolean } from './lib/utils/common_utils'; import { ajaxGet, convertPermissionToBoolean } from './lib/utils/common_utils';
import AjaxLoadingSpinner from './ajax_loading_spinner';
(function() { (function() {
var Dispatcher; var Dispatcher;
...@@ -238,7 +239,7 @@ import { ajaxGet, convertPermissionToBoolean } from './lib/utils/common_utils'; ...@@ -238,7 +239,7 @@ import { ajaxGet, convertPermissionToBoolean } from './lib/utils/common_utils';
new NewBranchForm($('.js-create-branch-form'), JSON.parse(document.getElementById('availableRefs').innerHTML)); new NewBranchForm($('.js-create-branch-form'), JSON.parse(document.getElementById('availableRefs').innerHTML));
break; break;
case 'projects:branches:index': case 'projects:branches:index':
gl.AjaxLoadingSpinner.init(); AjaxLoadingSpinner.init();
new DeleteModal(); new DeleteModal();
break; break;
case 'projects:issues:new': case 'projects:issues:new':
...@@ -316,7 +317,6 @@ import { ajaxGet, convertPermissionToBoolean } from './lib/utils/common_utils'; ...@@ -316,7 +317,6 @@ import { ajaxGet, convertPermissionToBoolean } from './lib/utils/common_utils';
new gl.Activities(); new gl.Activities();
break; break;
case 'projects:commit:show': case 'projects:commit:show':
new Commit();
new gl.Diff(); new gl.Diff();
new ZenMode(); new ZenMode();
shortcut_handler = new ShortcutsNavigation(); shortcut_handler = new ShortcutsNavigation();
...@@ -525,6 +525,11 @@ import { ajaxGet, convertPermissionToBoolean } from './lib/utils/common_utils'; ...@@ -525,6 +525,11 @@ import { ajaxGet, convertPermissionToBoolean } from './lib/utils/common_utils';
case 'admin:impersonation_tokens:index': case 'admin:impersonation_tokens:index':
new gl.DueDateSelectors(); new gl.DueDateSelectors();
break; break;
case 'projects:clusters:show':
import(/* webpackChunkName: "clusters" */ './clusters')
.then(cluster => new cluster.default()) // eslint-disable-line new-cap
.catch(() => {});
break;
} }
switch (path[0]) { switch (path[0]) {
case 'sessions': case 'sessions':
...@@ -557,7 +562,7 @@ import { ajaxGet, convertPermissionToBoolean } from './lib/utils/common_utils'; ...@@ -557,7 +562,7 @@ import { ajaxGet, convertPermissionToBoolean } from './lib/utils/common_utils';
new Labels(); new Labels();
} }
case 'abuse_reports': case 'abuse_reports':
new gl.AbuseReports(); new AbuseReports();
break; break;
} }
break; break;
......
export function createImageBadge(noteId, { x, y }, classNames = []) {
const buttonEl = document.createElement('button');
const classList = classNames.concat(['js-image-badge']);
classList.forEach(className => buttonEl.classList.add(className));
buttonEl.setAttribute('type', 'button');
buttonEl.setAttribute('disabled', true);
buttonEl.dataset.noteId = noteId;
buttonEl.style.left = `${x}px`;
buttonEl.style.top = `${y}px`;
return buttonEl;
}
export function addImageBadge(containerEl, { coordinate, badgeText, noteId }) {
const buttonEl = createImageBadge(noteId, coordinate, ['badge']);
buttonEl.innerText = badgeText;
containerEl.appendChild(buttonEl);
}
export function addImageCommentBadge(containerEl, { coordinate, noteId }) {
const buttonEl = createImageBadge(noteId, coordinate, ['image-comment-badge', 'inverted']);
const iconEl = document.createElement('i');
iconEl.className = 'fa fa-comment-o';
iconEl.setAttribute('aria-label', 'comment');
buttonEl.appendChild(iconEl);
containerEl.appendChild(buttonEl);
}
export function addAvatarBadge(el, event) {
const { noteId, badgeNumber } = event.detail;
// Add badge to new comment
const avatarBadgeEl = el.querySelector(`#${noteId} .badge`);
avatarBadgeEl.innerText = badgeNumber;
avatarBadgeEl.classList.remove('hidden');
}
export function addCommentIndicator(containerEl, { x, y }) {
const buttonEl = document.createElement('button');
buttonEl.classList.add('btn-transparent');
buttonEl.classList.add('comment-indicator');
buttonEl.setAttribute('type', 'button');
buttonEl.style.left = `${x}px`;
buttonEl.style.top = `${y}px`;
buttonEl.innerHTML = gl.utils.spriteIcon('image-comment-dark');
containerEl.appendChild(buttonEl);
}
export function removeCommentIndicator(imageFrameEl) {
const commentIndicatorEl = imageFrameEl.querySelector('.comment-indicator');
const imageEl = imageFrameEl.querySelector('img');
const willRemove = !!commentIndicatorEl;
let meta = {};
if (willRemove) {
meta = {
x: parseInt(commentIndicatorEl.style.left, 10),
y: parseInt(commentIndicatorEl.style.top, 10),
image: {
width: imageEl.width,
height: imageEl.height,
},
};
commentIndicatorEl.remove();
}
return Object.assign({}, meta, {
removed: willRemove,
});
}
export function showCommentIndicator(imageFrameEl, coordinate) {
const { x, y } = coordinate;
const commentIndicatorEl = imageFrameEl.querySelector('.comment-indicator');
if (commentIndicatorEl) {
commentIndicatorEl.style.left = `${x}px`;
commentIndicatorEl.style.top = `${y}px`;
} else {
addCommentIndicator(imageFrameEl, coordinate);
}
}
export function commentIndicatorOnClick(event) {
// Prevent from triggering onAddImageDiffNote in notes.js
event.stopPropagation();
const buttonEl = event.currentTarget;
const diffViewerEl = buttonEl.closest('.diff-viewer');
const textareaEl = diffViewerEl.querySelector('.note-container .note-textarea');
textareaEl.focus();
}
export function setPositionDataAttribute(el, options) {
// Update position data attribute so that the
// new comment form can use this data for ajax request
const { x, y, width, height } = options;
const position = el.dataset.position;
const positionObject = Object.assign({}, JSON.parse(position), {
x,
y,
width,
height,
});
el.setAttribute('data-position', JSON.stringify(positionObject));
}
export function updateDiscussionAvatarBadgeNumber(discussionEl, newBadgeNumber) {
const avatarBadgeEl = discussionEl.querySelector('.image-diff-avatar-link .badge');
avatarBadgeEl.innerText = newBadgeNumber;
}
export function updateDiscussionBadgeNumber(discussionEl, newBadgeNumber) {
const discussionBadgeEl = discussionEl.querySelector('.badge');
discussionBadgeEl.innerText = newBadgeNumber;
}
export function toggleCollapsed(event) {
const toggleButtonEl = event.currentTarget;
const discussionNotesEl = toggleButtonEl.closest('.discussion-notes');
const formEl = discussionNotesEl.querySelector('.discussion-form');
const isCollapsed = discussionNotesEl.classList.contains('collapsed');
if (isCollapsed) {
discussionNotesEl.classList.remove('collapsed');
} else {
discussionNotesEl.classList.add('collapsed');
}
// Override the inline display style set in notes.js
if (formEl && !isCollapsed) {
formEl.style.display = 'none';
} else if (formEl && isCollapsed) {
formEl.style.display = 'block';
}
}
import * as badgeHelper from './badge_helper';
import * as commentIndicatorHelper from './comment_indicator_helper';
import * as domHelper from './dom_helper';
import * as utilsHelper from './utils_helper';
export default {
addCommentIndicator: commentIndicatorHelper.addCommentIndicator,
removeCommentIndicator: commentIndicatorHelper.removeCommentIndicator,
showCommentIndicator: commentIndicatorHelper.showCommentIndicator,
commentIndicatorOnClick: commentIndicatorHelper.commentIndicatorOnClick,
addImageBadge: badgeHelper.addImageBadge,
addImageCommentBadge: badgeHelper.addImageCommentBadge,
addAvatarBadge: badgeHelper.addAvatarBadge,
setPositionDataAttribute: domHelper.setPositionDataAttribute,
updateDiscussionAvatarBadgeNumber: domHelper.updateDiscussionAvatarBadgeNumber,
updateDiscussionBadgeNumber: domHelper.updateDiscussionBadgeNumber,
toggleCollapsed: domHelper.toggleCollapsed,
resizeCoordinatesToImageElement: utilsHelper.resizeCoordinatesToImageElement,
generateBadgeFromDiscussionDOM: utilsHelper.generateBadgeFromDiscussionDOM,
getTargetSelection: utilsHelper.getTargetSelection,
initImageDiff: utilsHelper.initImageDiff,
};
import ImageBadge from '../image_badge';
import ImageDiff from '../image_diff';
import ReplacedImageDiff from '../replaced_image_diff';
import '../../commit/image_file';
export function resizeCoordinatesToImageElement(imageEl, meta) {
const { x, y, width, height } = meta;
const imageWidth = imageEl.width;
const imageHeight = imageEl.height;
const widthRatio = imageWidth / width;
const heightRatio = imageHeight / height;
return {
x: Math.round(x * widthRatio),
y: Math.round(y * heightRatio),
width: imageWidth,
height: imageHeight,
};
}
export function generateBadgeFromDiscussionDOM(imageFrameEl, discussionEl) {
const position = JSON.parse(discussionEl.dataset.position);
const firstNoteEl = discussionEl.querySelector('.note');
const badge = new ImageBadge({
actual: position,
imageEl: imageFrameEl.querySelector('img'),
noteId: firstNoteEl.id,
discussionId: discussionEl.dataset.discussionId,
});
return badge;
}
export function getTargetSelection(event) {
const containerEl = event.currentTarget;
const imageEl = containerEl.querySelector('img');
const x = event.offsetX;
const y = event.offsetY;
const width = imageEl.width;
const height = imageEl.height;
const actualWidth = imageEl.naturalWidth;
const actualHeight = imageEl.naturalHeight;
const widthRatio = actualWidth / width;
const heightRatio = actualHeight / height;
// Browser will include the frame as a clickable target,
// which would result in potential 1px out of bounds value
// This bound the coordinates to inside the frame
const normalizedX = Math.max(0, x) && Math.min(x, width);
const normalizedY = Math.max(0, y) && Math.min(y, height);
return {
browser: {
x: normalizedX,
y: normalizedY,
width,
height,
},
actual: {
// Round x, y so that we don't need to deal with decimals
x: Math.round(normalizedX * widthRatio),
y: Math.round(normalizedY * heightRatio),
width: actualWidth,
height: actualHeight,
},
};
}
export function initImageDiff(fileEl, canCreateNote, renderCommentBadge) {
const options = {
canCreateNote,
renderCommentBadge,
};
let diff;
// ImageFile needs to be invoked before initImageDiff so that badges
// can mount to the correct location
new gl.ImageFile(fileEl); // eslint-disable-line no-new
if (fileEl.querySelector('.diff-file .js-single-image')) {
diff = new ImageDiff(fileEl, options);
diff.init();
} else if (fileEl.querySelector('.diff-file .js-replaced-image')) {
diff = new ReplacedImageDiff(fileEl, options);
diff.init();
}
return diff;
}
import imageDiffHelper from './helpers/index';
const defaultMeta = {
x: 0,
y: 0,
width: 0,
height: 0,
};
export default class ImageBadge {
constructor(options) {
const { noteId, discussionId } = options;
this.actual = options.actual || defaultMeta;
this.browser = options.browser || defaultMeta;
this.noteId = noteId;
this.discussionId = discussionId;
if (options.imageEl && !options.browser) {
this.browser = imageDiffHelper.resizeCoordinatesToImageElement(options.imageEl, this.actual);
}
}
}
import imageDiffHelper from './helpers/index';
import ImageBadge from './image_badge';
import { isImageLoaded } from '../lib/utils/image_utility';
export default class ImageDiff {
constructor(el, options) {
this.el = el;
this.canCreateNote = !!(options && options.canCreateNote);
this.renderCommentBadge = !!(options && options.renderCommentBadge);
this.$noteContainer = $('.note-container', this.el);
this.imageBadges = [];
}
init() {
this.imageFrameEl = this.el.querySelector('.diff-file .js-image-frame');
this.imageEl = this.imageFrameEl.querySelector('img');
this.bindEvents();
}
bindEvents() {
this.imageClickedWrapper = this.imageClicked.bind(this);
this.imageBlurredWrapper = imageDiffHelper.removeCommentIndicator.bind(null, this.imageFrameEl);
this.addBadgeWrapper = this.addBadge.bind(this);
this.removeBadgeWrapper = this.removeBadge.bind(this);
this.renderBadgesWrapper = this.renderBadges.bind(this);
// Render badges
if (isImageLoaded(this.imageEl)) {
this.renderBadges();
} else {
this.imageEl.addEventListener('load', this.renderBadgesWrapper);
}
// jquery makes the event delegation here much simpler
this.$noteContainer.on('click', '.js-diff-notes-toggle', imageDiffHelper.toggleCollapsed);
$(this.el).on('click', '.comment-indicator', imageDiffHelper.commentIndicatorOnClick);
if (this.canCreateNote) {
this.el.addEventListener('click.imageDiff', this.imageClickedWrapper);
this.el.addEventListener('blur.imageDiff', this.imageBlurredWrapper);
this.el.addEventListener('addBadge.imageDiff', this.addBadgeWrapper);
this.el.addEventListener('removeBadge.imageDiff', this.removeBadgeWrapper);
}
}
imageClicked(event) {
const customEvent = event.detail;
const selection = imageDiffHelper.getTargetSelection(customEvent);
const el = customEvent.currentTarget;
imageDiffHelper.setPositionDataAttribute(el, selection.actual);
imageDiffHelper.showCommentIndicator(this.imageFrameEl, selection.browser);
}
renderBadges() {
const discussionsEls = this.el.querySelectorAll('.note-container .discussion-notes .notes');
[...discussionsEls].forEach(this.renderBadge.bind(this));
}
renderBadge(discussionEl, index) {
const imageBadge = imageDiffHelper
.generateBadgeFromDiscussionDOM(this.imageFrameEl, discussionEl);
this.imageBadges.push(imageBadge);
const options = {
coordinate: imageBadge.browser,
noteId: imageBadge.noteId,
};
if (this.renderCommentBadge) {
imageDiffHelper.addImageCommentBadge(this.imageFrameEl, options);
} else {
const numberBadgeOptions = Object.assign({}, options, {
badgeText: index + 1,
});
imageDiffHelper.addImageBadge(this.imageFrameEl, numberBadgeOptions);
}
}
addBadge(event) {
const { x, y, width, height, noteId, discussionId } = event.detail;
const badgeText = this.imageBadges.length + 1;
const imageBadge = new ImageBadge({
actual: {
x,
y,
width,
height,
},
imageEl: this.imageFrameEl.querySelector('img'),
noteId,
discussionId,
});
this.imageBadges.push(imageBadge);
imageDiffHelper.addImageBadge(this.imageFrameEl, {
coordinate: imageBadge.browser,
badgeText,
noteId,
});
imageDiffHelper.addAvatarBadge(this.el, {
detail: {
noteId,
badgeNumber: badgeText,
},
});
const discussionEl = this.el.querySelector(`#discussion_${discussionId}`);
imageDiffHelper.updateDiscussionBadgeNumber(discussionEl, badgeText);
}
removeBadge(event) {
const { badgeNumber } = event.detail;
const indexToRemove = badgeNumber - 1;
const imageBadgeEls = this.imageFrameEl.querySelectorAll('.badge');
if (this.imageBadges.length !== badgeNumber) {
// Cascade badges count numbers for (avatar badges + image badges)
this.imageBadges.forEach((badge, index) => {
if (index > indexToRemove) {
const { discussionId } = badge;
const updatedBadgeNumber = index;
const discussionEl = this.el.querySelector(`#discussion_${discussionId}`);
imageBadgeEls[index].innerText = updatedBadgeNumber;
imageDiffHelper.updateDiscussionBadgeNumber(discussionEl, updatedBadgeNumber);
imageDiffHelper.updateDiscussionAvatarBadgeNumber(discussionEl, updatedBadgeNumber);
}
});
}
this.imageBadges.splice(indexToRemove, 1);
const imageBadgeEl = imageBadgeEls[indexToRemove];
imageBadgeEl.remove();
}
}
import imageDiffHelper from './helpers/index';
export default () => {
// Always pass can-create-note as false because a user
// cannot place new badge markers on discussion tab
const canCreateNote = false;
const renderCommentBadge = true;
const diffFileEls = document.querySelectorAll('.timeline-content .diff-file.js-image-file');
[...diffFileEls].forEach(diffFileEl =>
imageDiffHelper.initImageDiff(diffFileEl, canCreateNote, renderCommentBadge));
};
import imageDiffHelper from './helpers/index';
import { viewTypes, isValidViewType } from './view_types';
import ImageDiff from './image_diff';
export default class ReplacedImageDiff extends ImageDiff {
init(defaultViewType = viewTypes.TWO_UP) {
this.imageFrameEls = {
[viewTypes.TWO_UP]: this.el.querySelector('.two-up .js-image-frame'),
[viewTypes.SWIPE]: this.el.querySelector('.swipe .js-image-frame'),
[viewTypes.ONION_SKIN]: this.el.querySelector('.onion-skin .js-image-frame'),
};
const viewModesEl = this.el.querySelector('.view-modes-menu');
this.viewModesEls = {
[viewTypes.TWO_UP]: viewModesEl.querySelector('.two-up'),
[viewTypes.SWIPE]: viewModesEl.querySelector('.swipe'),
[viewTypes.ONION_SKIN]: viewModesEl.querySelector('.onion-skin'),
};
this.currentView = defaultViewType;
this.generateImageEls();
this.bindEvents();
}
generateImageEls() {
this.imageEls = {};
const viewTypeNames = Object.getOwnPropertyNames(viewTypes);
viewTypeNames.forEach((viewType) => {
this.imageEls[viewType] = this.imageFrameEls[viewType].querySelector('img');
});
}
bindEvents() {
super.bindEvents();
this.changeToViewTwoUp = this.changeView.bind(this, viewTypes.TWO_UP);
this.changeToViewSwipe = this.changeView.bind(this, viewTypes.SWIPE);
this.changeToViewOnionSkin = this.changeView.bind(this, viewTypes.ONION_SKIN);
this.viewModesEls[viewTypes.TWO_UP].addEventListener('click', this.changeToViewTwoUp);
this.viewModesEls[viewTypes.SWIPE].addEventListener('click', this.changeToViewSwipe);
this.viewModesEls[viewTypes.ONION_SKIN].addEventListener('click', this.changeToViewOnionSkin);
}
get imageEl() {
return this.imageEls[this.currentView];
}
get imageFrameEl() {
return this.imageFrameEls[this.currentView];
}
changeView(newView) {
if (!isValidViewType(newView)) {
return;
}
const indicator = imageDiffHelper.removeCommentIndicator(this.imageFrameEl);
this.currentView = newView;
// Clear existing badges on new view
const existingBadges = this.imageFrameEl.querySelectorAll('.badge');
[...existingBadges].map(badge => badge.remove());
// Remove existing references to old view image badges
this.imageBadges = [];
// Image_file.js has a fade animation of 200ms for loading the view
// Need to wait an additional 250ms for the images to be displayed
// on window in order to re-normalize their dimensions
setTimeout(this.renderNewView.bind(this, indicator), 250);
}
renderNewView(indicator) {
// Generate badge coordinates on new view
this.renderBadges();
// Re-render indicator in new view
if (indicator.removed) {
const normalizedIndicator = imageDiffHelper
.resizeCoordinatesToImageElement(this.imageEl, {
x: indicator.x,
y: indicator.y,
width: indicator.image.width,
height: indicator.image.height,
});
imageDiffHelper.showCommentIndicator(this.imageFrameEl, normalizedIndicator);
}
}
}
export const viewTypes = {
TWO_UP: 'TWO_UP',
SWIPE: 'SWIPE',
ONION_SKIN: 'ONION_SKIN',
};
export function isValidViewType(validate) {
return !!Object.getOwnPropertyNames(viewTypes).find(viewType => viewType === validate);
}
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
import _ from 'underscore'; import _ from 'underscore';
import IssuableBulkUpdateActions from './issuable_bulk_update_actions'; import IssuableBulkUpdateActions from './issuable_bulk_update_actions';
import DropdownUtils from './filtered_search/dropdown_utils'; import DropdownUtils from './filtered_search/dropdown_utils';
import CreateLabelDropdown from './create_label';
(function() { (function() {
this.LabelsSelect = (function() { this.LabelsSelect = (function() {
...@@ -61,7 +62,7 @@ import DropdownUtils from './filtered_search/dropdown_utils'; ...@@ -61,7 +62,7 @@ import DropdownUtils from './filtered_search/dropdown_utils';
$sidebarLabelTooltip.tooltip(); $sidebarLabelTooltip.tooltip();
if ($dropdown.closest('.dropdown').find('.dropdown-new-label').length) { if ($dropdown.closest('.dropdown').find('.dropdown-new-label').length) {
new gl.CreateLabelDropdown($dropdown.closest('.dropdown').find('.dropdown-new-label'), namespacePath, projectPath); new CreateLabelDropdown($dropdown.closest('.dropdown').find('.dropdown-new-label'), namespacePath, projectPath);
} }
saveLabelData = function() { saveLabelData = function() {
......
...@@ -14,6 +14,9 @@ If you need to compose a headers object, use the spread operator: ...@@ -14,6 +14,9 @@ If you need to compose a headers object, use the spread operator:
someOtherHeader: '12345', someOtherHeader: '12345',
} }
``` ```
see also http://guides.rubyonrails.org/security.html#cross-site-request-forgery-csrf
and https://github.com/rails/jquery-rails/blob/v4.3.1/vendor/assets/javascripts/jquery_ujs.js#L59-L62
*/ */
const csrf = { const csrf = {
...@@ -53,4 +56,3 @@ if ($.rails) { ...@@ -53,4 +56,3 @@ if ($.rails) {
} }
export default csrf; export default csrf;
/* eslint-disable import/prefer-default-export */
export function isImageLoaded(element) {
return element.complete && element.naturalHeight !== 0;
}
...@@ -54,12 +54,14 @@ LineHighlighter.prototype.bindEvents = function() { ...@@ -54,12 +54,14 @@ LineHighlighter.prototype.bindEvents = function() {
$fileHolder.on('highlight:line', this.highlightHash); $fileHolder.on('highlight:line', this.highlightHash);
}; };
LineHighlighter.prototype.highlightHash = function() { LineHighlighter.prototype.highlightHash = function(newHash) {
var range; let range;
if (newHash && typeof newHash === 'string') this._hash = newHash;
this.clearHighlight();
if (this._hash !== '') { if (this._hash !== '') {
range = this.hashToRange(this._hash); range = this.hashToRange(this._hash);
if (range[0]) { if (range[0]) {
this.highlightRange(range); this.highlightRange(range);
const lineSelector = `#L${range[0]}`; const lineSelector = `#L${range[0]}`;
......
...@@ -35,12 +35,9 @@ import './shortcuts_network'; ...@@ -35,12 +35,9 @@ import './shortcuts_network';
import './templates/issuable_template_selector'; import './templates/issuable_template_selector';
import './templates/issuable_template_selectors'; import './templates/issuable_template_selectors';
// commit
import './commit/file';
import './commit/image_file'; import './commit/image_file';
// lib/utils // lib/utils
import './lib/utils/bootstrap_linked_tabs';
import { handleLocationHash } from './lib/utils/common_utils'; import { handleLocationHash } from './lib/utils/common_utils';
import './lib/utils/datetime_utility'; import './lib/utils/datetime_utility';
import './lib/utils/pretty_time'; import './lib/utils/pretty_time';
...@@ -57,10 +54,8 @@ import './u2f/register'; ...@@ -57,10 +54,8 @@ import './u2f/register';
import './u2f/util'; import './u2f/util';
// everything else // everything else
import './abuse_reports';
import './activities'; import './activities';
import './admin'; import './admin';
import './ajax_loading_spinner';
import './api'; import './api';
import './aside'; import './aside';
import './autosave'; import './autosave';
...@@ -71,14 +66,12 @@ import './build'; ...@@ -71,14 +66,12 @@ import './build';
import './build_artifacts'; import './build_artifacts';
import './build_variables'; import './build_variables';
import './ci_lint_editor'; import './ci_lint_editor';
import './commit';
import './commits'; import './commits';
import './compare'; import './compare';
import './compare_autocomplete'; import './compare_autocomplete';
import './confirm_danger_modal'; import './confirm_danger_modal';
import './copy_as_gfm'; import './copy_as_gfm';
import './copy_to_clipboard'; import './copy_to_clipboard';
import './create_label';
import './diff'; import './diff';
import './dropzone_input'; import './dropzone_input';
import './due_date_select'; import './due_date_select';
...@@ -111,7 +104,6 @@ import './merge_request'; ...@@ -111,7 +104,6 @@ import './merge_request';
import './merge_request_tabs'; import './merge_request_tabs';
import './milestone'; import './milestone';
import './milestone_select'; import './milestone_select';
import './mini_pipeline_graph_dropdown';
import './namespace_select'; import './namespace_select';
import './new_branch_form'; import './new_branch_form';
import './new_commit_form'; import './new_commit_form';
...@@ -119,7 +111,6 @@ import './notes'; ...@@ -119,7 +111,6 @@ import './notes';
import './notifications_dropdown'; import './notifications_dropdown';
import './notifications_form'; import './notifications_form';
import './pager'; import './pager';
import './pipelines';
import './preview_markdown'; import './preview_markdown';
import './project'; import './project';
import './project_avatar'; import './project_avatar';
......
...@@ -13,6 +13,8 @@ import { ...@@ -13,6 +13,8 @@ import {
isMetaClick, isMetaClick,
} from './lib/utils/common_utils'; } from './lib/utils/common_utils';
import initDiscussionTab from './image_diff/init_discussion_tab';
/* eslint-disable max-len */ /* eslint-disable max-len */
// MergeRequestTabs // MergeRequestTabs
// //
...@@ -154,6 +156,8 @@ import { ...@@ -154,6 +156,8 @@ import {
} }
this.resetViewContainer(); this.resetViewContainer();
this.destroyPipelinesView(); this.destroyPipelinesView();
initDiscussionTab();
} }
if (this.setUrl) { if (this.setUrl) {
this.setCurrentAction(action); this.setCurrentAction(action);
......
...@@ -79,7 +79,11 @@ ...@@ -79,7 +79,11 @@
}, },
formatMetricUsage(series) { formatMetricUsage(series) {
return `${formatRelevantDigits(series.values[this.currentDataIndex].value)} ${this.unitOfDisplay}`; const value = series.values[this.currentDataIndex].value;
if (isNaN(value)) {
return '-';
}
return `${formatRelevantDigits(value)} ${this.unitOfDisplay}`;
}, },
createSeriesString(index, series) { createSeriesString(index, series) {
......
...@@ -56,12 +56,16 @@ export default function createTimeSeries(queryData, graphWidth, graphHeight, gra ...@@ -56,12 +56,16 @@ export default function createTimeSeries(queryData, graphWidth, graphHeight, gra
timeSeriesScaleX.ticks(d3.time.minute, 60); timeSeriesScaleX.ticks(d3.time.minute, 60);
timeSeriesScaleY.domain([0, maxValueFromSeries.maxValue]); timeSeriesScaleY.domain([0, maxValueFromSeries.maxValue]);
const defined = d => !isNaN(d.value) && d.value != null;
const lineFunction = d3.svg.line() const lineFunction = d3.svg.line()
.defined(defined)
.interpolate('linear') .interpolate('linear')
.x(d => timeSeriesScaleX(d.time)) .x(d => timeSeriesScaleX(d.time))
.y(d => timeSeriesScaleY(d.value)); .y(d => timeSeriesScaleY(d.value));
const areaFunction = d3.svg.area() const areaFunction = d3.svg.area()
.defined(defined)
.interpolate('linear') .interpolate('linear')
.x(d => timeSeriesScaleX(d.time)) .x(d => timeSeriesScaleX(d.time))
.y0(graphHeight - graphHeightOffset) .y0(graphHeight - graphHeightOffset)
......
...@@ -24,6 +24,7 @@ import './autosave'; ...@@ -24,6 +24,7 @@ import './autosave';
import './dropzone_input'; import './dropzone_input';
import TaskList from './task_list'; import TaskList from './task_list';
import { ajaxPost, isInViewport, getPagePath, scrollToElement, isMetaKey } from './lib/utils/common_utils'; import { ajaxPost, isInViewport, getPagePath, scrollToElement, isMetaKey } from './lib/utils/common_utils';
import imageDiffHelper from './image_diff/helpers/index';
window.autosize = autosize; window.autosize = autosize;
window.Dropzone = Dropzone; window.Dropzone = Dropzone;
...@@ -42,6 +43,7 @@ export default class Notes { ...@@ -42,6 +43,7 @@ export default class Notes {
this.visibilityChange = this.visibilityChange.bind(this); this.visibilityChange = this.visibilityChange.bind(this);
this.cancelDiscussionForm = this.cancelDiscussionForm.bind(this); this.cancelDiscussionForm = this.cancelDiscussionForm.bind(this);
this.onAddDiffNote = this.onAddDiffNote.bind(this); this.onAddDiffNote = this.onAddDiffNote.bind(this);
this.onAddImageDiffNote = this.onAddImageDiffNote.bind(this);
this.setupDiscussionNoteForm = this.setupDiscussionNoteForm.bind(this); this.setupDiscussionNoteForm = this.setupDiscussionNoteForm.bind(this);
this.onReplyToDiscussionNote = this.onReplyToDiscussionNote.bind(this); this.onReplyToDiscussionNote = this.onReplyToDiscussionNote.bind(this);
this.removeNote = this.removeNote.bind(this); this.removeNote = this.removeNote.bind(this);
...@@ -114,6 +116,8 @@ export default class Notes { ...@@ -114,6 +116,8 @@ export default class Notes {
$(document).on('click', '.js-discussion-reply-button', this.onReplyToDiscussionNote); $(document).on('click', '.js-discussion-reply-button', this.onReplyToDiscussionNote);
// add diff note // add diff note
$(document).on('click', '.js-add-diff-note-button', this.onAddDiffNote); $(document).on('click', '.js-add-diff-note-button', this.onAddDiffNote);
// add diff note for images
$(document).on('click', '.js-add-image-diff-note-button', this.onAddImageDiffNote);
// hide diff note form // hide diff note form
$(document).on('click', '.js-close-discussion-note-form', this.cancelDiscussionForm); $(document).on('click', '.js-close-discussion-note-form', this.cancelDiscussionForm);
// toggle commit list // toggle commit list
...@@ -140,6 +144,7 @@ export default class Notes { ...@@ -140,6 +144,7 @@ export default class Notes {
$(document).off('click', '.js-note-attachment-delete'); $(document).off('click', '.js-note-attachment-delete');
$(document).off('click', '.js-discussion-reply-button'); $(document).off('click', '.js-discussion-reply-button');
$(document).off('click', '.js-add-diff-note-button'); $(document).off('click', '.js-add-diff-note-button');
$(document).off('click', '.js-add-image-diff-note-button');
$(document).off('visibilitychange'); $(document).off('visibilitychange');
$(document).off('keyup input', '.js-note-text'); $(document).off('keyup input', '.js-note-text');
$(document).off('click', '.js-note-target-reopen'); $(document).off('click', '.js-note-target-reopen');
...@@ -412,6 +417,11 @@ export default class Notes { ...@@ -412,6 +417,11 @@ export default class Notes {
this.note_ids.push(noteEntity.id); this.note_ids.push(noteEntity.id);
form = $form || $(`.js-discussion-note-form[data-discussion-id="${noteEntity.discussion_id}"]`); form = $form || $(`.js-discussion-note-form[data-discussion-id="${noteEntity.discussion_id}"]`);
row = form.closest('tr'); row = form.closest('tr');
if (noteEntity.on_image) {
row = form;
}
lineType = this.isParallelView() ? form.find('#line_type').val() : 'old'; lineType = this.isParallelView() ? form.find('#line_type').val() : 'old';
diffAvatarContainer = row.prevAll('.line_holder').first().find('.js-avatar-container.' + lineType + '_line'); diffAvatarContainer = row.prevAll('.line_holder').first().find('.js-avatar-container.' + lineType + '_line');
// is this the first note of discussion? // is this the first note of discussion?
...@@ -423,7 +433,7 @@ export default class Notes { ...@@ -423,7 +433,7 @@ export default class Notes {
if (noteEntity.diff_discussion_html) { if (noteEntity.diff_discussion_html) {
var $discussion = $(noteEntity.diff_discussion_html).renderGFM(); var $discussion = $(noteEntity.diff_discussion_html).renderGFM();
if (!this.isParallelView() || row.hasClass('js-temp-notes-holder')) { if (!this.isParallelView() || row.hasClass('js-temp-notes-holder') || noteEntity.on_image) {
// insert the note and the reply button after the temp row // insert the note and the reply button after the temp row
row.after($discussion); row.after($discussion);
} else { } else {
...@@ -449,6 +459,7 @@ export default class Notes { ...@@ -449,6 +459,7 @@ export default class Notes {
if (typeof gl.diffNotesCompileComponents !== 'undefined' && noteEntity.discussion_resolvable) { if (typeof gl.diffNotesCompileComponents !== 'undefined' && noteEntity.discussion_resolvable) {
gl.diffNotesCompileComponents(); gl.diffNotesCompileComponents();
this.renderDiscussionAvatar(diffAvatarContainer, noteEntity); this.renderDiscussionAvatar(diffAvatarContainer, noteEntity);
} }
...@@ -561,7 +572,7 @@ export default class Notes { ...@@ -561,7 +572,7 @@ export default class Notes {
form.find('#note_line_code').val(), form.find('#note_line_code').val(),
// DiffNote // DiffNote
form.find('#note_position').val() form.find('#note_position').val(),
]; ];
return new Autosave(textarea, key); return new Autosave(textarea, key);
} }
...@@ -783,9 +794,22 @@ export default class Notes { ...@@ -783,9 +794,22 @@ export default class Notes {
$(`.js-diff-avatars-${discussionId}`).trigger('remove.vue'); $(`.js-diff-avatars-${discussionId}`).trigger('remove.vue');
// The notes tr can contain multiple lists of notes, like on the parallel diff // The notes tr can contain multiple lists of notes, like on the parallel diff
if (notesTr.find('.discussion-notes').length > 1) { // notesTr does not exist for image diffs
if (notesTr.find('.discussion-notes').length > 1 || notesTr.length === 0) {
const $diffFile = $notes.closest('.diff-file');
if ($diffFile.length > 0) {
const removeBadgeEvent = new CustomEvent('removeBadge.imageDiff', {
detail: {
// badgeNumber's start with 1 and index starts with 0
badgeNumber: $notes.index() + 1,
},
});
$diffFile[0].dispatchEvent(removeBadgeEvent);
}
$notes.remove(); $notes.remove();
} else { } else if (notesTr.length > 0) {
notesTr.remove(); notesTr.remove();
} }
} }
...@@ -841,7 +865,11 @@ export default class Notes { ...@@ -841,7 +865,11 @@ export default class Notes {
*/ */
setupDiscussionNoteForm(dataHolder, form) { setupDiscussionNoteForm(dataHolder, form) {
// setup note target // setup note target
const diffFileData = dataHolder.closest('.text-file'); let diffFileData = dataHolder.closest('.text-file');
if (diffFileData.length === 0) {
diffFileData = dataHolder.closest('.image');
}
var discussionID = dataHolder.data('discussionId'); var discussionID = dataHolder.data('discussionId');
...@@ -907,6 +935,31 @@ export default class Notes { ...@@ -907,6 +935,31 @@ export default class Notes {
}); });
} }
onAddImageDiffNote(e) {
const $link = $(e.currentTarget || e.target);
const $diffFile = $link.closest('.diff-file');
const clickEvent = new CustomEvent('click.imageDiff', {
detail: e,
});
$diffFile[0].dispatchEvent(clickEvent);
// Setup comment form
let newForm;
const $noteContainer = $link.closest('.diff-viewer').find('.note-container');
const $form = $noteContainer.find('> .discussion-form');
if ($form.length === 0) {
newForm = this.cleanForm(this.formClone.clone());
newForm.appendTo($noteContainer);
} else {
newForm = $form;
}
this.setupDiscussionNoteForm($link, newForm);
}
toggleDiffNote({ toggleDiffNote({
target, target,
lineType, lineType,
...@@ -999,10 +1052,25 @@ export default class Notes { ...@@ -999,10 +1052,25 @@ export default class Notes {
} }
cancelDiscussionForm(e) { cancelDiscussionForm(e) {
var form;
e.preventDefault(); e.preventDefault();
form = $(e.target).closest('.js-discussion-note-form'); const $form = $(e.target).closest('.js-discussion-note-form');
return this.removeDiscussionNoteForm(form); const $discussionNote = $(e.target).closest('.discussion-notes');
if ($discussionNote.length === 0) {
// Only send blur event when the discussion form
// is not part of a discussion note
const $diffFile = $form.closest('.diff-file');
if ($diffFile.length > 0) {
const blurEvent = new CustomEvent('blur.imageDiff', {
detail: e,
});
$diffFile[0].dispatchEvent(blurEvent);
}
}
return this.removeDiscussionNoteForm($form);
} }
/** /**
...@@ -1414,6 +1482,15 @@ export default class Notes { ...@@ -1414,6 +1482,15 @@ export default class Notes {
// Submission successful! remove placeholder // Submission successful! remove placeholder
$notesContainer.find(`#${noteUniqueId}`).remove(); $notesContainer.find(`#${noteUniqueId}`).remove();
const $diffFile = $form.closest('.diff-file');
if ($diffFile.length > 0) {
const blurEvent = new CustomEvent('blur.imageDiff', {
detail: e,
});
$diffFile[0].dispatchEvent(blurEvent);
}
// Reset cached commands list when command is applied // Reset cached commands list when command is applied
if (hasQuickActions) { if (hasQuickActions) {
$form.find('textarea.js-note-text').trigger('clear-commands-cache.atwho'); $form.find('textarea.js-note-text').trigger('clear-commands-cache.atwho');
...@@ -1436,7 +1513,28 @@ export default class Notes { ...@@ -1436,7 +1513,28 @@ export default class Notes {
} }
// Show final note element on UI // Show final note element on UI
this.addDiscussionNote($form, note, $notesContainer.length === 0); const isNewDiffComment = $notesContainer.length === 0;
this.addDiscussionNote($form, note, isNewDiffComment);
if (isNewDiffComment) {
// Add image badge, avatar badge and toggle discussion badge for new image diffs
const notePosition = $form.find('#note_position').val();
if ($diffFile.length > 0 && notePosition.length > 0) {
const { x, y, width, height } = JSON.parse(notePosition);
const addBadgeEvent = new CustomEvent('addBadge.imageDiff', {
detail: {
x,
y,
width,
height,
noteId: `note_${note.id}`,
discussionId: note.discussion_id,
},
});
$diffFile[0].dispatchEvent(addBadgeEvent);
}
}
// append flash-container to the Notes list // append flash-container to the Notes list
if ($notesContainer.length) { if ($notesContainer.length) {
...@@ -1457,6 +1555,16 @@ export default class Notes { ...@@ -1457,6 +1555,16 @@ export default class Notes {
// Submission failed, remove placeholder note and show Flash error message // Submission failed, remove placeholder note and show Flash error message
$notesContainer.find(`#${noteUniqueId}`).remove(); $notesContainer.find(`#${noteUniqueId}`).remove();
const blurEvent = new CustomEvent('blur.imageDiff', {
detail: e,
});
const closestDiffFile = $form.closest('.diff-file');
if (closestDiffFile.length) {
closestDiffFile[0].dispatchEvent(blurEvent);
}
if (hasQuickActions) { if (hasQuickActions) {
$notesContainer.find(`#${systemNoteUniqueId}`).remove(); $notesContainer.find(`#${systemNoteUniqueId}`).remove();
} }
...@@ -1500,6 +1608,8 @@ export default class Notes { ...@@ -1500,6 +1608,8 @@ export default class Notes {
const $noteBody = $editingNote.find('.js-task-list-container'); const $noteBody = $editingNote.find('.js-task-list-container');
const $noteBodyText = $noteBody.find('.note-text'); const $noteBodyText = $noteBody.find('.note-text');
const { formData, formContent, formAction } = this.getFormData($form); const { formData, formContent, formAction } = this.getFormData($form);
const $diffFile = $form.closest('.diff-file');
const $notesContainer = $form.closest('.notes');
// Cache original comment content // Cache original comment content
const cachedNoteBodyText = $noteBodyText.html(); const cachedNoteBodyText = $noteBodyText.html();
......
...@@ -7,10 +7,12 @@ ...@@ -7,10 +7,12 @@
import TaskList from '../../task_list'; import TaskList from '../../task_list';
import * as constants from '../constants'; import * as constants from '../constants';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
import confidentialIssue from '../../vue_shared/components/issue/confidential_issue_warning.vue'; import issueWarning from '../../vue_shared/components/issue/issue_warning.vue';
import issueNoteSignedOutWidget from './issue_note_signed_out_widget.vue'; import issueNoteSignedOutWidget from './issue_note_signed_out_widget.vue';
import issueDiscussionLockedWidget from './issue_discussion_locked_widget.vue';
import markdownField from '../../vue_shared/components/markdown/field.vue'; import markdownField from '../../vue_shared/components/markdown/field.vue';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue'; import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import issuableStateMixin from '../mixins/issuable_state';
export default { export default {
name: 'issueCommentForm', name: 'issueCommentForm',
...@@ -26,8 +28,9 @@ ...@@ -26,8 +28,9 @@
}; };
}, },
components: { components: {
confidentialIssue, issueWarning,
issueNoteSignedOutWidget, issueNoteSignedOutWidget,
issueDiscussionLockedWidget,
markdownField, markdownField,
userAvatarLink, userAvatarLink,
}, },
...@@ -55,6 +58,9 @@ ...@@ -55,6 +58,9 @@
isIssueOpen() { isIssueOpen() {
return this.issueState === constants.OPENED || this.issueState === constants.REOPENED; return this.issueState === constants.OPENED || this.issueState === constants.REOPENED;
}, },
canCreateNote() {
return this.getIssueData.current_user.can_create_note;
},
issueActionButtonTitle() { issueActionButtonTitle() {
if (this.note.length) { if (this.note.length) {
const actionText = this.isIssueOpen ? 'close' : 'reopen'; const actionText = this.isIssueOpen ? 'close' : 'reopen';
...@@ -90,9 +96,6 @@ ...@@ -90,9 +96,6 @@
endpoint() { endpoint() {
return this.getIssueData.create_note_path; return this.getIssueData.create_note_path;
}, },
isConfidentialIssue() {
return this.getIssueData.confidential;
},
}, },
methods: { methods: {
...mapActions([ ...mapActions([
...@@ -220,6 +223,9 @@ ...@@ -220,6 +223,9 @@
}); });
}, },
}, },
mixins: [
issuableStateMixin,
],
mounted() { mounted() {
// jQuery is needed here because it is a custom event being dispatched with jQuery. // jQuery is needed here because it is a custom event being dispatched with jQuery.
$(document).on('issuable:change', (e, isClosed) => { $(document).on('issuable:change', (e, isClosed) => {
...@@ -235,6 +241,7 @@ ...@@ -235,6 +241,7 @@
<template> <template>
<div> <div>
<issue-note-signed-out-widget v-if="!isLoggedIn" /> <issue-note-signed-out-widget v-if="!isLoggedIn" />
<issue-discussion-locked-widget v-else-if="!canCreateNote" />
<ul <ul
v-else v-else
class="notes notes-form timeline"> class="notes notes-form timeline">
...@@ -253,15 +260,22 @@ ...@@ -253,15 +260,22 @@
<div class="timeline-content timeline-content-form"> <div class="timeline-content timeline-content-form">
<form <form
ref="commentForm" ref="commentForm"
class="new-note js-quick-submit common-note-form gfm-form js-main-target-form"> class="new-note js-quick-submit common-note-form gfm-form js-main-target-form"
<confidentialIssue v-if="isConfidentialIssue" /> >
<div class="error-alert"></div> <div class="error-alert"></div>
<issue-warning
v-if="hasWarning(getIssueData)"
:is-locked="isLocked(getIssueData)"
:is-confidential="isConfidential(getIssueData)"
/>
<markdown-field <markdown-field
:markdown-preview-path="markdownPreviewPath" :markdown-preview-path="markdownPreviewPath"
:markdown-docs-path="markdownDocsPath" :markdown-docs-path="markdownDocsPath"
:quick-actions-docs-path="quickActionsDocsPath" :quick-actions-docs-path="quickActionsDocsPath"
:add-spacing-classes="false" :add-spacing-classes="false"
:is-confidential-issue="isConfidentialIssue"
ref="markdownField"> ref="markdownField">
<textarea <textarea
id="note-body" id="note-body"
......
<script>
export default {
computed: {
lockIcon() {
return gl.utils.spriteIcon('lock');
},
},
};
</script>
<template>
<div class="disabled-comment text-center">
<span class="issuable-note-warning">
<span class="icon" v-html="lockIcon"></span>
<span>This issue is locked. Only <b>project members</b> can comment.</span>
</span>
</div>
</template>
<script> <script>
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
import confidentialIssue from '../../vue_shared/components/issue/confidential_issue_warning.vue'; import issueWarning from '../../vue_shared/components/issue/issue_warning.vue';
import markdownField from '../../vue_shared/components/markdown/field.vue'; import markdownField from '../../vue_shared/components/markdown/field.vue';
import issuableStateMixin from '../mixins/issuable_state';
export default { export default {
name: 'issueNoteForm', name: 'issueNoteForm',
...@@ -39,12 +40,13 @@ ...@@ -39,12 +40,13 @@
}; };
}, },
components: { components: {
confidentialIssue, issueWarning,
markdownField, markdownField,
}, },
computed: { computed: {
...mapGetters([ ...mapGetters([
'getDiscussionLastNote', 'getDiscussionLastNote',
'getIssueData',
'getIssueDataByProp', 'getIssueDataByProp',
'getNotesDataByProp', 'getNotesDataByProp',
'getUserDataByProp', 'getUserDataByProp',
...@@ -67,9 +69,6 @@ ...@@ -67,9 +69,6 @@
isDisabled() { isDisabled() {
return !this.note.length || this.isSubmitting; return !this.note.length || this.isSubmitting;
}, },
isConfidentialIssue() {
return this.getIssueDataByProp('confidential');
},
}, },
methods: { methods: {
handleUpdate() { handleUpdate() {
...@@ -95,6 +94,9 @@ ...@@ -95,6 +94,9 @@
this.$emit('cancelFormEdition', shouldConfirm, this.noteBody !== this.note); this.$emit('cancelFormEdition', shouldConfirm, this.noteBody !== this.note);
}, },
}, },
mixins: [
issuableStateMixin,
],
mounted() { mounted() {
this.$refs.textarea.focus(); this.$refs.textarea.focus();
}, },
...@@ -125,7 +127,13 @@ ...@@ -125,7 +127,13 @@
<div class="flash-container timeline-content"></div> <div class="flash-container timeline-content"></div>
<form <form
class="edit-note common-note-form js-quick-submit gfm-form"> class="edit-note common-note-form js-quick-submit gfm-form">
<confidentialIssue v-if="isConfidentialIssue" />
<issue-warning
v-if="hasWarning(getIssueData)"
:is-locked="isLocked(getIssueData)"
:is-confidential="isConfidential(getIssueData)"
/>
<markdown-field <markdown-field
:markdown-preview-path="markdownPreviewPath" :markdown-preview-path="markdownPreviewPath"
:markdown-docs-path="markdownDocsPath" :markdown-docs-path="markdownDocsPath"
......
export default {
methods: {
isConfidential(issue) {
return !!issue.confidential;
},
isLocked(issue) {
return !!issue.discussion_locked;
},
hasWarning(issue) {
return this.isConfidential(issue) || this.isLocked(issue);
},
},
};
<script>
import popupDialog from '../../../vue_shared/components/popup_dialog.vue';
import { __, s__, sprintf } from '../../../locale';
import csrf from '../../../lib/utils/csrf';
export default {
props: {
actionUrl: {
type: String,
required: true,
},
confirmWithPassword: {
type: Boolean,
required: true,
},
username: {
type: String,
required: true,
},
},
data() {
return {
enteredPassword: '',
enteredUsername: '',
isOpen: false,
};
},
components: {
popupDialog,
},
computed: {
csrfToken() {
return csrf.token;
},
inputLabel() {
let confirmationValue;
if (this.confirmWithPassword) {
confirmationValue = __('password');
} else {
confirmationValue = __('username');
}
confirmationValue = `<code>${confirmationValue}</code>`;
return sprintf(
s__('Profiles|Type your %{confirmationValue} to confirm:'),
{ confirmationValue },
false,
);
},
text() {
return sprintf(
s__(`Profiles|
You are about to permanently delete %{yourAccount}, and all of the issues, merge requests, and groups linked to your account.
Once you confirm %{deleteAccount}, it cannot be undone or recovered.`),
{
yourAccount: `<strong>${s__('Profiles|your account')}</strong>`,
deleteAccount: `<strong>${s__('Profiles|Delete Account')}</strong>`,
},
false,
);
},
},
methods: {
canSubmit() {
if (this.confirmWithPassword) {
return this.enteredPassword !== '';
}
return this.enteredUsername === this.username;
},
onSubmit(status) {
if (status) {
if (!this.canSubmit()) {
return;
}
this.$refs.form.submit();
}
this.toggleOpen(false);
},
toggleOpen(isOpen) {
this.isOpen = isOpen;
},
},
};
</script>
<template>
<div>
<popup-dialog
v-if="isOpen"
:title="s__('Profiles|Delete your account?')"
:text="text"
:kind="`danger ${!canSubmit() && 'disabled'}`"
:primary-button-label="s__('Profiles|Delete account')"
@toggle="toggleOpen"
@submit="onSubmit">
<template slot="body" scope="props">
<p v-html="props.text"></p>
<form
ref="form"
:action="actionUrl"
method="post">
<input
type="hidden"
name="_method"
value="delete" />
<input
type="hidden"
name="authenticity_token"
:value="csrfToken" />
<p id="input-label" v-html="inputLabel"></p>
<input
v-if="confirmWithPassword"
name="password"
class="form-control"
type="password"
v-model="enteredPassword"
aria-labelledby="input-label" />
<input
v-else
name="username"
class="form-control"
type="text"
v-model="enteredUsername"
aria-labelledby="input-label" />
</form>
</template>
</popup-dialog>
<button
type="button"
class="btn btn-danger"
@click="toggleOpen(true)">
{{ s__('Profiles|Delete account') }}
</button>
</div>
</template>
import Vue from 'vue';
import deleteAccountModal from './components/delete_account_modal.vue';
const deleteAccountModalEl = document.getElementById('delete-account-modal');
// eslint-disable-next-line no-new
new Vue({
el: deleteAccountModalEl,
components: {
deleteAccountModal,
},
render(createElement) {
return createElement('delete-account-modal', {
props: {
actionUrl: deleteAccountModalEl.dataset.actionUrl,
confirmWithPassword: !!deleteAccountModalEl.dataset.confirmWithPassword,
username: deleteAccountModalEl.dataset.username,
},
});
},
});
...@@ -62,7 +62,7 @@ export default { ...@@ -62,7 +62,7 @@ export default {
:primary-button-label="__('Discard changes')" :primary-button-label="__('Discard changes')"
kind="warning" kind="warning"
:title="__('Are you sure?')" :title="__('Are you sure?')"
:body="__('Are you sure you want to discard your changes?')" :text="__('Are you sure you want to discard your changes?')"
@toggle="toggleDialogOpen" @toggle="toggleDialogOpen"
@submit="dialogSubmitted" @submit="dialogSubmitted"
/> />
......
...@@ -63,12 +63,7 @@ const RepoEditor = { ...@@ -63,12 +63,7 @@ const RepoEditor = {
const lineNumber = e.target.position.lineNumber; const lineNumber = e.target.position.lineNumber;
if (e.target.element.classList.contains('line-numbers')) { if (e.target.element.classList.contains('line-numbers')) {
location.hash = `L${lineNumber}`; location.hash = `L${lineNumber}`;
Store.activeLine = lineNumber; Store.setActiveLine(lineNumber);
Helper.monacoInstance.setPosition({
lineNumber: this.activeLine,
column: 1,
});
} }
}, },
}, },
...@@ -101,6 +96,15 @@ const RepoEditor = { ...@@ -101,6 +96,15 @@ const RepoEditor = {
this.setupEditor(); this.setupEditor();
} }
}, },
activeLine() {
if (Helper.monacoInstance) {
Helper.monacoInstance.setPosition({
lineNumber: this.activeLine,
column: 1,
});
}
},
}, },
computed: { computed: {
shouldHideEditor() { shouldHideEditor() {
......
...@@ -14,6 +14,11 @@ export default { ...@@ -14,6 +14,11 @@ export default {
highlightFile() { highlightFile() {
$(this.$el).find('.file-content').syntaxHighlight(); $(this.$el).find('.file-content').syntaxHighlight();
}, },
highlightLine() {
if (Store.activeLine > -1) {
this.lineHighlighter.highlightHash(`#L${Store.activeLine}`);
}
},
}, },
mounted() { mounted() {
this.highlightFile(); this.highlightFile();
...@@ -26,8 +31,12 @@ export default { ...@@ -26,8 +31,12 @@ export default {
html() { html() {
this.$nextTick(() => { this.$nextTick(() => {
this.highlightFile(); this.highlightFile();
this.highlightLine();
}); });
}, },
activeLine() {
this.highlightLine();
},
}, },
}; };
</script> </script>
......
...@@ -18,22 +18,40 @@ export default { ...@@ -18,22 +18,40 @@ export default {
}, },
created() { created() {
this.addPopEventListener(); window.addEventListener('popstate', this.checkHistory);
},
destroyed() {
window.removeEventListener('popstate', this.checkHistory);
}, },
data: () => Store, data: () => Store,
methods: { methods: {
addPopEventListener() { checkHistory() {
window.addEventListener('popstate', () => { let selectedFile = this.files.find(file => location.pathname.indexOf(file.url) > -1);
if (location.href.indexOf('#') > -1) return; if (!selectedFile) {
this.linkClicked({ // Maybe it is not in the current tree but in the opened tabs
selectedFile = Helper.getFileFromPath(location.pathname);
}
let lineNumber = null;
if (location.hash.indexOf('#L') > -1) lineNumber = Number(location.hash.substr(2));
if (selectedFile) {
if (selectedFile.url !== this.activeFile.url) {
this.fileClicked(selectedFile, lineNumber);
} else {
Store.setActiveLine(lineNumber);
}
} else {
// Not opened at all lets open new tab
this.fileClicked({
url: location.href, url: location.href,
}); }, lineNumber);
}); }
}, },
fileClicked(clickedFile) { fileClicked(clickedFile, lineNumber) {
let file = clickedFile; let file = clickedFile;
if (file.loading) return; if (file.loading) return;
file.loading = true; file.loading = true;
...@@ -41,17 +59,20 @@ export default { ...@@ -41,17 +59,20 @@ export default {
if (file.type === 'tree' && file.opened) { if (file.type === 'tree' && file.opened) {
file = Store.removeChildFilesOfTree(file); file = Store.removeChildFilesOfTree(file);
file.loading = false; file.loading = false;
Store.setActiveLine(lineNumber);
} else { } else {
const openFile = Helper.getFileFromPath(file.url); const openFile = Helper.getFileFromPath(file.url);
if (openFile) { if (openFile) {
file.loading = false; file.loading = false;
Store.setActiveFiles(openFile); Store.setActiveFiles(openFile);
Store.setActiveLine(lineNumber);
} else { } else {
Service.url = file.url; Service.url = file.url;
Helper.getContent(file) Helper.getContent(file)
.then(() => { .then(() => {
file.loading = false; file.loading = false;
Helper.scrollTabsRight(); Helper.scrollTabsRight();
Store.setActiveLine(lineNumber);
}) })
.catch(Helper.loadingError); .catch(Helper.loadingError);
} }
......
...@@ -254,7 +254,9 @@ const RepoHelper = { ...@@ -254,7 +254,9 @@ const RepoHelper = {
RepoHelper.key = RepoHelper.genKey(); RepoHelper.key = RepoHelper.genKey();
if (document.location.pathname !== url) {
history.pushState({ key: RepoHelper.key }, '', url); history.pushState({ key: RepoHelper.key }, '', url);
}
if (title) { if (title) {
document.title = title; document.title = title;
......
...@@ -26,7 +26,7 @@ const RepoStore = { ...@@ -26,7 +26,7 @@ const RepoStore = {
}, },
activeFile: Helper.getDefaultActiveFile(), activeFile: Helper.getDefaultActiveFile(),
activeFileIndex: 0, activeFileIndex: 0,
activeLine: 0, activeLine: -1,
activeFileLabel: 'Raw', activeFileLabel: 'Raw',
files: [], files: [],
isCommitable: false, isCommitable: false,
...@@ -85,6 +85,7 @@ const RepoStore = { ...@@ -85,6 +85,7 @@ const RepoStore = {
if (!file.loading) Helper.updateHistoryEntry(file.url, file.pageTitle || file.name); if (!file.loading) Helper.updateHistoryEntry(file.url, file.pageTitle || file.name);
RepoStore.binary = file.binary; RepoStore.binary = file.binary;
RepoStore.setActiveLine(-1);
}, },
setFileActivity(file, openedFile, i) { setFileActivity(file, openedFile, i) {
...@@ -101,6 +102,10 @@ const RepoStore = { ...@@ -101,6 +102,10 @@ const RepoStore = {
RepoStore.activeFileIndex = i; RepoStore.activeFileIndex = i;
}, },
setActiveLine(activeLine) {
if (!isNaN(activeLine)) RepoStore.activeLine = activeLine;
},
setActiveToRaw() { setActiveToRaw() {
RepoStore.activeFile.raw = false; RepoStore.activeFile.raw = false;
// can't get vue to listen to raw for some reason so RepoStore for now. // can't get vue to listen to raw for some reason so RepoStore for now.
......
...@@ -18,23 +18,8 @@ import findAndFollowLink from './shortcuts_dashboard_navigation'; ...@@ -18,23 +18,8 @@ import findAndFollowLink from './shortcuts_dashboard_navigation';
Mousetrap.bind('f', (e => this.focusFilter(e))); Mousetrap.bind('f', (e => this.focusFilter(e)));
Mousetrap.bind('p b', this.onTogglePerfBar); Mousetrap.bind('p b', this.onTogglePerfBar);
const $globalDropdownMenu = $('.global-dropdown-menu');
const $globalDropdownToggle = $('.global-dropdown-toggle');
const findFileURL = document.body.dataset.findFile; const findFileURL = document.body.dataset.findFile;
$('.global-dropdown').on('hide.bs.dropdown', () => {
$globalDropdownMenu.removeClass('shortcuts');
});
Mousetrap.bind('n', () => {
$globalDropdownMenu.toggleClass('shortcuts');
$globalDropdownToggle.trigger('click');
if (!$globalDropdownMenu.is(':visible')) {
$globalDropdownToggle.blur();
}
});
Mousetrap.bind('shift+t', () => findAndFollowLink('.shortcuts-todos')); Mousetrap.bind('shift+t', () => findAndFollowLink('.shortcuts-todos'));
Mousetrap.bind('shift+a', () => findAndFollowLink('.dashboard-shortcuts-activity')); Mousetrap.bind('shift+a', () => findAndFollowLink('.dashboard-shortcuts-activity'));
Mousetrap.bind('shift+i', () => findAndFollowLink('.dashboard-shortcuts-issues')); Mousetrap.bind('shift+i', () => findAndFollowLink('.dashboard-shortcuts-issues'));
......
...@@ -47,9 +47,9 @@ export default { ...@@ -47,9 +47,9 @@ export default {
</script> </script>
<template> <template>
<div class="block confidentiality"> <div class="block issuable-sidebar-item confidentiality">
<div class="sidebar-collapsed-icon"> <div class="sidebar-collapsed-icon">
<i class="fa" :class="faEye" aria-hidden="true" data-hidden="true"></i> <i class="fa" :class="faEye" aria-hidden="true"></i>
</div> </div>
<div class="title hide-collapsed"> <div class="title hide-collapsed">
Confidentiality Confidentiality
...@@ -62,19 +62,19 @@ export default { ...@@ -62,19 +62,19 @@ export default {
Edit Edit
</a> </a>
</div> </div>
<div class="value confidential-value hide-collapsed"> <div class="value sidebar-item-value hide-collapsed">
<editForm <editForm
v-if="edit" v-if="edit"
:toggle-form="toggleForm" :toggle-form="toggleForm"
:is-confidential="isConfidential" :is-confidential="isConfidential"
:update-confidential-attribute="updateConfidentialAttribute" :update-confidential-attribute="updateConfidentialAttribute"
/> />
<div v-if="!isConfidential" class="no-value confidential-value"> <div v-if="!isConfidential" class="no-value sidebar-item-value">
<i class="fa fa-eye is-not-confidential"></i> <i class="fa fa-eye sidebar-item-icon"></i>
Not confidential Not confidential
</div> </div>
<div v-else class="value confidential-value hide-collapsed"> <div v-else class="value sidebar-item-value hide-collapsed">
<i aria-hidden="true" data-hidden="true" class="fa fa-eye-slash is-confidential"></i> <i aria-hidden="true" class="fa fa-eye-slash sidebar-item-icon is-active"></i>
This issue is confidential This issue is confidential
</div> </div>
</div> </div>
......
...@@ -2,9 +2,6 @@ ...@@ -2,9 +2,6 @@
import editFormButtons from './edit_form_buttons.vue'; import editFormButtons from './edit_form_buttons.vue';
export default { export default {
components: {
editFormButtons,
},
props: { props: {
isConfidential: { isConfidential: {
required: true, required: true,
...@@ -19,12 +16,16 @@ export default { ...@@ -19,12 +16,16 @@ export default {
type: Function, type: Function,
}, },
}, },
components: {
editFormButtons,
},
}; };
</script> </script>
<template> <template>
<div class="dropdown open"> <div class="dropdown open">
<div class="dropdown-menu confidential-warning-message"> <div class="dropdown-menu sidebar-item-warning-message">
<div> <div>
<p v-if="!isConfidential"> <p v-if="!isConfidential">
You are going to turn on the confidentiality. This means that only team members with You are going to turn on the confidentiality. This means that only team members with
......
...@@ -15,7 +15,7 @@ export default { ...@@ -15,7 +15,7 @@ export default {
}, },
}, },
computed: { computed: {
onOrOff() { toggleButtonText() {
return this.isConfidential ? 'Turn Off' : 'Turn On'; return this.isConfidential ? 'Turn Off' : 'Turn On';
}, },
updateConfidentialBool() { updateConfidentialBool() {
...@@ -26,7 +26,7 @@ export default { ...@@ -26,7 +26,7 @@ export default {
</script> </script>
<template> <template>
<div class="confidential-warning-message-actions"> <div class="sidebar-item-warning-message-actions">
<button <button
type="button" type="button"
class="btn btn-default append-right-10" class="btn btn-default append-right-10"
...@@ -39,7 +39,7 @@ export default { ...@@ -39,7 +39,7 @@ export default {
class="btn btn-close" class="btn btn-close"
@click.prevent="updateConfidentialAttribute(updateConfidentialBool)" @click.prevent="updateConfidentialAttribute(updateConfidentialBool)"
> >
{{ onOrOff }} {{ toggleButtonText }}
</button> </button>
</div> </div>
</template> </template>
<script>
import editFormButtons from './edit_form_buttons.vue';
import issuableMixin from '../../../vue_shared/mixins/issuable';
export default {
props: {
isLocked: {
required: true,
type: Boolean,
},
toggleForm: {
required: true,
type: Function,
},
updateLockedAttribute: {
required: true,
type: Function,
},
issuableType: {
required: true,
type: String,
},
},
mixins: [
issuableMixin,
],
components: {
editFormButtons,
},
};
</script>
<template>
<div class="dropdown open">
<div class="dropdown-menu sidebar-item-warning-message">
<p class="text" v-if="isLocked">
Unlock this {{ issuableDisplayName(issuableType) }}?
<strong>Everyone</strong>
will be able to comment.
</p>
<p class="text" v-else>
Lock this {{ issuableDisplayName(issuableType) }}?
Only
<strong>project members</strong>
will be able to comment.
</p>
<edit-form-buttons
:is-locked="isLocked"
:toggle-form="toggleForm"
:update-locked-attribute="updateLockedAttribute"
/>
</div>
</div>
</template>
<script>
export default {
props: {
isLocked: {
required: true,
type: Boolean,
},
toggleForm: {
required: true,
type: Function,
},
updateLockedAttribute: {
required: true,
type: Function,
},
},
computed: {
buttonText() {
return this.isLocked ? this.__('Unlock') : this.__('Lock');
},
toggleLock() {
return !this.isLocked;
},
},
};
</script>
<template>
<div class="sidebar-item-warning-message-actions">
<button
type="button"
class="btn btn-default append-right-10"
@click="toggleForm"
>
{{ __('Cancel') }}
</button>
<button
type="button"
class="btn btn-close"
@click.prevent="updateLockedAttribute(toggleLock)"
>
{{ buttonText }}
</button>
</div>
</template>
<script>
/* global Flash */
import editForm from './edit_form.vue';
import issuableMixin from '../../../vue_shared/mixins/issuable';
export default {
props: {
isLocked: {
required: true,
type: Boolean,
},
isEditable: {
required: true,
type: Boolean,
},
mediator: {
required: true,
type: Object,
validator(mediatorObject) {
return mediatorObject.service && mediatorObject.service.update && mediatorObject.store;
},
},
issuableType: {
required: true,
type: String,
},
},
mixins: [
issuableMixin,
],
components: {
editForm,
},
computed: {
lockIconClass() {
return this.isLocked ? 'fa-lock' : 'fa-unlock';
},
isLockDialogOpen() {
return this.mediator.store.isLockDialogOpen;
},
},
methods: {
toggleForm() {
this.mediator.store.isLockDialogOpen = !this.mediator.store.isLockDialogOpen;
},
updateLockedAttribute(locked) {
this.mediator.service.update(this.issuableType, {
discussion_locked: locked,
})
.then(() => location.reload())
.catch(() => Flash(this.__(`Something went wrong trying to change the locked state of this ${this.issuableDisplayName(this.issuableType)}`)));
},
},
};
</script>
<template>
<div class="block issuable-sidebar-item lock">
<div class="sidebar-collapsed-icon">
<i
class="fa"
:class="lockIconClass"
aria-hidden="true"
></i>
</div>
<div class="title hide-collapsed">
Lock {{issuableDisplayName(issuableType) }}
<button
v-if="isEditable"
class="pull-right lock-edit btn btn-blank"
type="button"
@click.prevent="toggleForm"
>
{{ __('Edit') }}
</button>
</div>
<div class="value sidebar-item-value hide-collapsed">
<edit-form
v-if="isLockDialogOpen"
:toggle-form="toggleForm"
:is-locked="isLocked"
:update-locked-attribute="updateLockedAttribute"
:issuable-type="issuableType"
/>
<div
v-if="isLocked"
class="value sidebar-item-value"
>
<i
aria-hidden="true"
class="fa fa-lock sidebar-item-icon is-active"
></i>
{{ __('Locked') }}
</div>
<div
v-else
class="no-value sidebar-item-value hide-collapsed"
>
<i
aria-hidden="true"
class="fa fa-unlock sidebar-item-icon"
></i>
{{ __('Unlocked') }}
</div>
</div>
</div>
</template>
import Vue from 'vue'; import Vue from 'vue';
import sidebarTimeTracking from './components/time_tracking/sidebar_time_tracking'; import SidebarTimeTracking from './components/time_tracking/sidebar_time_tracking';
import sidebarAssignees from './components/assignees/sidebar_assignees'; import SidebarAssignees from './components/assignees/sidebar_assignees';
import confidential from './components/confidential/confidential_issue_sidebar.vue'; import ConfidentialIssueSidebar from './components/confidential/confidential_issue_sidebar.vue';
import SidebarMoveIssue from './lib/sidebar_move_issue'; import SidebarMoveIssue from './lib/sidebar_move_issue';
import LockIssueSidebar from './components/lock/lock_issue_sidebar.vue';
import Translate from '../vue_shared/translate';
import Mediator from './sidebar_mediator'; import Mediator from './sidebar_mediator';
function domContentLoaded() { Vue.use(Translate);
const sidebarOptions = JSON.parse(document.querySelector('.js-sidebar-options').innerHTML);
const mediator = new Mediator(sidebarOptions);
mediator.fetch();
const sidebarAssigneesEl = document.querySelector('#js-vue-sidebar-assignees'); function mountConfidentialComponent(mediator) {
const confidentialEl = document.querySelector('#js-confidential-entry-point'); const el = document.getElementById('js-confidential-entry-point');
// Only create the sidebarAssignees vue app if it is found in the DOM
// We currently do not use sidebarAssignees for the MR page if (!el) return;
if (sidebarAssigneesEl) {
new Vue(sidebarAssignees).$mount(sidebarAssigneesEl);
}
if (confidentialEl) {
const dataNode = document.getElementById('js-confidential-issue-data'); const dataNode = document.getElementById('js-confidential-issue-data');
const initialData = JSON.parse(dataNode.innerHTML); const initialData = JSON.parse(dataNode.innerHTML);
const ConfidentialComp = Vue.extend(confidential); const ConfidentialComp = Vue.extend(ConfidentialIssueSidebar);
new ConfidentialComp({ new ConfidentialComp({
propsData: { propsData: {
...@@ -31,16 +26,51 @@ function domContentLoaded() { ...@@ -31,16 +26,51 @@ function domContentLoaded() {
isEditable: initialData.is_editable, isEditable: initialData.is_editable,
service: mediator.service, service: mediator.service,
}, },
}).$mount(confidentialEl); }).$mount(el);
}
function mountLockComponent(mediator) {
const el = document.getElementById('js-lock-entry-point');
if (!el) return;
const dataNode = document.getElementById('js-lock-issue-data');
const initialData = JSON.parse(dataNode.innerHTML);
const LockComp = Vue.extend(LockIssueSidebar);
new LockComp({
propsData: {
isLocked: initialData.is_locked,
isEditable: initialData.is_editable,
mediator,
issuableType: gl.utils.isInIssuePage() ? 'issue' : 'merge_request',
},
}).$mount(el);
}
function domContentLoaded() {
const sidebarOptions = JSON.parse(document.querySelector('.js-sidebar-options').innerHTML);
const mediator = new Mediator(sidebarOptions);
mediator.fetch();
const sidebarAssigneesEl = document.getElementById('js-vue-sidebar-assignees');
// Only create the sidebarAssignees vue app if it is found in the DOM
// We currently do not use sidebarAssignees for the MR page
if (sidebarAssigneesEl) {
new Vue(SidebarAssignees).$mount(sidebarAssigneesEl);
}
mountConfidentialComponent(mediator);
mountLockComponent(mediator);
new SidebarMoveIssue( new SidebarMoveIssue(
mediator, mediator,
$('.js-move-issue'), $('.js-move-issue'),
$('.js-move-issue-confirmation-button'), $('.js-move-issue-confirmation-button'),
).init(); ).init();
}
new Vue(sidebarTimeTracking).$mount('#issuable-time-tracker'); new Vue(SidebarTimeTracking).$mount('#issuable-time-tracker');
} }
document.addEventListener('DOMContentLoaded', domContentLoaded); document.addEventListener('DOMContentLoaded', domContentLoaded);
......
...@@ -15,6 +15,7 @@ export default class SidebarStore { ...@@ -15,6 +15,7 @@ export default class SidebarStore {
}; };
this.autocompleteProjects = []; this.autocompleteProjects = [];
this.moveToProjectId = 0; this.moveToProjectId = 0;
this.isLockDialogOpen = false;
SidebarStore.singleton = this; SidebarStore.singleton = this;
} }
......
/* eslint-disable func-names, prefer-arrow-callback, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, one-var, one-var-declaration-per-line, consistent-return, no-param-reassign, max-len */ /* eslint-disable func-names, prefer-arrow-callback, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, one-var, one-var-declaration-per-line, consistent-return, no-param-reassign, max-len */
import FilesCommentButton from './files_comment_button'; import FilesCommentButton from './files_comment_button';
import imageDiffHelper from './image_diff/helpers/index';
const WRAPPER = '<div class="diff-content"></div>'; const WRAPPER = '<div class="diff-content"></div>';
const LOADING_HTML = '<i class="fa fa-spinner fa-spin"></i>'; const LOADING_HTML = '<i class="fa fa-spinner fa-spin"></i>';
...@@ -74,7 +75,11 @@ export default class SingleFileDiff { ...@@ -74,7 +75,11 @@ export default class SingleFileDiff {
gl.diffNotesCompileComponents(); gl.diffNotesCompileComponents();
} }
FilesCommentButton.init($(_this.file)); const $file = $(_this.file);
FilesCommentButton.init($file);
const canCreateNote = $file.closest('.files').is('[data-can-create-note]');
imageDiffHelper.initImageDiff($file[0], canCreateNote);
if (cb) cb(); if (cb) cb();
}; };
......
<script>
export default {
name: 'confidentialIssueWarning',
};
</script>
<template>
<div class="confidential-issue-warning">
<i
aria-hidden="true"
class="fa fa-eye-slash">
</i>
<span>
This is a confidential issue. Your comment will not be visible to the public.
</span>
</div>
</template>
<script>
export default {
props: {
isLocked: {
type: Boolean,
default: false,
required: false,
},
isConfidential: {
type: Boolean,
default: false,
required: false,
},
},
computed: {
iconClass() {
return {
'fa-eye-slash': this.isConfidential,
'fa-lock': this.isLocked,
};
},
isLockedAndConfidential() {
return this.isConfidential && this.isLocked;
},
},
};
</script>
<template>
<div class="issuable-note-warning">
<i
aria-hidden="true"
class="fa icon"
:class="iconClass"
v-if="!isLockedAndConfidential"
></i>
<span v-if="isLockedAndConfidential">
{{ __('This issue is confidential and locked.') }}
{{ __('People without permission will never get a notification and won\'t be able to comment.') }}
</span>
<span v-else-if="isConfidential">
{{ __('This is a confidential issue.') }}
{{ __('Your comment will not be visible to the public.') }}
</span>
<span v-else-if="isLocked">
{{ __('This issue is locked.') }}
{{ __('Only project members can comment.') }}
</span>
</div>
</template>
...@@ -7,7 +7,7 @@ export default { ...@@ -7,7 +7,7 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
body: { text: {
type: String, type: String,
required: true, required: true,
}, },
...@@ -63,7 +63,9 @@ export default { ...@@ -63,7 +63,9 @@ export default {
<h4 class="modal-title">{{this.title}}</h4> <h4 class="modal-title">{{this.title}}</h4>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<p>{{this.body}}</p> <slot name="body" :text="text">
<p>{{text}}</p>
</slot>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button <button
......
export default {
methods: {
issuableDisplayName(issuableType) {
const displayName = issuableType.replace(/_/, ' ');
return this.__ ? this.__(displayName) : displayName;
},
},
};
...@@ -30,16 +30,16 @@ ...@@ -30,16 +30,16 @@
@import "framework/media_object"; @import "framework/media_object";
@import "framework/mobile"; @import "framework/mobile";
@import "framework/modal"; @import "framework/modal";
@import "framework/nav";
@import "framework/new-nav";
@import "framework/pagination"; @import "framework/pagination";
@import "framework/panels"; @import "framework/panels";
@import "framework/secondary-navigation-elements";
@import "framework/selects"; @import "framework/selects";
@import "framework/sidebar"; @import "framework/sidebar";
@import "framework/new-sidebar"; @import "framework/new-sidebar";
@import "framework/tables"; @import "framework/tables";
@import "framework/notes"; @import "framework/notes";
@import "framework/timeline"; @import "framework/timeline";
@import "framework/tooltips";
@import "framework/typography"; @import "framework/typography";
@import "framework/zen"; @import "framework/zen";
@import "framework/blank"; @import "framework/blank";
......
...@@ -115,8 +115,7 @@ ...@@ -115,8 +115,7 @@
@return $unfoldedTransition; @return $unfoldedTransition;
} }
.btn, .btn {
.global-dropdown-toggle {
@include transition(background-color, border-color, color, box-shadow); @include transition(background-color, border-color, color, box-shadow);
} }
......
...@@ -207,6 +207,16 @@ ...@@ -207,6 +207,16 @@
&.user-cover-block { &.user-cover-block {
padding: 24px 0 0; padding: 24px 0 0;
.nav-links {
justify-content: center;
width: 100%;
float: none;
&.scrolling-tabs {
float: none;
}
}
} }
.group-info { .group-info {
......
@mixin btn-comment-icon {
border-radius: 50%;
background: $white-light;
padding: 1px 5px;
font-size: 12px;
color: $blue-500;
width: 23px;
height: 23px;
border: 1px solid $blue-500;
&:hover,
&.inverted {
background: $blue-500;
border-color: $blue-600;
color: $white-light;
}
&:active {
outline: 0;
}
}
@mixin btn-default { @mixin btn-default {
border-radius: 3px; border-radius: 3px;
font-size: $gl-font-size; font-size: $gl-font-size;
...@@ -381,7 +403,11 @@ ...@@ -381,7 +403,11 @@
background: transparent; background: transparent;
border: 0; border: 0;
&:hover,
&:active,
&:focus { &:focus {
outline: 0; outline: 0;
background: transparent;
box-shadow: none;
} }
} }
...@@ -749,7 +749,7 @@ ...@@ -749,7 +749,7 @@
margin-bottom: $dropdown-vertical-offset; margin-bottom: $dropdown-vertical-offset;
} }
li { li:not(.dropdown-bold-header) {
display: block; display: block;
padding: 0 1px; padding: 0 1px;
...@@ -889,7 +889,7 @@ ...@@ -889,7 +889,7 @@
@include new-style-dropdown('.breadcrumbs-list .dropdown '); @include new-style-dropdown('.breadcrumbs-list .dropdown ');
@include new-style-dropdown('.js-namespace-select + '); @include new-style-dropdown('.js-namespace-select + ');
header.navbar-gitlab-new .header-content .dropdown-menu.projects-dropdown-menu { header.header-content .dropdown-menu.projects-dropdown-menu {
padding: 0; padding: 0;
} }
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
@mixin gitlab-theme($color-100, $color-200, $color-500, $color-700, $color-800, $color-900, $color-alternate) { @mixin gitlab-theme($color-100, $color-200, $color-500, $color-700, $color-800, $color-900, $color-alternate) {
// Header // Header
header.navbar-gitlab-new { .navbar-gitlab {
background-color: $color-900; background-color: $color-900;
.navbar-collapse { .navbar-collapse {
...@@ -95,7 +95,7 @@ ...@@ -95,7 +95,7 @@
} }
} }
.title { .navbar .title {
> a { > a {
&:hover, &:hover,
&:focus { &:focus {
...@@ -200,9 +200,9 @@ body { ...@@ -200,9 +200,9 @@ body {
&.ui_light { &.ui_light {
@include gitlab-theme($theme-gray-900, $theme-gray-700, $theme-gray-800, $theme-gray-700, $theme-gray-700, $theme-gray-100, $theme-gray-700); @include gitlab-theme($theme-gray-900, $theme-gray-700, $theme-gray-800, $theme-gray-700, $theme-gray-700, $theme-gray-100, $theme-gray-700);
header.navbar-gitlab-new { .navbar-gitlab {
background-color: $theme-gray-100; background-color: $theme-gray-100;
box-shadow: 0 2px 0 0 $border-color; box-shadow: 0 1px 0 0 $border-color;
.logo-text svg { .logo-text svg {
fill: $theme-gray-900; fill: $theme-gray-900;
......
...@@ -28,6 +28,7 @@ ...@@ -28,6 +28,7 @@
svg { svg {
&.s8 { @include svg-size(8px); } &.s8 { @include svg-size(8px); }
&.s12 { @include svg-size(12px); }
&.s16 { @include svg-size(16px); } &.s16 { @include svg-size(16px); }
&.s18 { @include svg-size(18px); } &.s18 { @include svg-size(18px); }
&.s24 { @include svg-size(24px); } &.s24 { @include svg-size(24px); }
......
...@@ -25,10 +25,6 @@ body { ...@@ -25,10 +25,6 @@ body {
.content-wrapper { .content-wrapper {
padding-bottom: 100px; padding-bottom: 100px;
&:not(.page-with-layout-nav) {
margin-top: $header-height;
}
} }
.container { .container {
......
.modal-header {
padding: #{3 * $grid-size} #{2 * $grid-size};
.page-title {
margin-top: 0;
}
}
.modal-body { .modal-body {
position: relative; position: relative;
padding: 15px; padding: #{3 * $grid-size} #{2 * $grid-size};
.form-actions { .form-actions {
margin: -$gl-padding + 1; margin: #{2 * $grid-size} #{-2 * $grid-size} #{-2 * $grid-size};
margin-top: 15px;
} }
.text-danger { .text-danger {
......
@import "framework/variables";
@import 'framework/tw_bootstrap_variables';
@import "bootstrap/variables";
@import "framework/mixins";
.content-wrapper.page-with-new-nav {
margin-top: $new-navbar-height;
}
header.navbar-gitlab-new {
color: $white-light;
border-bottom: 0;
min-height: $new-navbar-height;
.logo-text {
line-height: initial;
svg {
width: 55px;
height: 14px;
margin: 0;
fill: $white-light;
}
}
.header-content {
display: -webkit-flex;
display: flex;
padding-left: 0;
min-height: $new-navbar-height;
.title-container {
display: -webkit-flex;
display: flex;
-webkit-align-items: stretch;
align-items: stretch;
-webkit-flex: 1 1 auto;
flex: 1 1 auto;
padding-top: 0;
overflow: visible;
}
.title {
display: -webkit-flex;
display: flex;
padding-right: 0;
color: currentColor;
img {
height: 28px;
margin-right: 8px;
}
a {
display: -webkit-flex;
display: flex;
align-items: center;
padding: 2px 8px;
margin: 5px 2px 5px -8px;
border-radius: $border-radius-default;
svg {
@media (min-width: $screen-sm-min) {
margin-right: 8px;
}
}
}
}
.dropdown.open {
> a {
border-bottom-color: $white-light;
}
}
.dropdown-menu {
margin-top: 4px;
min-width: 130px;
@media (max-width: $screen-xs-max) {
left: auto;
right: 0;
}
}
&.menu-expanded {
@media (max-width: $screen-xs-max) {
.title-container,
.header-logo, {
display: none;
}
}
}
}
.dropdown-bold-header {
color: $gl-text-color-secondary;
font-size: 12px;
}
.navbar-collapse {
padding-left: 0;
box-shadow: 0;
@media (max-width: $screen-xs-max) {
margin-left: -8px;
margin-right: -10px;
}
.nav {
> li:not(.hidden-xs) a {
@media (max-width: $screen-xs-max) {
margin-left: 0;
min-width: 100%;
}
}
}
}
.container-fluid {
.navbar-toggle {
min-width: 45px;
padding: 0 $gl-padding;
margin-right: -7px;
text-align: center;
color: currentColor;
svg {
fill: currentColor;
}
&:hover,
&:focus,
&.active {
color: currentColor;
background-color: transparent;
svg {
fill: currentColor;
}
}
}
.navbar-nav {
@media (max-width: $screen-xs-max) {
display: flex;
padding-right: 10px;
}
li {
.badge {
box-shadow: none;
font-weight: $gl-font-weight-bold;
}
}
}
.nav > li {
&.header-user {
@media (max-width: $screen-xs-max) {
padding-left: 10px;
}
}
> a {
will-change: color;
margin: 4px 2px;
padding: 6px 8px;
height: 32px;
@media (max-width: $screen-xs-max) {
padding: 0;
}
&.header-user-dropdown-toggle {
margin-left: 2px;
.header-user-avatar {
margin-right: 0;
}
}
&:hover,
&:focus {
text-decoration: none;
outline: 0;
opacity: 1;
color: $white-light;
svg {
fill: currentColor;
}
&.header-user-dropdown-toggle {
.header-user-avatar {
border-color: $white-light;
}
}
}
}
.header-new-dropdown-toggle {
margin-right: 0;
}
.impersonated-user,
.impersonated-user:hover {
margin-right: 1px;
background-color: $white-light;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
.impersonation-btn,
.impersonation-btn:hover {
background-color: $white-light;
margin-left: 0;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
i {
color: $orange-500;
font-size: 20px;
}
}
&.active > a,
&.dropdown.open > a {
svg {
fill: currentColor;
}
}
}
}
}
.navbar-sub-nav {
display: -webkit-flex;
display: flex;
margin: 0 0 0 6px;
.dropdown-chevron {
position: relative;
top: -1px;
font-size: 10px;
}
}
.navbar-gitlab-new {
.navbar-sub-nav,
.navbar-nav {
> li {
> a:hover,
> a:focus {
text-decoration: none;
outline: 0;
color: $white-light;
svg {
fill: currentColor;
}
}
> a {
display: flex;
align-items: center;
justify-content: center;
padding: 6px 8px;
margin: 4px 2px;
font-size: 12px;
color: currentColor;
border-radius: $border-radius-default;
height: 32px;
font-weight: $gl-font-weight-bold;
svg {
fill: currentColor;
}
}
&.line-separator {
margin: 8px;
}
}
}
}
.caret-down {
height: 11px;
width: 11px;
margin-left: 4px;
fill: currentColor;
}
.header-user .dropdown-menu-nav,
.header-new .dropdown-menu-nav {
margin-top: $dropdown-vertical-offset;
}
.breadcrumbs {
display: flex;
min-height: 48px;
color: $gl-text-color;
}
.breadcrumbs-container {
display: -webkit-flex;
display: flex;
width: 100%;
position: relative;
padding-top: $gl-padding / 2;
padding-bottom: $gl-padding / 2;
align-items: center;
border-bottom: 1px solid $border-color;
}
.breadcrumbs-links {
-webkit-flex: 1;
flex: 1;
min-width: 0;
align-self: center;
color: $gl-text-color-secondary;
.avatar-tile {
margin-right: 4px;
border: 1px solid $border-color;
border-radius: 50%;
vertical-align: sub;
}
.text-expander {
margin-left: 0;
margin-right: 2px;
> i {
position: relative;
top: 1px;
}
}
}
.breadcrumbs-list {
display: -webkit-flex;
display: flex;
flex-wrap: wrap;
margin-bottom: 0;
line-height: 16px;
> li {
display: flex;
align-items: center;
position: relative;
padding: 2px 0;
&:not(:last-child) {
margin-right: 20px;
}
> a {
font-size: 12px;
color: currentColor;
}
}
}
.breadcrumb-item-text {
@include str-truncated(128px);
text-decoration: inherit;
}
.breadcrumbs-list-angle {
position: absolute;
right: -12px;
top: 50%;
color: $gl-text-color-tertiary;
transform: translateY(-50%);
}
.breadcrumbs-extra {
display: flex;
flex: 0 0 auto;
margin-left: auto;
}
.breadcrumbs-sub-title {
margin: 0;
font-size: 12px;
font-weight: 600;
line-height: 16px;
a {
color: $gl-text-color;
}
}
.btn-sign-in {
margin-top: 3px;
font-weight: $gl-font-weight-bold;
&:hover {
background-color: $white-light;
}
}
...@@ -24,7 +24,7 @@ $new-sidebar-collapsed-width: 50px; ...@@ -24,7 +24,7 @@ $new-sidebar-collapsed-width: 50px;
// Override position: absolute // Override position: absolute
.right-sidebar { .right-sidebar {
position: fixed; position: fixed;
height: calc(100% - #{$new-navbar-height}); height: calc(100% - #{$header-height});
} }
.issues-bulk-update.right-sidebar.right-sidebar-expanded .issuable-sidebar-header { .issues-bulk-update.right-sidebar.right-sidebar-expanded .issuable-sidebar-header {
...@@ -87,7 +87,7 @@ $new-sidebar-collapsed-width: 50px; ...@@ -87,7 +87,7 @@ $new-sidebar-collapsed-width: 50px;
z-index: 400; z-index: 400;
width: $new-sidebar-width; width: $new-sidebar-width;
transition: left $sidebar-transition-duration; transition: left $sidebar-transition-duration;
top: $new-navbar-height; top: $header-height;
bottom: 0; bottom: 0;
left: 0; left: 0;
background-color: $gray-normal; background-color: $gray-normal;
...@@ -197,7 +197,7 @@ $new-sidebar-collapsed-width: 50px; ...@@ -197,7 +197,7 @@ $new-sidebar-collapsed-width: 50px;
} }
.with-performance-bar .nav-sidebar { .with-performance-bar .nav-sidebar {
top: $new-navbar-height + $performance-bar-height; top: $header-height + $performance-bar-height;
} }
.sidebar-sub-level-items { .sidebar-sub-level-items {
...@@ -495,7 +495,7 @@ $new-sidebar-collapsed-width: 50px; ...@@ -495,7 +495,7 @@ $new-sidebar-collapsed-width: 50px;
// Make issue boards full-height now that sub-nav is gone // Make issue boards full-height now that sub-nav is gone
.boards-list { .boards-list {
height: calc(100vh - #{$new-navbar-height}); height: calc(100vh - #{$header-height});
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
height: 475px; // Needed for PhantomJS height: 475px; // Needed for PhantomJS
...@@ -506,5 +506,5 @@ $new-sidebar-collapsed-width: 50px; ...@@ -506,5 +506,5 @@ $new-sidebar-collapsed-width: 50px;
} }
.with-performance-bar .boards-list { .with-performance-bar .boards-list {
height: calc(100vh - #{$new-navbar-height} - #{$performance-bar-height}); height: calc(100vh - #{$header-height} - #{$performance-bar-height});
} }
// For tabbed navigation links, scrolling tabs, etc. For all top/main navigation,
// please check nav.scss
.nav-links { .nav-links {
display: flex; display: flex;
padding: 0; padding: 0;
margin: 0; margin: 0;
list-style: none; list-style: none;
height: auto; height: auto;
border-bottom: 1px solid $border-color;
li { li {
display: flex; display: flex;
...@@ -24,7 +23,6 @@ ...@@ -24,7 +23,6 @@
&:active, &:active,
&:focus { &:focus {
text-decoration: none; text-decoration: none;
border-bottom: 2px solid $gray-darkest;
color: $black; color: $black;
.badge { .badge {
...@@ -34,7 +32,6 @@ ...@@ -34,7 +32,6 @@
} }
&.active a { &.active a {
border-bottom: 2px solid $link-underline-blue;
color: $black; color: $black;
font-weight: $gl-font-weight-bold; font-weight: $gl-font-weight-bold;
...@@ -43,35 +40,6 @@ ...@@ -43,35 +40,6 @@
} }
} }
} }
&.sub-nav {
text-align: center;
background-color: $gray-normal;
.container-fluid {
background-color: $gray-normal;
margin-bottom: 0;
display: flex;
}
li {
&.active a {
border-bottom: none;
color: $link-underline-blue;
}
a {
margin: 0;
padding: 11px 10px 9px;
&:hover,
&:active,
&:focus {
border-color: transparent;
}
}
}
}
} }
.top-area { .top-area {
...@@ -91,17 +59,6 @@ ...@@ -91,17 +59,6 @@
} }
} }
.nav-search {
display: inline-block;
width: 100%;
padding: 11px 0;
/* Small devices (phones, tablets, 768px and lower) */
@media (min-width: $screen-sm-min) {
width: 50%;
}
}
.nav-links { .nav-links {
margin-bottom: 0; margin-bottom: 0;
border-bottom: none; border-bottom: none;
...@@ -150,12 +107,6 @@ ...@@ -150,12 +107,6 @@
} }
} }
&.nav-controls-new-nav {
> .dropdown {
margin-right: 0;
}
}
> .btn-grouped { > .btn-grouped {
float: none; float: none;
} }
...@@ -248,114 +199,43 @@ ...@@ -248,114 +199,43 @@
pre { pre {
width: 100%; width: 100%;
} }
}
.project-item-select-holder.btn-group {
display: flex;
max-width: 350px;
overflow: hidden;
float: right;
.new-project-item-link {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.new-project-item-select-button { @media (max-width: $screen-xs-max) {
width: 32px; flex-flow: row wrap;
}
}
.empty-state .project-item-select-holder.btn-group {
float: none;
display: inline-block;
.btn {
// overrides styles applied to plain `.empty-state .btn`
margin: 10px 0;
max-width: 300px;
width: auto;
@media(max-width: $screen-xs-max) { .nav-controls {
max-width: 250px; $controls-margin: $btn-xs-side-margin - 2px;
} flex: 0 0 100%;
&.controls-flex {
display: flex;
flex-flow: row wrap;
align-items: center;
justify-content: center;
padding: 0 0 $gl-padding-top;
} }
}
.new-project-item-select-button .fa-caret-down {
margin-left: 2px;
}
.layout-nav { .controls-item,
.controls-item-full,
.controls-item:last-child {
flex: 1 1 35%;
display: block;
width: 100%; width: 100%;
background: $gray-light; margin: $controls-margin;
border-bottom: 1px solid $border-color;
transition: padding $sidebar-transition-duration;
text-align: center;
margin-top: $new-navbar-height;
.container-fluid {
position: relative;
.nav-control {
@media (max-width: $screen-sm-max) {
margin-right: 2px;
}
}
}
.controls {
float: right;
padding: 7px 0 0;
i {
color: $layout-link-gray;
}
.fa-rss,
.fa-cog {
font-size: 16px;
}
.fa-caret-down {
margin-left: 5px;
color: $gl-text-color-secondary;
}
.btn,
.dropdown { .dropdown {
position: absolute; margin: 0;
top: 7px;
right: 15px;
z-index: 300;
li.active {
font-weight: $gl-font-weight-bold;
}
} }
} }
.nav-links { .controls-item-full {
border-bottom: none; flex: 1 1 100%;
height: 51px;
@media (min-width: $screen-sm-min) {
justify-content: center;
}
li {
a {
padding-top: 10px;
} }
} }
} }
} }
.with-performance-bar .layout-nav {
margin-top: $header-height + $performance-bar-height;
}
.scrolling-tabs-container { .scrolling-tabs-container {
position: relative; position: relative;
...@@ -385,26 +265,42 @@ ...@@ -385,26 +265,42 @@
left: -7px; left: -7px;
} }
} }
}
&.sub-nav-scroll { .inner-page-scroll-tabs {
position: relative;
.fade-right { .fade-right {
@include fade(left, $gray-normal); @include fade(left, $white-light);
right: 0; right: 0;
text-align: right;
.fa { .fa {
right: -23px; right: 5px;
} }
} }
.fade-left { .fade-left {
@include fade(right, $gray-normal); @include fade(right, $white-light);
left: 0; left: 0;
text-align: left;
.fa { .fa {
left: 10px; left: 5px;
} }
} }
.fade-right,
.fade-left {
top: 16px;
bottom: auto;
}
&.is-smaller {
.fade-right,
.fade-left {
top: 11px;
}
} }
} }
...@@ -432,41 +328,7 @@ ...@@ -432,41 +328,7 @@
} }
} }
} }
}
.page-with-layout-nav {
.right-sidebar {
top: ($header-height + 1) * 2;
}
&.page-with-sub-nav {
.right-sidebar {
top: ($header-height + 1) * 3;
&.affix {
top: $header-height;
}
}
}
}
.with-performance-bar .page-with-layout-nav {
.right-sidebar {
top: ($header-height + 1) * 2 + $performance-bar-height;
}
&.page-with-sub-nav {
.right-sidebar {
top: ($header-height + 1) * 3 + $performance-bar-height;
&.affix {
top: $header-height + $performance-bar-height;
}
}
}
}
.nav-block {
&.activities { &.activities {
border-bottom: 1px solid $border-color; border-bottom: 1px solid $border-color;
...@@ -476,76 +338,39 @@ ...@@ -476,76 +338,39 @@
} }
} }
@media (max-width: $screen-xs-max) { .project-item-select-holder.btn-group {
.top-area {
flex-flow: row wrap;
.nav-controls {
$controls-margin: $btn-xs-side-margin - 2px;
flex: 0 0 100%;
&.controls-flex {
display: flex; display: flex;
flex-flow: row wrap; max-width: 350px;
align-items: center; overflow: hidden;
justify-content: center; float: right;
padding: 0 0 $gl-padding-top;
}
.controls-item,
.controls-item-full,
.controls-item:last-child {
flex: 1 1 35%;
display: block;
width: 100%;
margin: $controls-margin;
.btn, .new-project-item-link {
.dropdown { white-space: nowrap;
margin: 0; overflow: hidden;
} text-overflow: ellipsis;
} }
.controls-item-full { .new-project-item-select-button {
flex: 1 1 100%; width: 32px;
}
}
} }
} }
.inner-page-scroll-tabs { .empty-state .project-item-select-holder.btn-group {
position: relative; float: none;
display: inline-block;
.fade-right {
@include fade(left, $white-light);
right: 0;
text-align: right;
.fa {
right: 5px;
}
}
.fade-left { .btn {
@include fade(right, $white-light); // overrides styles applied to plain `.empty-state .btn`
left: 0; margin: 10px 0;
text-align: left; max-width: 300px;
width: auto;
.fa { @media(max-width: $screen-xs-max) {
left: 5px; max-width: 250px;
}
} }
.fade-right,
.fade-left {
top: 16px;
bottom: auto;
} }
}
&.is-smaller { .new-project-item-select-button .fa-caret-down {
.fade-right, margin-left: 2px;
.fade-left {
top: 11px;
}
}
} }
...@@ -78,16 +78,16 @@ ...@@ -78,16 +78,16 @@
.right-sidebar { .right-sidebar {
border-left: 1px solid $border-color; border-left: 1px solid $border-color;
height: calc(100% - #{$new-navbar-height}); height: calc(100% - #{$header-height});
&.affix { &.affix {
position: fixed; position: fixed;
top: $new-navbar-height; top: $header-height;
} }
} }
.with-performance-bar .right-sidebar.affix { .with-performance-bar .right-sidebar.affix {
top: $new-navbar-height + $performance-bar-height; top: $header-height + $performance-bar-height;
} }
@mixin maintain-sidebar-dimensions { @mixin maintain-sidebar-dimensions {
......
...@@ -17,15 +17,19 @@ ...@@ -17,15 +17,19 @@
.diff-file { .diff-file {
border: 1px solid $border-color; border: 1px solid $border-color;
border-bottom: none;
margin: 0; margin: 0;
} }
&.text-file .diff-file {
border-bottom: none;
}
} }
.timeline-entry { .timeline-entry {
border-color: $white-normal; border-color: $white-normal;
color: $gl-text-color; color: $gl-text-color;
border-bottom: 1px solid $border-white-light; border-bottom: 1px solid $border-white-light;
background: $white-light;
.timeline-entry-inner { .timeline-entry-inner {
position: relative; position: relative;
......
.tooltip-inner {
font-size: $tooltip-font-size;
border-radius: $border-radius-default;
line-height: 16px;
font-weight: $gl-font-weight-normal;
padding: $gl-btn-padding;
}
/* /*
* Layout * Layout
*/ */
$grid-size: 8px;
$gutter_collapsed_width: 62px; $gutter_collapsed_width: 62px;
$gutter_width: 290px; $gutter_width: 290px;
$gutter_inner_width: 250px; $gutter_inner_width: 250px;
...@@ -202,6 +203,11 @@ $md-area-border: #ddd; ...@@ -202,6 +203,11 @@ $md-area-border: #ddd;
$code_font_size: 12px; $code_font_size: 12px;
$code_line_height: 1.6; $code_line_height: 1.6;
/*
* Tooltips
*/
$tooltip-font-size: 12px;
/* /*
* Padding * Padding
*/ */
...@@ -219,8 +225,7 @@ $gl-sidebar-padding: 22px; ...@@ -219,8 +225,7 @@ $gl-sidebar-padding: 22px;
$row-hover: $blue-50; $row-hover: $blue-50;
$row-hover-border: $blue-200; $row-hover-border: $blue-200;
$progress-color: #c0392b; $progress-color: #c0392b;
$header-height: 50px; $header-height: 40px;
$new-navbar-height: 40px;
$fixed-layout-width: 1280px; $fixed-layout-width: 1280px;
$limited-layout-width: 990px; $limited-layout-width: 990px;
$limited-layout-width-sm: 790px; $limited-layout-width-sm: 790px;
...@@ -317,6 +322,7 @@ $diff-image-info-color: grey; ...@@ -317,6 +322,7 @@ $diff-image-info-color: grey;
$diff-swipe-border: #999; $diff-swipe-border: #999;
$diff-view-modes-color: grey; $diff-view-modes-color: grey;
$diff-view-modes-border: #c1c1c1; $diff-view-modes-border: #c1c1c1;
$diff-jagged-border-gradient-color: darken($white-normal, 8%);
/* /*
* Fonts * Fonts
...@@ -700,3 +706,15 @@ Project Templates Icons ...@@ -700,3 +706,15 @@ Project Templates Icons
$rails: #c00; $rails: #c00;
$node: #353535; $node: #353535;
$java: #70ad51; $java: #70ad51;
/*
Issuable warning
*/
$issuable-warning-size: 24px;
$issuable-warning-icon-margin: 4px;
/*
Image Commenting cursor
*/
$image-comment-cursor-left-offset: 12;
$image-comment-cursor-top-offset: 30;
...@@ -414,7 +414,6 @@ ...@@ -414,7 +414,6 @@
margin: 5px; margin: 5px;
} }
.page-with-layout-nav.page-with-sub-nav .issue-boards-sidebar,
.page-with-new-sidebar.page-with-sidebar .issue-boards-sidebar { .page-with-new-sidebar.page-with-sidebar .issue-boards-sidebar {
.issuable-sidebar-header { .issuable-sidebar-header {
position: relative; position: relative;
......
...@@ -64,10 +64,10 @@ ...@@ -64,10 +64,10 @@
color: $gl-text-color; color: $gl-text-color;
position: sticky; position: sticky;
position: -webkit-sticky; position: -webkit-sticky;
top: $new-navbar-height; top: $header-height;
&.affix { &.affix {
top: $new-navbar-height; top: $header-height;
} }
// with sidebar // with sidebar
...@@ -174,10 +174,10 @@ ...@@ -174,10 +174,10 @@
.with-performance-bar .build-page { .with-performance-bar .build-page {
.top-bar { .top-bar {
top: $new-navbar-height + $performance-bar-height; top: $header-height + $performance-bar-height;
&.affix { &.affix {
top: $new-navbar-height + $performance-bar-height; top: $header-height + $performance-bar-height;
} }
} }
} }
......
.edit-cluster-form {
.clipboard-addon {
background-color: $white-light;
}
.alert-block {
margin-bottom: 20px;
}
}
...@@ -297,6 +297,7 @@ ...@@ -297,6 +297,7 @@
.drag-track { .drag-track {
display: block; display: block;
position: absolute; position: absolute;
top: 0;
left: 12px; left: 12px;
height: 10px; height: 10px;
width: 276px; width: 276px;
...@@ -547,16 +548,23 @@ ...@@ -547,16 +548,23 @@
} }
.diff-notes-collapse { .diff-notes-collapse {
width: 19px; width: 24px;
height: 19px; height: 24px;
border-radius: 50%;
padding: 0; padding: 0;
transition: transform .1s ease-out; transition: transform .1s ease-out;
z-index: 100; z-index: 100;
.collapse-icon {
height: 50%;
width: 100%;
}
svg { svg {
vertical-align: text-top; vertical-align: middle;
} }
.collapse-icon,
path { path {
fill: $white-light; fill: $white-light;
} }
...@@ -644,3 +652,157 @@ ...@@ -644,3 +652,157 @@
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
.note-container {
background-color: $gray-light;
border-top: 1px solid $white-normal;
// double jagged line divider
.discussion-notes + .discussion-notes::before,
.discussion-notes + .discussion-form::before {
content: '';
position: relative;
display: block;
width: 100%;
height: 10px;
background-color: $white-light;
background-image: linear-gradient(45deg, transparent, transparent 73%, $diff-jagged-border-gradient-color 75%, $white-light 80%),
linear-gradient(225deg, transparent, transparent 73%, $diff-jagged-border-gradient-color 75%, $white-light 80%),
linear-gradient(135deg, transparent, transparent 73%, $diff-jagged-border-gradient-color 75%, $white-light 80%),
linear-gradient(-45deg, transparent, transparent 73%, $diff-jagged-border-gradient-color 75%, $white-light 80%);
background-position: 5px 5px,0 5px,0 5px,5px 5px;
background-size: 10px 10px;
background-repeat: repeat;
}
.notes {
position: relative;
}
.diff-notes-collapse {
position: absolute;
left: -12px;
}
}
.diff-file .note-container > .new-note,
.note-container .discussion-notes {
margin-left: 100px;
border-left: 1px solid $white-normal;
}
.notes.active {
.diff-file .note-container > .new-note,
.note-container .discussion-notes {
// Override our margin and border (set for diff tab)
// when user is on the discussion tab for MR
margin-left: inherit;
border-left: inherit;
}
}
.files:not([data-can-create-note]) .frame {
cursor: auto;
}
.frame.click-to-comment {
position: relative;
cursor: url(icon_image_comment.svg)
$image-comment-cursor-left-offset $image-comment-cursor-top-offset, auto;
// Retina cursor
cursor: -webkit-image-set(url(icon_image_comment.svg) 1x, url(icon_image_comment@2x.svg) 2x)
$image-comment-cursor-left-offset $image-comment-cursor-top-offset, auto;
.comment-indicator {
position: absolute;
padding: 0;
width: (2px * $image-comment-cursor-left-offset);
height: (1px * $image-comment-cursor-top-offset);
// center the indicator to match the top left click region
margin-top: (-1px * $image-comment-cursor-top-offset) + 2;
margin-left: (-1px * $image-comment-cursor-left-offset) + 1;
svg {
width: 100%;
height: 100%;
}
&:focus {
outline: none;
}
}
}
.frame .badge,
.image-diff-avatar-link .badge,
.notes > .badge {
position: absolute;
background-color: $blue-400;
color: $white-light;
border: $white-light 1px solid;
min-height: $gl-padding;
padding: 5px 8px;
border-radius: 12px;
&:focus {
outline: none;
}
}
.frame .badge,
.frame .image-comment-badge {
// Center align badges on the frame
transform: translate3d(-50%, -50%, 0);
}
.image-comment-badge {
@include btn-comment-icon;
position: absolute;
&.inverted {
border-color: $white-light;
}
}
.image-diff-avatar-link {
position: relative;
.badge,
.image-comment-badge {
top: 25px;
right: 8px;
}
}
.notes > .badge {
display: none;
left: -13px;
}
.discussion-notes {
min-height: 35px;
&:first-child {
// First child does not have the jagged borders
min-height: 25px;
}
&.collapsed {
background-color: $white-light;
.diff-notes-collapse,
.note,
.discussion-reply-holder, {
display: none;
}
.notes > .badge {
display: block;
}
}
}
.discussion-body .image .frame {
position: relative;
}
...@@ -5,27 +5,29 @@ ...@@ -5,27 +5,29 @@
margin-right: auto; margin-right: auto;
} }
.is-confidential { .issuable-warning-icon {
color: $orange-600; color: $orange-600;
background-color: $orange-100; background-color: $orange-100;
border-radius: $border-radius-default; border-radius: $border-radius-default;
padding: 5px; padding: 5px;
margin: 0 3px 0 -4px; margin: 0 $btn-side-margin 0 0;
width: $issuable-warning-size;
height: $issuable-warning-size;
text-align: center;
&:first-of-type {
margin-right: $issuable-warning-icon-margin;
}
} }
.is-not-confidential { .sidebar-item-icon {
border-radius: $border-radius-default; border-radius: $border-radius-default;
padding: 5px; padding: 5px;
margin: 0 3px 0 -4px; margin: 0 3px 0 -4px;
}
.confidentiality {
.is-not-confidential {
margin: auto;
}
.is-confidential { &.is-active {
margin: auto; color: $orange-600;
background-color: $orange-50;
} }
} }
...@@ -220,7 +222,7 @@ ...@@ -220,7 +222,7 @@
.right-sidebar { .right-sidebar {
position: absolute; position: absolute;
top: $new-navbar-height; top: $header-height;
bottom: 0; bottom: 0;
right: 0; right: 0;
transition: width $right-sidebar-transition-duration; transition: width $right-sidebar-transition-duration;
...@@ -485,10 +487,10 @@ ...@@ -485,10 +487,10 @@
} }
.with-performance-bar .right-sidebar { .with-performance-bar .right-sidebar {
top: $new-navbar-height + $performance-bar-height; top: $header-height + $performance-bar-height;
.issuable-sidebar { .issuable-sidebar {
height: calc(100% - #{$new-navbar-height} - #{$performance-bar-height}); height: calc(100% - #{$header-height} - #{$performance-bar-height});
} }
} }
......
...@@ -649,7 +649,7 @@ ...@@ -649,7 +649,7 @@
} }
.merge-request-tabs-holder { .merge-request-tabs-holder {
top: $new-navbar-height; top: $header-height;
z-index: 200; z-index: 200;
background-color: $white-light; background-color: $white-light;
border-bottom: 1px solid $border-color; border-bottom: 1px solid $border-color;
...@@ -679,7 +679,7 @@ ...@@ -679,7 +679,7 @@
} }
.with-performance-bar .merge-request-tabs-holder { .with-performance-bar .merge-request-tabs-holder {
top: $new-navbar-height + $performance-bar-height; top: $header-height + $performance-bar-height;
} }
.merge-request-tabs { .merge-request-tabs {
......
...@@ -101,7 +101,7 @@ ...@@ -101,7 +101,7 @@
} }
} }
.confidential-issue-warning { .issuable-note-warning {
color: $orange-600; color: $orange-600;
background-color: $orange-100; background-color: $orange-100;
border-radius: $border-radius-default $border-radius-default 0 0; border-radius: $border-radius-default $border-radius-default 0 0;
...@@ -110,37 +110,64 @@ ...@@ -110,37 +110,64 @@
padding: 3px 12px; padding: 3px 12px;
margin: auto; margin: auto;
align-items: center; align-items: center;
.icon {
margin-right: $issuable-warning-icon-margin;
}
}
.disabled-comment .issuable-note-warning {
border: none;
border-radius: $label-border-radius;
padding-top: $gl-vert-padding;
padding-bottom: $gl-vert-padding;
.icon svg {
position: relative;
top: 2px;
margin-right: $btn-xs-side-margin;
width: $gl-font-size;
height: $gl-font-size;
fill: $orange-600;
}
} }
.confidential-value { .sidebar-item-value {
.fa { .fa {
background-color: inherit; background-color: inherit;
} }
} }
.confidential-warning-message { .sidebar-item-warning-message {
line-height: 1.5; line-height: 1.5;
padding: 16px; padding: 16px;
.confidential-warning-message-actions { .text {
color: $text-color;
}
.sidebar-item-warning-message-actions {
display: flex; display: flex;
button { .btn {
flex-grow: 1; flex-grow: 1;
} }
} }
} }
.confidential-issue-warning + .md-area { .issuable-note-warning + .md-area {
border-top-left-radius: 0; border-top-left-radius: 0;
border-top-right-radius: 0; border-top-right-radius: 0;
} }
.discussion-form { .discussion-form {
padding: $gl-padding-top $gl-padding $gl-padding;
background-color: $white-light; background-color: $white-light;
} }
.discussion-form-container {
padding: $gl-padding-top $gl-padding $gl-padding;
}
.discussion-notes .disabled-comment { .discussion-notes .disabled-comment {
padding: 6px 0; padding: 6px 0;
} }
......
...@@ -650,29 +650,12 @@ ul.notes { ...@@ -650,29 +650,12 @@ ul.notes {
} }
.add-diff-note { .add-diff-note {
@include btn-comment-icon;
opacity: 0; opacity: 0;
margin-top: -2px; margin-top: -2px;
border-radius: 50%;
background: $white-light;
padding: 1px 5px;
font-size: 12px;
color: $blue-500;
margin-left: -55px; margin-left: -55px;
position: absolute; position: absolute;
z-index: 10; z-index: 10;
width: 23px;
height: 23px;
border: 1px solid $blue-500;
&:hover {
background: $blue-500;
border-color: $blue-600;
color: $white-light;
}
&:active {
outline: 0;
}
} }
.discussion-body, .discussion-body,
...@@ -703,6 +686,12 @@ ul.notes { ...@@ -703,6 +686,12 @@ ul.notes {
color: $note-disabled-comment-color; color: $note-disabled-comment-color;
padding: 90px 0; padding: 90px 0;
&.discussion-locked {
border: none;
background-color: $white-light;
}
a { a {
color: $gl-link-color; color: $gl-link-color;
} }
......
...@@ -108,6 +108,15 @@ ...@@ -108,6 +108,15 @@
} }
} }
.subkeys-list {
@include basic-list;
li {
padding: 3px 0;
border: none;
}
}
.key-list-item { .key-list-item {
.key-list-item-info { .key-list-item-info {
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
......
...@@ -47,6 +47,7 @@ input[type="checkbox"]:hover { ...@@ -47,6 +47,7 @@ input[type="checkbox"]:hover {
} }
.location-badge { .location-badge {
height: 32px;
font-size: 12px; font-size: 12px;
margin: -4px 4px -4px -4px; margin: -4px 4px -4px -4px;
line-height: 25px; line-height: 25px;
......
...@@ -3,9 +3,23 @@ ...@@ -3,9 +3,23 @@
# Automatically sets the layout and ensures an administrator is logged in # Automatically sets the layout and ensures an administrator is logged in
class Admin::ApplicationController < ApplicationController class Admin::ApplicationController < ApplicationController
before_action :authenticate_admin! before_action :authenticate_admin!
before_action :display_read_only_information
layout 'admin' layout 'admin'
def authenticate_admin! def authenticate_admin!
render_404 unless current_user.admin? render_404 unless current_user.admin?
end end
def display_read_only_information
return unless Gitlab::Database.read_only?
flash.now[:notice] = read_only_message
end
private
# Overridden in EE
def read_only_message
_('You are on a read-only GitLab instance.')
end
end end
...@@ -10,7 +10,7 @@ module Boards ...@@ -10,7 +10,7 @@ module Boards
def index def index
issues = Boards::Issues::ListService.new(board_parent, current_user, filter_params).execute issues = Boards::Issues::ListService.new(board_parent, current_user, filter_params).execute
issues = issues.page(params[:page]).per(params[:per] || 20) issues = issues.page(params[:page]).per(params[:per] || 20)
make_sure_position_is_set(issues) make_sure_position_is_set(issues) if Gitlab::Database.read_write?
issues = issues.preload(:project, issues = issues.preload(:project,
:milestone, :milestone,
:assignees, :assignees,
......
...@@ -96,7 +96,8 @@ module NotesActions ...@@ -96,7 +96,8 @@ module NotesActions
id: note.id, id: note.id,
discussion_id: note.discussion_id(noteable), discussion_id: note.discussion_id(noteable),
html: note_html(note), html: note_html(note),
note: note.note note: note.note,
on_image: note.try(:on_image?)
) )
discussion = note.to_discussion(noteable) discussion = note.to_discussion(noteable)
...@@ -122,7 +123,9 @@ module NotesActions ...@@ -122,7 +123,9 @@ module NotesActions
def diff_discussion_html(discussion) def diff_discussion_html(discussion)
return unless discussion.diff_discussion? return unless discussion.diff_discussion?
if params[:view] == 'parallel' on_image = discussion.on_image?
if params[:view] == 'parallel' && !on_image
template = "discussions/_parallel_diff_discussion" template = "discussions/_parallel_diff_discussion"
locals = locals =
if params[:line_type] == 'old' if params[:line_type] == 'old'
...@@ -132,7 +135,9 @@ module NotesActions ...@@ -132,7 +135,9 @@ module NotesActions
end end
else else
template = "discussions/_diff_discussion" template = "discussions/_diff_discussion"
locals = { discussions: [discussion] } @fresh_discussion = true
locals = { discussions: [discussion], on_image: on_image }
end end
render_to_string( render_to_string(
......
module GoogleApi
class AuthorizationsController < ApplicationController
def callback
token, expires_at = GoogleApi::CloudPlatform::Client
.new(nil, callback_google_api_auth_url)
.get_token(params[:code])
session[GoogleApi::CloudPlatform::Client.session_key_for_token] = token
session[GoogleApi::CloudPlatform::Client.session_key_for_expires_at] =
expires_at.to_s
state_redirect_uri = redirect_uri_from_session_key(params[:state])
if state_redirect_uri
redirect_to state_redirect_uri
else
redirect_to root_path
end
end
private
def redirect_uri_from_session_key(state)
key = GoogleApi::CloudPlatform::Client
.session_key_for_redirect_uri(params[:state])
session[key] if key
end
end
end
...@@ -2,7 +2,7 @@ class Profiles::GpgKeysController < Profiles::ApplicationController ...@@ -2,7 +2,7 @@ class Profiles::GpgKeysController < Profiles::ApplicationController
before_action :set_gpg_key, only: [:destroy, :revoke] before_action :set_gpg_key, only: [:destroy, :revoke]
def index def index
@gpg_keys = current_user.gpg_keys @gpg_keys = current_user.gpg_keys.with_subkeys
@gpg_key = GpgKey.new @gpg_key = GpgKey.new
end end
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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