summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRubén Dávila <ruben@gitlab.com>2017-08-16 18:29:04 -0500
committerRubén Dávila <ruben@gitlab.com>2017-08-16 18:29:04 -0500
commita3fbe3fedeb700f43bfa0de7e9113b3cff943c4e (patch)
tree4075549fa4b898c1187e8adecd112cea50ee880b
parent72d5165bd57472692c77d6a9d159e65058513bf3 (diff)
downloadgitlab-ce-revert-merge-request-9199.tar.gz
WIP: Revert "Merge branch '26200-convert-sidebar-to-dropdown' into 'master'"revert-merge-request-9199
This reverts commit aa792b91bbddeeb61ce77f9525fcaf238a9ad331, reversing changes made to 5d8f5328baca93b9134f10ae593e71834578a9f8. Conflicts: app/assets/javascripts/application.js app/assets/javascripts/right_sidebar.js app/assets/stylesheets/framework.scss app/assets/stylesheets/framework/header.scss app/assets/stylesheets/framework/sidebar.scss app/assets/stylesheets/framework/variables.scss app/assets/stylesheets/pages/issuable.scss app/assets/stylesheets/print.scss app/controllers/profiles/preferences_controller.rb app/helpers/nav_helper.rb app/models/user.rb app/views/layouts/_page.html.haml app/views/layouts/application.html.haml app/views/layouts/header/_default.html.haml app/views/layouts/nav/_dashboard.html.haml app/views/profiles/preferences/show.html.haml db/schema.rb doc/api/users.md spec/features/dashboard/active_tab_spec.rb spec/features/dashboard/issuables_counter_spec.rb spec/features/dashboard/shortcuts_spec.rb spec/features/profiles/preferences_spec.rb spec/fixtures/api/schemas/user/public.json spec/models/user_spec.rb Please enter the commit message for your changes. Lines starting with '#' will be ignored, and an empty message aborts the commit. On branch revert-merge-request-9199 You are currently reverting commit aa792b91bb.
-rw-r--r--app/assets/javascripts/project_label_subscription.js6
-rw-r--r--app/assets/javascripts/right_sidebar.js7
-rw-r--r--app/assets/javascripts/sidebar.js.es6111
-rw-r--r--app/assets/stylesheets/framework.scss37
-rw-r--r--app/assets/stylesheets/framework/animations.scss3
-rw-r--r--app/assets/stylesheets/framework/gitlab-theme.scss144
-rw-r--r--app/assets/stylesheets/framework/header.scss19
-rw-r--r--app/assets/stylesheets/framework/sidebar.scss196
-rw-r--r--app/assets/stylesheets/framework/variables.scss6
-rw-r--r--app/assets/stylesheets/pages/issuable.scss9
-rw-r--r--app/assets/stylesheets/pages/profiles/preferences.scss39
-rw-r--r--app/assets/stylesheets/print.scss7
-rw-r--r--app/controllers/admin/users_controller.rb1
-rw-r--r--app/controllers/profiles/preferences_controller.rb3
-rw-r--r--app/helpers/nav_helper.rb19
-rw-r--r--app/helpers/preferences_helper.rb4
-rw-r--r--app/models/user.rb1
-rw-r--r--app/views/layouts/_page.html.haml27
-rw-r--r--app/views/layouts/application.html.haml7
-rw-r--r--app/views/layouts/header/_default.html.haml18
-rw-r--r--app/views/layouts/nav/_dashboard.html.haml44
-rw-r--r--app/views/layouts/nav/_explore.html.haml2
-rw-r--r--app/views/profiles/preferences/show.html.haml17
-rw-r--r--app/views/profiles/preferences/update.js.erb4
-rw-r--r--config/gitlab.yml.example10
-rw-r--r--config/initializers/1_settings.rb1
-rw-r--r--db/post_migrate/20170215200045_remove_theme_id_from_users.rb9
-rw-r--r--doc/api/keys.md1
-rw-r--r--doc/api/session.md1
-rw-r--r--doc/api/users.md5
-rw-r--r--features/project/labels.feature15
-rw-r--r--features/steps/project/labels.rb32
-rw-r--r--lib/api/entities.rb2
-rw-r--r--lib/gitlab/themes.rb87
-rw-r--r--spec/controllers/profiles/preferences_controller_spec.rb6
-rw-r--r--spec/features/dashboard/active_tab_spec.rb5
-rw-r--r--spec/features/dashboard/shortcuts_spec.rb4
-rw-r--r--spec/features/profiles/preferences_spec.rb2
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/user/login.json1
-rw-r--r--spec/helpers/preferences_helper_spec.rb26
-rw-r--r--spec/javascripts/dashboard_spec.js.es637
-rw-r--r--spec/javascripts/fixtures/dashboard.html.haml45
-rw-r--r--spec/javascripts/project_dashboard_spec.js.es686
-rw-r--r--spec/lib/gitlab/themes_spec.rb48
-rw-r--r--spec/models/user_spec.rb2
-rw-r--r--spec/support/gitlab_stubs/session.json4
-rw-r--r--spec/support/gitlab_stubs/user.json4
47 files changed, 1132 insertions, 32 deletions
diff --git a/app/assets/javascripts/project_label_subscription.js b/app/assets/javascripts/project_label_subscription.js
index 0a811627600..8365f7118d5 100644
--- a/app/assets/javascripts/project_label_subscription.js
+++ b/app/assets/javascripts/project_label_subscription.js
@@ -38,15 +38,13 @@
this.$buttons.attr('data-status', newStatus);
this.$buttons.find('> span').text(newAction);
- this.$buttons.map((button) => {
+ for (const button of this.$buttons) {
const $button = $(button);
if ($button.attr('data-original-title')) {
$button.tooltip('hide').attr('data-original-title', newAction).tooltip('fixTitle');
}
-
- return button;
- });
+ }
});
}
}
diff --git a/app/assets/javascripts/right_sidebar.js b/app/assets/javascripts/right_sidebar.js
index fa958d75fa4..668277ef111 100644
--- a/app/assets/javascripts/right_sidebar.js
+++ b/app/assets/javascripts/right_sidebar.js
@@ -23,15 +23,22 @@ import SidebarHeightManager from './sidebar_height_manager';
};
Sidebar.prototype.addEventListeners = function() {
+<<<<<<< HEAD
SidebarHeightManager.init();
const $document = $(document);
+=======
+>>>>>>> parent of aa792b91bb... Merge branch '26200-convert-sidebar-to-dropdown' into 'master'
this.sidebar.on('click', '.sidebar-collapsed-icon', this, this.sidebarCollapseClicked);
$('.dropdown').on('hidden.gl.dropdown', this, this.onSidebarDropdownHidden);
$('.dropdown').on('loading.gl.dropdown', this.sidebarDropdownLoading);
$('.dropdown').on('loaded.gl.dropdown', this.sidebarDropdownLoaded);
+<<<<<<< HEAD
$document.on('click', '.js-sidebar-toggle', function(e, triggered) {
+=======
+ $(document).on('click', '.js-sidebar-toggle', function(e, triggered) {
+>>>>>>> parent of aa792b91bb... Merge branch '26200-convert-sidebar-to-dropdown' into 'master'
var $allGutterToggleIcons, $this, $thisIcon;
e.preventDefault();
$this = $(this);
diff --git a/app/assets/javascripts/sidebar.js.es6 b/app/assets/javascripts/sidebar.js.es6
new file mode 100644
index 00000000000..33e4b7db681
--- /dev/null
+++ b/app/assets/javascripts/sidebar.js.es6
@@ -0,0 +1,111 @@
+/* eslint-disable arrow-parens, class-methods-use-this, no-param-reassign */
+/* global Cookies */
+
+(() => {
+ const pinnedStateCookie = 'pin_nav';
+ const sidebarBreakpoint = 1024;
+
+ const pageSelector = '.page-with-sidebar';
+ const navbarSelector = '.navbar-gitlab';
+ const sidebarWrapperSelector = '.sidebar-wrapper';
+ const sidebarContentSelector = '.nav-sidebar';
+
+ const pinnedToggleSelector = '.js-nav-pin';
+ const sidebarToggleSelector = '.toggle-nav-collapse, .side-nav-toggle';
+
+ const pinnedPageClass = 'page-sidebar-pinned';
+ const expandedPageClass = 'page-sidebar-expanded';
+
+ const pinnedNavbarClass = 'header-sidebar-pinned';
+ const expandedNavbarClass = 'header-sidebar-expanded';
+
+ class Sidebar {
+ constructor() {
+ if (!Sidebar.singleton) {
+ Sidebar.singleton = this;
+ Sidebar.singleton.init();
+ }
+
+ return Sidebar.singleton;
+ }
+
+ init() {
+ this.isPinned = Cookies.get(pinnedStateCookie) === 'true';
+ this.isExpanded = (
+ window.innerWidth >= sidebarBreakpoint &&
+ $(pageSelector).hasClass(expandedPageClass)
+ );
+ $(window).on('resize', () => this.setSidebarHeight());
+ $(document)
+ .on('click', sidebarToggleSelector, () => this.toggleSidebar())
+ .on('click', pinnedToggleSelector, () => this.togglePinnedState())
+ .on('click', 'html, body, a, button', (e) => this.handleClickEvent(e))
+ .on('DOMContentLoaded', () => this.renderState())
+ .on('scroll', () => this.setSidebarHeight())
+ .on('todo:toggle', (e, count) => this.updateTodoCount(count));
+ this.renderState();
+ this.setSidebarHeight();
+ }
+
+ handleClickEvent(e) {
+ if (this.isExpanded && (!this.isPinned || window.innerWidth < sidebarBreakpoint)) {
+ const $target = $(e.target);
+ const targetIsToggle = $target.closest(sidebarToggleSelector).length > 0;
+ const targetIsSidebar = $target.closest(sidebarWrapperSelector).length > 0;
+ if (!targetIsToggle && (!targetIsSidebar || $target.closest('a'))) {
+ this.toggleSidebar();
+ }
+ }
+ }
+
+ updateTodoCount(count) {
+ $('.js-todos-count').text(gl.text.addDelimiter(count));
+ }
+
+ toggleSidebar() {
+ this.isExpanded = !this.isExpanded;
+ this.renderState();
+ }
+
+ setSidebarHeight() {
+ const $navHeight = $('.navbar-gitlab').outerHeight() + $('.layout-nav').outerHeight();
+ const diff = $navHeight - $('body').scrollTop();
+ if (diff > 0) {
+ $('.js-right-sidebar').outerHeight($(window).height() - diff);
+ } else {
+ $('.js-right-sidebar').outerHeight('100%');
+ }
+ }
+
+ togglePinnedState() {
+ this.isPinned = !this.isPinned;
+ if (!this.isPinned) {
+ this.isExpanded = false;
+ }
+ Cookies.set(pinnedStateCookie, this.isPinned ? 'true' : 'false', { expires: 3650 });
+ this.renderState();
+ }
+
+ renderState() {
+ $(pageSelector)
+ .toggleClass(pinnedPageClass, this.isPinned && this.isExpanded)
+ .toggleClass(expandedPageClass, this.isExpanded);
+ $(navbarSelector)
+ .toggleClass(pinnedNavbarClass, this.isPinned && this.isExpanded)
+ .toggleClass(expandedNavbarClass, this.isExpanded);
+
+ const $pinnedToggle = $(pinnedToggleSelector);
+ const tooltipText = this.isPinned ? 'Unpin navigation' : 'Pin navigation';
+ const tooltipState = $pinnedToggle.attr('aria-describedby') && this.isExpanded ? 'show' : 'hide';
+ $pinnedToggle.attr('title', tooltipText).tooltip('fixTitle').tooltip(tooltipState);
+
+ if (this.isExpanded) {
+ const sidebarContent = $(sidebarContentSelector);
+ setTimeout(() => { sidebarContent.niceScroll().updateScrollBar(); }, 200);
+ }
+ }
+ }
+
+ window.gl = window.gl || {};
+ gl.Sidebar = Sidebar;
+})();
diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss
index b2b3297e880..f1df760b915 100644
--- a/app/assets/stylesheets/framework.scss
+++ b/app/assets/stylesheets/framework.scss
@@ -4,6 +4,7 @@
@import 'framework/tw_bootstrap';
@import "framework/layout";
+<<<<<<< HEAD
@import "framework/animations";
@import "framework/avatar";
@import "framework/asciidoctor";
@@ -39,6 +40,42 @@
@import "framework/timeline";
@import "framework/typography";
@import "framework/zen";
+=======
+@import "framework/animations.scss";
+@import "framework/avatar.scss";
+@import "framework/asciidoctor.scss";
+@import "framework/blocks.scss";
+@import "framework/buttons.scss";
+@import "framework/badges.scss";
+@import "framework/calendar.scss";
+@import "framework/callout.scss";
+@import "framework/common.scss";
+@import "framework/dropdowns.scss";
+@import "framework/files.scss";
+@import "framework/filters.scss";
+@import "framework/flash.scss";
+@import "framework/forms.scss";
+@import "framework/gfm.scss";
+@import "framework/gitlab-theme.scss";
+@import "framework/header.scss";
+@import "framework/highlight.scss";
+@import "framework/issue_box.scss";
+@import "framework/jquery.scss";
+@import "framework/lists.scss";
+@import "framework/logo.scss";
+@import "framework/markdown_area.scss";
+@import "framework/mobile.scss";
+@import "framework/modal.scss";
+@import "framework/nav.scss";
+@import "framework/pagination.scss";
+@import "framework/panels.scss";
+@import "framework/selects.scss";
+@import "framework/sidebar.scss";
+@import "framework/tables.scss";
+@import "framework/timeline.scss";
+@import "framework/typography.scss";
+@import "framework/zen.scss";
+>>>>>>> parent of aa792b91bb... Merge branch '26200-convert-sidebar-to-dropdown' into 'master'
@import "framework/blank";
@import "framework/wells";
@import "framework/page-header";
diff --git a/app/assets/stylesheets/framework/animations.scss b/app/assets/stylesheets/framework/animations.scss
index 667b73e150d..234ca0b11a9 100644
--- a/app/assets/stylesheets/framework/animations.scss
+++ b/app/assets/stylesheets/framework/animations.scss
@@ -116,7 +116,7 @@
}
.btn,
-.global-dropdown-toggle {
+.side-nav-toggle {
@include transition(background-color, border-color, color, box-shadow);
}
@@ -140,6 +140,7 @@ a {
@include transition(background-color, box-shadow);
}
+.nav-sidebar a,
.dropdown-menu a,
.dropdown-menu button,
.dropdown-menu-nav a {
diff --git a/app/assets/stylesheets/framework/gitlab-theme.scss b/app/assets/stylesheets/framework/gitlab-theme.scss
new file mode 100644
index 00000000000..d6566dc4ec9
--- /dev/null
+++ b/app/assets/stylesheets/framework/gitlab-theme.scss
@@ -0,0 +1,144 @@
+/**
+ * Styles the GitLab application with a specific color theme
+ *
+ * $color-light -
+ * $color -
+ * $color-darker -
+ * $color-dark -
+ */
+@mixin gitlab-theme($color-light, $color, $color-darker, $color-dark) {
+ .page-with-sidebar {
+ .toggle-nav-collapse,
+ .pin-nav-btn {
+ color: $color-light;
+
+ &:hover {
+ color: $white-light;
+ }
+ }
+
+ .sidebar-wrapper {
+ background: $color-darker;
+ }
+
+ .sidebar-action-buttons {
+ color: $color-light;
+ background-color: lighten($color-darker, 5%);
+ }
+
+ .nav-sidebar {
+ li {
+ a {
+ color: $color-light;
+
+ &:hover,
+ &:focus,
+ &:active {
+ background: $color-dark;
+ }
+
+ i {
+ color: $color-light;
+ }
+
+ path,
+ polygon {
+ fill: $color-light;
+ }
+
+ .count {
+ color: $color-light;
+ background: $color-dark;
+ }
+
+ svg {
+ position: relative;
+ top: 3px;
+ }
+ }
+
+ &.separate-item {
+ border-top: 1px solid $color;
+ }
+
+ &.active a {
+ color: $white-light;
+ background: $color-dark;
+
+ &.no-highlight {
+ border: none;
+ }
+
+ i {
+ color: $white-light;
+ }
+
+ path,
+ polygon {
+ fill: $white-light;
+ }
+ }
+ }
+
+ .about-gitlab {
+ color: $color-light;
+ }
+ }
+ }
+}
+
+$theme-charcoal-light: #b9bbbe;
+$theme-charcoal: #485157;
+$theme-charcoal-dark: #3d454d;
+$theme-charcoal-darker: #383f45;
+
+$theme-blue-light: #becde9;
+$theme-blue: #2980b9;
+$theme-blue-dark: #1970a9;
+$theme-blue-darker: #096099;
+
+$theme-graphite-light: #ccc;
+$theme-graphite: #777;
+$theme-graphite-dark: #666;
+$theme-graphite-darker: #555;
+
+$theme-black-light: #979797;
+$theme-black: #373737;
+$theme-black-dark: #272727;
+$theme-black-darker: #222;
+
+$theme-green-light: #adc;
+$theme-green: #019875;
+$theme-green-dark: #018865;
+$theme-green-darker: #017855;
+
+$theme-violet-light: #98c;
+$theme-violet: #548;
+$theme-violet-dark: #436;
+$theme-violet-darker: #325;
+
+body {
+ &.ui_blue {
+ @include gitlab-theme($theme-blue-light, $theme-blue, $theme-blue-dark, $theme-blue-darker);
+ }
+
+ &.ui_charcoal {
+ @include gitlab-theme($theme-charcoal-light, $theme-charcoal, $theme-charcoal-dark, $theme-charcoal-darker);
+ }
+
+ &.ui_graphite {
+ @include gitlab-theme($theme-graphite-light, $theme-graphite, $theme-graphite-dark, $theme-graphite-darker);
+ }
+
+ &.ui_black {
+ @include gitlab-theme($theme-black-light, $theme-black, $theme-black-dark, $theme-black-darker);
+ }
+
+ &.ui_green {
+ @include gitlab-theme($theme-green-light, $theme-green, $theme-green-dark, $theme-green-darker);
+ }
+
+ &.ui_violet {
+ @include gitlab-theme($theme-violet-light, $theme-violet, $theme-violet-dark, $theme-violet-darker);
+ }
+}
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index b677882eba4..59fb1d760e8 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -132,6 +132,7 @@ header {
}
}
}
+<<<<<<< HEAD
}
&.navbar-gitlab-new {
@@ -161,10 +162,26 @@ header {
li {
&.active a {
font-weight: bold;
+=======
+
+ .side-nav-toggle {
+ position: absolute;
+ left: -10px;
+ margin: 7px 0;
+ font-size: 18px;
+ padding: 6px 10px;
+ border: none;
+ background-color: $gray-light;
+
+ &:hover {
+ background-color: $white-normal;
+ color: $gl-header-nav-hover-color;
+>>>>>>> parent of aa792b91bb... Merge branch '26200-convert-sidebar-to-dropdown' into 'master'
}
}
}
+<<<<<<< HEAD
.global-dropdown-toggle {
margin: 7px 0;
font-size: 18px;
@@ -182,6 +199,8 @@ header {
}
}
+=======
+>>>>>>> parent of aa792b91bb... Merge branch '26200-convert-sidebar-to-dropdown' into 'master'
.header-content {
display: flex;
justify-content: space-between;
diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss
index 40e8a928e6e..50d8198577e 100644
--- a/app/assets/stylesheets/framework/sidebar.scss
+++ b/app/assets/stylesheets/framework/sidebar.scss
@@ -1,3 +1,36 @@
+.page-with-sidebar {
+ padding-bottom: 25px;
+ transition: padding $sidebar-transition-duration;
+
+ &.page-sidebar-pinned {
+ .sidebar-wrapper {
+ box-shadow: none;
+ }
+ }
+
+ .sidebar-wrapper {
+ position: fixed;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ height: 100%;
+ width: 0;
+ overflow: hidden;
+ transition: width $sidebar-transition-duration;
+ box-shadow: 2px 0 16px 0 $black-transparent;
+ }
+}
+
+.sidebar-wrapper {
+ z-index: 1000;
+ background: $gray-light;
+
+ .nicescroll-rails-hr {
+ // TODO: Figure out why nicescroll doesn't hide horizontal bar
+ display: none!important;
+ }
+}
+
.content-wrapper {
width: 100%;
transition: padding $sidebar-transition-duration;
@@ -14,6 +47,105 @@
}
}
+.nav-sidebar {
+ position: absolute;
+ top: 50px;
+ bottom: 0;
+ width: $sidebar_width;
+ overflow-y: auto;
+ overflow-x: hidden;
+
+ &.navbar-collapse {
+ padding: 0 !important;
+ }
+
+ li {
+ &.separate-item {
+ padding-top: 10px;
+ margin-top: 10px;
+ }
+
+ .icon-container {
+ width: 34px;
+ display: inline-block;
+ text-align: center;
+ }
+
+ a {
+ padding: 7px $gl-sidebar-padding;
+ font-size: $gl-font-size;
+ line-height: 24px;
+ display: block;
+ text-decoration: none;
+ font-weight: normal;
+
+ &:hover,
+ &:active,
+ &:focus {
+ text-decoration: none;
+ }
+
+ i {
+ font-size: 16px;
+ }
+
+ i,
+ svg {
+ margin-right: 13px;
+ }
+ }
+ }
+
+ .count {
+ float: right;
+ padding: 0 8px;
+ border-radius: 6px;
+ }
+
+ .about-gitlab {
+ padding: 7px $gl-sidebar-padding;
+ font-size: $gl-font-size;
+ line-height: 24px;
+ display: block;
+ text-decoration: none;
+ font-weight: normal;
+ position: absolute;
+ bottom: 10px;
+ }
+}
+
+.sidebar-action-buttons {
+ width: $sidebar_width;
+ position: absolute;
+ top: 0;
+ left: 0;
+ min-height: 50px;
+ padding: 5px 0;
+ font-size: 18px;
+ line-height: 30px;
+
+ .toggle-nav-collapse {
+ left: 0;
+ }
+
+ .pin-nav-btn {
+ right: 0;
+ display: none;
+
+ @media (min-width: $sidebar-breakpoint) {
+ display: block;
+ }
+
+ .fa {
+ transition: transform .15s;
+
+ .page-sidebar-pinned & {
+ transform: rotate(90deg);
+ }
+ }
+ }
+}
+
.nav-header-btn {
padding: 10px $gl-sidebar-padding;
color: inherit;
@@ -29,16 +161,64 @@
}
}
+.page-sidebar-expanded {
+ .sidebar-wrapper {
+ width: $sidebar_width;
+ }
+}
+
+.page-sidebar-pinned {
+ .content-wrapper,
+ .layout-nav {
+ @media (min-width: $sidebar-breakpoint) {
+ padding-left: $sidebar_width;
+ }
+ }
+
+ .merge-request-tabs-holder.affix {
+ @media (min-width: $sidebar-breakpoint) {
+ left: $sidebar_width;
+ }
+ }
+
+ &.right-sidebar-expanded {
+ .line-resolve-all-container {
+ @media (min-width: $sidebar-breakpoint) {
+ display: none;
+ }
+ }
+ }
+}
+
+header.header-sidebar-pinned {
+ @media (min-width: $sidebar-breakpoint) {
+ padding-left: ($sidebar_width + $gl-padding);
+
+ .side-nav-toggle {
+ display: none;
+ }
+
+ .header-content {
+ padding-left: 0;
+ }
+ }
+}
+
.right-sidebar-collapsed {
padding-right: 0;
@media (min-width: $screen-sm-min) {
+<<<<<<< HEAD
&:not(.wiki-sidebar):not(.build-sidebar):not(.issuable-bulk-update-sidebar) .content-wrapper {
padding-right: $gutter_collapsed_width;
+=======
+ .content-wrapper {
+ padding-right: $sidebar_collapsed_width;
+>>>>>>> parent of aa792b91bb... Merge branch '26200-convert-sidebar-to-dropdown' into 'master'
}
.merge-request-tabs-holder.affix {
- right: $gutter_collapsed_width;
+ right: $sidebar_collapsed_width;
}
}
@@ -56,8 +236,13 @@
z-index: 300;
@media (min-width: $screen-sm-min) and (max-width: $screen-sm-max) {
+<<<<<<< HEAD
&:not(.wiki-sidebar):not(.build-sidebar):not(.issuable-bulk-update-sidebar) .content-wrapper {
padding-right: $gutter_collapsed_width;
+=======
+ &:not(.build-sidebar):not(.wiki-sidebar) {
+ padding-right: $sidebar_collapsed_width;
+>>>>>>> parent of aa792b91bb... Merge branch '26200-convert-sidebar-to-dropdown' into 'master'
}
}
@@ -71,9 +256,16 @@
}
&.with-overlay .merge-request-tabs-holder.affix {
- right: $gutter_collapsed_width;
+ right: $sidebar_collapsed_width;
}
}
+<<<<<<< HEAD
+=======
+
+ &.with-overlay {
+ padding-right: $sidebar_collapsed_width;
+ }
+>>>>>>> parent of aa792b91bb... Merge branch '26200-convert-sidebar-to-dropdown' into 'master'
}
.right-sidebar {
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 3c109a5a929..f302292eaa9 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -1,6 +1,8 @@
/*
* Layout
*/
+$sidebar_collapsed_width: 62px;
+$sidebar_width: 220px;
$gutter_collapsed_width: 62px;
$gutter_width: 290px;
$gutter_inner_width: 250px;
@@ -590,6 +592,7 @@ Pipeline Graph
*/
$stage-hover-bg: #eaf3fc;
$stage-hover-border: #d1e7fc;
+<<<<<<< HEAD
$action-icon-color: #d6d6d6;
/*
@@ -645,3 +648,6 @@ Project Templates Icons
$rails: #c00;
$node: #353535;
$java: #70ad51;
+=======
+$action-icon-color: #d6d6d6;
+>>>>>>> parent of aa792b91bb... Merge branch '26200-convert-sidebar-to-dropdown' into 'master'
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 1dac38f2b6d..68af3ec2327 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -322,11 +322,20 @@
display: block;
}
+<<<<<<< HEAD
width: $gutter_collapsed_width;
padding: 0;
.block {
width: $gutter_collapsed_width - 2px;
+=======
+ width: $sidebar_collapsed_width;
+ padding-top: 0;
+
+ .block {
+ width: $sidebar_collapsed_width - 2px;
+ margin-left: -19px;
+>>>>>>> parent of aa792b91bb... Merge branch '26200-convert-sidebar-to-dropdown' into 'master'
padding: 15px 0 0;
border-bottom: none;
overflow: hidden;
diff --git a/app/assets/stylesheets/pages/profiles/preferences.scss b/app/assets/stylesheets/pages/profiles/preferences.scss
index 305feaacaa1..100ace41f2a 100644
--- a/app/assets/stylesheets/pages/profiles/preferences.scss
+++ b/app/assets/stylesheets/pages/profiles/preferences.scss
@@ -1,3 +1,42 @@
+.application-theme {
+ label {
+ margin-right: 20px;
+ text-align: center;
+
+ .preview {
+ border-radius: 4px;
+
+ height: 80px;
+ margin-bottom: 10px;
+ width: 160px;
+
+ &.ui_blue {
+ background: $theme-blue;
+ }
+
+ &.ui_charcoal {
+ background: $theme-charcoal;
+ }
+
+ &.ui_graphite {
+ background: $theme-graphite;
+ }
+
+ &.ui_black {
+ background: $theme-black;
+ }
+
+ &.ui_green {
+ background: $theme-green;
+ }
+
+ &.ui_violet {
+ background: $theme-violet;
+ }
+ }
+ }
+}
+
.syntax-theme {
label {
margin-right: 20px;
diff --git a/app/assets/stylesheets/print.scss b/app/assets/stylesheets/print.scss
index 113e6e86bb5..0570c38f6f7 100644
--- a/app/assets/stylesheets/print.scss
+++ b/app/assets/stylesheets/print.scss
@@ -28,6 +28,13 @@ nav.navbar-collapse.collapse,
.profiler-results,
.tree-ref-holder,
.tree-holder .breadcrumb,
+<<<<<<< HEAD
+=======
+.blob-commit-info,
+.file-title,
+.file-holder,
+.sidebar-wrapper,
+>>>>>>> parent of aa792b91bb... Merge branch '26200-convert-sidebar-to-dropdown' into 'master'
.nav,
.btn,
ul.notes-form,
diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
index fa1bc72560e..345e02e5f4b 100644
--- a/app/controllers/admin/users_controller.rb
+++ b/app/controllers/admin/users_controller.rb
@@ -204,6 +204,7 @@ class Admin::UsersController < Admin::ApplicationController
:provider,
:remember_me,
:skype,
+ :theme_id,
:twitter,
:username,
:website_url
diff --git a/app/controllers/profiles/preferences_controller.rb b/app/controllers/profiles/preferences_controller.rb
index 1e557c47638..cce2a847b53 100644
--- a/app/controllers/profiles/preferences_controller.rb
+++ b/app/controllers/profiles/preferences_controller.rb
@@ -35,7 +35,8 @@ class Profiles::PreferencesController < Profiles::ApplicationController
:color_scheme_id,
:layout,
:dashboard,
- :project_view
+ :project_view,
+ :theme_id
)
end
end
diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb
index b63b3b70903..dad4c1f666a 100644
--- a/app/helpers/nav_helper.rb
+++ b/app/helpers/nav_helper.rb
@@ -1,10 +1,17 @@
module NavHelper
+<<<<<<< HEAD
def page_with_sidebar_class
class_name = page_gutter_class
class_name << 'page-with-new-sidebar' if defined?(@new_sidebar) && @new_sidebar
class_name << 'page-with-icon-sidebar' if collapsed_sidebar? && @new_sidebar
class_name
+=======
+ def page_sidebar_class
+ if pinned_nav?
+ "page-sidebar-expanded page-sidebar-pinned"
+ end
+>>>>>>> parent of aa792b91bb... Merge branch '26200-convert-sidebar-to-dropdown' into 'master'
end
def page_gutter_class
@@ -34,7 +41,15 @@ module NavHelper
class_names = []
class_names << 'with-horizontal-nav' if defined?(nav) && nav
+<<<<<<< HEAD
class_names
+=======
+ if pinned_nav?
+ class_name << " header-sidebar-expanded header-sidebar-pinned"
+ end
+
+ class_name
+>>>>>>> parent of aa792b91bb... Merge branch '26200-convert-sidebar-to-dropdown' into 'master'
end
def layout_nav_class
@@ -50,4 +65,8 @@ module NavHelper
def nav_control_class
"nav-control" if current_user
end
+
+ def pinned_nav?
+ cookies[:pin_nav] == 'true'
+ end
end
diff --git a/app/helpers/preferences_helper.rb b/app/helpers/preferences_helper.rb
index d36bb4ab074..64605908c05 100644
--- a/app/helpers/preferences_helper.rb
+++ b/app/helpers/preferences_helper.rb
@@ -40,6 +40,10 @@ module PreferencesHelper
]
end
+ def user_application_theme
+ Gitlab::Themes.for_user(current_user).css_class
+ end
+
def user_color_scheme
Gitlab::ColorSchemes.for_user(current_user).css_class
end
diff --git a/app/models/user.rb b/app/models/user.rb
index 0e2654ff757..20341cf6f1c 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -31,6 +31,7 @@ class User < ActiveRecord::Base
default_value_for :project_view, :files
default_value_for :notified_of_own_activity, false
default_value_for :preferred_language, I18n.default_locale
+ default_value_for :theme_id, gitlab_config.default_theme
attr_encrypted :otp_secret,
key: Gitlab::Application.secrets.otp_key_base,
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
index c4f8cd71395..ede337f084e 100644
--- a/app/views/layouts/_page.html.haml
+++ b/app/views/layouts/_page.html.haml
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
.page-with-sidebar{ class: page_with_sidebar_class }
- if show_new_nav?
- if defined?(nav) && nav
@@ -12,6 +13,32 @@
.content-wrapper{ class: layout_nav_class }
- if show_new_nav?
.mobile-overlay
+=======
+.page-with-sidebar{ class: "#{page_sidebar_class} #{page_gutter_class}" }
+ .sidebar-wrapper.nicescroll
+ .sidebar-action-buttons
+ .nav-header-btn.toggle-nav-collapse{ title: "Open/Close" }
+ %span.sr-only Toggle navigation
+ = icon('bars')
+
+ %div{ class: "nav-header-btn pin-nav-btn has-tooltip #{'is-active' if pinned_nav?} js-nav-pin", title: pinned_nav? ? "Unpin navigation" : "Pin Navigation", data: { placement: 'right', container: 'body' } }
+ %span.sr-only Toggle navigation pinning
+ = icon('fw thumb-tack')
+
+ - if defined?(sidebar) && sidebar
+ = render "layouts/nav/#{sidebar}"
+ - elsif current_user
+ = render 'layouts/nav/dashboard'
+ - else
+ = render 'layouts/nav/explore'
+
+ - if defined?(nav) && nav
+ .layout-nav
+ .container-fluid
+ = render "layouts/nav/#{nav}"
+ .content-wrapper{ class: "#{layout_nav_class}" }
+ = yield :sub_nav
+>>>>>>> parent of aa792b91bb... Merge branch '26200-convert-sidebar-to-dropdown' into 'master'
.alert-wrapper
= render "layouts/broadcast"
- if show_new_nav?
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
index b53f382fa3d..a479dddf068 100644
--- a/app/views/layouts/application.html.haml
+++ b/app/views/layouts/application.html.haml
@@ -1,6 +1,7 @@
!!! 5
%html{ lang: I18n.locale, class: page_class }
= render "layouts/head"
+<<<<<<< HEAD
%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 'peek/bar'
@@ -8,6 +9,12 @@
= render "layouts/header/new"
- else
= render "layouts/header/default", title: header_title
+=======
+ %body{ class: "#{user_application_theme}", data: { page: body_data_page, project: "#{@project.path if @project}", group: "#{@group.path if @group}" } }
+ = Gon::Base.render_data
+
+ = render "layouts/header/default", title: header_title
+>>>>>>> parent of aa792b91bb... Merge branch '26200-convert-sidebar-to-dropdown' into 'master'
= render 'layouts/page', sidebar: sidebar, nav: nav
= yield :scripts_body
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index b32cfe158bb..fce3f9cb251 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -3,6 +3,7 @@
%a.sr-only.gl-accessibility{ href: "#content-body", tabindex: "1" } Skip to content
.container-fluid
.header-content
+<<<<<<< HEAD
.dropdown.global-dropdown
%button.global-dropdown-toggle{ type: 'button', 'data-toggle' => 'dropdown' }
%span.sr-only Toggle navigation
@@ -19,6 +20,14 @@
.title-container.js-title-container
%h1.title{ class: ('initializing' if @has_group_title) }= title
+=======
+ %button.side-nav-toggle{ type: 'button', "aria-label" => "Toggle global navigation" }
+ %span.sr-only Toggle navigation
+ = icon('bars')
+ %button.navbar-toggle{ type: 'button' }
+ %span.sr-only Toggle navigation
+ = icon('ellipsis-v')
+>>>>>>> parent of aa792b91bb... Merge branch '26200-convert-sidebar-to-dropdown' into 'master'
.navbar-collapse.collapse
%ul.nav.navbar-nav
@@ -84,9 +93,18 @@
%div
= link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes'), class: 'btn btn-sign-in btn-success'
+<<<<<<< HEAD
%button.navbar-toggle{ type: 'button' }
%span.sr-only Toggle navigation
= icon('ellipsis-v')
+=======
+
+ %h1.title= title
+
+ .header-logo
+ = link_to root_path, class: 'home', title: 'Dashboard', id: 'logo' do
+ = brand_header_logo
+>>>>>>> parent of aa792b91bb... Merge branch '26200-convert-sidebar-to-dropdown' into 'master'
= yield :header_content
diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml
index be7d27df2a0..98bebbee103 100644
--- a/app/views/layouts/nav/_dashboard.html.haml
+++ b/app/views/layouts/nav/_dashboard.html.haml
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
%ul
= nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: {class: "#{project_tab_class} home"}) do
= link_to dashboard_projects_path, title: 'Projects', class: 'dashboard-shortcuts-projects' do
@@ -65,3 +66,46 @@
%li.divider
%li
= link_to "Help", help_path, title: 'About GitLab CE', class: 'about-gitlab'
+=======
+.nav-sidebar
+ %ul.nav
+ = nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: {class: "#{project_tab_class} home"}) do
+ = link_to dashboard_projects_path, title: 'Projects', class: 'dashboard-shortcuts-projects' do
+ %span
+ Projects
+ = nav_link(path: 'dashboard#activity') do
+ = link_to activity_dashboard_path, class: 'dashboard-shortcuts-activity', title: 'Activity' do
+ %span
+ Activity
+ - if koding_enabled?
+ = nav_link(controller: :koding) do
+ = link_to koding_path, title: 'Koding' do
+ %span
+ Koding
+ = nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do
+ = link_to dashboard_groups_path, title: 'Groups' do
+ %span
+ Groups
+ = nav_link(controller: 'dashboard/milestones') do
+ = link_to dashboard_milestones_path, title: 'Milestones' do
+ %span
+ Milestones
+ = nav_link(path: 'dashboard#issues') do
+ = link_to assigned_issues_dashboard_path, title: 'Issues', class: 'dashboard-shortcuts-issues' do
+ %span
+ Issues
+ %span.count= number_with_delimiter(cached_assigned_issuables_count(current_user, :issues, :opened))
+ = nav_link(path: 'dashboard#merge_requests') do
+ = link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'dashboard-shortcuts-merge_requests' do
+ %span
+ Merge Requests
+ %span.count= number_with_delimiter(cached_assigned_issuables_count(current_user, :merge_requests, :opened))
+ = nav_link(controller: 'dashboard/snippets') do
+ = link_to dashboard_snippets_path, title: 'Snippets' do
+ %span
+ Snippets
+
+ = link_to help_path, title: 'About GitLab CE', class: 'about-gitlab' do
+ %span
+ About GitLab CE
+>>>>>>> parent of aa792b91bb... Merge branch '26200-convert-sidebar-to-dropdown' into 'master'
diff --git a/app/views/layouts/nav/_explore.html.haml b/app/views/layouts/nav/_explore.html.haml
index 0cb367452f7..572163a16b1 100644
--- a/app/views/layouts/nav/_explore.html.haml
+++ b/app/views/layouts/nav/_explore.html.haml
@@ -1,4 +1,4 @@
-%ul
+%ul.nav.nav-sidebar
= nav_link(path: ['dashboard#show', 'root#show', 'projects#trending', 'projects#starred', 'projects#index'], html_options: {class: 'home'}) do
= link_to explore_root_path, title: 'Projects', class: 'dashboard-shortcuts-projects' do
.shortcut-mappings
diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml
index f08dcc0c242..f2f8cedfe3a 100644
--- a/app/views/profiles/preferences/show.html.haml
+++ b/app/views/profiles/preferences/show.html.haml
@@ -3,7 +3,24 @@
= render 'profiles/head'
= form_for @user, url: profile_preferences_path, remote: true, method: :put, html: { class: 'row prepend-top-default js-preferences-form' } do |f|
+<<<<<<< HEAD
.col-lg-4.profile-settings-sidebar
+=======
+ .col-lg-3.profile-settings-sidebar
+ %h4.prepend-top-0
+ Application theme
+ %p
+ This setting allows you to customize the appearance of the site, e.g. the sidebar.
+ .col-lg-9.application-theme
+ - Gitlab::Themes.each do |theme|
+ = label_tag do
+ .preview{ class: theme.css_class }
+ = f.radio_button :theme_id, theme.id
+ = theme.name
+ .col-sm-12
+ %hr
+ .col-lg-3.profile-settings-sidebar
+>>>>>>> parent of aa792b91bb... Merge branch '26200-convert-sidebar-to-dropdown' into 'master'
%h4.prepend-top-0
Syntax highlighting theme
%p
diff --git a/app/views/profiles/preferences/update.js.erb b/app/views/profiles/preferences/update.js.erb
index 431ab9d052b..8966dd3fd86 100644
--- a/app/views/profiles/preferences/update.js.erb
+++ b/app/views/profiles/preferences/update.js.erb
@@ -1,3 +1,7 @@
+// Remove body class for any previous theme, re-add current one
+$('body').removeClass('<%= Gitlab::Themes.body_classes %>')
+$('body').addClass('<%= user_application_theme %>')
+
// Toggle container-fluid class
if ('<%= current_user.layout %>' === 'fluid') {
$('.content-wrapper .container-fluid').removeClass('container-limited')
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index 25285525846..1db08b578a3 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -76,6 +76,14 @@ production: &base
# default_can_create_group: false # default: true
# username_changing_enabled: false # default: true - User can change her username/namespace
+ ## Default theme ID
+ ## 1 - Graphite
+ ## 2 - Charcoal
+ ## 3 - Green
+ ## 4 - Gray
+ ## 5 - Violet
+ ## 6 - Blue
+ # default_theme: 2 # default: 2
## Automatic issue closing
# If a commit message matches this regular expression, all issues referenced from the matched text will be closed.
@@ -735,4 +743,4 @@ test:
admin_group: ''
staging:
- <<: *base
+ <<: *base \ No newline at end of file
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 38ade18bdc0..bcaee8bd6ca 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -222,6 +222,7 @@ Settings['gitlab'] ||= Settingslogic.new({})
Settings.gitlab['default_projects_limit'] ||= 100000
Settings.gitlab['default_branch_protection'] ||= 2
Settings.gitlab['default_can_create_group'] = true if Settings.gitlab['default_can_create_group'].nil?
+Settings.gitlab['default_theme'] = Gitlab::Themes::APPLICATION_DEFAULT if Settings.gitlab['default_theme'].nil?
Settings.gitlab['host'] ||= ENV['GITLAB_HOST'] || 'localhost'
Settings.gitlab['ssh_host'] ||= Settings.gitlab.host
Settings.gitlab['https'] = false if Settings.gitlab['https'].nil?
diff --git a/db/post_migrate/20170215200045_remove_theme_id_from_users.rb b/db/post_migrate/20170215200045_remove_theme_id_from_users.rb
deleted file mode 100644
index c51646fbe52..00000000000
--- a/db/post_migrate/20170215200045_remove_theme_id_from_users.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-class RemoveThemeIdFromUsers < ActiveRecord::Migration
- include Gitlab::Database::MigrationHelpers
-
- DOWNTIME = false
-
- def change
- remove_column :users, :theme_id, :integer
- end
-end
diff --git a/doc/api/keys.md b/doc/api/keys.md
index 376ac27df3a..ddcf7830621 100644
--- a/doc/api/keys.md
+++ b/doc/api/keys.md
@@ -32,6 +32,7 @@ Parameters:
"twitter": "",
"website_url": "",
"email": "john@example.com",
+ "theme_id": 2,
"color_scheme_id": 1,
"projects_limit": 10,
"current_sign_in_at": null,
diff --git a/doc/api/session.md b/doc/api/session.md
index f79eac11689..b97e26f34a2 100644
--- a/doc/api/session.md
+++ b/doc/api/session.md
@@ -39,6 +39,7 @@ Example response:
"twitter": "",
"website_url": "",
"email": "john@example.com",
+ "theme_id": 1,
"color_scheme_id": 1,
"projects_limit": 10,
"current_sign_in_at": "2015-07-07T07:10:58.392Z",
diff --git a/doc/api/users.md b/doc/api/users.md
index 57a13eb477d..8c7bfa25c88 100644
--- a/doc/api/users.md
+++ b/doc/api/users.md
@@ -72,6 +72,7 @@ GET /users
"organization": "",
"last_sign_in_at": "2012-06-01T11:41:01Z",
"confirmed_at": "2012-05-23T09:05:22Z",
+ "theme_id": 1,
"last_activity_on": "2012-05-23",
"color_scheme_id": 2,
"projects_limit": 100,
@@ -105,6 +106,7 @@ GET /users
"organization": "",
"last_sign_in_at": null,
"confirmed_at": "2012-05-30T16:53:06.148Z",
+ "theme_id": 1,
"last_activity_on": "2012-05-23",
"color_scheme_id": 3,
"projects_limit": 100,
@@ -215,6 +217,7 @@ Parameters:
"organization": "",
"last_sign_in_at": "2012-06-01T11:41:01Z",
"confirmed_at": "2012-05-23T09:05:22Z",
+ "theme_id": 1,
"last_activity_on": "2012-05-23",
"color_scheme_id": 2,
"projects_limit": 100,
@@ -344,6 +347,7 @@ GET /user
"organization": "",
"last_sign_in_at": "2012-06-01T11:41:01Z",
"confirmed_at": "2012-05-23T09:05:22Z",
+ "theme_id": 1,
"last_activity_on": "2012-05-23",
"color_scheme_id": 2,
"projects_limit": 100,
@@ -390,6 +394,7 @@ GET /user
"organization": "",
"last_sign_in_at": "2012-06-01T11:41:01Z",
"confirmed_at": "2012-05-23T09:05:22Z",
+ "theme_id": 1,
"last_activity_on": "2012-05-23",
"color_scheme_id": 2,
"projects_limit": 100,
diff --git a/features/project/labels.feature b/features/project/labels.feature
new file mode 100644
index 00000000000..955bc3d8b1b
--- /dev/null
+++ b/features/project/labels.feature
@@ -0,0 +1,15 @@
+@labels
+Feature: Labels
+ Background:
+ Given I sign in as a user
+ And I own project "Shop"
+ And project "Shop" has labels: "bug", "feature", "enhancement"
+ When I visit project "Shop" labels page
+
+ @javascript
+ Scenario: I can subscribe to a label
+ Then I should see that I am not subscribed to the "bug" label
+ When I click button "Subscribe" for the "bug" label
+ Then I should see that I am subscribed to the "bug" label
+ When I click button "Unsubscribe" for the "bug" label
+ Then I should see that I am not subscribed to the "bug" label
diff --git a/features/steps/project/labels.rb b/features/steps/project/labels.rb
new file mode 100644
index 00000000000..dbeb07c78db
--- /dev/null
+++ b/features/steps/project/labels.rb
@@ -0,0 +1,32 @@
+class Spinach::Features::Labels < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedIssuable
+ include SharedProject
+ include SharedPaths
+
+ step 'And I visit project "Shop" labels page' do
+ visit namespace_project_labels_path(project.namespace, project)
+ end
+
+ step 'I should see that I am subscribed to the "bug" label' do
+ expect(subscribe_button).to have_content 'Unsubscribe'
+ end
+
+ step 'I should see that I am not subscribed to the "bug" label' do
+ expect(subscribe_button).to have_content 'Subscribe'
+ end
+
+ step 'I click button "Unsubscribe" for the "bug" label' do
+ subscribe_button.click
+ end
+
+ step 'I click button "Subscribe" for the "bug" label' do
+ subscribe_button.click
+ end
+
+ private
+
+ def subscribe_button
+ first('.js-subscribe-button', visible: true)
+ end
+end
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index e8dd61e493f..7d87197a270 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -35,7 +35,7 @@ module API
expose :confirmed_at
expose :last_activity_on
expose :email
- expose :color_scheme_id, :projects_limit, :current_sign_in_at
+ expose :theme_id, :color_scheme_id, :projects_limit, :current_sign_in_at
expose :identities, using: Entities::Identity
expose :can_create_group?, as: :can_create_group
expose :can_create_project?, as: :can_create_project
diff --git a/lib/gitlab/themes.rb b/lib/gitlab/themes.rb
new file mode 100644
index 00000000000..19ab76ae80f
--- /dev/null
+++ b/lib/gitlab/themes.rb
@@ -0,0 +1,87 @@
+module Gitlab
+ # Module containing GitLab's application theme definitions and helper methods
+ # for accessing them.
+ module Themes
+ extend self
+
+ # Theme ID used when no `default_theme` configuration setting is provided.
+ APPLICATION_DEFAULT = 2
+
+ # Struct class representing a single Theme
+ Theme = Struct.new(:id, :name, :css_class)
+
+ # All available Themes
+ THEMES = [
+ Theme.new(1, 'Graphite', 'ui_graphite'),
+ Theme.new(2, 'Charcoal', 'ui_charcoal'),
+ Theme.new(3, 'Green', 'ui_green'),
+ Theme.new(4, 'Black', 'ui_black'),
+ Theme.new(5, 'Violet', 'ui_violet'),
+ Theme.new(6, 'Blue', 'ui_blue')
+ ].freeze
+
+ # Convenience method to get a space-separated String of all the theme
+ # classes that might be applied to the `body` element
+ #
+ # Returns a String
+ def body_classes
+ THEMES.collect(&:css_class).uniq.join(' ')
+ end
+
+ # Get a Theme by its ID
+ #
+ # If the ID is invalid, returns the default Theme.
+ #
+ # id - Integer ID
+ #
+ # Returns a Theme
+ def by_id(id)
+ THEMES.detect { |t| t.id == id } || default
+ end
+
+ # Returns the number of defined Themes
+ def count
+ THEMES.size
+ end
+
+ # Get the default Theme
+ #
+ # Returns a Theme
+ def default
+ by_id(default_id)
+ end
+
+ # Iterate through each Theme
+ #
+ # Yields the Theme object
+ def each(&block)
+ THEMES.each(&block)
+ end
+
+ # Get the Theme for the specified user, or the default
+ #
+ # user - User record
+ #
+ # Returns a Theme
+ def for_user(user)
+ if user
+ by_id(user.theme_id)
+ else
+ default
+ end
+ end
+
+ private
+
+ def default_id
+ id = Gitlab.config.gitlab.default_theme.to_i
+
+ # Prevent an invalid configuration setting from causing an infinite loop
+ if id < THEMES.first.id || id > THEMES.last.id
+ APPLICATION_DEFAULT
+ else
+ id
+ end
+ end
+ end
+end
diff --git a/spec/controllers/profiles/preferences_controller_spec.rb b/spec/controllers/profiles/preferences_controller_spec.rb
index a5f544b4f92..a66b4ab0902 100644
--- a/spec/controllers/profiles/preferences_controller_spec.rb
+++ b/spec/controllers/profiles/preferences_controller_spec.rb
@@ -25,7 +25,8 @@ describe Profiles::PreferencesController do
def go(params: {}, format: :js)
params.reverse_merge!(
color_scheme_id: '1',
- dashboard: 'stars'
+ dashboard: 'stars',
+ theme_id: '1'
)
patch :update, user: params, format: format
@@ -40,7 +41,8 @@ describe Profiles::PreferencesController do
it "changes the user's preferences" do
prefs = {
color_scheme_id: '1',
- dashboard: 'stars'
+ dashboard: 'stars',
+ theme_id: '2'
}.with_indifferent_access
expect(user).to receive(:assign_attributes).with(prefs)
diff --git a/spec/features/dashboard/active_tab_spec.rb b/spec/features/dashboard/active_tab_spec.rb
index 067e4337e6a..9bcff868756 100644
--- a/spec/features/dashboard/active_tab_spec.rb
+++ b/spec/features/dashboard/active_tab_spec.rb
@@ -7,9 +7,8 @@ RSpec.describe 'Dashboard Active Tab', js: true do
shared_examples 'page has active tab' do |title|
it "#{title} tab" do
- find('.global-dropdown-toggle').trigger('click')
- expect(page).to have_selector('.global-dropdown-menu li.active', count: 1)
- expect(find('.global-dropdown-menu li.active')).to have_content(title)
+ expect(page).to have_selector('.nav-sidebar li.active', count: 1)
+ expect(find('.nav-sidebar li.active')).to have_content(title)
end
end
diff --git a/spec/features/dashboard/shortcuts_spec.rb b/spec/features/dashboard/shortcuts_spec.rb
index 5f1f0c10339..2f77bbde23a 100644
--- a/spec/features/dashboard/shortcuts_spec.rb
+++ b/spec/features/dashboard/shortcuts_spec.rb
@@ -49,7 +49,7 @@ feature 'Dashboard shortcuts', :js do
end
end
- def check_page_title(title)
- expect(find('.header-content .title')).to have_content(title)
+ def ensure_active_main_tab(content)
+ expect(find('.nav-sidebar li.active')).to have_content(content)
end
end
diff --git a/spec/features/profiles/preferences_spec.rb b/spec/features/profiles/preferences_spec.rb
index c935cdfd5c4..73b14b1d338 100644
--- a/spec/features/profiles/preferences_spec.rb
+++ b/spec/features/profiles/preferences_spec.rb
@@ -8,7 +8,7 @@ describe 'Profile > Preferences', :js do
visit profile_preferences_path
end
- describe 'User changes their syntax highlighting theme' do
+ describe 'User changes their application theme', js: true do
it 'creates a flash message' do
choose 'user_color_scheme_id_5'
diff --git a/spec/fixtures/api/schemas/public_api/v4/user/login.json b/spec/fixtures/api/schemas/public_api/v4/user/login.json
index 6181b3ccc86..e6c1d9c9d84 100644
--- a/spec/fixtures/api/schemas/public_api/v4/user/login.json
+++ b/spec/fixtures/api/schemas/public_api/v4/user/login.json
@@ -19,6 +19,7 @@
"organization",
"last_sign_in_at",
"confirmed_at",
+ "theme_id",
"color_scheme_id",
"projects_limit",
"current_sign_in_at",
diff --git a/spec/helpers/preferences_helper_spec.rb b/spec/helpers/preferences_helper_spec.rb
index a04c87b08eb..50ac3886a36 100644
--- a/spec/helpers/preferences_helper_spec.rb
+++ b/spec/helpers/preferences_helper_spec.rb
@@ -26,6 +26,32 @@ describe PreferencesHelper do
end
end
+ describe 'user_application_theme' do
+ context 'with a user' do
+ it "returns user's theme's css_class" do
+ stub_user(theme_id: 3)
+
+ expect(helper.user_application_theme).to eq 'ui_green'
+ end
+
+ it 'returns the default when id is invalid' do
+ stub_user(theme_id: Gitlab::Themes.count + 5)
+
+ allow(Gitlab.config.gitlab).to receive(:default_theme).and_return(2)
+
+ expect(helper.user_application_theme).to eq 'ui_charcoal'
+ end
+ end
+
+ context 'without a user' do
+ it 'returns the default theme' do
+ stub_user
+
+ expect(helper.user_application_theme).to eq Gitlab::Themes.default.css_class
+ end
+ end
+ end
+
describe 'user_color_scheme' do
context 'with a user' do
it "returns user's scheme's css_class" do
diff --git a/spec/javascripts/dashboard_spec.js.es6 b/spec/javascripts/dashboard_spec.js.es6
new file mode 100644
index 00000000000..c0bdb89ed63
--- /dev/null
+++ b/spec/javascripts/dashboard_spec.js.es6
@@ -0,0 +1,37 @@
+/* eslint-disable no-new */
+
+require('~/sidebar');
+require('~/lib/utils/text_utility');
+
+((global) => {
+ describe('Dashboard', () => {
+ const fixtureTemplate = 'static/dashboard.html.raw';
+
+ function todosCountText() {
+ return $('.js-todos-count').text();
+ }
+
+ function triggerToggle(newCount) {
+ $(document).trigger('todo:toggle', newCount);
+ }
+
+ preloadFixtures(fixtureTemplate);
+ beforeEach(() => {
+ loadFixtures(fixtureTemplate);
+ new global.Sidebar();
+ });
+
+ it('should update todos-count after receiving the todo:toggle event', () => {
+ triggerToggle(5);
+ expect(todosCountText()).toEqual('5');
+ });
+
+ it('should display todos-count with delimiter', () => {
+ triggerToggle(1000);
+ expect(todosCountText()).toEqual('1,000');
+
+ triggerToggle(1000000);
+ expect(todosCountText()).toEqual('1,000,000');
+ });
+ });
+})(window.gl);
diff --git a/spec/javascripts/fixtures/dashboard.html.haml b/spec/javascripts/fixtures/dashboard.html.haml
new file mode 100644
index 00000000000..32446acfd60
--- /dev/null
+++ b/spec/javascripts/fixtures/dashboard.html.haml
@@ -0,0 +1,45 @@
+%ul.nav.nav-sidebar
+ %li.home.active
+ %a.dashboard-shortcuts-projects
+ %span
+ Projects
+ %li
+ %a
+ %span
+ Todos
+ %span.count.js-todos-count
+ 1
+ %li
+ %a.dashboard-shortcuts-activity
+ %span
+ Activity
+ %li
+ %a
+ %span
+ Groups
+ %li
+ %a
+ %span
+ Milestones
+ %li
+ %a.dashboard-shortcuts-issues
+ %span
+ Issues
+ %span
+ 1
+ %li
+ %a.dashboard-shortcuts-merge_requests
+ %span
+ Merge Requests
+ %li
+ %a
+ %span
+ Snippets
+ %li
+ %a
+ %span
+ Help
+ %li
+ %a
+ %span
+ Profile Settings
diff --git a/spec/javascripts/project_dashboard_spec.js.es6 b/spec/javascripts/project_dashboard_spec.js.es6
new file mode 100644
index 00000000000..24833b4eb57
--- /dev/null
+++ b/spec/javascripts/project_dashboard_spec.js.es6
@@ -0,0 +1,86 @@
+require('~/sidebar');
+
+(() => {
+ describe('Project dashboard page', () => {
+ let $pageWithSidebar = null;
+ let $sidebarToggle = null;
+ let sidebar = null;
+ const fixtureTemplate = 'projects/dashboard.html.raw';
+
+ const assertSidebarStateExpanded = (shouldBeExpanded) => {
+ expect(sidebar.isExpanded).toBe(shouldBeExpanded);
+ expect($pageWithSidebar.hasClass('page-sidebar-expanded')).toBe(shouldBeExpanded);
+ };
+
+ preloadFixtures(fixtureTemplate);
+ beforeEach(() => {
+ loadFixtures(fixtureTemplate);
+
+ $pageWithSidebar = $('.page-with-sidebar');
+ $sidebarToggle = $('.toggle-nav-collapse');
+
+ // otherwise instantiating the Sidebar for the second time
+ // won't do anything, as the Sidebar is a singleton class
+ gl.Sidebar.singleton = null;
+ sidebar = new gl.Sidebar();
+ });
+
+ it('can show the sidebar when the toggler is clicked', () => {
+ assertSidebarStateExpanded(false);
+ $sidebarToggle.click();
+ assertSidebarStateExpanded(true);
+ });
+
+ it('should dismiss the sidebar when clone button clicked', () => {
+ $sidebarToggle.click();
+ assertSidebarStateExpanded(true);
+
+ const cloneButton = $('.project-clone-holder a.clone-dropdown-btn');
+ cloneButton.click();
+ assertSidebarStateExpanded(false);
+ });
+
+ it('should dismiss the sidebar when download button clicked', () => {
+ $sidebarToggle.click();
+ assertSidebarStateExpanded(true);
+
+ const downloadButton = $('.project-action-button .btn:has(i.fa-download)');
+ downloadButton.click();
+ assertSidebarStateExpanded(false);
+ });
+
+ it('should dismiss the sidebar when add button clicked', () => {
+ $sidebarToggle.click();
+ assertSidebarStateExpanded(true);
+
+ const addButton = $('.project-action-button .btn:has(i.fa-plus)');
+ addButton.click();
+ assertSidebarStateExpanded(false);
+ });
+
+ it('should dismiss the sidebar when notification button clicked', () => {
+ $sidebarToggle.click();
+ assertSidebarStateExpanded(true);
+
+ const notifButton = $('.js-notification-toggle-btns .notifications-btn');
+ notifButton.click();
+ assertSidebarStateExpanded(false);
+ });
+
+ it('should dismiss the sidebar when clicking on the body', () => {
+ $sidebarToggle.click();
+ assertSidebarStateExpanded(true);
+
+ $('body').click();
+ assertSidebarStateExpanded(false);
+ });
+
+ it('should dismiss the sidebar when clicking on the project description header', () => {
+ $sidebarToggle.click();
+ assertSidebarStateExpanded(true);
+
+ $('.project-home-panel').click();
+ assertSidebarStateExpanded(false);
+ });
+ });
+})();
diff --git a/spec/lib/gitlab/themes_spec.rb b/spec/lib/gitlab/themes_spec.rb
new file mode 100644
index 00000000000..7a140518dd2
--- /dev/null
+++ b/spec/lib/gitlab/themes_spec.rb
@@ -0,0 +1,48 @@
+require 'spec_helper'
+
+describe Gitlab::Themes, lib: true do
+ describe '.body_classes' do
+ it 'returns a space-separated list of class names' do
+ css = described_class.body_classes
+
+ expect(css).to include('ui_graphite')
+ expect(css).to include(' ui_charcoal ')
+ expect(css).to include(' ui_blue')
+ end
+ end
+
+ describe '.by_id' do
+ it 'returns a Theme by its ID' do
+ expect(described_class.by_id(1).name).to eq 'Graphite'
+ expect(described_class.by_id(6).name).to eq 'Blue'
+ end
+ end
+
+ describe '.default' do
+ it 'returns the default application theme' do
+ allow(described_class).to receive(:default_id).and_return(2)
+ expect(described_class.default.id).to eq 2
+ end
+
+ it 'prevents an infinite loop when configuration default is invalid' do
+ default = described_class::APPLICATION_DEFAULT
+ themes = described_class::THEMES
+
+ config = double(default_theme: 0).as_null_object
+ allow(Gitlab).to receive(:config).and_return(config)
+ expect(described_class.default.id).to eq default
+
+ config = double(default_theme: themes.size + 5).as_null_object
+ allow(Gitlab).to receive(:config).and_return(config)
+ expect(described_class.default.id).to eq default
+ end
+ end
+
+ describe '.each' do
+ it 'passes the block to the THEMES Array' do
+ ids = []
+ described_class.each { |theme| ids << theme.id }
+ expect(ids).not_to be_empty
+ end
+ end
+end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 97bb91a6ac8..34902cfb69d 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -716,6 +716,7 @@ describe User do
it "applies defaults to user" do
expect(user.projects_limit).to eq(Gitlab.config.gitlab.default_projects_limit)
expect(user.can_create_group).to eq(Gitlab.config.gitlab.default_can_create_group)
+ expect(user.theme_id).to eq(Gitlab.config.gitlab.default_theme)
expect(user.external).to be_falsey
end
end
@@ -726,6 +727,7 @@ describe User do
it "applies defaults to user" do
expect(user.projects_limit).to eq(123)
expect(user.can_create_group).to be_falsey
+ expect(user.theme_id).to eq(1)
end
end
diff --git a/spec/support/gitlab_stubs/session.json b/spec/support/gitlab_stubs/session.json
index cd55d63125e..ce8dfe5ae75 100644
--- a/spec/support/gitlab_stubs/session.json
+++ b/spec/support/gitlab_stubs/session.json
@@ -7,7 +7,7 @@
"skype":"aertert",
"linkedin":"",
"twitter":"",
- "color_scheme_id":2,
+ "theme_id":2,"color_scheme_id":2,
"state":"active",
"created_at":"2012-12-21T13:02:20Z",
"extern_uid":null,
@@ -17,4 +17,4 @@
"can_create_project":false,
"private_token":"Wvjy2Krpb7y8xi93owUz",
"access_token":"Wvjy2Krpb7y8xi93owUz"
-}
+} \ No newline at end of file
diff --git a/spec/support/gitlab_stubs/user.json b/spec/support/gitlab_stubs/user.json
index cd55d63125e..ce8dfe5ae75 100644
--- a/spec/support/gitlab_stubs/user.json
+++ b/spec/support/gitlab_stubs/user.json
@@ -7,7 +7,7 @@
"skype":"aertert",
"linkedin":"",
"twitter":"",
- "color_scheme_id":2,
+ "theme_id":2,"color_scheme_id":2,
"state":"active",
"created_at":"2012-12-21T13:02:20Z",
"extern_uid":null,
@@ -17,4 +17,4 @@
"can_create_project":false,
"private_token":"Wvjy2Krpb7y8xi93owUz",
"access_token":"Wvjy2Krpb7y8xi93owUz"
-}
+} \ No newline at end of file