Commit acb9f869 authored by Dennis Tang's avatar Dennis Tang Committed by Phil Hughes

port "Improve project overview UI" to EE

parent 0b6b3bf6
......@@ -13,45 +13,57 @@ export default class Project {
constructor() {
const $cloneOptions = $('ul.clone-options-dropdown');
const $projectCloneField = $('#project_clone');
const $cloneBtnText = $('a.clone-dropdown-btn span');
const $cloneBtnLabel = $('.js-git-clone-holder .js-clone-dropdown-label');
const selectedCloneOption = $cloneBtnText.text().trim();
const selectedCloneOption = $cloneBtnLabel.text().trim();
if (selectedCloneOption.length > 0) {
$(`a:contains('${selectedCloneOption}')`, $cloneOptions).addClass('is-active');
}
$('a', $cloneOptions).on('click', (e) => {
$('a', $cloneOptions).on('click', e => {
e.preventDefault();
const $this = $(e.currentTarget);
const url = $this.attr('href');
const activeText = $this.find('.dropdown-menu-inner-title').text();
const cloneType = $this.data('cloneType');
e.preventDefault();
$('.is-active', $cloneOptions).removeClass('is-active');
$(`a[data-clone-type="${cloneType}"]`).each(function() {
const $el = $(this);
const activeText = $el.find('.dropdown-menu-inner-title').text();
const $container = $el.closest('.project-clone-holder');
const $label = $container.find('.js-clone-dropdown-label');
$('.is-active', $cloneOptions).not($this).removeClass('is-active');
$this.toggleClass('is-active');
$projectCloneField.val(url);
$cloneBtnText.text(activeText);
$el.toggleClass('is-active');
$label.text(activeText);
});
$('#modal-geo-info').data({
cloneUrlSecondary: $this.attr('href'),
cloneUrlPrimary: $this.data('primaryUrl') || '',
});
return $('.clone').text(url);
$projectCloneField.val(url);
$('.js-git-empty .js-clone').text(url);
});
// Ref switcher
Project.initRefSwitcher();
$('.project-refs-select').on('change', function() {
return $(this).parents('form').submit();
return $(this)
.parents('form')
.submit();
});
$('.hide-no-ssh-message').on('click', function(e) {
Cookies.set('hide_no_ssh_message', 'false');
$(this).parents('.no-ssh-key-message').remove();
$(this)
.parents('.no-ssh-key-message')
.remove();
return e.preventDefault();
});
$('.hide-no-password-message').on('click', function(e) {
Cookies.set('hide_no_password_message', 'false');
$(this).parents('.no-password-message').remove();
$(this)
.parents('.no-password-message')
.remove();
return e.preventDefault();
});
$('.hide-shared-runner-limit-message').on('click', function(e) {
......@@ -70,7 +82,7 @@ export default class Project {
}
static changeProject(url) {
return window.location = url;
return (window.location = url);
}
static initRefSwitcher() {
......@@ -85,14 +97,15 @@ export default class Project {
selected = $dropdown.data('selected');
return $dropdown.glDropdown({
data(term, callback) {
axios.get($dropdown.data('refsUrl'), {
params: {
ref: $dropdown.data('ref'),
search: term,
},
})
.then(({ data }) => callback(data))
.catch(() => flash(__('An error occurred while getting projects')));
axios
.get($dropdown.data('refsUrl'), {
params: {
ref: $dropdown.data('ref'),
search: term,
},
})
.then(({ data }) => callback(data))
.catch(() => flash(__('An error occurred while getting projects')));
},
selectable: true,
filterable: true,
......
......@@ -8,15 +8,18 @@ import BlobViewer from '~/blob/viewer/index';
import Activities from '~/activities';
import { ajaxGet } from '~/lib/utils/common_utils';
import GpgBadges from '~/gpg_badges';
import initReadMore from '~/read_more';
import Star from '../../../star';
import notificationsDropdown from '../../../notifications_dropdown';
document.addEventListener('DOMContentLoaded', () => {
initReadMore();
new Star(); // eslint-disable-line no-new
notificationsDropdown();
new ShortcutsNavigation(); // eslint-disable-line no-new
new NotificationsForm(); // eslint-disable-line no-new
new UserCallout({ // eslint-disable-line no-new
// eslint-disable-next-line no-new
new UserCallout({
setCalloutPerProject: false,
className: 'js-autodevops-banner',
});
......
/**
* ReadMore
*
* Adds "read more" functionality to elements.
*
* Specifically, it looks for a trigger, by default ".js-read-more-trigger", and adds the class
* "is-expanded" to the previous element in order to provide a click to expand functionality.
*
* This is useful for long text elements that you would like to truncate, especially for mobile.
*
* Example Markup
* <div class="read-more-container">
* <p>Some text that should be long enough to have to truncate within a specified container.</p>
* <p>This text will not appear in the container, as only the first line can be truncated.</p>
* <p>This should also not appear, if everything is working correctly!</p>
* </div>
* <button class="js-read-more-trigger">Read more</button>
*
*/
export default function initReadMore(triggerSelector = '.js-read-more-trigger') {
const triggerEls = document.querySelectorAll(triggerSelector);
if (!triggerEls) return;
triggerEls.forEach(triggerEl => {
const targetEl = triggerEl.previousElementSibling;
if (!targetEl) {
return;
}
triggerEl.addEventListener(
'click',
e => {
targetEl.classList.add('is-expanded');
e.target.remove();
},
{ once: true },
);
});
}
......@@ -66,3 +66,4 @@
@import 'framework/ci_variable_list';
@import 'framework/feature_highlight';
@import 'framework/terms';
@import 'framework/read_more';
......@@ -44,12 +44,8 @@
.project-repo-buttons {
display: block;
.count-buttons .btn {
margin: 0 10px;
}
.count-buttons .count-with-arrow {
display: none;
.count-buttons .count-badge {
margin-top: $gl-padding-8;
}
}
}
......
.read-more-container {
@include media-breakpoint-down(md) {
&:not(.is-expanded) {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
> * {
display: inline;
}
}
}
}
......@@ -274,6 +274,7 @@ $performance-bar-height: 35px;
$flash-height: 52px;
$context-header-height: 60px;
$breadcrumb-min-height: 48px;
$project-title-row-height: 24px;
// EE-only CSS variables START
$system-header-height: 35px;
......
......@@ -115,7 +115,7 @@
.project-feature-controls {
display: flex;
align-items: center;
margin: 8px 0;
margin: $gl-padding-8 0;
max-width: 432px;
.toggle-wrapper {
......@@ -144,12 +144,8 @@
.group-home-panel {
padding-top: 24px;
padding-bottom: 24px;
border-bottom: 1px solid $border-color;
@include media-breakpoint-up(sm) {
border-bottom: 1px solid $border-color;
}
.project-avatar,
.group-avatar {
float: none;
margin: 0 auto;
......@@ -175,7 +171,6 @@
}
}
.project-home-desc,
.group-home-desc {
margin-left: auto;
margin-right: auto;
......@@ -199,6 +194,62 @@
}
}
.project-home-panel {
padding-top: $gl-padding-8;
padding-bottom: $gl-padding-24;
.project-title-row {
margin-right: $gl-padding-8;
}
.project-avatar {
width: $project-title-row-height;
height: $project-title-row-height;
flex-shrink: 0;
flex-basis: $project-title-row-height;
margin: 0 $gl-padding-8 0 0;
}
.project-title {
font-size: 20px;
line-height: $project-title-row-height;
font-weight: bold;
}
.project-metadata {
font-weight: normal;
font-size: 14px;
line-height: $gl-btn-line-height;
color: $gl-text-color-secondary;
.icon {
margin-right: $gl-padding-4;
font-size: 16px;
}
.project-visibility,
.project-license,
.project-tag-list {
margin-right: $gl-padding-8;
}
.project-license {
.btn {
line-height: 0;
border-width: 0;
}
}
.project-tag-list,
.project-license {
.icon {
position: relative;
top: 2px;
}
}
}
}
.nav > .project-repo-buttons {
margin-top: 0;
}
......@@ -206,8 +257,6 @@
.project-repo-buttons,
.group-buttons {
.btn {
padding: 3px 10px;
&:last-child {
margin-left: 0;
}
......@@ -222,11 +271,15 @@
.fa-caret-down {
margin-left: 3px;
&.dropdown-btn-icon {
margin-left: 0;
}
}
}
.project-action-button {
margin: 15px 5px 0;
margin: $gl-padding $gl-padding-8 0 0;
vertical-align: top;
}
......@@ -243,82 +296,45 @@
.count-buttons {
display: inline-block;
vertical-align: top;
margin-top: 15px;
}
margin-top: $gl-padding;
.project-clone-holder {
display: inline-block;
margin: 15px 5px 0 0;
.count-badge {
height: $input-height;
input {
height: 28px;
.icon {
top: -1px;
}
}
}
.count-with-arrow {
display: inline-block;
position: relative;
margin-left: 4px;
.count-badge-count,
.count-badge-button {
border: 1px solid $border-color;
line-height: 1;
}
.arrow {
&::before {
content: '';
display: inline-block;
position: absolute;
width: 0;
height: 0;
border-color: transparent;
border-style: solid;
top: 50%;
left: 0;
margin-top: -6px;
border-width: 7px 5px 7px 0;
border-right-color: $count-arrow-border;
pointer-events: none;
}
.count,
.count-badge-button {
color: $gl-text-color;
}
&::after {
content: '';
position: absolute;
width: 0;
height: 0;
border-color: transparent;
border-style: solid;
top: 50%;
left: 1px;
margin-top: -9px;
border-width: 10px 7px 10px 0;
border-right-color: $white-light;
pointer-events: none;
}
.count-badge-count {
padding: 0 12px;
border-right: 0;
border-radius: $border-radius-base 0 0 $border-radius-base;
background: $gray-light;
}
.count {
@include btn-white;
display: inline-block;
background: $white-light;
border-radius: 2px;
border-width: 1px;
border-style: solid;
font-size: 13px;
font-weight: $gl-font-weight-bold;
line-height: 13px;
letter-spacing: 0.4px;
padding: 6px 14px;
text-align: center;
vertical-align: middle;
touch-action: manipulation;
background-image: none;
white-space: nowrap;
margin: 0 10px 0 4px;
.count-badge-button {
border-radius: 0 $border-radius-base $border-radius-base 0;
}
}
a {
color: inherit;
}
.project-clone-holder {
display: inline-block;
margin: $gl-padding $gl-padding-8 0 0;
&:hover {
background: $white-light;
}
input {
height: $input-height;
}
}
......@@ -333,6 +349,14 @@
min-width: 320px;
}
}
.mobile-git-clone {
margin-top: $gl-padding-8;
.dropdown-menu-inner-content {
@extend .monospace;
}
}
}
.split-one {
......@@ -512,7 +536,6 @@
.controls {
margin-left: auto;
}
}
.choose-template {
......@@ -575,7 +598,7 @@
flex-wrap: wrap;
.btn {
padding: 8px;
padding: $gl-padding-8;
margin-right: 10px;
}
......@@ -652,7 +675,7 @@
left: -10px;
top: 50%;
z-index: 10;
padding: 8px 0;
padding: $gl-padding-8 0;
text-align: center;
background-color: $white-light;
color: $gl-text-color-tertiary;
......@@ -666,7 +689,7 @@
left: 50%;
top: 0;
transform: translateX(-50%);
padding: 0 8px;
padding: 0 $gl-padding-8;
}
}
......@@ -700,17 +723,51 @@
.project-stats {
font-size: 0;
text-align: center;
max-width: 100%;
border-bottom: 1px solid $border-color;
.nav {
margin-top: $gl-padding-8;
margin-bottom: $gl-padding-8;
.scrolling-tabs-container {
.scrolling-tabs {
margin-top: $gl-padding-8;
margin-bottom: $gl-padding-8;
flex-wrap: wrap;
border-bottom: 0;
}
.fade-left,
.fade-right {
top: 0;
height: 100%;
.fa {
top: 50%;
margin-top: -$gl-padding-8;
}
}
.nav {
flex-basis: 100%;
+ .nav {
margin: $gl-padding-8 0;
}
}
@include media-breakpoint-down(md) {
flex-direction: column;
.nav {
flex-wrap: nowrap;
}
.nav:first-child {
margin-right: $gl-padding-8;
}
}
}
.nav {
> li {
display: inline-block;
margin-top: $gl-padding-4;
margin-bottom: $gl-padding-4;
&:not(:last-child) {
margin-right: $gl-padding;
......@@ -733,13 +790,17 @@
font-size: $gl-font-size;
line-height: $gl-btn-line-height;
color: $gl-text-color-secondary;
white-space: nowrap;
}
.stat-link {
border-bottom: 0;
&:hover,
&:focus {
color: $gl-text-color;
text-decoration: underline;
border-bottom: 0;
}
}
......@@ -869,7 +930,7 @@ pre.light-well {
}
.git-clone-holder {
width: 380px;
width: 320px;
.btn-clipboard {
border: 1px solid $border-color;
......
......@@ -60,9 +60,8 @@ module ButtonHelper
protocol = gitlab_config.protocol.upcase
dropdown_description = http_dropdown_description(protocol)
append_url = project.http_url_to_repo if append_link
geo_url = geo_primary_http_url_to_repo(project) if Gitlab::Geo.secondary?
dropdown_item_with_description(protocol, dropdown_description, href: append_url, geo_url: geo_url)
dropdown_item_with_description(protocol, dropdown_description, href: append_url, data: { clone_type: 'http' })
end
def http_dropdown_description(protocol)
......@@ -80,12 +79,11 @@ module ButtonHelper
end
append_url = project.ssh_url_to_repo if append_link
geo_url = geo_primary_ssh_url_to_repo(project) if Gitlab::Geo.secondary?
dropdown_item_with_description('SSH', dropdown_description, href: append_url, geo_url: geo_url)
dropdown_item_with_description('SSH', dropdown_description, href: append_url, data: { clone_type: 'ssh' })
end
def dropdown_item_with_description(title, description, href: nil, geo_url: nil)
def dropdown_item_with_description(title, description, href: nil, data: nil)
button_content = content_tag(:strong, title, class: 'dropdown-menu-inner-title')
button_content << content_tag(:span, description, class: 'dropdown-menu-inner-content') if description
......@@ -93,9 +91,7 @@ module ButtonHelper
(href ? button_content : title),
class: "#{title.downcase}-selector",
href: (href if href),
data: {
primary_url: (geo_url if geo_url)
}
data: (data if data)
end
def kerberos_clone_button(project)
......
......@@ -86,7 +86,7 @@ module IconsHelper
end
end
def visibility_level_icon(level, fw: true)
def visibility_level_icon(level, fw: true, options: {})
name =
case level
when Gitlab::VisibilityLevel::PRIVATE
......@@ -99,7 +99,7 @@ module IconsHelper
name << " fw" if fw
icon(name)
icon(name, options)
end
def file_type_icon_class(type, mode, name)
......
......@@ -353,6 +353,10 @@ module ProjectsHelper
end
end
def default_clone_label
_("Copy %{protocol} clone URL") % { protocol: default_clone_protocol.upcase }
end
def default_clone_protocol
if allowed_protocols_present?
enabled_protocol
......
......@@ -138,7 +138,7 @@ module VisibilityLevelHelper
end
def project_visibility_icon_description(level)
"#{visibility_level_label(level)} - #{project_visibility_level_description(level)}"
"#{project_visibility_level_description(level)}"
end
def visibility_level_label(level)
......
This diff is collapsed.
- empty_repo = @project.empty_repo?
.project-home-panel.text-center{ class: ("empty-project" if empty_repo) }
- license = @project.license_anchor_data
.project-home-panel{ class: ("empty-project" if empty_repo) }
.limit-container-width{ class: container_class }
.avatar-container.s70.project-avatar
= project_icon(@project, alt: @project.name, class: 'avatar s70 avatar-tile', width: 70, height: 70)
%h1.project-title.qa-project-name
= @project.name
%span.visibility-icon.has-tooltip{ data: { container: 'body' }, title: visibility_icon_description(@project) }
= visibility_level_icon(@project.visibility_level, fw: false)
.project-header.d-flex.flex-row.flex-wrap.align-items-center.append-bottom-8
.project-title-row.d-flex.align-items-center
.avatar-container.project-avatar.float-none
= project_icon(@project, alt: @project.name, class: 'avatar avatar-tile')
%h1.project-title.d-flex.align-items-baseline.qa-project-name
= @project.name
.project-metadata.d-flex.flex-row.flex-wrap.align-items-baseline
.project-visibility.d-inline-flex.align-items-baseline.visibility-icon.has-tooltip{ data: { container: 'body' }, title: visibility_icon_description(@project) }
= visibility_level_icon(@project.visibility_level, fw: false, options: {class: 'icon'})
= visibility_level_label(@project.visibility_level)
- if license.present?
.project-license.d-inline-flex.align-items-baseline
= link_to_if license.link, sprite_icon('scale', size: 16, css_class: 'icon') + license.label, license.link, class: license.enabled ? 'btn btn-link btn-secondary-hover-link' : 'btn btn-link'
- if @project.tag_list.present?
.project-tag-list.d-inline-flex.align-items-baseline.has-tooltip{ data: { container: 'body' }, title: @project.has_extra_tags? ? @project.tag_list.join(', ') : nil }
= sprite_icon('tag', size: 16, css_class: 'icon')
= @project.tags_to_show
- if @project.has_extra_tags?
= _("+ %{count} more") % { count: @project.count_of_extra_tags_not_shown }
.project-home-desc
- if @project.description.present?
= markdown_field(@project, :description)
.project-description
.project-description-markdown.read-more-container
= markdown_field(@project, :description)
%button.btn.btn-blank.btn-link.text-secondary.js-read-more-trigger.text-secondary.d-lg-none{ type: "button" }
= _("Read more")
- if can?(current_user, :read_project, @project)
.text-secondary.prepend-top-8
= s_('ProjectPage|Project ID: %{project_id}') % { project_id: @project.id }
......@@ -32,34 +51,42 @@
%br
= render "shared/mirror_status"
.project-badges.prepend-top-default.append-bottom-default
- @project.badges.each do |badge|
%a.append-right-8{ href: badge.rendered_link_url(@project),
target: '_blank',
rel: 'noopener noreferrer' }>
%img.project-badge{ src: badge.rendered_image_url(@project),
'aria-hidden': true,
alt: '' }>
- if @project.badges.present?
.project-badges.prepend-top-default.append-bottom-default
- @project.badges.each do |badge|
%a.append-right-8{ href: badge.rendered_link_url(@project),
target: '_blank',
rel: 'noopener noreferrer' }>
%img.project-badge{ src: badge.rendered_image_url(@project),
'aria-hidden': true,
alt: 'Project badge' }>
.project-repo-buttons
.count-buttons
.project-repo-buttons.d-inline-flex.flex-wrap
.count-buttons.d-inline-flex
= render 'projects/buttons/star'
= render 'projects/buttons/fork'
%span.d-none.d-sm-inline
- if can?(current_user, :download_code, @project)
.project-clone-holder
= render "shared/clone_panel"
- if can?(current_user, :download_code, @project)
.project-clone-holder.d-inline-flex.d-sm-none
= render "shared/mobile_clone_panel"
- if show_xcode_link?(@project)
.project-action-button.project-xcode.inline
= render "projects/buttons/xcode_link"
.project-clone-holder.d-none.d-sm-inline-flex
= render "shared/clone_panel"
- if current_user
- if can?(current_user, :download_code, @project)
- if show_xcode_link?(@project)
.project-action-button.project-xcode.inline
= render "projects/buttons/xcode_link"
- if current_user
- if can?(current_user, :download_code, @project)
.d-none.d-sm-inline-flex
= render 'projects/buttons/download', project: @project, ref: @ref
.d-none.d-sm-inline-flex
= render 'projects/buttons/dropdown'
.d-none.d-sm-inline-flex
= render 'projects/buttons/koding'
.d-none.d-sm-inline-flex
= render 'shared/notifications/button', notification_setting: @notification_setting
.d-none.d-sm-inline-flex
= render 'shared/members/access_request_buttons', source: @project
- anchors = local_assigns.fetch(:anchors, [])
- return unless anchors.any?
%ul.nav.justify-content-center
%ul.nav
- anchors.each do |anchor|
%li.nav-item
= link_to_if anchor.link, anchor.label, anchor.link, class: anchor.enabled ? 'nav-link stat-link' : "nav-link btn btn-#{anchor.class_modifier || 'missing'}" do
......
- unless @project.empty_repo?
- if current_user && can?(current_user, :fork_project, @project)
- if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2
= link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: _('Go to your fork'), class: 'btn has-tooltip' do
= custom_icon('icon_fork')
%span= s_('GoToYourFork|Fork')
- else
- can_create_fork = current_user.can?(:create_fork)
= link_to new_project_fork_path(@project),
class: "btn btn-default #{'has-tooltip disabled' unless can_create_fork}",
title: (_('You have reached your project limit') unless can_create_fork) do
= custom_icon('icon_fork')
%span= s_('CreateNewFork|Fork')
.count-with-arrow
%span.arrow
= link_to project_forks_path(@project), title: n_('Fork', 'Forks', @project.forks_count), class: 'count' do
= @project.forks_count
.count-badge.d-inline-flex.align-item-stretch.append-right-8
%span.fork-count.count-badge-count.d-flex.align-items-center
= link_to project_forks_path(@project), title: n_(s_('ProjectOverview|Fork'), s_('ProjectOverview|Forks'), @project.forks_count), class: 'count' do
= @project.forks_count
- if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2
= link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: s_('ProjectOverview|Go to your fork'), class: 'btn btn-default has-tooltip count-badge-button d-flex align-items-center fork-btn' do
= sprite_icon('fork', { css_class: 'icon' })
%span= s_('ProjectOverview|Fork')
- else
- can_create_fork = current_user.can?(:create_fork)
= link_to new_project_fork_path(@project),
class: "btn btn-default has-tooltip count-badge-button d-flex align-items-center fork-btn #{'has-tooltip disabled' unless can_create_fork}",
title: (s_('ProjectOverview|You have reached your project limit') unless can_create_fork) do
= sprite_icon('fork', { css_class: 'icon' })
%span= s_('ProjectOverview|Fork')
- if current_user
%button.btn.btn-default.star-btn.toggle-star{ type: "button", data: { endpoint: toggle_star_project_path(@project, :json) } }>
- if current_user.starred?(@project)
= sprite_icon('star')
%span.starred= _('Unstar')
- else
= sprite_icon('star-o')
%span= s_('StarProject|Star')
.count-with-arrow
%span.arrow
%span.count.star-count
.count-badge.d-inline-flex.align-item-stretch.append-right-8
%span.star-count.count-badge-count.d-flex.align-items-center
= @project.star_count
%button.count-badge-button.btn.btn-default.d-flex.align-items-center.star-btn.toggle-star{ type: "button", data: { endpoint: toggle_star_project_path(@project, :json) } }
- if current_user.starred?(@project)
= sprite_icon('star', { css_class: 'icon' })
%span.starred= s_('ProjectOverview|Unstar')
- else
= sprite_icon('star-o', { css_class: 'icon' })
%span= s_('ProjectOverview|Star')
- else
= link_to new_user_session_path, class: 'btn has-tooltip star-btn', title: _('You must sign in to star a project') do
= sprite_icon('star')
#{ s_('StarProject|Star') }
.count-with-arrow
%span.arrow
%span.count
.count-badge.d-inline-flex.align-item-stretch.append-right-8
%span.star-count.count-badge-count.d-flex.align-items-center
= @project.star_count
= link_to new_user_session_path, class: 'btn btn-default has-tooltip count-badge-button d-flex align-items-center star-btn', title: s_('ProjectOverview|You must sign in to star a project') do
= sprite_icon('star-o', { css_class: 'icon' })
%span= s_('ProjectOverview|Star')
......@@ -32,9 +32,13 @@
= _('Otherwise it is recommended you start with one of the options below.')
.prepend-top-20
%nav.project-stats{ class: container_class }
= render 'stat_anchor_list', anchors: @project.empty_repo_statistics_anchors
= render 'stat_anchor_list', anchors: @project.empty_repo_statistics_buttons
%nav.project-stats{ class: [container_class, ("limit-container-width" unless fluid_layout)] }
.scrolling-tabs-container.inner-page-scroll-tabs.is-smaller
.fade-left= icon('angle-left')
.fade-right= icon('angle-right')
.nav-links.scrolling-tabs
= render 'stat_anchor_list', anchors: @project.empty_repo_statistics_anchors
= render 'stat_anchor_list', anchors: @project.empty_repo_statistics_buttons
- if can?(current_user, :push_code, @project)
%div{ class: [container_class, ("limit-container-width" unless fluid_layout)] }
......@@ -42,7 +46,7 @@
.empty_wrapper
%h3#repo-command-line-instructions.page-title-empty
Command line instructions
.git-empty
.git-empty.js-git-empty
%fieldset
%h5 Git global setup
%pre.bg-light
......@@ -54,7 +58,7 @@
%h5 Create a new repository
%pre.bg-light
:preserve
git clone #{ content_tag(:span, default_url_to_repo, class: 'clone')}
git clone #{ content_tag(:span, default_url_to_repo, class: 'js-clone')}
cd #{h @project.path}
touch README.md
git add README.md
......@@ -69,7 +73,7 @@
:preserve
cd existing_folder
git init
git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'clone')}
git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'js-clone')}
git add .
git commit -m "Initial commit"
- if @project.can_current_user_push_to_default_branch?
......@@ -82,7 +86,7 @@
:preserve
cd existing_repo
git remote rename origin old-origin
git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'clone')}
git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'js-clone')}
- if @project.can_current_user_push_to_default_branch?
%span><
git push -u origin --all
......
......@@ -19,8 +19,13 @@
- if can?(current_user, :download_code, @project)
%nav.project-stats{ class: [container_class, ("limit-container-width" unless fluid_layout)] }
= render 'stat_anchor_list', anchors: @project.statistics_anchors(show_auto_devops_callout: show_auto_devops_callout)
= render 'stat_anchor_list', anchors: @project.statistics_buttons(show_auto_devops_callout: show_auto_devops_callout)
.scrolling-tabs-container.inner-page-scroll-tabs.is-smaller
.fade-left= icon('angle-left')
.fade-right= icon('angle-right')
.nav-links.scrolling-tabs
= render 'stat_anchor_list', anchors: @project.statistics_anchors(show_auto_devops_callout: show_auto_devops_callout)
= render 'stat_anchor_list', anchors: @project.statistics_buttons(show_auto_devops_callout: show_auto_devops_callout)
= repository_languages_bar(@project.repository_languages)
%div{ class: [container_class, ("limit-container-width" unless fluid_layout)] }
......
- project = project || @project
.git-clone-holder.input-group
.git-clone-holder.js-git-clone-holder.input-group
.input-group-prepend
- if allowed_protocols_present?
.input-group-text.clone-dropdown-btn.btn
%span
%span.js-clone-dropdown-label
= enabled_project_button(project, enabled_protocol)
- else
%a#clone-dropdown.input-group-text.btn.clone-dropdown-btn.qa-clone-dropdown{ href: '#', data: { toggle: 'dropdown' } }
%span
%span.js-clone-dropdown-label
= default_clone_protocol.upcase
= icon('caret-down')
%ul.dropdown-menu.dropdown-menu-selectable.clone-options-dropdown
......
- project = project || @project
- ssh_copy_label = _("Copy SSH clone URL")
- http_copy_label = _("Copy HTTPS clone URL")
.btn-group.mobile-git-clone.js-mobile-git-clone
= clipboard_button(button_text: default_clone_label, target: '#project_clone', hide_button_icon: true, class: "input-group-text clone-dropdown-btn js-clone-dropdown-label btn btn-default")
%button.btn.btn-default.dropdown-toggle.js-dropdown-toggle{ type: "button", data: { toggle: "dropdown" } }
= icon("caret-down", class: "dropdown-btn-icon")
%ul.dropdown-menu.dropdown-menu-selectable.dropdown-menu-right.clone-options-dropdown{ data: { dropdown: true } }
%li
= dropdown_item_with_description(ssh_copy_label, project.ssh_url_to_repo, href: project.ssh_url_to_repo, data: { clone_type: 'ssh' })
%li
= dropdown_item_with_description(http_copy_label, project.http_url_to_repo, href: project.http_url_to_repo, data: { clone_type: 'http' })
---
title: Update design of project overview page
merge_request: 20536
author:
type: changed
module EE
module ButtonHelper
extend ::Gitlab::Utils::Override
override :ssh_clone_button
def ssh_clone_button(project, append_link: true)
return super unless ::Gitlab::Geo.secondary?
if ::Gitlab::CurrentSettings.user_show_add_ssh_key_message? &&
current_user.try(:require_ssh_key?)
dropdown_description = _("You won't be able to pull or push project code via SSH until you add an SSH key to your profile")
end
append_url = project.ssh_url_to_repo if append_link
geo_url = geo_primary_http_url_to_repo(project)
dropdown_item_with_description('SSH', dropdown_description, href: append_url, data: { primary_url: geo_url, clone_type: 'ssh' })
end
override :http_clone_button
def http_clone_button(project, append_link: true)
return super unless ::Gitlab::Geo.secondary?
protocol = gitlab_config.protocol.upcase
dropdown_description = http_dropdown_description(protocol)
append_url = project.http_url_to_repo if append_link
geo_url = geo_primary_http_url_to_repo(project)
dropdown_item_with_description(protocol, dropdown_description, href: append_url, data: { primary_url: geo_url, clone_type: 'http' })
end
end
end
......@@ -191,6 +191,9 @@ msgstr[1] ""
msgid "%{unstaged} unstaged and %{staged} staged changes"
msgstr ""
msgid "+ %{count} more"
msgstr ""
msgid "+ %{moreCount} more"
msgstr ""
......@@ -399,15 +402,15 @@ msgstr ""
msgid "Add Kubernetes cluster"
msgstr ""
msgid "Add License"
msgstr ""
msgid "Add Readme"
msgstr ""
msgid "Add additional text to appear in all email communications. %{character_limit} character limit"
msgstr ""
msgid "Add license"
msgstr ""
msgid "Add new application"
msgstr ""
......@@ -2223,6 +2226,15 @@ msgstr ""
msgid "ConvDev Index"
msgstr ""
msgid "Copy %{protocol} clone URL"
msgstr ""
msgid "Copy HTTPS clone URL"
msgstr ""
msgid "Copy SSH clone URL"
msgstr ""
msgid "Copy SSH public key to clipboard"
msgstr ""
......@@ -2328,9 +2340,6 @@ msgstr ""
msgid "Create project label"
msgstr ""
msgid "CreateNewFork|Fork"
msgstr ""
msgid "CreateTag|Tag"
msgstr ""
......@@ -3233,11 +3242,6 @@ msgstr ""
msgid "For public projects, anyone can view pipelines and access job details (output logs and artifacts)"
msgstr ""
msgid "Fork"
msgid_plural "Forks"
msgstr[0] ""
msgstr[1] ""
msgid "ForkedFromProjectPath|Forked from"
msgstr ""
......@@ -3646,12 +3650,6 @@ msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
msgid "Go to your fork"
msgstr ""
msgid "GoToYourFork|Fork"
msgstr ""
msgid "Google Code import"
msgstr ""
......@@ -4967,6 +4965,9 @@ msgstr ""
msgid "No labels with such name or description"
msgstr ""
msgid "No license. All rights reserved"
msgstr ""
msgid "No merge requests for the selected time period."
msgstr ""
......@@ -5707,6 +5708,27 @@ msgstr ""
msgid "ProjectLifecycle|Stage"
msgstr ""
msgid "ProjectOverview|Fork"
msgstr ""
msgid "ProjectOverview|Forks"
msgstr ""
msgid "ProjectOverview|Go to your fork"
msgstr ""
msgid "ProjectOverview|Star"
msgstr ""
msgid "ProjectOverview|Unstar"
msgstr ""
msgid "ProjectOverview|You have reached your project limit"
msgstr ""
msgid "ProjectOverview|You must sign in to star a project"
msgstr ""
msgid "ProjectPage|Project ID: %{project_id}"
msgstr ""
......@@ -8154,9 +8176,6 @@ msgstr ""
msgid "You must have maintainer access to force delete a lock"
msgstr ""
msgid "You must sign in to star a project"
msgstr ""
msgid "You need a different license to enable FileLocks feature"
msgstr ""
......
......@@ -25,7 +25,7 @@ module QA
end
view 'app/views/projects/buttons/_fork.html.haml' do
element :fork_label, "%span= s_('GoToYourFork|Fork')"
element :fork_label, "%span= s_('ProjectOverview|Fork')"
element :fork_link, "link_to new_project_fork_path(@project)"
end
......@@ -34,7 +34,7 @@ module QA
end
view 'app/presenters/project_presenter.rb' do
element :new_file_button, "label: _('New file'),"
element :new_file_button, "_('New file'),"
end
def project_name
......
......@@ -36,7 +36,7 @@ describe 'Projects > Files > Project owner creates a license file', :js do
end
it 'project maintainer creates a license file from the "Add license" link' do
click_link 'Add License'
click_link 'Add license'
expect(page).to have_content('New file')
expect(current_path).to eq(
......
......@@ -10,7 +10,7 @@ describe 'Projects > Files > Project owner sees a link to create a license file
it 'project maintainer creates a license file from a template' do
visit project_path(project)
click_on 'Add License'
click_on 'Add license'
expect(page).to have_content('New file')
expect(current_path).to eq(
......
require 'spec_helper'
describe 'Projects > Show > User sees setup shortcut buttons' do
# For "New file", "Add License" functionality,
# For "New file", "Add license" functionality,
# see spec/features/projects/files/project_owner_creates_license_file_spec.rb
# see spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb
......@@ -58,9 +58,9 @@ describe 'Projects > Show > User sees setup shortcut buttons' do
end
end
it '"Add License" button linked to new file populated for a license' do
page.within('.project-stats') do
expect(page).to have_link('Add License', href: presenter.add_license_path)
it '"Add license" button linked to new file populated for a license' do
page.within('.project-metadata') do
expect(page).to have_link('Add license', href: presenter.add_license_path)
end
end
......@@ -201,13 +201,13 @@ describe 'Projects > Show > User sees setup shortcut buttons' do
end
end
it 'no "Add License" button if the project already has a license' do
it 'no "Add license" button if the project already has a license' do
visit project_path(project)
expect(project.repository.license_blob).not_to be_nil
page.within('.project-stats') do
expect(page).not_to have_link('Add License')
expect(page).not_to have_link('Add license')
end
end
......
......@@ -2,6 +2,7 @@ require 'spec_helper'
describe 'Project' do
include ProjectForksHelper
include MobileHelpers
describe 'creating from template' do
let(:user) { create(:user) }
......@@ -54,25 +55,72 @@ describe 'Project' do
it 'parses Markdown' do
project.update_attribute(:description, 'This is **my** project')
visit path
expect(page).to have_css('.project-home-desc > p > strong')
expect(page).to have_css('.project-description > .project-description-markdown > p > strong')
end
it 'passes through html-pipeline' do
project.update_attribute(:description, 'This project is the :poop:')
visit path
expect(page).to have_css('.project-home-desc > p > gl-emoji')
expect(page).to have_css('.project-description > .project-description-markdown > p > gl-emoji')
end
it 'sanitizes unwanted tags' do
project.update_attribute(:description, "```\ncode\n```")
visit path
expect(page).not_to have_css('.project-home-desc code')
expect(page).not_to have_css('.project-description code')
end
it 'permits `rel` attribute on links' do
project.update_attribute(:description, 'https://google.com/')
visit path
expect(page).to have_css('.project-home-desc a[rel]')
expect(page).to have_css('.project-description a[rel]')
end
context 'read more', :js do
let(:read_more_selector) { '.read-more-container' }
let(:read_more_trigger_selector) { '.project-home-desc .js-read-more-trigger' }
it 'does not display "read more" link on desktop breakpoint' do
project.update_attribute(:description, 'This is **my** project')
visit path
expect(find(read_more_trigger_selector, visible: false)).not_to be_visible
end
it 'displays "read more" link on mobile breakpoint' do
project.update_attribute(:description, 'This is **my** project')
visit path
resize_screen_xs
find(read_more_trigger_selector).click
expect(page).to have_css('.project-description .is-expanded')
end
end
end
describe 'copy clone URL to clipboard', :js do
let(:project) { create(:project, :repository) }
let(:path) { project_path(project) }
before do
sign_in(create(:admin))
visit path
end
context 'desktop component' do
it 'shows on md and larger breakpoints' do
expect(find('.git-clone-holder')).to be_visible
expect(find('.mobile-git-clone', visible: false)).not_to be_visible
end
end
context 'mobile component' do
it 'shows mobile component on sm and smaller breakpoints' do
resize_screen_xs
expect(find('.mobile-git-clone')).to be_visible
expect(find('.git-clone-holder', visible: false)).not_to be_visible
end
end
end
......
......@@ -6,6 +6,7 @@ describe 'Projects (JavaScript fixtures)', type: :controller do
let(:admin) { create(:admin) }
let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
let(:project) { create(:project, namespace: namespace, path: 'builds-project') }
let(:project_with_repo) { create(:project, :repository, description: 'Code and stuff') }
let(:project_variable_populated) { create(:project, namespace: namespace, path: 'builds-project2') }
let!(:variable1) { create(:ci_variable, project: project_variable_populated) }
let!(:variable2) { create(:ci_variable, project: project_variable_populated) }
......@@ -38,6 +39,15 @@ describe 'Projects (JavaScript fixtures)', type: :controller do
store_frontend_fixture(response, example.description)
end
it 'projects/overview.html.raw' do |example|
get :show,
namespace_id: project_with_repo.namespace.to_param,
id: project_with_repo
expect(response).to be_success
store_frontend_fixture(response, example.description)
end
it 'projects/edit.html.raw' do |example|
get :edit,
namespace_id: project.namespace.to_param,
......
import initReadMore from '~/read_more';
describe('Read more click-to-expand functionality', () => {
const fixtureName = 'projects/overview.html.raw';
preloadFixtures(fixtureName);
beforeEach(() => {
loadFixtures(fixtureName);
});
describe('expands target element', () => {
it('adds "is-expanded" class to target element', () => {
const target = document.querySelector('.read-more-container');
const trigger = document.querySelector('.js-read-more-trigger');
initReadMore();
trigger.click();
expect(target.classList.contains('is-expanded')).toEqual(true);
});
});
});
This diff is collapsed.
......@@ -9,6 +9,7 @@ describe 'projects/_home_panel' do
allow(view).to receive(:current_user).and_return(user)
allow(view).to receive(:can?).with(user, :read_project, project).and_return(false)
allow(project).to receive(:license_anchor_data).and_return(false)
end
context 'when user is signed in' do
......@@ -63,6 +64,7 @@ describe 'projects/_home_panel' do
allow(view).to receive(:current_user).and_return(user)
allow(view).to receive(:can?).with(user, :read_project, project).and_return(false)
allow(project).to receive(:license_anchor_data).and_return(false)
end
context 'has no badges' do
......@@ -71,8 +73,7 @@ describe 'projects/_home_panel' do
it 'should not render any badge' do
render
expect(rendered).to have_selector('.project-badges')
expect(rendered).not_to have_selector('.project-badges > a')
expect(rendered).not_to have_selector('.project-badges')
end
end
......@@ -118,6 +119,7 @@ describe 'projects/_home_panel' do
assign(:project, project)
allow(view).to receive(:current_user).and_return(user)
allow(project).to receive(:license_anchor_data).and_return(false)
end
context 'user can read project' do
......
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