diff options
-rw-r--r-- | app/helpers/application_helper.rb | 3 | ||||
-rw-r--r-- | app/views/layouts/base.html.erb | 44 | ||||
-rw-r--r-- | public/javascripts/responsive.js | 83 | ||||
-rw-r--r-- | public/stylesheets/responsive.css | 595 |
4 files changed, 723 insertions, 2 deletions
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index c345083b7..fce5d6e22 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -340,6 +340,7 @@ module ApplicationHelper { :value => project_path(:id => p, :jump => current_menu_item) } end + content_tag( :span, nil, :class => 'jump-box-arrow') + select_tag('project_quick_jump_box', options, :onchange => 'if (this.value != \'\') { window.location = this.value; }') end end @@ -1267,7 +1268,7 @@ module ApplicationHelper # Returns the javascript tags that are included in the html layout head def javascript_heads - tags = javascript_include_tag('jquery-1.11.1-ui-1.11.0-ujs-3.1.4', 'application') + tags = javascript_include_tag('jquery-1.11.1-ui-1.11.0-ujs-3.1.4', 'application', 'responsive') unless User.current.pref.warn_on_leaving_unsaved == '0' tags << "\n".html_safe + javascript_tag("$(window).load(function(){ warnLeavingUnsaved('#{escape_javascript l(:text_warn_on_leaving_unsaved)}'); });") end diff --git a/app/views/layouts/base.html.erb b/app/views/layouts/base.html.erb index b1ffcaf13..147749290 100644 --- a/app/views/layouts/base.html.erb +++ b/app/views/layouts/base.html.erb @@ -4,11 +4,12 @@ <meta charset="utf-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge"/> <title><%= html_title %></title> +<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="description" content="<%= Redmine::Info.app_name %>" /> <meta name="keywords" content="issue,bug,tracker" /> <%= csrf_meta_tag %> <%= favicon %> -<%= stylesheet_link_tag 'jquery/jquery-ui-1.11.0', 'application', :media => 'all' %> +<%= stylesheet_link_tag 'jquery/jquery-ui-1.11.0', 'application', 'responsive', :media => 'all' %> <%= stylesheet_link_tag 'rtl', :media => 'all' if l(:direction) == 'rtl' %> <%= javascript_heads %> <%= heads_for_theme %> @@ -18,6 +19,44 @@ </head> <body class="<%= body_css_classes %>"> <div id="wrapper"> + +<div class="flyout-menu js-flyout-menu"> + + + <% if User.current.logged? || !Setting.login_required? %> + <div class="flyout-menu__search"> + <%= form_tag({:controller => 'search', :action => 'index', :id => @project}, :method => :get ) do %> + <%= hidden_field_tag(controller.default_search_scope, 1, :id => nil) if controller.default_search_scope %> + <%= label_tag 'flyout-search', '⚲'.html_safe, :class => 'search-magnifier search-magnifier--flyout' %> + <%= text_field_tag 'q', @question, :id => 'flyout-search', :class => 'small js-search-input', :placeholder => l(:label_search) %> + <% end %> + </div> + <% end %> + + <% if User.current.logged? %> + <div class="flyout-menu__avatar <% if !Setting.gravatar_enabled? %>flyout-menu__avatar--no-avatar<% end %>"> + <% if Setting.gravatar_enabled? %> + <%= link_to(avatar(User.current, :size => "80"), user_path(User.current)) %> + <% end %> + <%= link_to_user(User.current, :format => :username) %> + </div> + <% end %> + + <% if display_main_menu?(@project) %> + <h3><%= l(:label_project) %></h3> + <span class="js-project-menu"></span> + <% end %> + + <h3><%= l(:label_general) %></h3> + <span class="js-general-menu"></span> + + <span class="js-sidebar flyout-menu__sidebar"></span> + + <h3><%= l(:label_profile) %></h3> + <span class="js-profile-menu"></span> + +</div> + <div id="wrapper2"> <div id="wrapper3"> <div id="top-menu"> @@ -29,6 +68,9 @@ </div> <div id="header"> + + <a href="#" class="mobile-toggle-button js-flyout-menu-toggle-button"></a> + <% if User.current.logged? || !Setting.login_required? %> <div id="quick-search"> <%= form_tag({:controller => 'search', :action => 'index', :id => @project}, :method => :get ) do %> diff --git a/public/javascripts/responsive.js b/public/javascripts/responsive.js new file mode 100644 index 000000000..4c6c3be8f --- /dev/null +++ b/public/javascripts/responsive.js @@ -0,0 +1,83 @@ +// generic layout specific responsive stuff goes here + +function openFlyout() { + $('html').addClass('flyout-is-active'); + $('#wrapper2').on('click', function(e){ + e.preventDefault(); + e.stopPropagation(); + closeFlyout(); + }); +} + +function closeFlyout() { + $('html').removeClass('flyout-is-active'); + $('#wrapper2').off('click'); +} + + +function isMobile() { + return $('.js-flyout-menu-toggle-button').is(":visible"); +} + +function setupFlyout() { + var mobileInit = false, + desktopInit = false; + + /* click handler for mobile menu toggle */ + $('.js-flyout-menu-toggle-button').on('click', function(e) { + e.preventDefault(); + e.stopPropagation(); + if($('html').hasClass('flyout-is-active')) { + closeFlyout(); + } else { + openFlyout(); + } + }); + + /* bind resize handler */ + $(window).resize(function() { + initMenu(); + }) + + /* menu init function for dom detaching and appending on mobile / desktop view */ + function initMenu() { + + var _initMobileMenu = function() { + /* only init mobile menu, if it hasn't been done yet */ + if(!mobileInit) { + + $('#main-menu > ul').detach().appendTo('.js-project-menu'); + $('#top-menu > ul').detach().appendTo('.js-general-menu'); + $('#sidebar > *').detach().appendTo('.js-sidebar'); + $('#account ul').detach().appendTo('.js-profile-menu'); + + mobileInit = true; + desktopInit = false; + } + } + + var _initDesktopMenu = function() { + if(!desktopInit) { + + $('.js-project-menu > ul').detach().appendTo('#main-menu'); + $('.js-general-menu ul').detach().appendTo('#top-menu'); + $('.js-sidebar > *').detach().appendTo('#sidebar'); + $('.js-profile-menu ul').detach().appendTo('#account'); + + desktopInit = true; + mobileInit = false; + } + } + + if(isMobile()) { + _initMobileMenu(); + } else { + _initDesktopMenu(); + } + } + + // init menu on page load + initMenu(); +} + +$(document).ready(setupFlyout); diff --git a/public/stylesheets/responsive.css b/public/stylesheets/responsive.css new file mode 100644 index 000000000..a01d126a4 --- /dev/null +++ b/public/stylesheets/responsive.css @@ -0,0 +1,595 @@ +/*----------------------------------------*\ + RESPONSIVE CSS +\*----------------------------------------*/ + + +/* + + CONTENTS + + A) BASIC MOBILE RESETS + B) HEADER & TOP MENUS + C) MAIN CONTENT & SIDEBAR + D) TOGGLE BUTTON & FLYOUT MENU + +*/ + + +/* Hide new elements (toggle button and flyout menu) above 900px */ +.mobile-toggle-button, +.flyout-menu +{ + display: none; +} + +/* + redmine's body is set to min-width: 900px + add first breakpoint here and start adding responsiveness +*/ + +@media all and (max-width: 899px) +{ + /*----------------------------------------*\ + A) BASIC MOBILE RESETS + \*----------------------------------------*/ + + /* + apply natural border box, see: http://www.paulirish.com/2012/box-sizing-border-box-ftw/ + this helps us to better deal with percentages and padding / margin + */ + *, + *:before, + *:after + { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + } + + body, + html + { + height: 100%; + margin: 0; + padding: 0; + } + + html + { + overflow-y: auto; /* avoid 2nd scrollbar on desktop */ + } + + body + { + overflow-x: hidden; /* hide horizontal overflow */ + + min-width: 0; /* reset the min-width of 900px */ + } + + + body, + input, + select, + textarea, + button + { + font-size: 14px; /* Set font-size for standard elements to 14px */ + } + + + select + { + max-width: 100%; /* prevent long names within select menues from breaking content */ + } + + + #wrapper + { + position: relative; + + max-width: 100%; + } + + #wrapper, + #wrapper2 + { + margin: 0; + } + + /*----------------------------------------*\ + B) HEADER & TOP MENUS + \*----------------------------------------*/ + + #header + { + width: 100%; + height: 64px; /* the height of our header on mobile */ + min-height: 0; + margin: 0; + padding: 0; + + border: none; + background-color: #628db6; + } + + /* Hide project name on mobile (project name is still visible in select menu) */ + #header h1 + { + display: none !important; + } + + /* reset #header a color for mobile toggle button */ + #header a.mobile-toggle-button + { + color: #f8f8f8; + } + + + /* Hide top-menu and main-menu on mobile, because it's placed in our flyout menu */ + #top-menu, + #header #main-menu + { + display: none; + } + + /* the quick search within header holding search form and #project_quick_jump_box box*/ + #header #quick-search + { + float: none; + clear: none; /* there are themes which set clear property, this resets it */ + + max-width: 100%; /* reset max-width */ + margin: 0; + + background: inherit; + } + + /* this represents the dropdown arrow to left of the mobile project menu */ + #header .jump-box-arrow:before + { + /* set a font-size in order to achive same result in different themes */ + font-family: Verdana, sans-serif; + font-size: 2em; + line-height: 64px; + + position: absolute; + left: 0; + + width: 2em; + padding: 0 .5em; + /* achieve dropdwon arrow by scaling a caret character */ + + content: '^'; + -webkit-transform: scale(1,-.8); + -ms-transform: scale(1,-.8); + transform: scale(1,-.8); + text-align: right; + pointer-events: none; + + opacity: .6; + } + + /* styles for combobox within quick-search (#project_quick_jump_box) */ + #header #quick-search select + { + font-size: 1.5em; + font-weight: bold; + line-height: 1.2; + + position: absolute; + top: 15px; + left: 0; + + float: left; + + width: 100%; + max-width: 100%; + height: 2em; + height: 35px; + padding: 5px; + padding-right: 72px; + padding-left: 50px; + + text-indent: .01px; + + color: inherit; + border: 0; + -webkit-border-radius: 0; + border-radius: 0; + background: none; + -webkit-box-shadow: none; + box-shadow: none; + /* hide default browser arrow */ + + -webkit-appearance: none; + -moz-appearance: none; + } + + #header #quick-search form + { + display: none; + } + + /*----------------------------------------*\ + C) MAIN CONTENT & SIDEBAR + \*----------------------------------------*/ + + #main + { + padding: 0; + } + + #main.nosidebar #content, + div#content + { + width: 100%; + min-height: 0; /* reset min-height of #content */ + margin: 0; + } + + + /* hide sidebar and sidebar switch panel, since it's placed in mobile flyout menu */ + #sidebar, + #sidebar-switch-panel + { + display: none; + } + + .splitcontentleft + { + width: 100%; /* use full width */ + } + + .splitcontentright + { + width: 100%; /* use full width */ + } + + /*----------------------------------------*\ + D) TOGGLE BUTTON & FLYOUT MENU + \*----------------------------------------*/ + + /* Mobile toggle button */ + + .mobile-toggle-button + { + font-size: 42px; + line-height: 64px; + + position: relative; + z-index: 10; + + display: block; /* remove display: none; of non-mobile version */ + float: right; + + width: 60px; + height: 64px; + margin-top: 0; + + text-align: center; + + border-left: 1px solid #ddd; + } + + .mobile-toggle-button:hover, + .mobile-toggle-button:active + { + text-decoration: none; + } + + .mobile-toggle-button:after + { + font-family: Verdana, sans-serif; + + display: block; + + margin-top: -3px; + + content: '\2261'; + } + + /* search magnifier icon */ + .search-magnifier + { + font-family: Verdana; + + cursor: pointer; + -webkit-transform: rotate(-45deg); + -moz-transform: rotate(45deg); + -o-transform: rotate(45deg); + + color: #bbb; + } + + .search-magnifier--flyout + { + font-size: 25px; + line-height: 54px; + + position: absolute; + z-index: 1; + left: 12px; + } + + + /* Flyout Menu */ + + .flyout-menu + { + position: absolute; + right: -250px; + + display: block; /* remove display: none; of non-mobile version */ + overflow-x: hidden; + + width: 250px; + height: 100%; + margin: 0; /* reset margin for themes that define it */ + padding: 0; /* reset padding for themes that define it */ + + color: white; + background-color: #3e5b76; + } + + + /* avoid zoom on search input focus for ios devices */ + .flyout-menu input[type='text'] + { + font-size: 16px; + } + + .flyout-menu h3 + { + font-size: 11px; + line-height: 19px; + + height: 20px; + margin: 0; + padding: 0; + + letter-spacing: .1em; + text-transform: uppercase; + + color: white; + border-top: 1px solid #506a83; + border-bottom: 1px solid #506a83; + background-color: #628db6; + } + + .flyout-menu h4 + { + color: white; + } + + .flyout-menu h3, + .flyout-menu h4, + .flyout-menu > p, + .flyout-menu > a, + .flyout-menu ul li a, + .flyout-menu__search, + .flyout-menu__sidebar > div, + .flyout-menu__sidebar > p, + .flyout-menu__sidebar > a, + .flyout-menu__sidebar > form, + .flyout-menu > div, + .flyout-menu > form + { + padding-left: 8px; + } + + .flyout-menu .flyout-menu__avatar + { + margin-top: -1px; /* move avatar up 1px */ + padding-left: 0; + } + + .flyout-menu__sidebar > form + { + display: block; + } + + .flyout-menu__sidebar > form h3 + { + margin-left: -8px; + } + + .flyout-menu__sidebar > form label + { + display: inline-block; + + margin: 8px 0; + } + + .flyout-menu__sidebar > form br br + { + display: none; + } + + .flyout-menu ul + { + margin: 0; + padding: 0; + + list-style: none; + } + + .flyout-menu ul li a + { + line-height: 40px; + + display: block; + overflow: hidden; + + height: 40px; + + white-space: nowrap; + text-overflow: ellipsis; + + border-top: 1px solid rgba(255,255,255,.1); + } + + .flyout-menu ul li:first-child a + { + line-height: 39px; + + height: 39px; + + border-top: none; + } + + .flyout-menu a + { + color: white; + } + + .flyout-menu ul li a:hover + { + text-decoration: none; + } + + .flyout-menu ul li a.new-object, + .new-object ~ .menu-children + { + display: none; + } + + /* Left flyout search container */ + .flyout-menu__search + { + line-height: 54px; + + height: 64px; + padding-top: 3px; + padding-right: 8px; + } + + .flyout-menu__search input[type='text'] + { + line-height: 2; + + width: 100%; + height: 38px; + padding-left: 27px; + + vertical-align: middle; + + border: none; + -webkit-border-radius: 3px; + border-radius: 3px; + background-color: #fff; + } + + .flyout-menu__avatar + { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + + width: 100%; + + border-top: 1px solid rgba(255,255,255,.1); + } + + + .flyout-menu__avatar img.gravatar + { + width: 40px; + height: 40px; + padding: 0; + + vertical-align: top; + + border-width: 0; + } + + .flyout-menu__avatar a + { + line-height: 40px; + + height: auto; + height: 40px; + + text-decoration: none; + + color: white; + } + + /* avatar */ + .flyout-menu__avatar a:first-child + { + line-height: 0; + + width: 40px; + padding: 0; + } + + .flyout-menu__avatar .user + { + padding-left: 15px; + } + + /* user link when no avatar is present */ + .flyout-menu__avatar--no-avatar a.user + { + line-height: 40px; + + padding-left: 8px; + } + + + .flyout-is-active body + { + overflow: hidden; /* for body not to have scrollbars when left flyout menu is active */ + } + + html.flyout-is-active + { + overflow: hidden; + } + + + .flyout-is-active #wrapper + { + right: 250px; /* when left flyout is active, move body to the right (same amount like flyout-menu's width) */ + + height: 100%; + } + + .flyout-is-active .mobile-toggle-button:after + { + content: '\00D7'; /* close glyph */ + } + + .flyout-is-active #wrapper2 + { + + /* + * only relevant for devices with cursor when flyout it active, in order to show, + * that whole wrapper content is clickable and closes flyout menu + */ + cursor: pointer; + } + + + #admin-menu + { + padding-left: 0; + } + + #admin-menu li + { + padding-bottom: 0; + } + + #admin-menu a, + #admin-menu a.selected + { + line-height: 40px; + + padding: 0; + padding-left: 32px !important; + + background-position: 8px 50%; + } +} |