From b0a50c08979cc54b4c73fbd099d33e57e3403482 Mon Sep 17 00:00:00 2001
From: Jens Kraemer <jk@jkraemer.net>
Date: Wed, 31 Aug 2016 11:47:29 +0800
Subject: [PATCH] make the sidebar collapsible

- uses the localStorage API
- degrades to a non-persistent collapsible sidebar if localStorage isn't
  available
- degrades nicely if no JS is available ('hide sidebar' link is not there in
  this case)
---
 app/views/layouts/base.html.erb    |  2 +-
 public/javascripts/application.js  | 82 ++++++++++++++++++++++++++++++++++++++
 public/stylesheets/application.css | 22 ++++++++--
 3 files changed, 101 insertions(+), 5 deletions(-)

diff --git a/app/views/layouts/base.html.erb b/app/views/layouts/base.html.erb
index 6c5a9b2..5e75bad 100644
--- a/app/views/layouts/base.html.erb
+++ b/app/views/layouts/base.html.erb
@@ -98,7 +98,7 @@
     <% end %>
 </div>
 
-<div id="main" class="<%= sidebar_content? ? '' : 'nosidebar' %>">
+<div id="main" class="<%= sidebar_content? ? 'collapsiblesidebar' : 'nosidebar' %>">
     <div id="sidebar">
         <%= yield :sidebar %>
         <%= view_layouts_base_sidebar_hook_response %>
diff --git a/public/javascripts/application.js b/public/javascripts/application.js
index 64d3e8e..b1c1940 100644
--- a/public/javascripts/application.js
+++ b/public/javascripts/application.js
@@ -781,8 +781,90 @@ function keepAnchorOnSignIn(form){
   return true;
 }
 
+// collapsible sidebar JQuery plugin
+(function($) {
+
+  // main container this is applied to
+  var main;
+
+  // triggers show/hide
+  var button;
+
+  // the key to use in local storage
+  // this will later be expanded using the current controller and action to
+  // allow for different sidebar states for different pages
+  var localStorageKey = 'redmine-sidebar-state';
+
+  // true if local storage is available
+  var canUseLocalStorage = function(){
+    try {
+      if('localStorage' in window){
+        localStorage.setItem('redmine.test.storage', 'ok');
+        var item = localStorage.getItem('redmine.test.storage');
+        localStorage.removeItem('redmine.test.storage');
+        if(item === 'ok') return true;
+      }
+    } catch (err) {}
+    return false;
+  }();
+
+  // function to set current sidebar state
+  var setState = function(state){
+    if(canUseLocalStorage){
+      localStorage.setItem(localStorageKey, state);
+    }
+  };
+
+  var applyState = function(){
+    if(main.hasClass('nosidebar')){
+      button.html("&laquo;");
+      setState('hidden');
+    } else {
+      button.html("&raquo;");
+      setState('visible');
+    }
+  };
+
+  $.fn.collapsibleSidebar = function() {
+    main = this;
+
+    // create the toggle button
+    var panel = $('<div id="sidebar-switch-panel"></div>');
+    button = $('<a id="sidebar-switch-button" href="#"></a>');
+    button.click(function(e){
+      main.addClass("animate");
+      main.toggleClass('nosidebar');
+      applyState();
+      e.preventDefault();
+      return false;
+    });
+    panel.append(button);
+    main.find('#content').prepend(panel);
+
+    // determine previously stored sidebar state for this page
+    if(canUseLocalStorage) {
+      // determine current controller/action pair and use them as storage key
+      var bodyClass = $('body').attr('class');
+      if(bodyClass){
+        localStorageKey += '-' + bodyClass.split(/\s+/).filter(function(s){
+          return s.match(/(action|controller)-.*/);
+        }).sort().join('-');
+      }
+      var storedState = localStorage.getItem(localStorageKey);
+      main.toggleClass('nosidebar', storedState === 'hidden');
+    }
+
+    applyState();
+  };
+}(jQuery));
+
 $(document).ready(setupAjaxIndicator);
 $(document).ready(hideOnLoad);
 $(document).ready(addFormObserversForDoubleSubmit);
 $(document).ready(defaultFocus);
 $(document).ready(setupTabs);
+
+$(document).ready(function(){
+  $('#main.collapsiblesidebar').collapsibleSidebar();
+});
+
diff --git a/public/stylesheets/application.css b/public/stylesheets/application.css
index 6786cb9..9e91aa2 100644
--- a/public/stylesheets/application.css
+++ b/public/stylesheets/application.css
@@ -106,13 +106,27 @@ pre, code {font-family: Consolas, Menlo, "Liberation Mono", Courier, monospace;}
 #sidebar div.wiki ul {margin:inherit; padding-left:40px;}
 #sidebar div.wiki ul li {list-style-type:inherit;}
 
-#content { width: 75%; background-color: #fff; margin: 0px; border-right: 1px solid #ddd; padding: 6px 10px 10px 10px; z-index: 10; }
-* html #content{ width: 75%; padding-left: 0; margin-top: 0px; padding: 6px 10px 10px 10px;}
+#content { background-color: #fff; margin: 0 25% 0 0; border-right: 1px solid #ddd; padding: 6px 10px 10px 10px; z-index: 10; }
+* html #content{ margin-right: 25%; padding-left: 0; margin-top: 0px; padding: 6px 10px 10px 10px;}
 html>body #content { min-height: 600px; }
 * html body #content { height: 600px; } /* IE */
 
-#main.nosidebar #sidebar{ display: none; }
-#main.nosidebar #content{ width: auto; border-right: 0; }
+#main.nosidebar #sidebar { display: block; margin-right: -25%; }
+#main.nosidebar #content{ border-right: 0; margin-right: 0; }
+#main.animate { transition: background-position 0.2s ease-in-out; }
+#main.animate #sidebar, #main.animate #content { transition: margin 0.2s ease-in-out; }
+
+#sidebar-switch-panel {
+  background-color: #EEEEEE;
+  border-bottom: 1px solid #DDDDDD;
+  border-left: 1px solid #DDDDDD;
+  border-top: 1px solid #DDDDDD;
+  float: right;
+  left: 10px;
+  top: 2px;
+  position: relative;
+  padding: 2px;
+}
 
 #footer {clear: both; border-top: 1px solid #bbb; font-size: 0.9em; color: #aaa; padding: 5px; text-align:center; background:#fff;}
 
-- 
2.1.4

