Commit b373c56c authored by DJ Mountney's avatar DJ Mountney

Merge remote-tracking branch 'origin/master' into dev-master

parents ac38f36a 21935d85
...@@ -43,6 +43,7 @@ stages: ...@@ -43,6 +43,7 @@ stages:
# Predefined scopes # Predefined scopes
.dedicated-runner: &dedicated-runner .dedicated-runner: &dedicated-runner
retry: 1
tags: tags:
- gitlab-org - gitlab-org
...@@ -411,7 +412,6 @@ db:migrate:reset-mysql: ...@@ -411,7 +412,6 @@ db:migrate:reset-mysql:
.migration-paths: &migration-paths .migration-paths: &migration-paths
<<: *dedicated-runner <<: *dedicated-runner
<<: *only-canonical-masters
<<: *pull-cache <<: *pull-cache
stage: test stage: test
variables: variables:
......
...@@ -199,26 +199,8 @@ available in the package repositories. ...@@ -199,26 +199,8 @@ available in the package repositories.
## Release retrospective and kickoff ## Release retrospective and kickoff
### Retrospective - [Retrospective](https://about.gitlab.com/handbook/engineering/workflow/#retrospective)
- [Kickoff](https://about.gitlab.com/handbook/engineering/workflow/#kickoff)
After each release, we have a retrospective call where we discuss what went well,
what went wrong, and what we can improve for the next release. The
[retrospective notes] are public and you are invited to comment on them.
If you're interested, you can even join the
[retrospective call][retro-kickoff-call], on the first working day after the
22nd at 6pm CET / 9am PST.
### Kickoff
Before working on the next release, we have a
kickoff call to explain what we expect to ship in the next release. The
[kickoff notes] are public and you are invited to comment on them.
If you're interested, you can even join the [kickoff call][retro-kickoff-call],
on the first working day after the 7th at 6pm CET / 9am PST..
[retrospective notes]: https://docs.google.com/document/d/1nEkM_7Dj4bT21GJy0Ut3By76FZqCfLBmFQNVThmW2TY/edit?usp=sharing
[kickoff notes]: https://docs.google.com/document/d/1ElPkZ90A8ey_iOkTvUs_ByMlwKK6NAB2VOK5835wYK0/edit?usp=sharing
[retro-kickoff-call]: https://gitlab.zoom.us/j/918821206
## Copy & paste responses ## Copy & paste responses
......
export const addTooltipToEl = (el) => {
const textEl = el.querySelector('.js-breadcrumb-item-text');
if (textEl && textEl.scrollWidth > textEl.offsetWidth) {
el.setAttribute('title', el.textContent);
el.setAttribute('data-container', 'body');
el.classList.add('has-tooltip');
}
};
export default () => {
const breadcrumbs = document.querySelector('.js-breadcrumbs-list');
if (breadcrumbs) {
const topLevelLinks = [...breadcrumbs.children].filter(el => !el.classList.contains('dropdown'))
.map(el => el.querySelector('a'))
.filter(el => el);
const $expander = $('.js-breadcrumbs-collapsed-expander');
topLevelLinks.forEach(el => addTooltipToEl(el));
$expander.closest('.dropdown')
.on('show.bs.dropdown hide.bs.dropdown', (e) => {
$('.js-breadcrumbs-collapsed-expander', e.currentTarget).toggleClass('open')
.tooltip('hide');
});
}
};
...@@ -41,7 +41,6 @@ import Issue from './issue'; ...@@ -41,7 +41,6 @@ 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';
import Group from './group'; import Group from './group';
import GroupName from './group_name';
import GroupsList from './groups_list'; import GroupsList from './groups_list';
import ProjectsList from './projects_list'; import ProjectsList from './projects_list';
import setupProjectEdit from './project_edit'; import setupProjectEdit from './project_edit';
...@@ -489,6 +488,8 @@ import initChangesDropdown from './init_changes_dropdown'; ...@@ -489,6 +488,8 @@ import initChangesDropdown from './init_changes_dropdown';
initSettingsPanels(); initSettingsPanels();
break; break;
case 'projects:settings:ci_cd:show': case 'projects:settings:ci_cd:show':
// Initialize expandable settings panels
initSettingsPanels();
case 'groups:settings:ci_cd:show': case 'groups:settings:ci_cd:show':
new gl.ProjectVariables(); new gl.ProjectVariables();
break; break;
...@@ -554,9 +555,6 @@ import initChangesDropdown from './init_changes_dropdown'; ...@@ -554,9 +555,6 @@ import initChangesDropdown from './init_changes_dropdown';
case 'root': case 'root':
new UserCallout(); new UserCallout();
break; break;
case 'groups':
new GroupName();
break;
case 'profiles': case 'profiles':
new NotificationsForm(); new NotificationsForm();
new NotificationsDropdown(); new NotificationsDropdown();
...@@ -564,7 +562,6 @@ import initChangesDropdown from './init_changes_dropdown'; ...@@ -564,7 +562,6 @@ import initChangesDropdown from './init_changes_dropdown';
case 'projects': case 'projects':
new Project(); new Project();
new ProjectAvatar(); new ProjectAvatar();
new GroupName();
switch (path[1]) { switch (path[1]) {
case 'compare': case 'compare':
new CompareAutocomplete(); new CompareAutocomplete();
......
import Cookies from 'js-cookie';
import _ from 'underscore';
export default class GroupName {
constructor() {
this.titleContainer = document.querySelector('.js-title-container');
this.title = this.titleContainer.querySelector('.title');
if (this.title) {
this.titleWidth = this.title.offsetWidth;
this.groupTitle = this.titleContainer.querySelector('.group-title');
this.groups = this.titleContainer.querySelectorAll('.group-path');
this.toggle = null;
this.isHidden = false;
this.init();
}
}
init() {
if (this.groups.length > 0) {
this.groups[this.groups.length - 1].classList.remove('hidable');
this.toggleHandler();
window.addEventListener('resize', _.debounce(this.toggleHandler.bind(this), 100));
}
this.render();
}
toggleHandler() {
if (this.titleWidth > this.titleContainer.offsetWidth) {
if (!this.toggle) this.createToggle();
this.showToggle();
} else if (this.toggle) {
this.hideToggle();
}
}
createToggle() {
this.toggle = document.createElement('button');
this.toggle.setAttribute('type', 'button');
this.toggle.className = 'text-expander group-name-toggle';
this.toggle.setAttribute('aria-label', 'Toggle full path');
if (Cookies.get('new_nav') === 'true') {
this.toggle.innerHTML = '<i class="fa fa-ellipsis-h" aria-hidden="true"></i>';
} else {
this.toggle.innerHTML = '...';
}
this.toggle.addEventListener('click', this.toggleGroups.bind(this));
if (Cookies.get('new_nav') === 'true') {
this.title.insertBefore(this.toggle, this.groupTitle);
} else {
this.titleContainer.insertBefore(this.toggle, this.title);
}
this.toggleGroups();
}
showToggle() {
this.title.classList.add('wrap');
this.toggle.classList.remove('hidden');
if (this.isHidden) this.groupTitle.classList.add('hidden');
}
hideToggle() {
this.title.classList.remove('wrap');
this.toggle.classList.add('hidden');
if (this.isHidden) this.groupTitle.classList.remove('hidden');
}
toggleGroups() {
this.isHidden = !this.isHidden;
this.groupTitle.classList.toggle('hidden');
}
render() {
this.title.classList.remove('initializing');
}
}
...@@ -5,7 +5,6 @@ ...@@ -5,7 +5,6 @@
/* global SubscriptionSelect */ /* global SubscriptionSelect */
import IssuableBulkUpdateActions from './issuable_bulk_update_actions'; import IssuableBulkUpdateActions from './issuable_bulk_update_actions';
import SidebarHeightManager from './sidebar_height_manager';
const HIDDEN_CLASS = 'hidden'; const HIDDEN_CLASS = 'hidden';
const DISABLED_CONTENT_CLASS = 'disabled-content'; const DISABLED_CONTENT_CLASS = 'disabled-content';
...@@ -50,13 +49,6 @@ export default class IssuableBulkUpdateSidebar { ...@@ -50,13 +49,6 @@ export default class IssuableBulkUpdateSidebar {
new SubscriptionSelect(); new SubscriptionSelect();
} }
getNavHeight() {
const navbarHeight = $('.navbar-gitlab').outerHeight();
const layoutNavHeight = $('.layout-nav').outerHeight();
const subNavScroll = $('.sub-nav-scroll').outerHeight();
return navbarHeight + layoutNavHeight + subNavScroll;
}
setupBulkUpdateActions() { setupBulkUpdateActions() {
IssuableBulkUpdateActions.setOriginalDropdownData(); IssuableBulkUpdateActions.setOriginalDropdownData();
} }
...@@ -84,23 +76,6 @@ export default class IssuableBulkUpdateSidebar { ...@@ -84,23 +76,6 @@ export default class IssuableBulkUpdateSidebar {
this.toggleBulkEditButtonDisabled(enable); this.toggleBulkEditButtonDisabled(enable);
this.toggleOtherFiltersDisabled(enable); this.toggleOtherFiltersDisabled(enable);
this.toggleCheckboxDisplay(enable); this.toggleCheckboxDisplay(enable);
if (enable) {
this.initAffix();
SidebarHeightManager.init();
}
}
initAffix() {
if (!this.$sidebar.hasClass('affix-top')) {
const offsetTop = $('.scrolling-tabs-container').outerHeight() + $('.sub-nav-scroll').outerHeight();
this.$sidebar.affix({
offset: {
top: offsetTop,
},
});
}
} }
updateSelectedIssuableIds() { updateSelectedIssuableIds() {
......
...@@ -50,19 +50,10 @@ import initFlyOutNav from './fly_out_nav'; ...@@ -50,19 +50,10 @@ import initFlyOutNav from './fly_out_nav';
}); });
}); });
function applyScrollNavClass() {
const scrollOpacityHeight = 40;
$('.navbar-border').css('opacity', Math.min($(window).scrollTop() / scrollOpacityHeight, 1));
}
$(() => { $(() => {
if (Cookies.get('new_nav') === 'true') { const newNavSidebar = new NewNavSidebar();
const newNavSidebar = new NewNavSidebar(); newNavSidebar.bindEvents();
newNavSidebar.bindEvents();
initFlyOutNav();
}
$(window).on('scroll', _.throttle(applyScrollNavClass, 100)); initFlyOutNav();
}); });
}).call(window); }).call(window);
...@@ -144,6 +144,7 @@ import './smart_interval'; ...@@ -144,6 +144,7 @@ import './smart_interval';
import './star'; import './star';
import './subscription'; import './subscription';
import './subscription_select'; import './subscription_select';
import initBreadcrumbs from './breadcrumb';
import './dispatcher'; import './dispatcher';
...@@ -181,6 +182,8 @@ $(function () { ...@@ -181,6 +182,8 @@ $(function () {
var bootstrapBreakpoint = bp.getBreakpointSize(); var bootstrapBreakpoint = bp.getBreakpointSize();
var fitSidebarForSize; var fitSidebarForSize;
initBreadcrumbs();
// Set the default path for all cookies to GitLab's root directory // Set the default path for all cookies to GitLab's root directory
Cookies.defaults.path = gon.relative_url_root || '/'; Cookies.defaults.path = gon.relative_url_root || '/';
......
...@@ -63,7 +63,7 @@ export default class NewNavSidebar { ...@@ -63,7 +63,7 @@ export default class NewNavSidebar {
if (breakpoint === 'sm' || breakpoint === 'md') { if (breakpoint === 'sm' || breakpoint === 'md') {
this.toggleCollapsedSidebar(true); this.toggleCollapsedSidebar(true);
} else if (breakpoint === 'lg') { } else if (breakpoint === 'lg') {
const collapse = Cookies.get('sidebar_collapsed') === 'true'; const collapse = this.$sidebar.hasClass('sidebar-icons-only');
this.toggleCollapsedSidebar(collapse); this.toggleCollapsedSidebar(collapse);
} }
} }
......
...@@ -2,7 +2,6 @@ ...@@ -2,7 +2,6 @@
import _ from 'underscore'; import _ from 'underscore';
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
import SidebarHeightManager from './sidebar_height_manager';
(function() { (function() {
this.Sidebar = (function() { this.Sidebar = (function() {
...@@ -23,7 +22,6 @@ import SidebarHeightManager from './sidebar_height_manager'; ...@@ -23,7 +22,6 @@ import SidebarHeightManager from './sidebar_height_manager';
}; };
Sidebar.prototype.addEventListeners = function() { Sidebar.prototype.addEventListeners = function() {
SidebarHeightManager.init();
const $document = $(document); const $document = $(document);
this.sidebar.on('click', '.sidebar-collapsed-icon', this, this.sidebarCollapseClicked); this.sidebar.on('click', '.sidebar-collapsed-icon', this, this.sidebarCollapseClicked);
......
import _ from 'underscore';
import Cookies from 'js-cookie';
export default {
init() {
if (!this.initialized) {
if (Cookies.get('new_nav') === 'true' && $('.js-issuable-sidebar').length) return;
this.$window = $(window);
this.$rightSidebar = $('.js-right-sidebar');
this.$navHeight = $('.navbar-gitlab').outerHeight() +
$('.layout-nav').outerHeight() +
$('.sub-nav-scroll').outerHeight();
const throttledSetSidebarHeight = _.throttle(() => this.setSidebarHeight(), 20);
const debouncedSetSidebarHeight = _.debounce(() => this.setSidebarHeight(), 200);
this.$window.on('scroll', throttledSetSidebarHeight);
this.$window.on('resize', debouncedSetSidebarHeight);
this.initialized = true;
}
},
setSidebarHeight() {
const currentScrollDepth = window.pageYOffset || 0;
const diff = this.$navHeight - currentScrollDepth;
if (diff > 0) {
const newSidebarHeight = window.innerHeight - diff;
this.$rightSidebar.outerHeight(newSidebarHeight);
this.sidebarHeightIsCustom = true;
} else if (this.sidebarHeightIsCustom) {
this.$rightSidebar.outerHeight('100%');
this.sidebarHeightIsCustom = false;
}
},
};
...@@ -829,6 +829,7 @@ ...@@ -829,6 +829,7 @@
} }
} }
@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.navbar-gitlab-new .header-content .dropdown-menu.projects-dropdown-menu {
......
@import "framework/variables"; @import "framework/variables";
@import 'framework/tw_bootstrap_variables'; @import 'framework/tw_bootstrap_variables';
@import "bootstrap/variables"; @import "bootstrap/variables";
@import "framework/mixins";
.content-wrapper.page-with-new-nav { .content-wrapper.page-with-new-nav {
margin-top: $new-navbar-height; margin-top: $new-navbar-height;
...@@ -422,109 +423,38 @@ header.navbar-gitlab-new { ...@@ -422,109 +423,38 @@ header.navbar-gitlab-new {
.breadcrumbs { .breadcrumbs {
display: flex; display: flex;
min-height: 61px; min-height: 48px;
color: $gl-text-color; color: $gl-text-color;
border-bottom: 1px solid $border-color;
.dropdown-toggle-caret {
position: relative;
top: -1px;
padding: 0 5px;
color: $gl-text-color-secondary;
font-size: 10px;
line-height: 1;
background: none;
border: 0;
&:focus {
outline: 0;
}
}
// TODO: fallback to global style
.dropdown-menu {
.divider {
margin: 6px 0;
}
li {
padding: 0 1px;
a {
border-radius: 0;
padding: 8px 16px;
&.is-focused,
&:hover,
&:active,
&:focus {
background-color: $gray-darker;
}
}
}
}
} }
.breadcrumbs-container { .breadcrumbs-container {
display: -webkit-flex;
display: flex; display: flex;
width: 100%; width: 100%;
position: relative; position: relative;
padding-top: $gl-padding;
padding-bottom: $gl-padding;
align-items: center; align-items: center;
border-bottom: 1px solid $border-color;
.dropdown-menu-projects {
margin-top: -$gl-padding;
margin-left: $gl-padding;
}
} }
.breadcrumbs-links { .breadcrumbs-links {
-webkit-flex: 1;
flex: 1; flex: 1;
min-width: 0; min-width: 0;
align-self: center; align-self: center;
color: $gl-text-color-quaternary; color: $gl-text-color-secondary;
a {
color: $gl-text-color-secondary;
&:not(:first-child),
&.group-path {
margin-left: 4px;
}
&:not(:last-of-type),
&.group-path {
margin-right: 3px;
}
}
.title {
display: inline-block;
> a {
&:last-of-type:not(:first-child) {
font-weight: $gl-font-weight-bold;
}
}
}
.avatar-tile { .avatar-tile {
margin-right: 5px; margin-right: 4px;
border: 1px solid $border-color; border: 1px solid $border-color;
border-radius: 50%; border-radius: 50%;
vertical-align: sub; vertical-align: sub;
&.identicon {
float: left;
width: 16px;
height: 16px;
margin-top: 2px;
font-size: 10px;
}
} }
.text-expander { .text-expander {
margin-left: 4px; margin-left: 0;
margin-right: 4px; margin-right: 2px;
> i { > i {
position: relative; position: relative;
...@@ -533,37 +463,52 @@ header.navbar-gitlab-new { ...@@ -533,37 +463,52 @@ header.navbar-gitlab-new {
} }
} }
.breadcrumbs-extra { .breadcrumbs-list {
display: -webkit-flex;
display: flex; display: flex;
flex: 0 0 auto; flex-wrap: wrap;
margin-left: auto; margin-bottom: 0;
} line-height: 16px;
.breadcrumbs-sub-title { > li {
margin: 2px 0; display: flex;
font-size: 16px; align-items: center;
font-weight: $gl-font-weight-normal; position: relative;
line-height: 1;
ul {
margin: 0;
}
li {
display: inline-block;
&:not(:last-child) { &:not(:last-child) {
&::after { margin-right: 20px;
content: "/";
margin: 0 2px 0 5px;
color: rgba($black, .65);
}
} }
&:last-child a { > a {
font-weight: $gl-font-weight-bold; font-size: 12px;
color: currentColor;
} }
} }
}
.breadcrumb-item-text {
@include str-truncated(128px);
}
.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: 1;
a { a {
color: $gl-text-color; color: $gl-text-color;
......
...@@ -45,7 +45,6 @@ $new-sidebar-collapsed-width: 50px; ...@@ -45,7 +45,6 @@ $new-sidebar-collapsed-width: 50px;
margin-right: 2px; margin-right: 2px;
a { a {
border-bottom: 1px solid $border-color;
font-weight: $gl-font-weight-bold; font-weight: $gl-font-weight-bold;
display: flex; display: flex;
align-items: center; align-items: center;
...@@ -389,6 +388,10 @@ $new-sidebar-collapsed-width: 50px; ...@@ -389,6 +388,10 @@ $new-sidebar-collapsed-width: 50px;
} }
} }
.nav-icon-container {
margin-right: 0;
}
.toggle-sidebar-button { .toggle-sidebar-button {
width: $new-sidebar-collapsed-width - 2px; width: $new-sidebar-collapsed-width - 2px;
padding: 16px 18px; padding: 16px 18px;
......
...@@ -440,6 +440,7 @@ ...@@ -440,6 +440,7 @@
&.right-sidebar { &.right-sidebar {
top: 0; top: 0;
bottom: 0; bottom: 0;
height: 100%;
} }
.issuable-sidebar-header { .issuable-sidebar-header {
......
...@@ -141,17 +141,17 @@ ...@@ -141,17 +141,17 @@
display: inline-block; display: inline-block;
background: $white-light; background: $white-light;
color: $gl-text-color-secondary; color: $gl-text-color-secondary;
padding: 0 5px; padding: 0 4px;
cursor: pointer; cursor: pointer;
border: 1px solid $border-gray-dark; border: 1px solid $border-gray-dark;
border-radius: $border-radius-default; border-radius: $border-radius-default;
margin-left: 5px; margin-left: 5px;
font-size: $gl-font-size; font-size: 12px;
line-height: $gl-font-size; line-height: $gl-font-size;
outline: none; outline: none;
&.open { &.open {
background: $gray-light; background-color: darken($gray-light, 10%);
box-shadow: inset 0 0 2px rgba($black, 0.2); box-shadow: inset 0 0 2px rgba($black, 0.2);
} }
......
...@@ -516,7 +516,7 @@ ul.notes { ...@@ -516,7 +516,7 @@ ul.notes {
} }
.note-actions-item { .note-actions-item {
margin-left: 15px; margin-left: 12px;
display: flex; display: flex;
align-items: center; align-items: center;
...@@ -620,15 +620,25 @@ ul.notes { ...@@ -620,15 +620,25 @@ ul.notes {
.note-role { .note-role {
position: relative; position: relative;
padding: 0 7px; display: inline-block;
color: $notes-role-color; color: $notes-role-color;
font-size: 12px; font-size: 12px;
line-height: 20px; line-height: 20px;
border: 1px solid $border-color; margin: 0 3px;
border-radius: $label-border-radius;
&.note-role-access {
padding: 0 7px;
border: 1px solid $border-color;
border-radius: $label-border-radius;
}
&.note-role-special {
text-shadow: 0 0 15px $gl-text-color-inverted;
}
} }
/** /**
* Line note button on the side of diffs * Line note button on the side of diffs
*/ */
......
class Admin::LogsController < Admin::ApplicationController class Admin::LogsController < Admin::ApplicationController
before_action :loggers
def show def show
@loggers = [ end
private
def loggers
@loggers ||= [
Gitlab::AppLogger, Gitlab::AppLogger,
Gitlab::GitLogger, Gitlab::GitLogger,
Gitlab::EnvironmentLogger, Gitlab::EnvironmentLogger,
......
module RendersCommits
def prepare_commits_for_rendering(commits)
Banzai::CommitRenderer.render(commits, @project, current_user)
commits
end
end
module RendersNotes module RendersNotes
def prepare_notes_for_rendering(notes) def prepare_notes_for_rendering(notes, noteable = nil)
preload_noteable_for_regular_notes(notes) preload_noteable_for_regular_notes(notes)
preload_max_access_for_authors(notes, @project) preload_max_access_for_authors(notes, @project)
preload_first_time_contribution_for_authors(noteable, notes)
Banzai::NoteRenderer.render(notes, @project, current_user) Banzai::NoteRenderer.render(notes, @project, current_user)
notes notes
...@@ -19,4 +20,10 @@ module RendersNotes ...@@ -19,4 +20,10 @@ module RendersNotes
def preload_noteable_for_regular_notes(notes) def preload_noteable_for_regular_notes(notes)
ActiveRecord::Associations::Preloader.new.preload(notes.reject(&:for_commit?), :noteable) ActiveRecord::Associations::Preloader.new.preload(notes.reject(&:for_commit?), :noteable)
end end
def preload_first_time_contribution_for_authors(noteable, notes)
return unless noteable.is_a?(Issuable) && noteable.first_contribution?
notes.each {|n| n.specialize_for_first_contribution!(noteable)}
end
end end
...@@ -9,8 +9,6 @@ class ProfilesController < Profiles::ApplicationController ...@@ -9,8 +9,6 @@ class ProfilesController < Profiles::ApplicationController
end end
def update def update
user_params.except!(:email) if @user.external_email?
respond_to do |format| respond_to do |format|
result = Users::UpdateService.new(@user, user_params).execute result = Users::UpdateService.new(@user, user_params).execute
......
...@@ -7,7 +7,7 @@ class Projects::ArtifactsController < Projects::ApplicationController ...@@ -7,7 +7,7 @@ class Projects::ArtifactsController < Projects::ApplicationController
before_action :authorize_update_build!, only: [:keep] before_action :authorize_update_build!, only: [:keep]
before_action :extract_ref_name_and_path before_action :extract_ref_name_and_path
before_action :validate_artifacts! before_action :validate_artifacts!
before_action :set_path_and_entry, only: [:file, :raw] before_action :entry, only: [:file]
def download def download
if artifacts_file.file_storage? if artifacts_file.file_storage?
...@@ -41,7 +41,10 @@ class Projects::ArtifactsController < Projects::ApplicationController ...@@ -41,7 +41,10 @@ class Projects::ArtifactsController < Projects::ApplicationController
end end
def raw def raw
send_artifacts_entry(build, @entry) path = Gitlab::Ci::Build::Artifacts::Path
.new(params[:path])
send_artifacts_entry(build, path)
end end
def keep def keep
...@@ -93,9 +96,8 @@ class Projects::ArtifactsController < Projects::ApplicationController ...@@ -93,9 +96,8 @@ class Projects::ArtifactsController < Projects::ApplicationController
@artifacts_file ||= build.artifacts_file @artifacts_file ||= build.artifacts_file
end end
def set_path_and_entry def entry
@path = params[:path] @entry = build.artifacts_metadata_entry(params[:path])
@entry = build.artifacts_metadata_entry(@path)
render_404 unless @entry.exists? render_404 unless @entry.exists?
end end
......
...@@ -127,7 +127,7 @@ class Projects::CommitController < Projects::ApplicationController ...@@ -127,7 +127,7 @@ class Projects::CommitController < Projects::ApplicationController
@discussions = commit.discussions @discussions = commit.discussions
@notes = (@grouped_diff_discussions.values.flatten + @discussions).flat_map(&:notes) @notes = (@grouped_diff_discussions.values.flatten + @discussions).flat_map(&:notes)
@notes = prepare_notes_for_rendering(@notes) @notes = prepare_notes_for_rendering(@notes, @commit)
end end
def assign_change_commit_vars def assign_change_commit_vars
......
...@@ -2,6 +2,7 @@ require "base64" ...@@ -2,6 +2,7 @@ require "base64"
class Projects::CommitsController < Projects::ApplicationController class Projects::CommitsController < Projects::ApplicationController
include ExtractsPath include ExtractsPath
include RendersCommits
before_action :require_non_empty_project before_action :require_non_empty_project
before_action :assign_ref_vars before_action :assign_ref_vars
...@@ -56,5 +57,7 @@ class Projects::CommitsController < Projects::ApplicationController ...@@ -56,5 +57,7 @@ class Projects::CommitsController < Projects::ApplicationController
else else
@repository.commits(@ref, path: @path, limit: @limit, offset: @offset) @repository.commits(@ref, path: @path, limit: @limit, offset: @offset)
end end
@commits = prepare_commits_for_rendering(@commits)
end end
end end
...@@ -3,6 +3,7 @@ require 'addressable/uri' ...@@ -3,6 +3,7 @@ require 'addressable/uri'
class Projects::CompareController < Projects::ApplicationController class Projects::CompareController < Projects::ApplicationController
include DiffForPath include DiffForPath
include DiffHelper include DiffHelper
include RendersCommits
# Authorize # Authorize
before_action :require_non_empty_project before_action :require_non_empty_project
...@@ -50,7 +51,7 @@ class Projects::CompareController < Projects::ApplicationController ...@@ -50,7 +51,7 @@ class Projects::CompareController < Projects::ApplicationController
.execute(@project, @start_ref) .execute(@project, @start_ref)
if @compare if @compare
@commits = @compare.commits @commits = prepare_commits_for_rendering(@compare.commits)
@diffs = @compare.diffs(diff_options) @diffs = @compare.diffs(diff_options)
environment_params = @repository.branch_exists?(@head_ref) ? { ref: @head_ref } : { commit: @compare.commit } environment_params = @repository.branch_exists?(@head_ref) ? { ref: @head_ref } : { commit: @compare.commit }
......
...@@ -85,7 +85,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -85,7 +85,7 @@ class Projects::IssuesController < Projects::ApplicationController
@note = @project.notes.new(noteable: @issue) @note = @project.notes.new(noteable: @issue)
@discussions = @issue.discussions @discussions = @issue.discussions
@notes = prepare_notes_for_rendering(@discussions.flat_map(&:notes)) @notes = prepare_notes_for_rendering(@discussions.flat_map(&:notes), @noteable)
respond_to do |format| respond_to do |format|
format.html format.html
......
class Projects::MergeRequests::CreationsController < Projects::MergeRequests::ApplicationController class Projects::MergeRequests::CreationsController < Projects::MergeRequests::ApplicationController
include DiffForPath include DiffForPath
include DiffHelper include DiffHelper
include RendersCommits
skip_before_action :merge_request skip_before_action :merge_request
skip_before_action :ensure_ref_fetched skip_before_action :ensure_ref_fetched
...@@ -107,7 +108,7 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap ...@@ -107,7 +108,7 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
@target_project = @merge_request.target_project @target_project = @merge_request.target_project
@source_project = @merge_request.source_project @source_project = @merge_request.source_project
@commits = @merge_request.commits @commits = prepare_commits_for_rendering(@merge_request.commits)
@commit = @merge_request.diff_head_commit @commit = @merge_request.diff_head_commit
@note_counts = Note.where(commit_id: @commits.map(&:id)) @note_counts = Note.where(commit_id: @commits.map(&:id))
......
...@@ -61,6 +61,6 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic ...@@ -61,6 +61,6 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic
@use_legacy_diff_notes = !@merge_request.has_complete_diff_refs? @use_legacy_diff_notes = !@merge_request.has_complete_diff_refs?
@grouped_diff_discussions = @merge_request.grouped_diff_discussions(@compare.diff_refs) @grouped_diff_discussions = @merge_request.grouped_diff_discussions(@compare.diff_refs)
@notes = prepare_notes_for_rendering(@grouped_diff_discussions.values.flatten.flat_map(&:notes)) @notes = prepare_notes_for_rendering(@grouped_diff_discussions.values.flatten.flat_map(&:notes), @merge_request)
end end
end end
...@@ -2,6 +2,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo ...@@ -2,6 +2,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
include ToggleSubscriptionAction include ToggleSubscriptionAction
include IssuableActions include IssuableActions
include RendersNotes include RendersNotes
include RendersCommits
include ToggleAwardEmoji include ToggleAwardEmoji
include IssuableCollections include IssuableCollections
...@@ -60,12 +61,12 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo ...@@ -60,12 +61,12 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
# Build a note object for comment form # Build a note object for comment form
@note = @project.notes.new(noteable: @merge_request) @note = @project.notes.new(noteable: @merge_request)
@discussions = @merge_request.discussions
@notes = prepare_notes_for_rendering(@discussions.flat_map(&:notes))
@noteable = @merge_request @noteable = @merge_request
@commits_count = @merge_request.commits_count @commits_count = @merge_request.commits_count
@discussions = @merge_request.discussions
@notes = prepare_notes_for_rendering(@discussions.flat_map(&:notes), @noteable)
labels labels
set_pipeline_variables set_pipeline_variables
...@@ -94,7 +95,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo ...@@ -94,7 +95,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
def commits def commits
# Get commits from repository # Get commits from repository
# or from cache if already merged # or from cache if already merged
@commits = @merge_request.commits @commits = prepare_commits_for_rendering(@merge_request.commits)
@note_counts = Note.where(commit_id: @commits.map(&:id)) @note_counts = Note.where(commit_id: @commits.map(&:id))
.group(:commit_id).count .group(:commit_id).count
......
...@@ -64,7 +64,7 @@ class Projects::SnippetsController < Projects::ApplicationController ...@@ -64,7 +64,7 @@ class Projects::SnippetsController < Projects::ApplicationController
@noteable = @snippet @noteable = @snippet
@discussions = @snippet.discussions @discussions = @snippet.discussions
@notes = prepare_notes_for_rendering(@discussions.flat_map(&:notes)) @notes = prepare_notes_for_rendering(@discussions.flat_map(&:notes), @noteable)
render 'show' render 'show'
end end
......
...@@ -323,6 +323,7 @@ class ProjectsController < Projects::ApplicationController ...@@ -323,6 +323,7 @@ class ProjectsController < Projects::ApplicationController
:build_allow_git_fetch, :build_allow_git_fetch,
:build_coverage_regex, :build_coverage_regex,
:build_timeout_in_minutes, :build_timeout_in_minutes,
:resolve_outdated_diff_discussions,
:container_registry_enabled, :container_registry_enabled,
:default_branch, :default_branch,
:description, :description,
......
...@@ -2,6 +2,7 @@ class SearchController < ApplicationController ...@@ -2,6 +2,7 @@ class SearchController < ApplicationController
skip_before_action :authenticate_user! skip_before_action :authenticate_user!
include SearchHelper include SearchHelper
include RendersCommits
layout 'search' layout 'search'
...@@ -20,6 +21,8 @@ class SearchController < ApplicationController ...@@ -20,6 +21,8 @@ class SearchController < ApplicationController
@search_results = search_service.search_results @search_results = search_service.search_results
@search_objects = search_service.search_objects @search_objects = search_service.search_objects
render_commits if @scope == 'commits'
check_single_commit_result check_single_commit_result
end end
...@@ -38,6 +41,10 @@ class SearchController < ApplicationController ...@@ -38,6 +41,10 @@ class SearchController < ApplicationController
private private
def render_commits
@search_objects = prepare_commits_for_rendering(@search_objects)
end
def check_single_commit_result def check_single_commit_result
if @search_results.single_commit_result? if @search_results.single_commit_result?
only_commit = @search_results.objects('commits').first only_commit = @search_results.objects('commits').first
......
...@@ -66,7 +66,7 @@ class SnippetsController < ApplicationController ...@@ -66,7 +66,7 @@ class SnippetsController < ApplicationController
@noteable = @snippet @noteable = @snippet
@discussions = @snippet.discussions @discussions = @snippet.discussions
@notes = prepare_notes_for_rendering(@discussions.flat_map(&:notes)) @notes = prepare_notes_for_rendering(@discussions.flat_map(&:notes), @noteable)
respond_to do |format| respond_to do |format|
format.html do format.html do
......
...@@ -302,10 +302,6 @@ module ApplicationHelper ...@@ -302,10 +302,6 @@ module ApplicationHelper
end end
end end
def show_new_nav?
true
end
def collapsed_sidebar? def collapsed_sidebar?
cookies["sidebar_collapsed"] == "true" cookies["sidebar_collapsed"] == "true"
end end
......
...@@ -11,11 +11,15 @@ module BlameHelper ...@@ -11,11 +11,15 @@ module BlameHelper
end end
def age_map_class(commit_date, duration) def age_map_class(commit_date, duration)
commit_date_days_ago = (duration[:now] - commit_date).to_i / 1.day if duration[:started_days_ago] == 0
# Numbers 0 to 10 come from this calculation, but only commits on the oldest "blame-commit-age-0"
# day get number 10 (all other numbers can be multiple days), so the range else
# is normalized to 0-9 commit_date_days_ago = (duration[:now] - commit_date).to_i / 1.day
age_group = [(10 * commit_date_days_ago) / duration[:started_days_ago], 9].min # Numbers 0 to 10 come from this calculation, but only commits on the oldest
"blame-commit-age-#{age_group}" # day get number 10 (all other numbers can be multiple days), so the range
# is normalized to 0-9
age_group = [(10 * commit_date_days_ago) / duration[:started_days_ago], 9].min
"blame-commit-age-#{age_group}"
end
end end
end end
...@@ -22,4 +22,16 @@ module BreadcrumbsHelper ...@@ -22,4 +22,16 @@ module BreadcrumbsHelper
@breadcrumb_title = title @breadcrumb_title = title
end end
def breadcrumb_list_item(link)
content_tag "li" do
link + icon("angle-right", class: "breadcrumbs-list-angle")
end
end
def add_to_breadcrumb_dropdown(link, location: :before)
@breadcrumb_dropdown_links ||= {}
@breadcrumb_dropdown_links[location] ||= []
@breadcrumb_dropdown_links[location] << link
end
end end
...@@ -15,18 +15,20 @@ module GroupsHelper ...@@ -15,18 +15,20 @@ module GroupsHelper
@has_group_title = true @has_group_title = true
full_title = '' full_title = ''
group.ancestors.reverse.each do |parent| group.ancestors.reverse.each_with_index do |parent, index|
full_title += group_title_link(parent, hidable: true) if index > 0
add_to_breadcrumb_dropdown(group_title_link(parent, hidable: false, show_avatar: true), location: :before)
full_title += '<span class="hidable"> / </span>'.html_safe else
full_title += breadcrumb_list_item group_title_link(parent, hidable: false)
end
end end
full_title += group_title_link(group) full_title += render "layouts/nav/breadcrumbs/collapsed_dropdown", location: :before, title: _("Show parent subgroups")
full_title += ' &middot; '.html_safe + link_to(simple_sanitize(name), url, class: 'group-path') if name
content_tag :span, class: 'group-title' do full_title += breadcrumb_list_item group_title_link(group)
full_title.html_safe full_title += ' &middot; '.html_safe + link_to(simple_sanitize(name), url, class: 'group-path breadcrumb-item-text js-breadcrumb-item-text') if name
end
full_title.html_safe
end end
def projects_lfs_status(group) def projects_lfs_status(group)
...@@ -65,11 +67,11 @@ module GroupsHelper ...@@ -65,11 +67,11 @@ module GroupsHelper
private private
def group_title_link(group, hidable: false) def group_title_link(group, hidable: false, show_avatar: false)
link_to(group_path(group), class: "group-path #{'hidable' if hidable}") do link_to(group_path(group), class: "group-path breadcrumb-item-text js-breadcrumb-item-text #{'hidable' if hidable}") do
output = output =
if show_new_nav? && !Rails.env.test? if (group.try(:avatar_url) || show_avatar) && !Rails.env.test?
image_tag(group_icon(group), class: "avatar-tile", width: 16, height: 16) image_tag(group_icon(group), class: "avatar-tile", width: 15, height: 15)
else else
"" ""
end end
......
...@@ -126,22 +126,20 @@ module IssuablesHelper ...@@ -126,22 +126,20 @@ module IssuablesHelper
end end
def issuable_meta(issuable, project, text) def issuable_meta(issuable, project, text)
output = content_tag(:strong, class: "identifier") do output = ""
concat("#{text} ") output << "Opened #{time_ago_with_tooltip(issuable.created_at)} by ".html_safe
concat(to_url_reference(issuable))
end
output << " opened #{time_ago_with_tooltip(issuable.created_at)} by ".html_safe
output << content_tag(:strong) do output << content_tag(:strong) do
author_output = link_to_member(project, issuable.author, size: 24, mobile_classes: "hidden-xs", tooltip: true) author_output = link_to_member(project, issuable.author, size: 24, mobile_classes: "hidden-xs", tooltip: true)
author_output << link_to_member(project, issuable.author, size: 24, by_username: true, avatar: false, mobile_classes: "hidden-sm hidden-md hidden-lg") author_output << link_to_member(project, issuable.author, size: 24, by_username: true, avatar: false, mobile_classes: "hidden-sm hidden-md hidden-lg")
end end
output << "&ensp;".html_safe output << "&ensp;".html_safe
output << content_tag(:span, (issuable_first_contribution_icon if issuable.first_contribution?), class: 'has-tooltip', title: _('1st contribution!'))
output << content_tag(:span, (issuable.task_status if issuable.tasks?), id: "task_status", class: "hidden-xs hidden-sm") output << content_tag(:span, (issuable.task_status if issuable.tasks?), id: "task_status", class: "hidden-xs hidden-sm")
output << content_tag(:span, (issuable.task_status_short if issuable.tasks?), id: "task_status_short", class: "hidden-md hidden-lg") output << content_tag(:span, (issuable.task_status_short if issuable.tasks?), id: "task_status_short", class: "hidden-md hidden-lg")
output output.html_safe
end end
def issuable_todo(issuable) def issuable_todo(issuable)
...@@ -173,6 +171,13 @@ module IssuablesHelper ...@@ -173,6 +171,13 @@ module IssuablesHelper
html.html_safe html.html_safe
end end
def issuable_first_contribution_icon
content_tag(:span, class: 'fa-stack') do
concat(icon('certificate', class: "fa-stack-2x"))
concat(content_tag(:strong, '1', class: 'fa-inverse fa-stack-1x'))
end
end
def assigned_issuables_count(issuable_type) def assigned_issuables_count(issuable_type)
case issuable_type case issuable_type
when :issues when :issues
......
...@@ -21,25 +21,28 @@ module MarkupHelper ...@@ -21,25 +21,28 @@ module MarkupHelper
end end
# Use this in places where you would normally use link_to(gfm(...), ...). # Use this in places where you would normally use link_to(gfm(...), ...).
# def link_to_markdown(body, url, html_options = {})
return '' if body.blank?
link_to_html(markdown(body, pipeline: :single_line), url, html_options)
end
def link_to_markdown_field(object, field, url, html_options = {})
rendered_field = markdown_field(object, field)
link_to_html(rendered_field, url, html_options)
end
# It solves a problem occurring with nested links (i.e. # It solves a problem occurring with nested links (i.e.
# "<a>outer text <a>gfm ref</a> more outer text</a>"). This will not be # "<a>outer text <a>gfm ref</a> more outer text</a>"). This will not be
# interpreted as intended. Browsers will parse something like # interpreted as intended. Browsers will parse something like
# "<a>outer text </a><a>gfm ref</a> more outer text" (notice the last part is # "<a>outer text </a><a>gfm ref</a> more outer text" (notice the last part is
# not linked any more). link_to_gfm corrects that. It wraps all parts to # not linked any more). link_to_html corrects that. It wraps all parts to
# explicitly produce the correct linking behavior (i.e. # explicitly produce the correct linking behavior (i.e.
# "<a>outer text </a><a>gfm ref</a><a> more outer text</a>"). # "<a>outer text </a><a>gfm ref</a><a> more outer text</a>").
def link_to_gfm(body, url, html_options = {}) def link_to_html(redacted, url, html_options = {})
return '' if body.blank? fragment = Nokogiri::HTML::DocumentFragment.parse(redacted)
context = {
project: @project,
current_user: (current_user if defined?(current_user)),
pipeline: :single_line
}
gfm_body = Banzai.render(body, context)
fragment = Nokogiri::HTML::DocumentFragment.parse(gfm_body)
if fragment.children.size == 1 && fragment.children[0].name == 'a' if fragment.children.size == 1 && fragment.children[0].name == 'a'
# Fragment has only one node, and it's a link generated by `gfm`. # Fragment has only one node, and it's a link generated by `gfm`.
# Replace it with our requested link. # Replace it with our requested link.
...@@ -82,7 +85,10 @@ module MarkupHelper ...@@ -82,7 +85,10 @@ module MarkupHelper
def markdown_field(object, field) def markdown_field(object, field)
object = object.for_display if object.respond_to?(:for_display) object = object.for_display if object.respond_to?(:for_display)
redacted_field_html = object.try(:"redacted_#{field}_html")
return '' unless object.present? return '' unless object.present?
return redacted_field_html if redacted_field_html
html = Banzai.render_field(object, field) html = Banzai.render_field(object, field)
prepare_for_rendering(html, object.banzai_render_context(field)) prepare_for_rendering(html, object.banzai_render_context(field))
......
module NavHelper module NavHelper
def page_with_sidebar_class def page_with_sidebar_class
class_name = page_gutter_class class_name = page_gutter_class
class_name << 'page-with-new-sidebar' if defined?(@new_sidebar) && @new_sidebar class_name << 'page-with-new-sidebar' if defined?(@left_sidebar) && @left_sidebar
class_name << 'page-with-icon-sidebar' if collapsed_sidebar? && @new_sidebar class_name << 'page-with-icon-sidebar' if collapsed_sidebar? && @left_sidebar
class_name class_name
end end
...@@ -30,23 +30,6 @@ module NavHelper ...@@ -30,23 +30,6 @@ module NavHelper
end end
end end
def nav_header_class
class_names = []
class_names << 'with-horizontal-nav' if defined?(nav) && nav
class_names
end
def layout_nav_class
return 'page-with-new-nav' if show_new_nav?
class_names = []
class_names << 'page-with-layout-nav' if defined?(nav) && nav
class_names << 'page-with-sub-nav' if content_for?(:sub_nav)
class_names
end
def nav_control_class def nav_control_class
"nav-control" if current_user "nav-control" if current_user
end end
......
...@@ -73,7 +73,7 @@ module NotesHelper ...@@ -73,7 +73,7 @@ module NotesHelper
end end
def note_max_access_for_user(note) def note_max_access_for_user(note)
note.project.team.human_max_access(note.author_id) note.project.team.max_member_access(note.author_id)
end end
def discussion_path(discussion) def discussion_path(discussion)
...@@ -146,4 +146,8 @@ module NotesHelper ...@@ -146,4 +146,8 @@ module NotesHelper
autocomplete: autocomplete autocomplete: autocomplete
} }
end end
def discussion_resolved_intro(discussion)
discussion.resolved_by_push? ? 'Automatically resolved' : 'Resolved'
end
end end
...@@ -4,7 +4,7 @@ module PageLayoutHelper ...@@ -4,7 +4,7 @@ module PageLayoutHelper
@page_title.push(*titles.compact) if titles.any? @page_title.push(*titles.compact) if titles.any?
if show_new_nav? && titles.any? && !defined?(@breadcrumb_title) if titles.any? && !defined?(@breadcrumb_title)
@breadcrumb_title = @page_title.last @breadcrumb_title = @page_title.last
end end
...@@ -80,7 +80,9 @@ module PageLayoutHelper ...@@ -80,7 +80,9 @@ module PageLayoutHelper
@header_title = title @header_title = title
@header_title_url = title_url @header_title_url = title_url
else else
@header_title_url ? link_to(@header_title, @header_title_url) : @header_title return @header_title unless @header_title_url
breadcrumb_list_item(link_to(@header_title, @header_title_url))
end end
end end
......
module ProfilesHelper module ProfilesHelper
def email_provider_label def attribute_provider_label(attribute)
return unless current_user.external_email? user_synced_attributes_metadata = current_user.user_synced_attributes_metadata
if user_synced_attributes_metadata&.synced?(attribute)
current_user.email_provider.present? ? Gitlab::OAuth::Provider.label_for(current_user.email_provider) : "LDAP" if user_synced_attributes_metadata.provider
Gitlab::OAuth::Provider.label_for(user_synced_attributes_metadata.provider)
else
'LDAP'
end
end
end end
end end
...@@ -54,25 +54,28 @@ module ProjectsHelper ...@@ -54,25 +54,28 @@ module ProjectsHelper
def project_title(project) def project_title(project)
namespace_link = namespace_link =
if project.group if project.group
group_title(project.group) group_title(project.group, nil, nil)
else else
owner = project.namespace.owner owner = project.namespace.owner
link_to(simple_sanitize(owner.name), user_path(owner)) link_to(simple_sanitize(owner.name), user_path(owner))
end end
project_link = link_to project_path(project), { class: "project-item-select-holder" } do project_link = link_to project_path(project) do
output = output =
if show_new_nav? && !Rails.env.test? if project.avatar_url && !Rails.env.test?
project_icon(project, alt: project.name, class: 'avatar-tile', width: 16, height: 16) project_icon(project, alt: project.name, class: 'avatar-tile', width: 15, height: 15)
else else
"" ""
end end
output << simple_sanitize(project.name) output << content_tag("span", simple_sanitize(project.name), class: "breadcrumb-item-text js-breadcrumb-item-text")
output.html_safe output.html_safe
end end
"#{namespace_link} / #{project_link}".html_safe namespace_link = breadcrumb_list_item(namespace_link) unless project.group
project_link = breadcrumb_list_item project_link
"#{namespace_link} #{project_link}".html_safe
end end
def remove_project_message(project) def remove_project_message(project)
......
...@@ -10,6 +10,7 @@ module SearchHelper ...@@ -10,6 +10,7 @@ module SearchHelper
search_pattern = Regexp.new(Regexp.escape(term), "i") search_pattern = Regexp.new(Regexp.escape(term), "i")
generic_results = project_autocomplete + default_autocomplete + help_autocomplete generic_results = project_autocomplete + default_autocomplete + help_autocomplete
generic_results.concat(default_autocomplete_admin) if current_user.admin?
generic_results.select! { |result| result[:label] =~ search_pattern } generic_results.select! { |result| result[:label] =~ search_pattern }
[ [
...@@ -41,8 +42,14 @@ module SearchHelper ...@@ -41,8 +42,14 @@ module SearchHelper
[ [
{ category: "Settings", label: "User settings", url: profile_path }, { category: "Settings", label: "User settings", url: profile_path },
{ category: "Settings", label: "SSH Keys", url: profile_keys_path }, { category: "Settings", label: "SSH Keys", url: profile_keys_path },
{ category: "Settings", label: "Dashboard", url: root_path }, { category: "Settings", label: "Dashboard", url: root_path }
{ category: "Settings", label: "Admin Section", url: admin_root_path } ]
end
# Autocomplete results for settings pages, for admins
def default_autocomplete_admin
[
{ category: "Settings", label: "Admin Section", url: admin_root_path }
] ]
end end
......
...@@ -10,4 +10,15 @@ module WikiHelper ...@@ -10,4 +10,15 @@ module WikiHelper
.map { |dir_or_page| WikiPage.unhyphenize(dir_or_page).capitalize } .map { |dir_or_page| WikiPage.unhyphenize(dir_or_page).capitalize }
.join(' / ') .join(' / ')
end end
def wiki_breadcrumb_dropdown_links(page_slug)
page_slug_split = page_slug.split('/')
page_slug_split.pop(1)
current_slug = ""
page_slug_split
.map do |dir_or_page|
current_slug = "#{current_slug}#{dir_or_page}/"
add_to_breadcrumb_dropdown link_to(WikiPage.unhyphenize(dir_or_page).capitalize, project_wiki_path(@project, current_slug)), location: :after
end
end
end end
...@@ -27,7 +27,6 @@ module Ci ...@@ -27,7 +27,6 @@ module Ci
validates :coverage, numericality: true, allow_blank: true validates :coverage, numericality: true, allow_blank: true
validates :ref, presence: true validates :ref, presence: true
validates :protected, inclusion: { in: [true, false], unless: :importing? }, on: :create
scope :unstarted, ->() { where(runner_id: nil) } scope :unstarted, ->() { where(runner_id: nil) }
scope :ignore_failures, ->() { where(allow_failure: false) } scope :ignore_failures, ->() { where(allow_failure: false) }
...@@ -451,6 +450,10 @@ module Ci ...@@ -451,6 +450,10 @@ module Ci
trace trace
end end
def serializable_hash(options = {})
super(options).merge(when: read_attribute(:when))
end
private private
def update_artifacts_size def update_artifacts_size
......
...@@ -36,7 +36,6 @@ module Ci ...@@ -36,7 +36,6 @@ module Ci
validates :sha, presence: { unless: :importing? } validates :sha, presence: { unless: :importing? }
validates :ref, presence: { unless: :importing? } validates :ref, presence: { unless: :importing? }
validates :status, presence: { unless: :importing? } validates :status, presence: { unless: :importing? }
validates :protected, inclusion: { in: [true, false], unless: :importing? }, on: :create
validate :valid_commit_sha, unless: :importing? validate :valid_commit_sha, unless: :importing?
after_create :keep_around_commits, unless: :importing? after_create :keep_around_commits, unless: :importing?
......
...@@ -16,6 +16,8 @@ class Commit ...@@ -16,6 +16,8 @@ class Commit
participant :notes_with_associations participant :notes_with_associations
attr_accessor :project, :author attr_accessor :project, :author
attr_accessor :redacted_description_html
attr_accessor :redacted_title_html
DIFF_SAFE_LINES = Gitlab::Git::DiffCollection::DEFAULT_LIMITS[:max_lines] DIFF_SAFE_LINES = Gitlab::Git::DiffCollection::DEFAULT_LIMITS[:max_lines]
...@@ -26,6 +28,13 @@ class Commit ...@@ -26,6 +28,13 @@ class Commit
# The SHA can be between 7 and 40 hex characters. # The SHA can be between 7 and 40 hex characters.
COMMIT_SHA_PATTERN = '\h{7,40}'.freeze COMMIT_SHA_PATTERN = '\h{7,40}'.freeze
def banzai_render_context(field)
context = { pipeline: :single_line, project: self.project }
context[:author] = self.author if self.author
context
end
class << self class << self
def decorate(commits, project) def decorate(commits, project)
commits.map do |commit| commits.map do |commit|
......
...@@ -334,4 +334,11 @@ module Issuable ...@@ -334,4 +334,11 @@ module Issuable
metrics = self.metrics || create_metrics metrics = self.metrics || create_metrics
metrics.record! metrics.record!
end end
##
# Override in issuable specialization
#
def first_contribution?
false
end
end end
...@@ -24,6 +24,7 @@ module ResolvableDiscussion ...@@ -24,6 +24,7 @@ module ResolvableDiscussion
delegate :resolved_at, delegate :resolved_at,
:resolved_by, :resolved_by,
:resolved_by_push?,
to: :last_resolved_note, to: :last_resolved_note,
allow_nil: true allow_nil: true
......
...@@ -51,22 +51,34 @@ module ResolvableNote ...@@ -51,22 +51,34 @@ module ResolvableNote
end end
# If you update this method remember to also update `.resolve!` # If you update this method remember to also update `.resolve!`
def resolve!(current_user) def resolve_without_save(current_user, resolved_by_push: false)
return unless resolvable? return false unless resolvable?
return if resolved? return false if resolved?
self.resolved_at = Time.now self.resolved_at = Time.now
self.resolved_by = current_user self.resolved_by = current_user
save! self.resolved_by_push = resolved_by_push
true
end end
# If you update this method remember to also update `.unresolve!` # If you update this method remember to also update `.unresolve!`
def unresolve! def unresolve_without_save
return unless resolvable? return false unless resolvable?
return unless resolved? return false unless resolved?
self.resolved_at = nil self.resolved_at = nil
self.resolved_by = nil self.resolved_by = nil
save!
true
end
def resolve!(current_user, resolved_by_push: false)
resolve_without_save(current_user, resolved_by_push: resolved_by_push) &&
save!
end
def unresolve!
unresolve_without_save && save!
end end
end end
...@@ -918,6 +918,12 @@ class MergeRequest < ActiveRecord::Base ...@@ -918,6 +918,12 @@ class MergeRequest < ActiveRecord::Base
active_diff_discussions.each do |discussion| active_diff_discussions.each do |discussion|
service.execute(discussion) service.execute(discussion)
end end
if project.resolve_outdated_diff_discussions?
MergeRequests::ResolvedDiscussionNotificationService
.new(project, current_user)
.execute(self)
end
end end
def keep_around_commit def keep_around_commit
...@@ -954,6 +960,12 @@ class MergeRequest < ActiveRecord::Base ...@@ -954,6 +960,12 @@ class MergeRequest < ActiveRecord::Base
Projects::OpenMergeRequestsCountService.new(target_project).refresh_cache Projects::OpenMergeRequestsCountService.new(target_project).refresh_cache
end end
def first_contribution?
return false if project.team.max_member_access(author_id) > Gitlab::Access::GUEST
project.merge_requests.merged.where(author_id: author_id).empty?
end
private private
def write_ref def write_ref
......
...@@ -15,6 +15,16 @@ class Note < ActiveRecord::Base ...@@ -15,6 +15,16 @@ class Note < ActiveRecord::Base
include IgnorableColumn include IgnorableColumn
include Editable include Editable
module SpecialRole
FIRST_TIME_CONTRIBUTOR = :first_time_contributor
class << self
def values
constants.map {|const| self.const_get(const)}
end
end
end
ignore_column :original_discussion_id ignore_column :original_discussion_id
cache_markdown_field :note, pipeline: :note, issuable_state_filter_enabled: true cache_markdown_field :note, pipeline: :note, issuable_state_filter_enabled: true
...@@ -32,9 +42,12 @@ class Note < ActiveRecord::Base ...@@ -32,9 +42,12 @@ class Note < ActiveRecord::Base
# Banzai::ObjectRenderer # Banzai::ObjectRenderer
attr_accessor :user_visible_reference_count attr_accessor :user_visible_reference_count
# Attribute used to store the attributes that have ben changed by quick actions. # Attribute used to store the attributes that have been changed by quick actions.
attr_accessor :commands_changes attr_accessor :commands_changes
# A special role that may be displayed on issuable's discussions
attr_accessor :special_role
default_value_for :system, false default_value_for :system, false
attr_mentionable :note, pipeline: :note attr_mentionable :note, pipeline: :note
...@@ -141,6 +154,10 @@ class Note < ActiveRecord::Base ...@@ -141,6 +154,10 @@ class Note < ActiveRecord::Base
.group(:noteable_id) .group(:noteable_id)
.where(noteable_type: type, noteable_id: ids) .where(noteable_type: type, noteable_id: ids)
end end
def has_special_role?(role, note)
note.special_role == role
end
end end
def cross_reference? def cross_reference?
...@@ -206,6 +223,22 @@ class Note < ActiveRecord::Base ...@@ -206,6 +223,22 @@ class Note < ActiveRecord::Base
super(noteable_type.to_s.classify.constantize.base_class.to_s) super(noteable_type.to_s.classify.constantize.base_class.to_s)
end end
def special_role=(role)
raise "Role is undefined, #{role} not found in #{SpecialRole.values}" unless SpecialRole.values.include?(role)
@special_role = role
end
def has_special_role?(role)
self.class.has_special_role?(role, self)
end
def specialize_for_first_contribution!(noteable)
return unless noteable.author_id == self.author_id
self.special_role = Note::SpecialRole::FIRST_TIME_CONTRIBUTOR
end
def editable? def editable?
!system? !system?
end end
......
...@@ -37,6 +37,7 @@ class Project < ActiveRecord::Base ...@@ -37,6 +37,7 @@ class Project < ActiveRecord::Base
default_value_for :archived, false default_value_for :archived, false
default_value_for :visibility_level, gitlab_config_features.visibility_level default_value_for :visibility_level, gitlab_config_features.visibility_level
default_value_for :resolve_outdated_diff_discussions, false
default_value_for :container_registry_enabled, gitlab_config_features.container_registry default_value_for :container_registry_enabled, gitlab_config_features.container_registry
default_value_for(:repository_storage) { current_application_settings.pick_repository_storage } default_value_for(:repository_storage) { current_application_settings.pick_repository_storage }
default_value_for(:shared_runners_enabled) { current_application_settings.shared_runners_enabled } default_value_for(:shared_runners_enabled) { current_application_settings.shared_runners_enabled }
......
...@@ -150,7 +150,7 @@ class ProjectTeam ...@@ -150,7 +150,7 @@ class ProjectTeam
end end
def human_max_access(user_id) def human_max_access(user_id)
Gitlab::Access.options_with_owner.key(max_member_access(user_id)) Gitlab::Access.human_access(max_member_access(user_id))
end end
# Determine the maximum access level for a group of users in bulk. # Determine the maximum access level for a group of users in bulk.
......
...@@ -15,10 +15,12 @@ class User < ActiveRecord::Base ...@@ -15,10 +15,12 @@ class User < ActiveRecord::Base
include IgnorableColumn include IgnorableColumn
include FeatureGate include FeatureGate
include CreatedAtFilterable include CreatedAtFilterable
include IgnorableColumn
DEFAULT_NOTIFICATION_LEVEL = :participating DEFAULT_NOTIFICATION_LEVEL = :participating
ignore_column :authorized_projects_populated ignore_column :external_email
ignore_column :email_provider
add_authentication_token_field :authentication_token add_authentication_token_field :authentication_token
add_authentication_token_field :incoming_email_token add_authentication_token_field :incoming_email_token
...@@ -85,6 +87,7 @@ class User < ActiveRecord::Base ...@@ -85,6 +87,7 @@ class User < ActiveRecord::Base
has_many :identities, dependent: :destroy, autosave: true # rubocop:disable Cop/ActiveRecordDependent has_many :identities, dependent: :destroy, autosave: true # rubocop:disable Cop/ActiveRecordDependent
has_many :u2f_registrations, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :u2f_registrations, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :chat_names, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :chat_names, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_one :user_synced_attributes_metadata, autosave: true
# Groups # Groups
has_many :members, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :members, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
...@@ -161,6 +164,7 @@ class User < ActiveRecord::Base ...@@ -161,6 +164,7 @@ class User < ActiveRecord::Base
after_update :update_emails_with_primary_email, if: :email_changed? after_update :update_emails_with_primary_email, if: :email_changed?
before_save :ensure_authentication_token, :ensure_incoming_email_token before_save :ensure_authentication_token, :ensure_incoming_email_token
before_save :ensure_user_rights_and_limits, if: :external_changed? before_save :ensure_user_rights_and_limits, if: :external_changed?
before_save :skip_reconfirmation!, if: ->(user) { user.email_changed? && user.read_only_attribute?(:email) }
after_save :ensure_namespace_correct after_save :ensure_namespace_correct
after_commit :update_invalid_gpg_signatures, on: :update, if: -> { previous_changes.key?('email') } after_commit :update_invalid_gpg_signatures, on: :update, if: -> { previous_changes.key?('email') }
after_initialize :set_projects_limit after_initialize :set_projects_limit
...@@ -1045,6 +1049,22 @@ class User < ActiveRecord::Base ...@@ -1045,6 +1049,22 @@ class User < ActiveRecord::Base
self.email == email self.email == email
end end
def sync_attribute?(attribute)
return true if ldap_user? && attribute == :email
attributes = Gitlab.config.omniauth.sync_profile_attributes
if attributes.is_a?(Array)
attributes.include?(attribute.to_s)
else
attributes
end
end
def read_only_attribute?(attribute)
user_synced_attributes_metadata&.read_only?(attribute)
end
protected protected
# override, from Devise::Validatable # override, from Devise::Validatable
......
class UserSyncedAttributesMetadata < ActiveRecord::Base
belongs_to :user
validates :user, presence: true
SYNCABLE_ATTRIBUTES = %i[name email location].freeze
def read_only?(attribute)
Gitlab.config.omniauth.sync_profile_from_provider && synced?(attribute)
end
def read_only_attributes
return [] unless Gitlab.config.omniauth.sync_profile_from_provider
SYNCABLE_ATTRIBUTES.select { |key| synced?(key) }
end
def synced?(attribute)
read_attribute("#{attribute}_synced")
end
def set_attribute_synced(attribute, value)
write_attribute("#{attribute}_synced", value)
end
end
...@@ -10,6 +10,10 @@ module Discussions ...@@ -10,6 +10,10 @@ module Discussions
discussion.notes.each do |note| discussion.notes.each do |note|
if outdated if outdated
note.change_position = position note.change_position = position
if project.resolve_outdated_diff_discussions?
note.resolve_without_save(current_user, resolved_by_push: true)
end
else else
note.position = position note.position = position
note.change_position = nil note.change_position = nil
......
...@@ -34,6 +34,10 @@ module Users ...@@ -34,6 +34,10 @@ module Users
private private
def assign_attributes(&block) def assign_attributes(&block)
if @user.user_synced_attributes_metadata
params.except!(*@user.user_synced_attributes_metadata.read_only_attributes)
end
@user.assign_attributes(params) if params.any? @user.assign_attributes(params) if params.any?
end end
end end
......
- add_to_breadcrumbs "Applications", admin_applications_path
- breadcrumb_title @application.name
- page_title "Edit", @application.name, "Applications" - page_title "Edit", @application.name, "Applications"
%h3.page-title Edit application %h3.page-title Edit application
......
- breadcrumb_title "Cohorts"
- @no_container = true - @no_container = true
= render "admin/dashboard/head" = render "admin/dashboard/head"
......
- @no_container = true - @no_container = true
- breadcrumb_title "Dashboard"
= render "admin/dashboard/head" = render "admin/dashboard/head"
%div{ class: container_class } %div{ class: container_class }
......
- add_to_breadcrumbs "Groups", admin_groups_path
- breadcrumb_title @group.name
- page_title @group.name, "Groups" - page_title @group.name, "Groups"
%h3.page-title %h3.page-title
Group: #{@group.full_name} Group: #{@group.full_name}
......
- add_to_breadcrumbs "System Hooks", admin_hooks_path
- page_title 'Edit System Hook' - page_title 'Edit System Hook'
%h3.page-title %h3.page-title
Edit System Hook Edit System Hook
......
- breadcrumb_title "Jobs"
- @no_container = true - @no_container = true
= render "admin/dashboard/head" = render "admin/dashboard/head"
......
- add_to_breadcrumbs "Labels", admin_labels_path
- breadcrumb_title "Edit Label"
- page_title "Edit", @label.name, "Labels" - page_title "Edit", @label.name, "Labels"
%h3.page-title %h3.page-title
Edit Label Edit Label
......
- add_to_breadcrumbs "Projects", admin_projects_path
- breadcrumb_title @project.name_with_namespace
- page_title @project.name_with_namespace, "Projects" - page_title @project.name_with_namespace, "Projects"
%h3.page-title %h3.page-title
Project: #{@project.name_with_namespace} Project: #{@project.name_with_namespace}
......
- breadcrumb_title "Runners"
- @no_container = true - @no_container = true
= render "admin/dashboard/head" = render "admin/dashboard/head"
......
- add_to_breadcrumbs "Service Templates", admin_application_settings_services_path
- breadcrumb_title @service.title
- page_title @service.title, "Service Templates" - page_title @service.title, "Service Templates"
= render 'form' = render 'form'
- add_to_breadcrumbs "Users", admin_users_path
- breadcrumb_title @user.name
- page_title @user.name, "Users" - page_title @user.name, "Users"
= render 'admin/users/head' = render 'admin/users/head'
......
%h4.prepend-top-0 %p.append-bottom-default
Secret variables Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags.
= link_to icon('question-circle'), help_page_path('ci/variables/README', anchor: 'secret-variables'), target: '_blank' You can use variables for passwords, secret keys, or whatever you want.
%p
These variables will be set to environment by the runner, and could be protected by exposing only to protected branches or tags.
%p
So you can use them for passwords, secret keys or whatever you want.
%p
The value of the variable can be visible in job log if explicitly asked to do so.
.row.prepend-top-default.append-bottom-default .row.prepend-top-default.append-bottom-default
.col-lg-4 .col-lg-12
= render "ci/variables/content"
.col-lg-8
%h5.prepend-top-0 %h5.prepend-top-0
Add a variable Add a variable
= render "ci/variables/form", btn_text: "Add new variable" = render "ci/variables/form", btn_text: "Add new variable"
......
...@@ -4,6 +4,6 @@ ...@@ -4,6 +4,6 @@
.col-lg-3 .col-lg-3
= render "ci/variables/content" = render "ci/variables/content"
.col-lg-9 .col-lg-9
%h5.prepend-top-0 %h4.prepend-top-0
Update variable Update variable
= render "ci/variables/form", btn_text: "Save variable" = render "ci/variables/form", btn_text: "Save variable"
- if show_new_nav? && current_user.can_create_group? - if current_user.can_create_group?
- content_for :breadcrumbs_extra do - content_for :breadcrumbs_extra do
= link_to "New group", new_group_path, class: "btn btn-new" = link_to "New group", new_group_path, class: "btn btn-new"
...@@ -10,8 +10,8 @@ ...@@ -10,8 +10,8 @@
= nav_link(page: explore_groups_path) do = nav_link(page: explore_groups_path) do
= link_to explore_groups_path, title: 'Explore public groups' do = link_to explore_groups_path, title: 'Explore public groups' do
Explore public groups Explore public groups
.nav-controls{ class: ("nav-controls-new-nav" if show_new_nav?) } .nav-controls.nav-controls-new-nav
= render 'shared/groups/search_form' = render 'shared/groups/search_form'
= render 'shared/groups/dropdown' = render 'shared/groups/dropdown'
- if current_user.can_create_group? - if current_user.can_create_group?
= link_to "New group", new_group_path, class: "btn btn-new #{("visible-xs" if show_new_nav?)}" = link_to "New group", new_group_path, class: "btn btn-new visible-xs"
= content_for :flash_message do = content_for :flash_message do
= render 'shared/project_limit' = render 'shared/project_limit'
- if show_new_nav? && current_user.can_create_project? - if current_user.can_create_project?
- content_for :breadcrumbs_extra do - content_for :breadcrumbs_extra do
= link_to "New project", new_project_path, class: 'btn btn-new' = link_to "New project", new_project_path, class: 'btn btn-new'
...@@ -19,8 +19,8 @@ ...@@ -19,8 +19,8 @@
= link_to explore_root_path, title: 'Explore', data: {placement: 'right'} do = link_to explore_root_path, title: 'Explore', data: {placement: 'right'} do
Explore projects Explore projects
.nav-controls{ class: ("nav-controls-new-nav" if show_new_nav?) } .nav-controls.nav-controls-new-nav
= render 'shared/projects/search_form' = render 'shared/projects/search_form'
= render 'shared/projects/dropdown' = render 'shared/projects/dropdown'
- if current_user.can_create_project? - if current_user.can_create_project?
= link_to "New project", new_project_path, class: "btn btn-new #{("visible-xs" if show_new_nav?)}" = link_to "New project", new_project_path, class: "btn btn-new visible-xs"
- if show_new_nav? && current_user - if current_user
- content_for :breadcrumbs_extra do - content_for :breadcrumbs_extra do
= link_to "New snippet", new_snippet_path, class: "btn btn-new", title: "New snippet" = link_to "New snippet", new_snippet_path, class: "btn btn-new", title: "New snippet"
...@@ -10,7 +10,3 @@ ...@@ -10,7 +10,3 @@
= nav_link(page: explore_snippets_path) do = nav_link(page: explore_snippets_path) do
= link_to explore_snippets_path, title: 'Explore snippets', data: {placement: 'right'} do = link_to explore_snippets_path, title: 'Explore snippets', data: {placement: 'right'} do
Explore Snippets Explore Snippets
- if current_user
.nav-controls.hidden-xs{ class: ("hidden-sm hidden-md hidden-lg" if show_new_nav?) }
= link_to "New snippet", new_snippet_path, class: "btn btn-new", title: "New snippet"
...@@ -4,15 +4,14 @@ ...@@ -4,15 +4,14 @@
= content_for :meta_tags do = content_for :meta_tags do
= auto_discovery_link_tag(:atom, params.merge(rss_url_options), title: "#{current_user.name} issues") = auto_discovery_link_tag(:atom, params.merge(rss_url_options), title: "#{current_user.name} issues")
- if show_new_nav? - content_for :breadcrumbs_extra do
- content_for :breadcrumbs_extra do = link_to params.merge(rss_url_options), class: 'btn has-tooltip append-right-10', title: 'Subscribe' do
= link_to params.merge(rss_url_options), class: 'btn has-tooltip append-right-10', title: 'Subscribe' do = icon('rss')
= icon('rss') = render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", with_feature_enabled: 'issues', type: :issues
= render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", with_feature_enabled: 'issues', type: :issues
.top-area .top-area
= render 'shared/issuable/nav', type: :issues = render 'shared/issuable/nav', type: :issues
.nav-controls{ class: ("visible-xs" if show_new_nav?) } .nav-controls.visible-xs
= link_to params.merge(rss_url_options), class: 'btn has-tooltip', title: 'Subscribe' do = link_to params.merge(rss_url_options), class: 'btn has-tooltip', title: 'Subscribe' do
= icon('rss') = icon('rss')
= render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", with_feature_enabled: 'issues', type: :issues = render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", with_feature_enabled: 'issues', type: :issues
......
...@@ -2,13 +2,12 @@ ...@@ -2,13 +2,12 @@
- page_title "Merge Requests" - page_title "Merge Requests"
- header_title "Merge Requests", merge_requests_dashboard_path(assignee_id: current_user.id) - header_title "Merge Requests", merge_requests_dashboard_path(assignee_id: current_user.id)
- if show_new_nav? - content_for :breadcrumbs_extra do
- content_for :breadcrumbs_extra do = render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New merge request", with_feature_enabled: 'merge_requests', type: :merge_requests
= render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New merge request", with_feature_enabled: 'merge_requests', type: :merge_requests
.top-area .top-area
= render 'shared/issuable/nav', type: :merge_requests = render 'shared/issuable/nav', type: :merge_requests
.nav-controls{ class: ("visible-xs" if show_new_nav?) } .nav-controls.visible-xs
= render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New merge request", with_feature_enabled: 'merge_requests', type: :merge_requests = render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New merge request", with_feature_enabled: 'merge_requests', type: :merge_requests
= render 'shared/issuable/filter', type: :merge_requests = render 'shared/issuable/filter', type: :merge_requests
......
...@@ -2,14 +2,13 @@ ...@@ -2,14 +2,13 @@
- page_title 'Milestones' - page_title 'Milestones'
- header_title 'Milestones', dashboard_milestones_path - header_title 'Milestones', dashboard_milestones_path
- if show_new_nav? - content_for :breadcrumbs_extra do
- content_for :breadcrumbs_extra do = render 'shared/new_project_item_select', path: 'milestones/new', label: 'New milestone', include_groups: true, type: :milestones
= render 'shared/new_project_item_select', path: 'milestones/new', label: 'New milestone', include_groups: true, type: :milestones
.top-area .top-area
= render 'shared/milestones_filter', counts: @milestone_states = render 'shared/milestones_filter', counts: @milestone_states
.nav-controls{ class: ("visible-xs" if show_new_nav?) } .nav-controls.visible-xs
= render 'shared/new_project_item_select', path: 'milestones/new', label: 'New milestone', include_groups: true, type: :milestones = render 'shared/new_project_item_select', path: 'milestones/new', label: 'New milestone', include_groups: true, type: :milestones
.milestones .milestones
......
- if discussion.resolved? - if discussion.resolved?
.discussion-headline-light.js-discussion-headline .discussion-headline-light.js-discussion-headline
Resolved = discussion_resolved_intro(discussion)
- if discussion.resolved_by - if discussion.resolved_by
by by
= link_to_member(@project, discussion.resolved_by, avatar: false) = link_to_member(@project, discussion.resolved_by, avatar: false)
- if discussion.resolved_by_push?
with a push
= time_ago_with_tooltip(discussion.resolved_at, placement: "bottom") = time_ago_with_tooltip(discussion.resolved_at, placement: "bottom")
- elsif discussion.updated? - elsif discussion.updated?
.discussion-headline-light.js-discussion-headline .discussion-headline-light.js-discussion-headline
......
- breadcrumb_title "General Settings"
= render "groups/settings_head" = render "groups/settings_head"
.panel.panel-default.prepend-top-default .panel.panel-default.prepend-top-default
.panel-heading .panel-heading
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
= webpack_bundle_tag 'common_vue' = webpack_bundle_tag 'common_vue'
= webpack_bundle_tag 'filtered_search' = webpack_bundle_tag 'filtered_search'
- if show_new_nav? && group_issues_exists - if group_issues_exists
- content_for :breadcrumbs_extra do - content_for :breadcrumbs_extra do
= link_to params.merge(rss_url_options), class: 'btn btn-default append-right-10' do = link_to params.merge(rss_url_options), class: 'btn btn-default append-right-10' do
= icon('rss') = icon('rss')
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
- if group_issues_exists - if group_issues_exists
.top-area .top-area
= render 'shared/issuable/nav', type: :issues = render 'shared/issuable/nav', type: :issues
.nav-controls{ class: ("visible-xs" if show_new_nav?) } .nav-controls.visible-xs
= link_to params.merge(rss_url_options), class: 'btn' do = link_to params.merge(rss_url_options), class: 'btn' do
= icon('rss') = icon('rss')
%span.icon-label %span.icon-label
......
- page_title 'Labels' - page_title 'Labels'
- if show_new_nav? && can?(current_user, :admin_label, @group) - if can?(current_user, :admin_label, @group)
- content_for :breadcrumbs_extra do - content_for :breadcrumbs_extra do
= link_to "New label", new_group_label_path(@group), class: "btn btn-new" = link_to "New label", new_group_label_path(@group), class: "btn btn-new"
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
.nav-text .nav-text
Labels can be applied to issues and merge requests. Group labels are available for any project within the group. Labels can be applied to issues and merge requests. Group labels are available for any project within the group.
.nav-controls{ class: ("visible-xs" if show_new_nav?) } .nav-controls.visible-xs
- if can?(current_user, :admin_label, @group) - if can?(current_user, :admin_label, @group)
= link_to "New label", new_group_label_path(@group), class: "btn btn-new" = link_to "New label", new_group_label_path(@group), class: "btn btn-new"
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
= webpack_bundle_tag 'common_vue' = webpack_bundle_tag 'common_vue'
= webpack_bundle_tag 'filtered_search' = webpack_bundle_tag 'filtered_search'
- if show_new_nav? && current_user - if current_user
- content_for :breadcrumbs_extra do - content_for :breadcrumbs_extra do
= render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New merge request", type: :merge_requests = render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New merge request", type: :merge_requests
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
.top-area .top-area
= render 'shared/issuable/nav', type: :merge_requests = render 'shared/issuable/nav', type: :merge_requests
- if current_user - if current_user
.nav-controls{ class: ("visible-xs" if show_new_nav?) } .nav-controls.visible-xs
= render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New merge request", type: :merge_requests = render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New merge request", type: :merge_requests
= render 'shared/issuable/search_bar', type: :merge_requests = render 'shared/issuable/search_bar', type: :merge_requests
......
- page_title "Milestones" - page_title "Milestones"
- if show_new_nav? && can?(current_user, :admin_milestones, @group) - if can?(current_user, :admin_milestones, @group)
- content_for :breadcrumbs_extra do - content_for :breadcrumbs_extra do
= link_to "New milestone", new_group_milestone_path(@group), class: "btn btn-new" = link_to "New milestone", new_group_milestone_path(@group), class: "btn btn-new"
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
.top-area .top-area
= render 'shared/milestones_filter', counts: @milestone_states = render 'shared/milestones_filter', counts: @milestone_states
.nav-controls{ class: ("visible-xs" if show_new_nav?) } .nav-controls.visible-xs
- if can?(current_user, :admin_milestones, @group) - if can?(current_user, :admin_milestones, @group)
= link_to "New milestone", new_group_milestone_path(@group), class: "btn btn-new" = link_to "New milestone", new_group_milestone_path(@group), class: "btn btn-new"
......
- breadcrumb_title "Projects"
= render "groups/settings_head" = render "groups/settings_head"
.panel.panel-default.prepend-top-default .panel.panel-default.prepend-top-default
......
- page_title "Pipelines" - breadcrumb_title "CI / CD Settings"
- page_title "CI / CD"
= render "groups/settings_head" = render "groups/settings_head"
= render 'ci/variables/index' = render 'ci/variables/index'
- @no_container = true - @no_container = true
- breadcrumb_title "Group" - breadcrumb_title "Details"
= content_for :meta_tags do = content_for :meta_tags do
= auto_discovery_link_tag(:atom, group_url(@group, rss_url_options), title: "#{@group.name} activity") = auto_discovery_link_tag(:atom, group_url(@group, rss_url_options), title: "#{@group.name} activity")
......
- breadcrumb_title "Details"
- @no_container = true - @no_container = true
= render 'head' = render 'head'
......
...@@ -32,9 +32,9 @@ ...@@ -32,9 +32,9 @@
= stylesheet_link_tag "test", media: "all" if Rails.env.test? = stylesheet_link_tag "test", media: "all" if Rails.env.test?
= stylesheet_link_tag 'performance_bar' if performance_bar_enabled? = stylesheet_link_tag 'performance_bar' if performance_bar_enabled?
- if show_new_nav? // TODO: Combine these 2 stylesheets into application.scss
= stylesheet_link_tag "new_nav", media: "all" = stylesheet_link_tag "new_nav", media: "all"
= stylesheet_link_tag "new_sidebar", media: "all" = stylesheet_link_tag "new_sidebar", media: "all"
= Gon::Base.render_data = Gon::Base.render_data
......
.page-with-sidebar{ class: page_with_sidebar_class } .page-with-sidebar{ class: page_with_sidebar_class }
- if show_new_nav? - if defined?(nav) && nav
- if defined?(nav) && nav = render "layouts/nav/sidebar/#{nav}"
= render "layouts/nav/#{nav}" .content-wrapper.page-with-new-nav
- else .mobile-overlay
- if defined?(nav) && nav
.layout-nav
.container-fluid
= render "layouts/nav/#{nav}"
- if content_for?(:sub_nav)
= yield :sub_nav
.content-wrapper{ class: layout_nav_class }
- if show_new_nav?
.mobile-overlay
.alert-wrapper .alert-wrapper
= render "layouts/broadcast" = render "layouts/broadcast"
- if show_new_nav?
- if content_for?(:new_global_flash)
= yield :new_global_flash
- unless @hide_breadcrumbs
= render "layouts/nav/breadcrumbs"
= render "layouts/flash"
= yield :flash_message = yield :flash_message
- unless @hide_breadcrumbs
= render "layouts/nav/breadcrumbs"
= render "layouts/flash"
%div{ class: "#{(container_class unless @no_container)} #{@content_class}" } %div{ class: "#{(container_class unless @no_container)} #{@content_class}" }
.content{ id: "content-body" } .content{ id: "content-body" }
= yield = yield
- page_title "Admin Area" - page_title "Admin Area"
- header_title "Admin Area", admin_root_path - header_title "Admin Area", admin_root_path
- if show_new_nav? - nav "admin"
- nav "new_admin_sidebar" - @left_sidebar = true
- @new_sidebar = true
- else
- nav "admin"
= render template: "layouts/application" = render template: "layouts/application"
...@@ -4,10 +4,7 @@ ...@@ -4,10 +4,7 @@
%body{ class: @body_class, data: { page: body_data_page, project: "#{@project.path if @project}", group: "#{@group.path if @group}", find_file: find_file_path } } %body{ class: @body_class, data: { page: body_data_page, project: "#{@project.path if @project}", group: "#{@group.path if @group}", find_file: find_file_path } }
= render "layouts/init_auto_complete" if @gfm_form = render "layouts/init_auto_complete" if @gfm_form
= render 'peek/bar' = render 'peek/bar'
- if show_new_nav? = render "layouts/header/default"
= render "layouts/header/new"
- else
= render "layouts/header/default", title: header_title
= render 'layouts/page', sidebar: sidebar, nav: nav = render 'layouts/page', sidebar: sidebar, nav: nav
= yield :scripts_body = yield :scripts_body
- page_title @group.name - page_title @group.name
- page_description @group.description unless page_description - page_description @group.description unless page_description
- header_title group_title(@group) unless header_title - header_title group_title(@group) unless header_title
- if show_new_nav? - nav "group"
- nav "new_group_sidebar" - @left_sidebar = true
- @new_sidebar = true
- else
- nav "group"
= render template: "layouts/application" = render template: "layouts/application"
%header.navbar.navbar-gitlab{ class: nav_header_class } %header.navbar.navbar-gitlab.navbar-gitlab-new
.navbar-border
%a.sr-only.gl-accessibility{ href: "#content-body", tabindex: "1" } Skip to content %a.sr-only.gl-accessibility{ href: "#content-body", tabindex: "1" } Skip to content
.container-fluid .container-fluid
.header-content .header-content
.dropdown.global-dropdown .title-container
%button.global-dropdown-toggle{ type: 'button', 'data-toggle' => 'dropdown' } %h1.title
%span.sr-only Toggle navigation = link_to root_path, title: 'Dashboard', id: 'logo' do
= icon('bars') = brand_header_logo
.dropdown-menu-nav.global-dropdown-menu %span.logo-text.hidden-xs
- if current_user = render 'shared/logo_type.svg'
= render 'layouts/nav/dashboard'
- else
= render 'layouts/nav/explore'
.header-logo
= link_to root_path, class: 'home', title: 'Dashboard', id: 'logo' do
= brand_header_logo
.title-container.js-title-container - if current_user
%h1.title{ class: ('initializing' if @has_group_title) }= title = render "layouts/nav/dashboard"
- else
= render "layouts/nav/explore"
.navbar-collapse.collapse .navbar-collapse.collapse
%ul.nav.navbar-nav %ul.nav.navbar-nav
- if current_user
= render 'layouts/header/new_dropdown'
%li.hidden-sm.hidden-xs %li.hidden-sm.hidden-xs
= render 'layouts/search' unless current_controller?(:search) = render 'layouts/search' unless current_controller?(:search)
%li.visible-sm-inline-block.visible-xs-inline-block %li.visible-sm-inline-block.visible-xs-inline-block
= link_to search_path, title: 'Search', aria: { label: "Search" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do = link_to search_path, title: 'Search', aria: { label: "Search" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('search') = icon('search')
- if current_user - if current_user
- if session[:impersonator_id]
%li.impersonation
= link_to admin_impersonation_path, method: :delete, title: "Stop impersonation", aria: { label: 'Stop impersonation' }, data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do
= icon('user-secret fw')
- if current_user.admin?
%li
= link_to admin_root_path, title: 'Admin area', aria: { label: "Admin area" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('wrench fw')
= render 'layouts/header/new_dropdown'
- if Gitlab::Sherlock.enabled?
%li
= link_to sherlock_transactions_path, title: 'Sherlock Transactions',
data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('tachometer fw')
%li.user-counter %li.user-counter
= link_to assigned_issues_dashboard_path, title: 'Issues', aria: { label: "Issues" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do = link_to assigned_issues_dashboard_path, title: 'Issues', class: 'dashboard-shortcuts-issues', aria: { label: "Issues" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= custom_icon('issues') = custom_icon('issues')
- issues_count = assigned_issuables_count(:issues) - issues_count = assigned_issuables_count(:issues)
%span.badge.issues-count{ class: ('hidden' if issues_count.zero?) } %span.badge.issues-count{ class: ('hidden' if issues_count.zero?) }
= number_with_delimiter(issues_count) = number_with_delimiter(issues_count)
%li.user-counter %li.user-counter
= link_to assigned_mrs_dashboard_path, title: 'Merge requests', aria: { label: "Merge requests" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do = link_to assigned_mrs_dashboard_path, title: 'Merge requests', class: 'dashboard-shortcuts-merge_requests', aria: { label: "Merge requests" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= custom_icon('mr_bold') = custom_icon('mr_bold')
- merge_requests_count = assigned_issuables_count(:merge_requests) - merge_requests_count = assigned_issuables_count(:merge_requests)
%span.badge.merge-requests-count{ class: ('hidden' if merge_requests_count.zero?) } %span.badge.merge-requests-count{ class: ('hidden' if merge_requests_count.zero?) }
...@@ -60,9 +42,9 @@ ...@@ -60,9 +42,9 @@
%span.badge.todos-count{ class: ('hidden' if todos_pending_count.zero?) } %span.badge.todos-count{ class: ('hidden' if todos_pending_count.zero?) }
= todos_count_format(todos_pending_count) = todos_count_format(todos_pending_count)
%li.header-user.dropdown %li.header-user.dropdown
= link_to current_user, class: "header-user-dropdown-toggle", data: { toggle: "dropdown" } do = link_to current_user, class: user_dropdown_class, data: { toggle: "dropdown" } do
= image_tag avatar_icon(current_user, 26), width: 26, height: 26, class: "header-user-avatar" = image_tag avatar_icon(current_user, 23), width: 23, height: 23, class: "header-user-avatar"
= icon('caret-down') = custom_icon('caret_down')
.dropdown-menu-nav.dropdown-menu-align-right .dropdown-menu-nav.dropdown-menu-align-right
%ul %ul
%li.current-user %li.current-user
...@@ -74,18 +56,24 @@ ...@@ -74,18 +56,24 @@
= link_to "Profile", current_user, class: 'profile-link', data: { user: current_user.username } = link_to "Profile", current_user, class: 'profile-link', data: { user: current_user.username }
%li %li
= link_to "Settings", profile_path = link_to "Settings", profile_path
- if current_user
%li
= link_to "Help", help_path
%li.divider %li.divider
%li %li
= link_to "Sign out", destroy_user_session_path, method: :delete, class: "sign-out-link" = link_to "Sign out", destroy_user_session_path, method: :delete, class: "sign-out-link"
- if session[:impersonator_id]
%li.impersonation
= link_to admin_impersonation_path, class: 'impersonation-btn', method: :delete, title: "Stop impersonation", aria: { label: 'Stop impersonation' }, data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do
= icon('user-secret')
- else - else
%li %li
%div %div
= link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes'), class: 'btn btn-sign-in btn-success' = link_to "Sign in / Register", new_session_path(:user, redirect_to_referer: 'yes'), class: 'btn btn-sign-in'
%button.navbar-toggle{ type: 'button' } %button.navbar-toggle.hidden-sm.hidden-md.hidden-lg{ type: 'button' }
%span.sr-only Toggle navigation %span.sr-only Toggle navigation
= icon('ellipsis-v') = icon('ellipsis-v', class: 'js-navbar-toggle-right')
= icon('times', class: 'js-navbar-toggle-left')
= yield :header_content
= render 'shared/outdated_browser' = render 'shared/outdated_browser'
%header.navbar.navbar-gitlab.navbar-gitlab-new{ class: nav_header_class }
%a.sr-only.gl-accessibility{ href: "#content-body", tabindex: "1" } Skip to content
.container-fluid
.header-content
.title-container
%h1.title
= link_to root_path, title: 'Dashboard', id: 'logo' do
= brand_header_logo
%span.logo-text.hidden-xs
= render 'shared/logo_type.svg'
- if current_user
= render "layouts/nav/new_dashboard"
- else
= render "layouts/nav/new_explore"
.navbar-collapse.collapse
%ul.nav.navbar-nav
- if current_user
= render 'layouts/header/new_dropdown'
%li.hidden-sm.hidden-xs
= render 'layouts/search' unless current_controller?(:search)
%li.visible-sm-inline-block.visible-xs-inline-block
= link_to search_path, title: 'Search', aria: { label: "Search" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('search')
- if current_user
%li.user-counter
= link_to assigned_issues_dashboard_path, title: 'Issues', class: 'dashboard-shortcuts-issues', aria: { label: "Issues" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= custom_icon('issues')
- issues_count = assigned_issuables_count(:issues)
%span.badge.issues-count{ class: ('hidden' if issues_count.zero?) }
= number_with_delimiter(issues_count)
%li.user-counter
= link_to assigned_mrs_dashboard_path, title: 'Merge requests', class: 'dashboard-shortcuts-merge_requests', aria: { label: "Merge requests" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= custom_icon('mr_bold')
- merge_requests_count = assigned_issuables_count(:merge_requests)
%span.badge.merge-requests-count{ class: ('hidden' if merge_requests_count.zero?) }
= number_with_delimiter(merge_requests_count)
%li.user-counter
= link_to dashboard_todos_path, title: 'Todos', aria: { label: "Todos" }, class: 'shortcuts-todos', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= custom_icon('todo_done')
%span.badge.todos-count{ class: ('hidden' if todos_pending_count.zero?) }
= todos_count_format(todos_pending_count)
%li.header-user.dropdown
= link_to current_user, class: user_dropdown_class, data: { toggle: "dropdown" } do
= image_tag avatar_icon(current_user, 23), width: 23, height: 23, class: "header-user-avatar"
= custom_icon('caret_down')
.dropdown-menu-nav.dropdown-menu-align-right
%ul
%li.current-user
.user-name.bold
= current_user.name
@#{current_user.username}
%li.divider
%li
= link_to "Profile", current_user, class: 'profile-link', data: { user: current_user.username }
%li
= link_to "Settings", profile_path
- if current_user
%li
= link_to "Help", help_path
%li.divider
%li
= link_to "Sign out", destroy_user_session_path, method: :delete, class: "sign-out-link"
- if session[:impersonator_id]
%li.impersonation
= link_to admin_impersonation_path, class: 'impersonation-btn', method: :delete, title: "Stop impersonation", aria: { label: 'Stop impersonation' }, data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do
= icon('user-secret')
- else
%li
%div
= link_to "Sign in / Register", new_session_path(:user, redirect_to_referer: 'yes'), class: 'btn btn-sign-in'
%button.navbar-toggle.hidden-sm.hidden-md.hidden-lg{ type: 'button' }
%span.sr-only Toggle navigation
= icon('ellipsis-v', class: 'js-navbar-toggle-right')
= icon('times', class: 'js-navbar-toggle-left')
= render 'shared/outdated_browser'
%li.header-new.dropdown %li.header-new.dropdown
= link_to new_project_path, class: "header-new-dropdown-toggle has-tooltip", title: "New...", ref: 'tooltip', aria: { label: "New..." }, data: { toggle: 'dropdown', placement: 'bottom', container: 'body' } do = link_to new_project_path, class: "header-new-dropdown-toggle has-tooltip", title: "New...", ref: 'tooltip', aria: { label: "New..." }, data: { toggle: 'dropdown', placement: 'bottom', container: 'body' } do
- if show_new_nav? = custom_icon('plus_square')
= custom_icon('plus_square') = custom_icon('caret_down')
= custom_icon('caret_down')
- else
= icon('plus fw')
= custom_icon('caret_down')
.dropdown-menu-nav.dropdown-menu-align-right .dropdown-menu-nav.dropdown-menu-align-right
%ul %ul
- if @group&.persisted? - if @group&.persisted?
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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