Browse Source

Relayout & rewrite sidebar

Denis K 8 years ago
parent
commit
9b0ecd030b

+ 2 - 2
jet/static/jet/css/_base.scss

@@ -84,10 +84,10 @@ body {
 .sidebar {
   position: fixed;
   width: $sidebar-width;
-  height: 100%;
   top: 0;
   left: 0;
-  overflow-y: auto;
+  bottom: 0;
+  z-index: 3;
 }
 
 .dialog-confirm {

+ 18 - 39
jet/static/jet/css/_header.scss

@@ -3,51 +3,30 @@
 
 /* HEADER */
 
-//#header {
-    //width: auto;
-    //height: 40px;
-    //padding: 10px 40px;
-    //background: #417690;
-    //line-height: 40px;
-    //color: #ffc;
-    //overflow: hidden;
-//}
-
-//#header a:link, #header a:visited {
-//    color: #fff;
-//}
-//
-//#header a:focus , #header a:hover {
-//    text-decoration: underline;
-//}
-
-#branding {
+#header {
   display: none;
-    float: left;
 }
 
-#branding h1 {
-    padding: 0;
-    margin: 0 20px 0 0;
-    font-weight: 300;
-    font-size: 24px;
-    color: #f5dd5d;
-}
+#branding {
+  background-color: $sidebar-contrast-background-color;
+  color: $sidebar-contrast-text-color;
+  padding: 14px;
+  text-align: center;
 
-#branding h1, #branding h1 a:link, #branding h1 a:visited {
-    color: #f5dd5d;
-}
+  h1, h2 {
+    padding: 0;
+    margin: 0;
+    text-transform: uppercase;
+    font-size: 11px;
+  }
 
-#branding h2 {
-    padding: 0 10px;
-    font-size: 14px;
-    margin: -8px 0 8px 0;
-    font-weight: normal;
-    color: #ffc;
-}
+  a, a:visited, a:hover {
+    color: $sidebar-contrast-text-color;
+  }
 
-#branding a:hover {
-    text-decoration: none;
+  a:hover {
+    color: $sidebar-hover-action-color;
+  }
 }
 
 #user-tools {

+ 179 - 218
jet/static/jet/css/_sidebar.scss

@@ -3,238 +3,213 @@
 .sidebar {
   background-color: $sidebar-background-color;
   transition: background-color $transitions-duration;
+  padding-bottom: 32px;
 
   @include for-mobile {
     display: none;
   }
 
-  &-menu {
-    @extend .clear-list;
-    margin-bottom: 32px !important;
+  &-wrapper {
+    height: 100%;
+    overflow-y: auto;
+  }
 
-    &-wrapper {
-      overflow: hidden;
-      height: 100%;
-      position: relative;
+  &-section {
+    padding: 20px 0;
+    border-bottom: 1px solid $sidebar-contrast-background-color;
+    transition: border-bottom-color 0.3s;
+
+    &:last-child {
+      border-bottom: 0;
     }
+  }
 
-    &-item {
-      padding: 20px 24px;
-      border-bottom: 1px solid $sidebar-contrast-background-color;
-      transition: border-bottom-color $transitions-duration;
+  &-title {
+    display: block;
+    color: $sidebar-text-color;
+    text-transform: uppercase;
+    font-size: 11px;
+    font-weight: bold;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+    padding: 0 14px 0 24px;
+    margin-bottom: 10px;
+    transition: color $transitions-duration;
+
+    &-link {
+      &, &:visited, &:hover {
+        color: $sidebar-text-color;
+        font-weight: bold;
+        transition: color $transitions-duration;
+      }
 
-      &:last-child {
-        border-bottom: 0;
+      &:hover {
+        color: $sidebar-hover-title-action-item-color;
       }
+    }
+  }
 
-      &.no-horizontal-padding {
-        padding-left: 0;
-        padding-right: 0;
+  &-link {
+    &, &:visited, &:hover {
+      display: block;
+      color: $sidebar-link-color;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+      padding: 8px 12px 8px 24px;
+      vertical-align: middle;
+      transition: color $transitions-duration, background-color $transitions-duration;
+      position: relative;
+
+      &.icon {
+        font-size: 11px;
+        text-transform: uppercase;
       }
+    }
+
+    &:hover, &.selected {
+      color: $sidebar-hover-link-color;
+      background-color: $sidebar-hover-background-color;
+    }
+
+    &-icon {
+      font-size: 18px;
+      vertical-align: middle;
+      margin-right: 6px;
+      color: $sidebar-icon-color;
+      transition: color $transitions-duration;
+    }
+
+    &-label {
+      vertical-align: middle;
+    }
+  }
 
-      &-icon {
-        font-size: 18px;
-        vertical-align: middle;
-        margin-right: 6px;
-        color: $sidebar-icon-color;
+  &-center-link {
+    &, &:visited, &:hover {
+      display: block;
+      color: $sidebar-action-color;
+      font-size: 11px;
+      text-align: center;
+      padding: 8px 0;
+      text-transform: uppercase;
+      transition: color $transitions-duration, background-color $transitions-duration;
+    }
+
+    &:hover {
+      color: $sidebar-hover-action-color;
+      background-color: $sidebar-hover-background-color;
+    }
+  }
+
+  &-left {
+    position: absolute;
+    left: 4px;
+
+    &.collapsible {
+      display: none;
+    }
+
+    &-pin, &-unpin {
+      &, &:visited, &:hover {
+        position: absolute;
+        top: 1px;
+        font-size: 14px;
+        color: $sidebar-action-color;
         transition: color $transitions-duration;
       }
 
-      &-link {
-        &, &:visited, &:hover {
-          color: $sidebar-link-color;
-          font-weight: bold;
-          overflow: hidden;
-          text-overflow: ellipsis;
-          white-space: nowrap;
-          width: 100%;
-          display: inline-block;
-          box-sizing: border-box;
-          transition: color $transitions-duration;
-        }
-
-        &:hover {
-          color: $sidebar-hover-link-color;
-        }
+      &:hover {
+        color: $sidebar-hover-action-color;
       }
+    }
 
-      &-title {
-        color: $sidebar-text-color;
-        text-transform: uppercase;
-        font-size: 11px;
-        font-weight: bold;
-        overflow: hidden;
-        text-overflow: ellipsis;
-        white-space: nowrap;
-        padding: 0 14px 0 24px;
-        width: 100%;
-        display: inline-block;
-        box-sizing: border-box;
-        transition: color $transitions-duration;
+    .apps-list-pinned &-pin {
+      display: none;
+    }
 
-        &-icon {
-          float: right;
-          font-size: 14px;
-          font-weight: bold !important;
+    .apps-list &-unpin {
+      display: none;
+    }
+  }
 
-          &, &:visited, &:hover {
-            color: $sidebar-title-action-color;
-            transition: color $transitions-duration;
-          }
+  &-link:hover &-left.collapsible {
+    display: inline-block;
+  }
 
-          &:hover {
-            color: $sidebar-hover-title-action-item-color;
-          }
-        }
-      }
+  &-right {
+    float: right;
+    margin-left: 10px;
 
-      &-link {
-        &, &:visited, &:hover {
-          color: $sidebar-text-color;
-          text-transform: uppercase;
-          font-size: 11px;
-          font-weight: bold;
-          overflow: hidden;
-          text-overflow: ellipsis;
-          white-space: nowrap;
-          padding: 0 24px;
-          transition: color $transitions-duration;
-        }
+    &.collapsible {
+      display: none;
+    }
 
-        &:hover {
-          color: $sidebar-hover-title-action-item-color;
-        }
-      }
+    &-plus {
+      font-size: 14px;
+      outline: 0;
+    }
 
-      &-action {
-        &, &:visited, &:hover {
-          display: block;
-          color: $sidebar-action-color;
-          font-size: 11px;
-          text-align: center;
-          margin-top: 10px;
-          padding: 8px 0;
-          text-transform: uppercase;
-          transition: color $transitions-duration, background-color $transitions-duration;
-        }
+    &-arrow {
+      color: $sidebar-arrow-color;
+      font-size: 16px;
+      font-weight: bold !important;
+      transition: color $transitions-duration;
+    }
 
-        &:hover {
-          color: $sidebar-hover-action-color;
-          background-color: $sidebar-hover-background-color;
-        }
+    &-remove {
+      &, &:visited, &:hover {
+        position: relative;
+        color: $sidebar-action-color;
+        transition: color $transitions-duration;
       }
 
-      &-list {
-        @extend .clear-list;
+      &:hover {
+        color: $sidebar-hover-action-color;
+      }
+    }
+  }
 
-        .sidebar-menu-item-title + &:not(:empty), .sidebar-menu-item-link + &:not(:empty) {
-          margin-top: 10px !important;
-        }
+  &-link:hover &-right.collapsible {
+    display: inline-block;
+  }
 
-        &-item {
-          &.empty {
-            display: none;
-          }
-
-          &-icon {
-            font-size: 18px;
-            vertical-align: middle;
-            margin-right: 6px;
-            color: $sidebar-icon-color;
-            transition: color $transitions-duration;
-          }
-
-          &.compact &-icon {
-            font-size: 16px;
-          }
-
-          &-link {
-            &, &:visited, &:hover {
-              display: block;
-              color: $sidebar-link-color;
-              overflow: hidden;
-              text-overflow: ellipsis;
-              white-space: nowrap;
-              padding: 8px 12px 8px 24px;
-              vertical-align: middle;
-              position: relative;
-              transition: color $transitions-duration, background-color $transitions-duration;
-            }
-
-            &:hover, &.hovered {
-              color: $sidebar-hover-link-color;
-              background-color: $sidebar-hover-background-color;
-            }
-
-            &-remove {
-              &, &:visited, &:hover {
-                position: relative;
-                float: right;
-                display: none;
-                color: $sidebar-action-color;
-                transition: color $transitions-duration;
-              }
-
-              &:hover {
-                color: $sidebar-hover-action-color;
-              }
-            }
-
-            &-pin, &-unpin {
-              &, &:visited, &:hover {
-                position: absolute;
-                display: none;
-                left: 4px;
-                font-size: 14px;
-                color: $sidebar-action-color;
-                transition: color $transitions-duration;
-              }
-
-              &:hover {
-                color: $sidebar-hover-action-color;
-              }
-            }
-
-            &:hover &-remove {
-              display: inline-block;
-            }
-          }
-
-          &:not(.pinned):hover &-link-pin {
-            display: inline-block;
-          }
-
-          &.pinned:hover &-link-unpin {
-            display: inline-block;
-          }
-
-          &-arrow {
-            float: right;
-            color: $sidebar-arrow-color;
-            font-size: 16px;
-            font-weight: bold !important;
-            margin-left: 4px;
-            transition: color $transitions-duration;
-          }
-
-          &-link:hover &-arrow {
-            color: $sidebar-hover-arrow-color;
-          }
-        }
+  &-link:hover &-right-arrow {
+    color: $sidebar-hover-arrow-color;
+  }
 
-        &.compact &-item-link {
-          &, &:visited, &:hover {
-            font-size: 11px;
-            padding: 6px 12px 6px 24px;
-            text-transform: uppercase;
-          }
+  .clone {
+    display: none;
+  }
 
-          &.padding-icon {
-            padding-left: 49px;
-          }
-        }
-      }
+  .apps-hide {
+    &-label {
+      display: none;
+    }
+
+    &.apps-visible .apps-hide-label.apps-visible {
+      display: inline;
     }
+
+    &.apps-hidden .apps-hide-label.apps-hidden {
+      display: inline;
+    }
+  }
+
+  &-copyright {
+    background-color: $sidebar-contrast-background-color;
+    color: $sidebar-contrast-text-color;
+    height: 32px;
+    line-height: 32px;
+    position: absolute;
+    bottom: 0;
+    left: 0;
+    right: 0;
+    text-align: center;
+    font-size: 11px;
+    font-weight: bold;
+    transition: background-color $transitions-duration, color $transitions-duration;
   }
 
   &-popup {
@@ -254,10 +229,9 @@
       left: $sidebar-width;
       bottom: 0;
       right: 0;
-      z-index: 3;
     }
 
-    &-item {
+    &-section {
       display: none;
     }
 
@@ -290,7 +264,9 @@
     }
 
     &-list {
-      @extend .clear-list;
+      margin: 0;
+      padding: 0;
+      list-style: none;
 
       &-item {
         a, a:visited, a:hover {
@@ -306,19 +282,4 @@
       }
     }
   }
-
-  &-copyright {
-    background-color: $sidebar-contrast-background-color;
-    color: $sidebar-contrast-text-color;
-    height: 32px;
-    line-height: 32px;
-    position: absolute;
-    bottom: 0;
-    left: 0;
-    right: 0;
-    text-align: center;
-    font-size: 11px;
-    font-weight: bold;
-    transition: background-color $transitions-duration, color $transitions-duration;
-  }
 }

+ 0 - 365
jet/static/jet/js/src/features/side-menu.js

@@ -1,365 +0,0 @@
-require('./../utils/jquery-icontains');
-require('./../utils/jquery-slidefade');
-
-var $ = window.jQuery = require('jquery');
-
-require('jquery-ui/ui/core');
-require('jquery-ui/ui/widget');
-require('jquery-ui/ui/mouse');
-require('jquery-ui/ui/draggable');
-require('jquery-ui/ui/resizable');
-require('jquery-ui/ui/button');
-require('jquery-ui/ui/dialog');
-
-require('perfect-scrollbar/jquery')($);
-
-var initSideMenu = function() {
-    var initPopupItems = function() {
-        var $popupContainer = $('.sidebar-popup-container');
-        var $popup = $('.sidebar-popup');
-        var $popupItems = $('.sidebar-popup-item');
-        var $popupLinks = $('.popup-item-link');
-        var $popupLink;
-        var t;
-        var $currentPopupItem;
-        var $currentPopupItemListItem;
-        var $currentPopupItemListItems = function() { return $currentPopupItem.find('.sidebar-popup-list-item:visible') };
-
-        var resetCurrentPopupItemListItems = function() {
-            $currentPopupItemListItems().removeClass('selected');
-        };
-
-        var initPopupItemsSearch = function() {
-            $popupItems.each(function() {
-                var $popupItem = $(this);
-                var $search = $popupItem.find('.sidebar-popup-search');
-                var $items = $popupItem.find('.sidebar-popup-list-item');
-
-                $search.on('change keyup', function() {
-                    var text = $(this).val();
-
-                    $items.hide();
-                    $popupItem
-                        .find('.sidebar-popup-list-item-link:icontains("' + text + '")')
-                        .closest('.sidebar-popup-list-item')
-                        .show();
-                });
-            });
-        };
-
-        var showPopup = function ($popupLink) {
-            clearHideTimeout();
-
-            var popupItemId = $popupLink.data('popup-item-id');
-            var $popupItem = $('#' + popupItemId);
-            var $search = $popupItem.find('.sidebar-popup-search');
-
-            $popupItems.hide();
-            $popupItem.show();
-            $popupContainer.stop().fadeIn(200, 'swing');
-            $popupLinks.removeClass('hovered');
-            $popupLink.addClass('hovered');
-            $('body').addClass('non-scrollable');
-
-            $currentPopupItem = $popupItem;
-            $currentPopupItemListItem = null;
-            resetCurrentPopupItemListItems();
-
-            $search.val('').trigger('change').focus();
-        };
-
-        var hidePopup = function () {
-            t = setTimeout(function() {
-                $popupItems.hide();
-                $popupContainer.stop().fadeOut(200, 'swing');
-                $popupLinks.removeClass('hovered');
-                $('body').removeClass('non-scrollable');
-
-                $currentPopupItem = null;
-            }, 200);
-        };
-
-        var clearHideTimeout = function() {
-            if (t != null) {
-                clearTimeout(t);
-            }
-            t = null;
-        };
-
-        $popupLinks.on('mouseenter', function () {
-            $popupLink = $(this);
-
-            showPopup($popupLink);
-        });
-
-        $popupLinks.on('mouseleave', function (e) {
-            var $toElement = $(e.toElement);
-
-            if ($toElement.hasClass('sidebar-popup') || $toElement.parents('.sidebar-popup').length) {
-                return;
-            }
-
-            hidePopup();
-        });
-
-        $popup.on('mouseenter', function (e) {
-            clearHideTimeout();
-        });
-
-        $popup.on('mouseleave', function (e) {
-            var $toElement = $(e.toElement);
-
-            if ($toElement.hasClass('popup-item-link')
-                && $popupLink.data('popup-item-id') == $toElement.data('popup-item-id')) {
-                return;
-            }
-
-            hidePopup();
-        });
-
-        $popup.find('.sidebar-popup-list-item-link').on('mouseenter', function() {
-            var $link = $(this);
-            var $item = $link.closest('.sidebar-popup-list-item');
-
-            $currentPopupItemListItem = $item;
-
-            resetCurrentPopupItemListItems();
-            $currentPopupItemListItem.addClass('selected');
-        });
-
-        var selectCurrentPopupItemListItem = function(next) {
-            if ($currentPopupItemListItem != null) {
-                $currentPopupItemListItem = next ? $currentPopupItemListItem.nextAll(':visible').first() : $currentPopupItemListItem.prevAll(':visible').first();
-            }
-
-            if ($currentPopupItemListItem == null || $currentPopupItemListItem.length == 0) {
-                $currentPopupItemListItem = next ? $currentPopupItemListItems().first() : $currentPopupItemListItems().last();
-            }
-
-            resetCurrentPopupItemListItems();
-            $currentPopupItemListItem.addClass('selected');
-        };
-
-        $(document).keydown(function(e) {
-            if ($currentPopupItem == null) {
-                return;
-            }
-
-            if (e.which == 38) { //up
-                selectCurrentPopupItemListItem(false);
-            } else if (e.which == 40) { //down
-                selectCurrentPopupItemListItem(true);
-            } else if (e.which == 13) {
-                if ($currentPopupItemListItem) {
-                    document.location = $currentPopupItemListItem.find('a').attr('href');
-                }
-            } else {
-                return;
-            }
-
-            e.preventDefault();
-        });
-
-        initPopupItemsSearch();
-    };
-
-    var initBookmarks = function() {
-        var $addForm = $('#bookmarks-add-form');
-        var $removeForm = $('#bookmarks-remove-form');
-        var $addTitleInput = $addForm.find('input[name="title"]');
-        var $addUrlInput = $addForm.find('input[name="url"]');
-        var $removeIdInput = $removeForm.find('input[name="id"]');
-
-        $('.bookmarks-add').on('click', function(e) {
-            e.preventDefault();
-
-            var $link = $(this);
-            var defaultTitle = $link.data('title') ? $link.data('title') : document.title;
-            var url = window.location.href;
-
-            $addTitleInput.val(defaultTitle);
-            $addUrlInput.val(url);
-
-            var addBookmark = function() {
-                $.ajax({
-                    url: $addForm.attr('action'),
-                    method: $addForm.attr('method'),
-                    dataType: 'json',
-                    data: $addForm.serialize(),
-                    success: function (result) {
-                        if (result.error) {
-                            return;
-                        }
-
-                        var $list = $('.bookmarks-list');
-                        var $item = $('.sidebar-menu-item-list-item.empty').clone().removeClass('empty');
-
-                        $item.find('.sidebar-menu-item-list-item-link')
-                            .attr('href', url)
-                            .append($addTitleInput.val());
-
-                        $item.find('.sidebar-menu-item-list-item-link-remove').attr('data-bookmark-id', result.id);
-
-                        $list.append($item);
-                    }
-                });
-            };
-
-            var buttons = {};
-
-            buttons[django.gettext('Add')] = function() {
-                addBookmark();
-                $(this).dialog('close');
-            };
-
-            buttons[django.gettext('Cancel')] = function() {
-                $(this).dialog('close');
-            };
-
-            $('#bookmarks-add-dialog').dialog({
-                resizable: false,
-                modal: true,
-                buttons: buttons
-            });
-        });
-
-        $(document).on('click', '.bookmarks-remove', function(e) {
-            e.preventDefault();
-
-            var $remove = $(this);
-            var bookmarkId = $remove.data('bookmark-id');
-
-            var deleteBookmark = function() {
-                $removeIdInput.val(bookmarkId);
-
-                $.ajax({
-                    url: $removeForm.attr('action'),
-                    method: $removeForm.attr('method'),
-                    dataType: 'json',
-                    data: $removeForm.serialize(),
-                    success: function (result) {
-                        if (result.error) {
-                            return;
-                        }
-
-                        var $item = $remove.closest('.sidebar-menu-item-list-item');
-
-                        $item.remove();
-                    }
-                });
-            };
-
-            var buttons = {};
-
-            buttons[django.gettext('Delete')] = function() {
-                deleteBookmark();
-                $(this).dialog('close');
-            };
-
-            buttons[django.gettext('Cancel')] = function() {
-                $(this).dialog('close');
-            };
-
-            $('#bookmarks-remove-dialog').dialog({
-                resizable: false,
-                modal: true,
-                buttons: buttons
-            });
-        });
-    };
-
-    var initApplicationPinning = function() {
-        var $appsList = $('.apps-list');
-        var $pinnedAppsList = $('.apps-list-pinned');
-        var $appsHide = $('.apps-hide');
-
-        var updateAppsHide = function () {
-            var text;
-
-            if ($appsList.is(':visible')) {
-                text = django.gettext('Hide applications');
-            } else {
-                text = django.gettext('Show hidden');
-            }
-
-            $appsHide.text(text);
-
-            if (($appsList.children().length == 0 || $pinnedAppsList.children().length == 0) && $appsList.is(':visible')) {
-                $appsHide.hide();
-            } else {
-                $appsHide.show();
-            }
-        };
-
-        $appsHide.on('click', function (e) {
-            e.preventDefault();
-
-            $appsList.slideFadeToggle(200, 'swing', function () {
-                localStorage['side_menu_apps_list_visible'] = $appsList.is(':visible');
-                updateAppsHide();
-            });
-        });
-
-        $('.app-item .pin-toggle').on('click', function (e) {
-            var $appItem = $(this).closest('.app-item');
-            var appLabel = $appItem.data('app-label');
-            var $form = $('#toggle-application-pin-form');
-
-            $form.find('input[name="app_label"]').val(appLabel);
-
-            $.ajax({
-                url: $form.attr('action'),
-                method: $form.attr('method'),
-                dataType: 'json',
-                data: $form.serialize(),
-                success: function (result) {
-                    if (result.error) {
-                        return;
-                    }
-
-                    var $target = result.pinned ? $('.apps-list-pinned') : $('.apps-list');
-
-                    if (result.pinned) {
-                        $appItem.addClass('pinned');
-                    } else {
-                        $appItem.removeClass('pinned');
-                    }
-
-                    $appItem.detach();
-                    $appItem.appendTo($target);
-
-                    updateAppsHide();
-                }
-            });
-
-            e.preventDefault();
-        });
-
-        if (localStorage['side_menu_apps_list_visible'] === 'false') {
-            if ($pinnedAppsList.children().length != 0) {
-                $appsList.hide();
-            } else {
-                localStorage['side_menu_apps_list_visible'] = true;
-            }
-        }
-
-        updateAppsHide();
-    };
-
-    initPopupItems();
-    initBookmarks();
-    initApplicationPinning();
-};
-
-var initScrollbars = function() {
-    $('.sidebar-menu-wrapper').perfectScrollbar();
-};
-
-$(document).ready(function() {
-    if ($('.sidebar').length == 0) {
-        return;
-    }
-
-    initSideMenu();
-    initScrollbars();
-});

+ 106 - 0
jet/static/jet/js/src/features/sidebar/application-pinning.js

@@ -0,0 +1,106 @@
+require('./../../utils/jquery-slidefade');
+
+var $ = window.jQuery = require('jquery');
+
+require('jquery-ui/ui/core');
+require('jquery-ui/ui/widget');
+require('jquery-ui/ui/mouse');
+require('jquery-ui/ui/draggable');
+require('jquery-ui/ui/resizable');
+require('jquery-ui/ui/button');
+require('jquery-ui/ui/dialog');
+
+var SideBarApplicationPinning = function($sidebar) {
+    this.$sidebar = $sidebar;
+};
+
+SideBarApplicationPinning.prototype = {
+    pinToggle: function($form, $sidebar, $appItem) {
+        var self = this;
+        var $appsList = $sidebar.find('.apps-list');
+        var $pinnedAppsList = $sidebar.find('.apps-list-pinned');
+
+        $.ajax({
+            url: $form.attr('action'),
+            method: $form.attr('method'),
+            dataType: 'json',
+            data: $form.serialize(),
+            success: function (result) {
+                if (result.error) {
+                    return;
+                }
+
+                var $target = result.pinned ? $pinnedAppsList : $appsList;
+
+                $appItem
+                    .toggleClass('pinned', result.pinned)
+                    .detach()
+                    .appendTo($target);
+
+                self.updateAppsHide($sidebar);
+            }
+        });
+    },
+    initApplicationPinning: function($sidebar) {
+        var self = this;
+
+        $sidebar.find('.pin-toggle').on('click', function(e) {
+            e.preventDefault();
+
+            var $appItem = $(this).closest('.app-item');
+            var appLabel = $appItem.data('app-label');
+            var $form = $sidebar.find('#toggle-application-pin-form');
+
+            $form.find('input[name="app_label"]').val(appLabel);
+
+            self.pinToggle($form, $sidebar, $appItem);
+        });
+    },
+    updateAppsHide: function($sidebar) {
+        var $appsList = $sidebar.find('.apps-list');
+        var $pinnedAppsList = $sidebar.find('.apps-list-pinned');
+        var $appsHide = $sidebar.find('.apps-hide');
+
+        if (($appsList.children().length == 0 || $pinnedAppsList.children().length == 0) && $appsList.is(':visible')) {
+            $appsHide.removeClass('apps-visible apps-hidden');
+        } else {
+            $appsHide.toggleClass('apps-visible', $appsList.is(':visible'));
+            $appsHide.toggleClass('apps-hidden', !$appsList.is(':visible'));
+        }
+    },
+    initAppsHide: function($sidebar) {
+        var self = this;
+        var $appsList = $sidebar.find('.apps-list');
+        var $pinnedAppsList = $sidebar.find('.apps-list-pinned');
+        var $appsHide = $sidebar.find('.apps-hide');
+
+        $appsHide.on('click', function (e) {
+            e.preventDefault();
+
+            $appsList.slideFadeToggle(200, 'swing', function () {
+                localStorage['side_menu_apps_list_visible'] = $appsList.is(':visible');
+                self.updateAppsHide($sidebar);
+            });
+        });
+
+        if (localStorage['side_menu_apps_list_visible'] === 'false') {
+            if ($pinnedAppsList.children().length != 0) {
+                $appsList.hide();
+            } else {
+                localStorage['side_menu_apps_list_visible'] = true;
+            }
+        }
+
+        this.updateAppsHide($sidebar);
+    },
+    run: function() {
+        try {
+            this.initApplicationPinning(this.$sidebar);
+            this.initAppsHide(this.$sidebar);
+        } catch (e) {
+            console.error(e);
+        }
+    }
+};
+
+module.exports = SideBarApplicationPinning;

+ 146 - 0
jet/static/jet/js/src/features/sidebar/bookmarks.js

@@ -0,0 +1,146 @@
+var $ = window.jQuery = require('jquery');
+
+require('jquery-ui/ui/core');
+require('jquery-ui/ui/widget');
+require('jquery-ui/ui/mouse');
+require('jquery-ui/ui/draggable');
+require('jquery-ui/ui/resizable');
+require('jquery-ui/ui/button');
+require('jquery-ui/ui/dialog');
+
+var SideBarBookmarks = function($sidebar) {
+    this.$sidebar = $sidebar;
+};
+
+SideBarBookmarks.prototype = {
+    t: function(s) {
+        if (window.django == undefined) {
+            return s;
+        }
+        return django.gettext(s);
+    },
+    addBookmark: function($form, $container) {
+        $.ajax({
+            url: $form.attr('action'),
+            method: $form.attr('method'),
+            dataType: 'json',
+            data: $form.serialize(),
+            success: function (result) {
+                if (result.error) {
+                    return;
+                }
+
+                var $item = $container
+                    .find('.bookmark-item.clone')
+                    .clone()
+                    .removeClass('clone');
+
+                $item
+                    .attr('href', result.url)
+                    .append(result.title)
+                    .find('.bookmarks-remove')
+                    .data('bookmark-id', result.id);
+
+                $container.append($item);
+            }
+        });
+    },
+    deleteBookmark: function($form, $item) {
+        $.ajax({
+            url: $form.attr('action'),
+            method: $form.attr('method'),
+            dataType: 'json',
+            data: $form.serialize(),
+            success: function (result) {
+                if (result.error) {
+                    return;
+                }
+
+                $item.remove();
+            }
+        });
+    },
+    initBookmarksAdding: function($sidebar) {
+        var self = this;
+        var $form = $sidebar.find('#bookmarks-add-form');
+        var $titleInput = $form.find('input[name="title"]');
+        var $urlInput = $form.find('input[name="url"]');
+        var $dialog = $sidebar.find('#bookmarks-add-dialog');
+        var $container = $sidebar.find('.bookmarks-list');
+
+        $sidebar.find('.bookmarks-add').on('click', function(e) {
+            e.preventDefault();
+
+            var $link = $(this);
+            var defaultTitle = $link.data('title') ? $link.data('title') : document.title;
+            var url = window.location.href;
+
+            $titleInput.val(defaultTitle);
+            $urlInput.val(url);
+
+            var buttons = {};
+
+            buttons[self.t('Add')] = function() {
+                self.addBookmark($form, $container);
+                $(this).dialog('close');
+            };
+
+            buttons[self.t('Cancel')] = function() {
+                $(this).dialog('close');
+            };
+
+            $dialog.dialog({
+                resizable: false,
+                modal: true,
+                buttons: buttons
+            });
+        });
+    },
+    initBookmarksRemoving: function($sidebar) {
+        var self = this;
+        var $form = $sidebar.find('#bookmarks-remove-form');
+        var $idInput = $form.find('input[name="id"]');
+        var $dialog = $sidebar.find('#bookmarks-remove-dialog');
+
+        $sidebar.on('click', '.bookmarks-remove', function(e) {
+            e.preventDefault();
+
+            var $remove = $(this);
+            var $item = $remove.closest('.bookmark-item');
+            var bookmarkId = $remove.data('bookmark-id');
+
+            $idInput.val(bookmarkId);
+
+            var buttons = {};
+
+            buttons[self.t('Delete')] = function() {
+                self.deleteBookmark($form, $item);
+                $(this).dialog('close');
+            };
+
+            buttons[self.t('Cancel')] = function() {
+                $(this).dialog('close');
+            };
+
+            $dialog.dialog({
+                resizable: false,
+                modal: true,
+                buttons: buttons
+            });
+        });
+    },
+    initBookmarks: function($sidebar) {
+        this.initBookmarksAdding($sidebar);
+        this.initBookmarksRemoving($sidebar);
+    },
+    run: function() {
+        try {
+            this.initBookmarksAdding(this.$sidebar);
+            this.initBookmarksRemoving(this.$sidebar);
+        } catch (e) {
+            console.error(e);
+        }
+    }
+};
+
+module.exports = SideBarBookmarks;

+ 37 - 0
jet/static/jet/js/src/features/sidebar/main.js

@@ -0,0 +1,37 @@
+var $ = window.jQuery = require('jquery');
+var SideBarApplicationPinning = require('./application-pinning');
+var SideBarBookmarks = require('./bookmarks');
+var SideBarPopup = require('./popup');
+
+require('perfect-scrollbar/jquery')($);
+
+var SideBar = function($sidebar) {
+    this.$sidebar = $sidebar;
+};
+
+SideBar.prototype = {
+    initScrollBars: function($sidebar) {
+        $sidebar.find('.sidebar-wrapper').perfectScrollbar();
+    },
+    run: function() {
+        var $sidebar = this.$sidebar;
+
+        new SideBarApplicationPinning($sidebar).run();
+        new SideBarBookmarks($sidebar).run();
+        new SideBarPopup($sidebar).run();
+
+        try {
+            this.initScrollBars($sidebar);
+        } catch (e) {
+            console.error(e);
+        }
+
+        $sidebar.addClass('initialized');
+    }
+};
+
+$(document).ready(function() {
+    $('.sidebar').each(function() {
+        new SideBar($(this)).run();
+    });
+});

+ 179 - 0
jet/static/jet/js/src/features/sidebar/popup.js

@@ -0,0 +1,179 @@
+require('./../../utils/jquery-icontains');
+
+var $ = window.jQuery = require('jquery');
+
+var SideBarPopup = function($sidebar) {
+    this.$sidebar = $sidebar;
+};
+
+SideBarPopup.prototype = {
+    popupDisplayTimeout: null,
+    $currentSectionLink: null,
+    $currentSection: null,
+    $currentSectionListItem: null,
+    resetPopupDisplayTimeout: function() {
+        if (this.popupDisplayTimeout != null) {
+            clearTimeout(this.popupDisplayTimeout);
+        }
+    },
+    setCurrentSectionLink: function($link) {
+        if (this.$currentSectionLink) {
+            this.$currentSectionLink.removeClass('selected');
+        }
+
+        this.$currentSectionLink = $link;
+
+        if (this.$currentSectionLink) {
+            this.$currentSectionLink.addClass('selected');
+        }
+    },
+    openPopup: function($popupContainer, delay) {
+        var self = this;
+
+        this.resetPopupDisplayTimeout();
+
+        delay = delay && delay != undefined ? delay : 200;
+
+        this.popupDisplayTimeout = setTimeout(function() {
+            self.popupDisplayTimeout = null;
+
+            var $sections = $popupContainer.find('.sidebar-popup-section');
+
+            $sections.hide();
+
+            if (self.$currentSectionLink) {
+                var $section = $sections.filter('.' + self.$currentSectionLink.data('popup-section-class'));
+                var $search = $section.find('.sidebar-popup-search');
+
+                $section.show();
+                $search.val('').trigger('change').focus();
+
+                self.$currentSection = $section;
+                self.resetCurrentSectionListItems();
+                self.$currentSectionListItem = null;
+            }
+
+            $popupContainer.stop().fadeIn(200, 'swing');
+            $('body').addClass('non-scrollable');
+        }, delay);
+    },
+    closePopup: function($popupContainer, delay) {
+        var self = this;
+
+        this.resetPopupDisplayTimeout();
+
+        delay = delay && delay != undefined ? delay : 50;
+
+        this.popupDisplayTimeout = setTimeout(function() {
+            self.popupDisplayTimeout = null;
+            self.setCurrentSectionLink(null);
+            self.$currentSection = null;
+
+            $popupContainer.stop().fadeOut(200, 'swing');
+            $('body').removeClass('non-scrollable');
+        }, delay);
+    },
+    initSectionsDisplay: function($sidebar) {
+        var self = this;
+        var $popupContainer = $sidebar.find('.sidebar-popup-container');
+        var $popup = $sidebar.find('.sidebar-popup');
+
+        $sidebar.find('.popup-section-link').on('mouseenter', function() {
+            var $link = $(this);
+            var changingSection = self.$currentSectionLink && $link !== self.$currentSectionLink;
+
+            self.setCurrentSectionLink($link);
+            self.openPopup($popupContainer, changingSection ? 500 : null);
+        }).on('mouseleave', function() {
+            self.closePopup($popupContainer);
+        });
+
+        $popup.on('mouseenter', function() {
+            self.openPopup($popupContainer, 0);
+        }).on('mouseleave', function() {
+            self.closePopup($popupContainer);
+        });
+    },
+    initSectionsSearch: function($sidebar) {
+        $sidebar.find('.sidebar-popup-section').each(function() {
+            var $section = $(this);
+            var $search = $section.find('.sidebar-popup-search');
+            var $items = $section.find('.sidebar-popup-list-item');
+
+            $search.on('change keyup', function() {
+                var text = $(this).val();
+
+                $items
+                    .hide()
+                    .find('.sidebar-popup-list-item-link:icontains("' + text + '")')
+                    .closest('.sidebar-popup-list-item')
+                    .show();
+            });
+        });
+    },
+    resetCurrentSectionListItems: function () {
+        this.$currentSection.find('.sidebar-popup-list-item:visible').removeClass('selected');
+    },
+    moveSectionListItemSelection: function(next) {
+        if (this.$currentSectionListItem != null) {
+            if (next) {
+                this.$currentSectionListItem = this.$currentSectionListItem.nextAll(':visible').first();
+            } else {
+                this.$currentSectionListItem = this.$currentSectionListItem.prevAll(':visible').first();
+            }
+        }
+
+        if (this.$currentSectionListItem == null || this.$currentSectionListItem.length == 0) {
+            var items = this.$currentSection.find('.sidebar-popup-list-item:visible');
+            this.$currentSectionListItem = next ? items.first() : items.last();
+        }
+
+        this.resetCurrentSectionListItems();
+        this.$currentSectionListItem.addClass('selected');
+    },
+    initSectionKeyboardControls: function() {
+        var self = this;
+
+        $(document).keydown(function(e) {
+            if (self.$currentSectionLink == null) {
+                return;
+            }
+
+            if (e.which == 38) { //up
+                self.moveSectionListItemSelection(false);
+            } else if (e.which == 40) { //down
+                self.moveSectionListItemSelection(true);
+            } else if (e.which == 13) {
+                if (self.$currentSectionListItem) {
+                    document.location = self.$currentSectionListItem.find('a').attr('href');
+                }
+            } else {
+                return;
+            }
+
+            e.preventDefault();
+        });
+    },
+    initSectionLists: function($sidebar) {
+        var self = this;
+
+        $sidebar.find('.sidebar-popup-list-item-link').on('mouseenter', function () {
+            self.$currentSectionListItem = $(this).closest('.sidebar-popup-list-item');
+            self.resetCurrentSectionListItems();
+            self.$currentSectionListItem.addClass('selected');
+        });
+
+        this.initSectionKeyboardControls();
+    },
+    run: function() {
+        try {
+            this.initSectionsDisplay(this.$sidebar);
+            this.initSectionsSearch(this.$sidebar);
+            this.initSectionLists(this.$sidebar);
+        } catch (e) {
+            console.error(e);
+        }
+    }
+};
+
+module.exports = SideBarPopup;

+ 1 - 1
jet/static/jet/js/src/main.js

@@ -14,7 +14,7 @@ require('./layout-updaters/tabular-inline');
 require('./layout-updaters/stacked-inline');
 require('./layout-updaters/related-widget-wrapper');
 require('./layout-updaters/delete-confirmation');
-require('./features/side-menu');
+require('./features/sidebar/main');
 require('./features/filters');
 require('./features/changeform-tabs');
 require('./features/checkboxes');

+ 157 - 123
jet/templates/admin/base.html

@@ -50,149 +50,184 @@
             <a href="#" class="related-popup-back"><span class="related-popup-back-icon icon-arrow-left"></span> <span class="related-popup-back-label">{% trans "back" %}</span></a>
             <span class="icon-refresh loading-indicator"></span>
         </div>
+
         <div class="sidebar">
-            <div class="sidebar-menu-wrapper">
+            <div class="sidebar-wrapper">
                 <div id="branding">
                     {% block branding %}{% endblock %}
                 </div>
-                <ul class="sidebar-menu">
-                    <li class="sidebar-menu-item no-horizontal-padding">
-                        <ul class="sidebar-menu-item-list compact">
-                            {% if user.is_active and user.is_staff %}
-                                <li class="sidebar-menu-item-list-item">
-                                    <a href="{% url 'admin:index' %}" class="sidebar-menu-item-list-item-link">
-                                        <span class="sidebar-menu-item-list-item-icon icon-data"></span> {% trans 'Home' %}
-                                    </a>
-                                </li>
-                            {% endif %}
-                            {% if site_url %}
-                                <li class="sidebar-menu-item-list-item">
-                                    <a href="{{ site_url }}" class="sidebar-menu-item-list-item-link">
-                                        <span class="sidebar-menu-item-list-item-icon icon-open-external"></span> {% trans 'View site' %}
-                                    </a>
-                                </li>
-                            {% endif %}
-                            {% url 'django-admindocs-docroot' as docsroot %}
-                            {% if docsroot %}
-                                <li class="sidebar-menu-item-list-item">
-                                    <a href="{{ docsroot }}" class="sidebar-menu-item-list-item-link">
-                                        <span class="sidebar-menu-item-list-item-icon icon-book"></span> {% trans 'Documentation' %}
-                                    </a>
-                                </li>
-                            {% endif %}
-                            {% block nav-global %}{% endblock %}
-                        </ul>
-                    </li>
-
+                <div class="sidebar-section">
                     {% if user.is_active and user.is_staff %}
-                        {% get_menu as app_list %}
-                        {% if app_list.apps or app_list.pinned_apps %}
+                        <div>
+                            <a href="{% url 'admin:index' %}" class="sidebar-link icon">
+                                <span class="sidebar-link-icon icon-data"></span>
+                                <span class="sidebar-link-label">{% trans 'Home' %}</span>
+                            </a>
+                        </div>
+                    {% endif %}
+                    {% if site_url %}
+                        <div>
+                            <a href="{{ site_url }}" class="sidebar-link icon">
+                                <span class="sidebar-link-icon icon-open-external"></span>
+                                <span class="sidebar-link-label">{% trans 'View site' %}</span>
+                            </a>
+                        </div>
+                    {% endif %}
+                    {% url 'django-admindocs-docroot' as docsroot %}
+                    {% if docsroot %}
+                        <div>
+                            <a href="{{ docsroot }}" class="sidebar-link icon">
+                                <span class="sidebar-link-icon icon-book"></span>
+                                <span class="sidebar-link-label">{% trans 'Documentation' %}</span>
+                            </a>
+                        </div>
+                    {% endif %}
+                    {% block nav-global %}{% endblock %}
+                </div>
+
+                {% if user.is_active and user.is_staff %}
+                    {% get_menu as app_list %}
+                    {% if app_list.apps or app_list.pinned_apps %}
+                        {% if SIDE_MENU_COMPACT %}
+                            {% for app in app_list.all_apps %}
+                                <div class="sidebar-section">
+                                    <div class="sidebar-title">
+                                        <a href="{{ app.app_url }}" class="sidebar-title-link">
+                                            {% if app.name != app.app_label|capfirst|escape %}
+                                                {{ app.name }}
+                                            {% else %}
+                                                {% trans app.app_label as app_label %}{{ app_label|capfirst|escape }}
+                                            {% endif %}
+                                        </a>
+                                    </div>
+                                    {% for model in app.models %}
+                                        {% if model.admin_url %}
+                                            <div>
+                                                <a href="{{ model.admin_url }}" class="sidebar-link">
+                                                    <span class="sidebar-right">
+                                                        <span class="sidebar-right-arrow icon-arrow-right"></span>
+                                                    </span>
+                                                    {{ model.name }}
+                                                </a>
+                                            </div>
+                                        {% endif %}
+                                    {% endfor %}
+                                </div>
+                            {% endfor %}
+                        {% else %}
                             <form action="{% url "jet:toggle_application_pin" %}" method="POST" id="toggle-application-pin-form">
                                 {% csrf_token %}
                                 <input type="hidden" name="app_label">
                             </form>
-                            {% if SIDE_MENU_COMPACT %}
-                                {% for app in app_list.all_apps %}
-                                    <li class="sidebar-menu-item no-horizontal-padding">
-                                        <a href="{{ app.app_url }}" class="sidebar-menu-item-link">
-                                            {% if app.name != app.app_label|capfirst|escape %}{{ app.name }}{% else %}{% trans app.app_label as app_label %}{{ app_label|capfirst|escape }}{% endif %}
+
+                            <div class="sidebar-section">
+                                <div class="sidebar-title">{% trans 'Applications' %}</div>
+
+                                <div class="apps-list-pinned">
+                                    {% for app in app_list.pinned_apps %}
+                                        <a href="{{ app.app_url }}" class="sidebar-link popup-section-link app-item" data-app-label="{{ app.app_label }}" data-popup-section-class="sidebar-popup-section-{{ app.app_label }}">
+                                            <span class="sidebar-left collapsible">
+                                                <span class="sidebar-left-pin icon-star pin-toggle"></span>
+                                                <span class="sidebar-left-unpin icon-cross pin-toggle"></span>
+                                            </span>
+
+                                            <span class="sidebar-right">
+                                                <span class="sidebar-right-arrow icon-arrow-right"></span>
+                                            </span>
+
+                                            {% if app.name != app.app_label|capfirst|escape %}
+                                                {{ app.name }}
+                                            {% else %}
+                                                {% trans app.app_label as app_label %}{{ app_label|capfirst|escape }}
+                                            {% endif %}
                                         </a>
-                                        <ul class="sidebar-menu-item-list">
-                                            {% for model in app.models %}
-                                                <li class="sidebar-menu-item-list-item">
-                                                    {% if model.admin_url %}
-                                                        <a href="{{ model.admin_url }}" class="sidebar-menu-item-list-item-link">{{ model.name }}</a>
-                                                    {% else %}
-                                                        {{ model.name }}
-                                                    {% endif %}
-                                                </li>
-                                            {% endfor %}
-                                        </ul>
-                                    </li>
-                                {% endfor %}
-                            {% else %}
-                                <li class="sidebar-menu-item no-horizontal-padding">
-                                    <span class="sidebar-menu-item-title">{% trans 'Applications' %}</span>
-                                    <ul class="sidebar-menu-item-list apps-list-pinned">
-                                        {% for app in app_list.pinned_apps %}
-                                            <li class="sidebar-menu-item-list-item app-item pinned app-{{ app.app_label }}" data-app-label="{{ app.app_label }}">
-                                                <a href="{{ app.app_url }}" class="sidebar-menu-item-list-item-link popup-item-link" data-popup-item-id="sidebar-popup-item-{{ app.app_label }}">
-                                                    <span class="sidebar-menu-item-list-item-arrow icon-arrow-right"></span>
-                                                    <span class="sidebar-menu-item-list-item-link-pin icon-star pin-toggle"></span>
-                                                    <span class="sidebar-menu-item-list-item-link-unpin icon-cross pin-toggle"></span>
-                                                    {% if app.name != app.app_label|capfirst|escape %}{{ app.name }}{% else %}{% trans app.app_label as app_label %}{{ app_label|capfirst|escape }}{% endif %}
-                                                </a>
-                                            </li>
-                                        {% endfor %}
-                                    </ul>
-                                    <a href="#" class="sidebar-menu-item-action apps-hide"></a>
-                                    <ul class="sidebar-menu-item-list apps-list">
-                                        {% for app in app_list.apps %}
-                                            <li class="sidebar-menu-item-list-item app-item app-{{ app.app_label }}" data-app-label="{{ app.app_label }}">
-                                                <a href="{{ app.app_url }}" class="sidebar-menu-item-list-item-link popup-item-link" data-popup-item-id="sidebar-popup-item-{{ app.app_label }}">
-                                                    <span class="sidebar-menu-item-list-item-arrow icon-arrow-right"></span>
-                                                    <span class="sidebar-menu-item-list-item-link-pin icon-star pin-toggle"></span>
-                                                    <span class="sidebar-menu-item-list-item-link-unpin icon-cross pin-toggle"></span>
-                                                    {% if app.name != app.app_label|capfirst|escape %}{{ app.name }}{% else %}{% trans app.app_label as app_label %}{{ app_label|capfirst|escape }}{% endif %}
-                                                </a>
-                                            </li>
-                                        {% endfor %}
-                                    </ul>
-                                </li>
-                            {% endif %}
-                        {% endif %}
-                        <li class="sidebar-menu-item no-horizontal-padding">
-                            <div class="dialog-confirm" id="bookmarks-add-dialog" title="{% trans "Add bookmark" %}">
-                                <form action="{% url "jet:add_bookmark" %}" method="POST" id="bookmarks-add-form">
-                                    {% csrf_token %}
-                                    <p>{% trans "Title" %}:</p>
-                                    <input type="text" name="title" class="fill_width">
-                                    <p>{% trans "URL" %}:</p>
-                                    <input type="text" name="url" class="fill_width">
-                                </form>
+                                    {% endfor %}
+                                </div>
+                                <a href="#" class="sidebar-center-link apps-hide">
+                                    <span class="apps-hide-label apps-visible">{% trans "Hide applications" %}</span>
+                                    <span class="apps-hide-label apps-hidden">{% trans "Show hidden" %}</span>
+                                </a>
+                                <div class="apps-list">
+                                    {% for app in app_list.apps %}
+                                        <a href="{{ app.app_url }}" class="sidebar-link popup-section-link app-item" data-app-label="{{ app.app_label }}" data-popup-section-class="sidebar-popup-section-{{ app.app_label }}">
+                                            <span class="sidebar-left collapsible">
+                                                <span class="sidebar-left-pin icon-star pin-toggle"></span>
+                                                <span class="sidebar-left-unpin icon-cross pin-toggle"></span>
+                                            </span>
+
+                                            <span class="sidebar-right">
+                                                <span class="sidebar-right-arrow icon-arrow-right"></span>
+                                            </span>
+
+                                            {% if app.name != app.app_label|capfirst|escape %}
+                                                {{ app.name }}
+                                            {% else %}
+                                                {% trans app.app_label as app_label %}{{ app_label|capfirst|escape }}
+                                            {% endif %}
+                                        </a>
+                                    {% endfor %}
+                                </div>
                             </div>
-                            <form action="{% url "jet:remove_bookmark" %}" method="POST" id="bookmarks-remove-form">
+                        {% endif %}
+                    {% endif %}
+
+                    <div class="sidebar-section">
+                        <div class="dialog-confirm" id="bookmarks-add-dialog" title="{% trans "Add bookmark" %}">
+                            <form action="{% url "jet:add_bookmark" %}" method="POST" id="bookmarks-add-form">
                                 {% csrf_token %}
-                                <input type="hidden" name="id">
+                                <p>{% trans "Title" %}:</p>
+                                <input type="text" name="title" class="fill_width">
+                                <p>{% trans "URL" %}:</p>
+                                <input type="text" name="url" class="fill_width">
                             </form>
-                            <div class="dialog-confirm" id="bookmarks-remove-dialog" title="{% trans "Delete bookmark" %}">
-                                <p>{% trans "Are you sure want to delete this bookmark?" %}</p>
-                            </div>
-                            <span class="sidebar-menu-item-title">
-                                <a href="#" class="sidebar-menu-item-title-icon bookmarks-add" title="{% trans "Add bookmark" %}"{% if title %} data-title="{{ title }}"{% endif %}><span class="icon-add"></span></a>
-                                {% trans 'bookmarks' %}
+                        </div>
+                        <form action="{% url "jet:remove_bookmark" %}" method="POST" id="bookmarks-remove-form">
+                            {% csrf_token %}
+                            <input type="hidden" name="id">
+                        </form>
+                        <div class="dialog-confirm" id="bookmarks-remove-dialog" title="{% trans "Delete bookmark" %}">
+                            <p>{% trans "Are you sure want to delete this bookmark?" %}</p>
+                        </div>
+
+                        <div class="sidebar-title">
+                            <span class="sidebar-right">
+                                <a href="#" class="sidebar-right-plus bookmarks-add" title="{% trans "Add bookmark" %}"{% if title %} data-title="{{ title }}"{% endif %}><span class="icon-add"></span></a>
                             </span>
-                            <ul class="sidebar-menu-item-list bookmarks-list">
-                                {% get_bookmarks user as bookmarks %}
-                                {% for bookmark in bookmarks %}
-                                    <li class="sidebar-menu-item-list-item">
-                                        <a href="{{ bookmark.url }}" class="sidebar-menu-item-list-item-link">
-                                            <span class="sidebar-menu-item-list-item-link-remove bookmarks-remove" data-bookmark-id="{{ bookmark.pk }}">{% trans "Remove" %}</span>
-                                            {{ bookmark.title }}
-                                        </a>
-                                    </li>
-                                {% endfor %}
-                                <li class="sidebar-menu-item-list-item empty">
-                                    <a href="" class="sidebar-menu-item-list-item-link">
-                                        <span class="sidebar-menu-item-list-item-link-remove bookmarks-remove">{% trans "Remove" %}</span>
-                                    </a>
-                                </li>
-                            </ul>
-                        </li>
-                    {% endif %}
-                </ul>
+                            {% trans 'bookmarks' %}
+                        </div>
+
+                        <div class="bookmarks-list">
+                            {% get_bookmarks user as bookmarks %}
+                            {% for bookmark in bookmarks %}
+                                <a href="{{ bookmark.url }}" class="sidebar-link bookmark-item">
+                                    <span class="sidebar-right collapsible">
+                                        <span class="sidebar-right-remove bookmarks-remove" data-bookmark-id="{{ bookmark.pk }}">{% trans "Remove" %}</span>
+                                    </span>
+                                    {{ bookmark.title }}
+                                </a>
+                            {% endfor %}
+                            <a class="sidebar-link bookmark-item clone">
+                                <span class="sidebar-right collapsible">
+                                    <span class="sidebar-right-remove bookmarks-remove">{% trans "Remove" %}</span>
+                                </span>
+                            </a>
+                        </div>
+                    </div>
+                {% endif %}
             </div>
             <div class="sidebar-copyright">powered by DJANGO JET</div>
-        </div>
 
-        {% if not SIDE_MENU_COMPACT %}
-            {% if app_list %}
+            {% if app_list and not SIDE_MENU_COMPACT %}
                 <div class="sidebar-popup-container">
                     <div class="sidebar-popup">
                         {% for app in app_list.apps|add:app_list.pinned_apps %}
-                            <div class="sidebar-popup-item" id="sidebar-popup-item-{{ app.app_label }}">
+                            <div class="sidebar-popup-section sidebar-popup-section-{{ app.app_label }}">
                                 <div class="sidebar-popup-title">
-                                    {% if app.name != app.app_label|capfirst|escape %}{{ app.name }}{% else %}{% trans app.app_label as app_label %}{{ app_label|capfirst|escape }}{% endif %}
+                                    {% if app.name != app.app_label|capfirst|escape %}
+                                        {{ app.name }}
+                                    {% else %}
+                                        {% trans app.app_label as app_label %}{{ app_label|capfirst|escape }}
+                                    {% endif %}
                                 </div>
 
                                 <input class="sidebar-popup-search" placeholder="Search...">
@@ -217,7 +252,6 @@
                     </div>
                 </div>
             {% endif %}
-        {% endif %}
         </div>
     {% endif %}
 {% endblock %}

+ 5 - 1
jet/views.py

@@ -11,7 +11,11 @@ def add_bookmark_view(request):
 
     if form.is_valid():
         bookmark = form.save()
-        result['id'] = bookmark.pk
+        result.update({
+            'id': bookmark.pk,
+            'title': bookmark.title,
+            'url': bookmark.url
+        })
     else:
         result['error'] = True