diff options
Diffstat (limited to 'app')
-rw-r--r-- | app/assets/javascripts/multiawesome.js | 583 | ||||
-rw-r--r-- | app/assets/stylesheets/multiawesome.css | 178 | ||||
-rw-r--r-- | app/controllers/projects/milestones_controller.rb | 4 | ||||
-rw-r--r-- | app/helpers/milestones_helper.rb | 13 | ||||
-rw-r--r-- | app/helpers/selects_helper.rb | 35 | ||||
-rw-r--r-- | app/views/shared/issuable/_filter.html.haml | 4 |
6 files changed, 807 insertions, 10 deletions
diff --git a/app/assets/javascripts/multiawesome.js b/app/assets/javascripts/multiawesome.js new file mode 100644 index 00000000000..6e00874c421 --- /dev/null +++ b/app/assets/javascripts/multiawesome.js @@ -0,0 +1,583 @@ +//= require sifter + +/** + * multiawesome.js (v0.12.1) + * Copyright (c) 2015 Jacob Schatz & contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this + * file except in compliance with the License. You may obtain a copy of the License at: + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF + * ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + * + * @author Jacob Schatz <jschatz@gitlab.com> + */ + +/*jshint curly:false */ +/*jshint browser:true */ +(function ( $ ) { + 'use strict'; + + var defaults = { + data: [], + title: '', + tip: '', + name: 'multi-awesome', + header: [], + multiple: false, + alwaysPrefixWithSearch: false, + placeholder: 'Filter...', + onChange: function(){}, + always: [], + dataObject: { + label: 'label', + data: 'data', + category: 'category', + subtitle: 'subtitle', + image: 'image' + }, + minSearchLength: 2 + }; + + $.fn.multiawesome = function( options ) { + var MultiAwesome = { + extraMenuContainerTemplate: '<li data-extra-menu-container class="dropdown-multi-menu-extras-container"></li>', + searchTemplate: '<li class="dropdown-multi-menu-search-container"><div class="input-with-icon"><i class="fa fa-search"></i><input type="text" id="multiawesome-search-input" /></div></li>', + itemContainerTemplate: '<li class="dropdown-multi-menu-selections"><ul></ul></li>', + itemTemplate: '<li><a href="#" class="item" data-item-selectable tabIndex="-1"><input type="checkbox" name="{{name}}" value="{{data}}"/>{{label}}</a></li>', + seperatorTemplate: '<li role="separator" class="divider"></li>', + headerTemplate: '<li class="dropdown-multi-menu-header"><div class="dropdown-multi-menu-header-area"><ul class="dropdown-multi-menu-header-list">{{header}}</ul></div></li>', + headerItemTemplate: '<li class="dropdown-multi-menu-header-item"><a href="#" data-header-selectable class="header-item" tabIndex="-1"><input name="{{name}}" type="checkbox" value="{{data}}" />{{headeritem}}</a></li>', + titleTemplate: '<li class="dropdown-multi-menu-title"><div class="dropdown-multi-menu-title-area"><a href="#" data-back-button class="dropdown-multi-menu-back-button"></a><h3 class="dropdown-multi-menu-title-text">{{title}}</h3></div></li>', + categoryContainerTemplate: '<li class="dropdown-multi-menu-category"><ul></ul></li>', + categoryItemTemplate: '<li><a href="#" class="category" data-category-selectable tabIndex="-1"><input type="checkbox" value="{{category}}"/>{{category}}</a></li>', + tipTemplate: '<li class="dropdown-multi-menu-tip"><div class="dropdown-multi-menu-tip-area"><p>{{tip}}</p></div></li>', + subtitleTemplate: '<p class="dropdown-multi-menu-subtitle">{{subtitle}}</p>', + imageTemplate: '<div class="dropdown-multi-menu-image" style="background-image:url(\'{{image}}\');"></div>' + }; + + + return this.each(function() { + var self = this, + categories = [], + $self = $(self), + $form = $(self).closest('form'), + $itemContainer, + $itemContainerUL, + $extraMenuContainer, + categoriesAppended = false, + categoriesSet = false, + selectedItems = [], + selectedCategories = [], + $searchInput, + $extraMenus, + $addedMenu, + $backButton, + $currentMenu, + toHideForExtraMenus = [], + sifter, + + // Merge the options and defaults into the settings. + // No need to check for undefined + settings = $.extend(true, {}, defaults, options, $self.data()); + + if( self.tagName !== 'UL') { + return; + } + + var prepareDropdown = function() { + var $searchTemplate = $(MultiAwesome.searchTemplate); + toHideForExtraMenus.push($searchTemplate); + $self.prepend($searchTemplate); + $searchInput = $searchTemplate.find('input'); + if( settings.placeholder ) { + $searchInput.prop( 'placeholder', settings.placeholder ); + } + }; + + var attachListeners = function() { + $self.on( 'click', '[data-category-selectable]' , dropdownCategoryLinkClicked ); + $self.on( 'click', + '[data-item-selectable], [data-header-selectable]', dropdownSelectionLinkClicked ); + $self.on( 'click', dropdownClickedAnywhere ); + $self.on( 'click', $backButton, backButtonClicked ); + $searchInput.on( 'keydown keyup update', inputSearched ); + }; + + var parseSearchResults = function(results) { + var finalData = []; + results.forEach( function( result ) { + finalData.push(settings.data[result.id]); + }); + renderData( finalData ); + }; + + var addCategories = function() { + var $categoryContainer = $(MultiAwesome.categoryContainerTemplate); + var $categoryContainerUL = $categoryContainer.find('ul'); + if( categories.length ) { + var $seperatorTemplate = $(MultiAwesome.seperatorTemplate); + toHideForExtraMenus.push($seperatorTemplate); + $self.prepend($seperatorTemplate); + categories.forEach(function( category ) { + $categoryContainerUL.prepend( MultiAwesome.categoryItemTemplate + .replace( /\{\{category\}\}/g, category ) ); + }); + $self.prepend($categoryContainer); + } + }; + + var addTitle = function() { + var titleTemplate; + if( settings.title ) { + var $seperatorTemplate = $(MultiAwesome.seperatorTemplate); + var $titleTemplate = $(MultiAwesome.titleTemplate); + $self.prepend($seperatorTemplate); + $self.prepend(MultiAwesome.titleTemplate + .replace(/\{\{title\}\}/g, settings.title) + ); + $backButton = $self.find('[data-back-button]'); + + } + }; + + var addExtrasContainer = function() { + $extraMenuContainer = $(MultiAwesome.extraMenuContainerTemplate); + $self.prepend($extraMenuContainer); + }; + + var addHeader = function() { + var $headerTemplate, + headerList = []; + // if we have some header data + if( settings.header.length ) { + settings.header.forEach( function(item) { + headerList.push( + MultiAwesome.headerItemTemplate + .replace(/\{\{headeritem\}\}/g, item[settings.dataObject.label]) + .replace(/\{\{data\}\}/g, item[settings.dataObject.data]) + .replace(/\{\{name\}\}/g, '_' + settings.name) + ); + }); + var $seperatorTemplate = $(MultiAwesome.seperatorTemplate); + toHideForExtraMenus.push($seperatorTemplate); + $self.prepend($seperatorTemplate); + $headerTemplate = $(MultiAwesome.headerTemplate.replace(/\{\{header\}\}/g, headerList.join(''))); + toHideForExtraMenus.push($headerTemplate); + $self.prepend($headerTemplate); + } + }; + + var addData = function(callback) { + function parseDataWhenReady() { + $itemContainer = $(MultiAwesome.itemContainerTemplate); + toHideForExtraMenus.push($itemContainer); + $itemContainerUL = $itemContainer.find('ul'); + + sifter = new Sifter(settings.data); + renderData( settings.data,callback ); + } + if ( settings.data ) { + if ( typeof settings.data === 'string') { + $.getJSON(settings.data, function(data) { + settings.data = data; + parseDataWhenReady(); + }); + } else if ( typeof settings.data === 'object' ) { + parseDataWhenReady(); + } else { + $.error('Data must be a string or array'); + } + } + }; + + var renderData = function( data, callback ) { + selectedItems = []; + $itemContainerUL.empty(); + var emptyObj = {}, + skipMatch = false, + searchInputVal = [], + o, + tempAlwaysData = [], + tempItemTemplate, + $itemTemplate; + + if( !data.length ) { + emptyObj[settings.dataObject.label] = 'No matches found'; + emptyObj[settings.dataObject.data] = 'dropdown-multi-menu-selectable:false'; + emptyObj[settings.dataObject.category] = ''; + emptyObj.selectable = false; + data.push(emptyObj); + skipMatch = true; + } + + if( settings.alwaysPrefixWithSearch && $searchInput ) { + searchInputVal = $searchInput.val(); + tempAlwaysData = settings.always.map(function(item){ + // copy, don't alter real object. + o = $.extend({}, item); + if(searchInputVal.length){ + o.label = '<strong>"' + searchInputVal + '"</strong>' + ' ' + o.label; + } + + o.always = true; + return o; + }); + } else { + tempAlwaysData = settings.always; + } + data = data.concat(tempAlwaysData); + data.forEach( function( item ) { + tempItemTemplate = MultiAwesome.itemTemplate; + if( !categoriesSet && !skipMatch ) { + var addCategory = item[settings.dataObject.category]; + if( item.hasOwnProperty( settings.dataObject.category ) && categories.indexOf( addCategory ) === -1 ) { + categories.push( addCategory ); + } + } else { + // only do this if the categories are already set... they won't search categories on the first time. + if( selectedCategories.length && + selectedCategories.indexOf(item[settings.dataObject.category]) === -1 && + !skipMatch) { + return; + } + } + + $itemTemplate = $(MultiAwesome.itemTemplate); + + if( item.hasOwnProperty('selectable') && !item.selectable ) { + tempItemTemplate = $itemTemplate + .find('a') + .addClass('disabled') + .find('input[type="checkbox"]') + .prop('disabled','disabled') + // back to the anchor tag + .end() + // back to the li + .end() + .get(0) + .outerHTML; + } + + if( item.hasOwnProperty( settings.dataObject.subtitle ) && + item[settings.dataObject.subtitle].length ) { + tempItemTemplate = $(tempItemTemplate) + .find('a') + .addClass('dropdown-multi-menu-item-with-subtitle') + .append( + MultiAwesome.subtitleTemplate + .replace(/\{\{subtitle\}\}/g, item[settings.dataObject.subtitle]) + ) + .end() + .get(0) + .outerHTML; + } + + if( item.hasOwnProperty( settings.dataObject.image ) && + item[settings.dataObject.image].length ) { + tempItemTemplate = $(tempItemTemplate) + .find('a') + .addClass('dropdown-multi-menu-with-image') + .prepend( + MultiAwesome.imageTemplate + .replace(/\{\{image\}\}/g, item[settings.dataObject.image]) + ) + .end() + .get(0) + .outerHTML; + } + + $itemContainerUL.append( + tempItemTemplate + .replace(/\{\{data\}\}/g,item[settings.dataObject.data]) + .replace(/\{\{label\}\}/g,item[settings.dataObject.label]) + .replace(/\{\{name\}\}/g, '_' + settings.name) + ); + }); + if( !categoriesSet && !categoriesAppended ) { + $self.append($itemContainer); + categoriesAppended = true; + } + + if( categories.length ){ + categoriesSet = true; + } + + if( callback ) { + callback(); + } + }; + + var addToForm = function( val ) { + $form.prepend('<input type="hidden" name="' + settings.name + '" value="' + val + '" />'); + }; + + var removeFromForm = function( val ) { + $form + .find('input[name="' + settings.name + '"][value="' + val + '"]') + .remove(); + }; + + var getExtraMenus = function() { + $extraMenus = $self + // get parent button group + .closest('div.button-group') + // get the extra menu divs + .find('[data-extra-menu]'); + }; + + /* * * * * * * * * * * * * * * */ + /* listeners + /* * * * * * * * * * * * * * * */ + + var backButtonClicked = function() { + toHideForExtraMenus.forEach(function(menuSection){ + menuSection.show(); + }); + $extraMenuContainer.empty(); + $backButton.hide(); + return false; + }; + + var inputSearched = function() { + + //remove current hidden inputs + $('input[type="hidden"][name="' + settings.name + '"]').remove(); + + if( $searchInput.val().length > settings.minSearchLength ) { + var results = sifter.search($searchInput.val(), { + fields: [settings.dataObject.label], + sort: [{field: settings.dataObject.label, direction: 'asc'}] + }); + parseSearchResults(results.items); + } else { + renderData( settings.data ); + } + }; + + var moveToExtraMenu = function($menu) { + var $cloneMenu = $menu.clone(); + toHideForExtraMenus.forEach(function(menuSection){ + menuSection.hide(); + }); + $currentMenu = $menu; + $extraMenuContainer.append($cloneMenu); + $cloneMenu.show(); + $backButton.show(); + }; + + var isExtraMenuValue = function( val ) { + var isMatch = false; + $extraMenus.each( function() { + var $this = $(this); + if(val === $this.data('menu-target-value')) { + isMatch = true; + moveToExtraMenu($this); + return; + } + }); + return isMatch; + }; + + var dropdownClickedAnywhere = function( e ) {}; + + var dropdownCategoryLinkClicked = function ( e ) { + var $target = $( e.currentTarget ), + $inp = $target.find( 'input' ), + val = $inp.val(), + i = selectedCategories.indexOf( val ); + + e.preventDefault(); + // if the checkbox is disabled. + if( $inp.prop('disabled') ) { + return false; + } + if ( i > -1 ) { + var spliced = selectedCategories.splice( i, 1 ); + $target.removeClass('selected'); + setTimeout( function() { + $inp.prop( 'checked', false ); + }, 0); + } else { + selectedCategories.push( val ); + $target.addClass('selected'); + setTimeout( function() { + $inp.prop( 'checked', true ); + }, 0); + } + + inputSearched(); + + $( e.target ).blur(); + return false; + }; + + var findItemWithData = function(searchData, id) { + var item = {}; + for (var i = searchData.length - 1; i >= 0; i--) { + item = searchData[i]; + if( item.hasOwnProperty(settings.dataObject.data) && + item[settings.dataObject.data] == id ) { + return item; + } + } + return undefined; + }; + + var dropdownSelectionLinkClicked = function ( e ) { + var $target = $( e.currentTarget ), + $inp = $target.find( 'input' ), + findItemInData, + val = $inp.val(), + i = selectedItems.indexOf( val ); + + e.preventDefault(); + + if( $inp.prop('disabled') ) { + return false; + } + + if( isExtraMenuValue( val ) ) { + return false; + } + + findItemInData = findItemWithData(settings.data, val); + if( typeof findItemInData === 'undefined' ) { + findItemInData = findItemWithData(settings.always, val); + } + + if( typeof findItemInData === 'undefined' ) { + findItemInData = findItemWithData(settings.header, val); + } + + if( typeof findItemInData === 'undefined' && + val === 'dropdown-multi-menu-selectable:false' ) { + // don't close the dropdown. + return false; + } + + if ( typeof findItemInData !== 'undefined' && + findItemInData.hasOwnProperty('selectable') && + findItemInData.selectable === false ) { + // don't close the dropdown + return false; + } + + if( findItemInData.hasOwnProperty( 'href' ) ) { + window.location.href = findItemInData.href; + return; + } + + if( findItemInData.hasOwnProperty('selectable') && + !findItemWithData.selectable ) { + return; + } + + + if ( i > -1 ) { + var spliced = selectedItems.splice( i, 1 ); + if( settings.multiple ) { + removeFromForm( spliced ); + } else { + $form + .find('input[name="' + settings.name + '"]') + .remove(); + } + + $target.removeClass('selected'); + setTimeout( function() { + $inp.prop( 'checked', false ); + }, 0); + } else { + if( !settings.multiple ) { + selectedItems = []; + $form + .find('input[name="' + settings.name + '"]') + .remove(); + $form + .find('input[name="_' + settings.name + '"]') + .parent() + .removeClass('selected'); + } + + selectedItems.push( val ); + addToForm( val ); + $target.addClass('selected'); + setTimeout( function() { + $inp.prop( 'checked', true ); + }, 0); + } + settings.onChange({"changed":findItemInData, "selected":selectedItems}); + // close the dropdown if single selection + // otherwise don't close the dropdown + if( settings.multiple ) { + $( e.target ).blur(); + return false; + } else { + var button = $self.siblings('.dropdown-toggle').first(); + button.contents() + .each( + function(){ + if ( this.nodeType === 3 && this.nodeValue.trim() ) { + this.textContent = $target.text(); + } + }); + } + }; + + var addTip = function() { + var $tipTemplate; + if( settings.tip ) { + var $seperatorTemplate = $(MultiAwesome.seperatorTemplate); + toHideForExtraMenus.push($seperatorTemplate); + $self.append($seperatorTemplate); + $tipTemplate = $(MultiAwesome.tipTemplate.replace(/\{\{tip\}\}/g, settings.tip)); + toHideForExtraMenus.push($tipTemplate); + $self.append($tipTemplate); + } + }; + + /* * * * * * * * * * * * * * * */ + /* setup + /* * * * * * * * * * * * * * * */ + + var shouldInit = function() { + if(!$self.hasClass('initialized')) { + $self.addClass('initialized'); + return true; + } + return false; + }; + + var setup = function() { + if(!shouldInit()){ + return; + } + addData(function(){ + addHeader(); + addCategories(); + prepareDropdown(); + addExtrasContainer(); + addTitle(); + attachListeners(); + addTip(); + getExtraMenus(); + }); + }; + + setup(); + + }); + }; + + $(function(){ + + $('[data-multi-awesome]').each(function(){ + $(this).multiawesome(); + }); + + }); +})( jQuery );
\ No newline at end of file diff --git a/app/assets/stylesheets/multiawesome.css b/app/assets/stylesheets/multiawesome.css new file mode 100644 index 00000000000..5e027fe6dc2 --- /dev/null +++ b/app/assets/stylesheets/multiawesome.css @@ -0,0 +1,178 @@ +.open>.dropdown-menu { + max-width: 320px; + border-radius: 0px; +} + +.dropdown-menu input[type='text'] { + margin: 0 5px; + width: 309px; + height: 35px; + padding-left: 6px; +} + +.dropdown-menu input[type='checkbox'] { + margin: 0 5px; + display: none; +} + +.dropdown-menu>li>a { + padding-left: 0; +} + +.dropdown-menu .dropdown-multi-menu-title-area h3 { + font-size: 15px; + text-align: center; + margin-top: 5px; +} + +.dropdown-menu .dropdown-multi-menu-title-area h3::after { + content: ""; + background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAACGUlEQVQ4jZ2VMXbiMBCGf83KKcxzntOmI1VMEyj2BLpD9gRbwQVygeUCpMsdcgKfQaYJqeIu1RYGC/stNjNbJOaBY8hu/lrz69PoH0mJCBaLhVEArqMoxhf0vFgYARBFUawWT08my7KxiMD3/fhmOLz/H7PE2ruyLL8rpRCG4b0WEQjzeVXXxq3X59ba8zAMZ/1+Pz9l9PLyEiyXy0lRFGZb18bTOhYR6GgwiOdJcu3Wa9R1bZxz5r1mespwuVxOnHO/mBla69jv9R6jwSAmALgZDu9934+JCMyMoiiMtfYuTdOgbZSmaWCtvSuKwjAziOigVbpZGIbhDACKojCnSLMsOyTz/bipPTB879nUWgvnnNkjxdnZ2TMAbDab6zbZaDQ62FCjpS5SIgIAMDOOkTVSItLZ9CRJxnmez5gZzRqlFIgIQRBMhkfiRZ1uADzPeyUiiMiOTERARPA87/VY3VHDr+pDDxtVVXXJzLtjAm9HZmZUVXV5rO5DD9M0DbIsmzSXQkQ4dSntifpA2JWzdmxO5XRnuE92KmddOd0n3Rl+NgGNPpsoAoB5koy7yLpenH6/n49Go2l79udJMgaAbz9ub02+Wv2s69poreNer/cQhuHs4uJi0zbbV1mWcxHJt9sttnVtmPmPc+63VkpBEa205z36vh8fm4C2rq6ucgDTxFqUZbkiolwplavmCwDenvB/MWtr/wv5CwCanfXE6iK0AAAAAElFTkSuQmCC'); + background-repeat: no-repeat; + background-size: 11px; + width: 12px; + height: 12px; + display: inline-block; + position: absolute; + right: 13px; + top: 13px; + cursor: pointer; +} + +.dropdown-menu .dropdown-multi-menu-selections { + max-height: 150px; + overflow-y: scroll; + margin-top: 15px; +} + +.dropdown-menu .dropdown-multi-menu-category ul{ + padding: 0 5px; + list-style: none; + max-height: 65px; + overflow-y: scroll; +} + +.dropdown-menu ul.dropdown-multi-menu-header-list { + padding: 0 5px; + list-style: none; + max-height: 75px; + overflow-y: scroll; +} + +.dropdown-menu .dropdown-multi-menu-selections ul{ + padding: 0 5px; + list-style: none; +} + +.dropdown-menu .dropdown-multi-menu-selections li, +.dropdown-menu .dropdown-multi-menu-header-list li{ + padding: 7px 5px; +} + +.dropdown-menu .dropdown-multi-menu-selections +li.dropdown-multi-menu-list-striped:nth-child(odd){ + background: #F5F5F5; +} + +.dropdown-menu .dropdown-multi-menu-selections +li a.disabled{ + cursor: default; +} + +.dropdown-menu .dropdown-multi-menu-selections .dropdown-multi-menu-subtitle { + font-size: 12px; + color: #9E9E9E; +} + +[data-extra-menu] { + display: none; +} + +.dropdown-multi-menu-back-button { + width: 12px; + height: 12px; + display: none; + background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAACDUlEQVRIibWWMZLaMBSGn2QBGS0MMJMUTCq7CdAEF9kLqNsqJ8gFlgvQUS0XIOcgF3CXagvMVvY2UKXF9srIYCQ5FQxkIdhL8kqN/H/vvV9PMsqyDPKG73ksA4BOp+Pk/Ybk3Thz3UGSJF8QQuB7HrRzQi4C5vN5LYqivhCCKSlZiRCnSNUXAVEU9eM4ftBaAyHEoTc3k063e32LDjPXWgPGGCilzude73te8b8CXmVOqdNoNMZFxE8CFotFLQzDw8wfKaWObdujouInAWEY7jMvlUoTSqnTK9iWk4A/MzcMA64VPwIEQdBfrVYPSqlr9F4Fevb92yAI7tM0bSutb7MsA4QQIISAEOKUy2WfEPIrj9huPjDGL5Vy2e90uw7hnH/lnH/DGAPCeL8xyzJI05RtNhuWd7B2+wzDgHeVyuTZ9zl+S9lFgtRqtR9Syo//o0Wf2u1HtFucTqeDQ5MJIVCtVvv/7BQ1m80xQgiEEExKya4RPYy9B6Zpctu2R5RSB2MMSikQQrDZbHZ/DcAYDodHC+v1+klrzZVSIKW8226375fL5YdWq/XzLQB07gi6rjs4ddmZpsmLAM7epvV6fQwAe0/iON75UujSOzsHlmUdeaK1BiEEeyroycUX7bASJSUTQrx4nufnffgvAizL4gAwmrkuJEnygjHmCKHcPpw1+VR4nscQQO4/CgCA3yx5RbfFRth/AAAAAElFTkSuQmCC'); + float: left; + background-size: 12px; + background-repeat: no-repeat; + margin-top: 3px; + margin-left: 10px; + margin-right: -10px; +} + +.dropdown-menu .dropdown-multi-menu-selections ul a, +.dropdown-menu .dropdown-multi-menu-category ul a, +.dropdown-menu .dropdown-multi-menu-header-item a{ + text-decoration: none; + color: #333; + display: inline-block; + width: 285px; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + padding-left: 30px; + vertical-align: middle; +} + +.dropdown-menu .dropdown-multi-menu-selections ul a:focus, +.dropdown-menu .dropdown-multi-menu-category ul a:focus, +.dropdown-menu .dropdown-multi-menu-header-item a:focus { + border: none; + outline: none; +} + +.dropdown-menu .dropdown-multi-menu-selections ul a.selected, +.dropdown-menu .dropdown-multi-menu-category ul a.selected, +.dropdown-menu .dropdown-multi-menu-header ul a.selected { + background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAYCAMAAADat72NAAABtlBMVEX////b29vc29vc3NzZ2dna2dnb2trY19fZ2NjV1dXW1tbT09PU09PV1NTR0dHS0dHS0tLPz8/Qz8/Q0NDNzc3Ozc3Ly8vMy8vMzMzJycnKycnKysrIyMjJyMjKyMjFxcXGxcXGxsbHxcXHxsbEw8PExMTFxMTBwcHBwsLCwcG/v7+9vb2+vr6/vr67u7u8vLy5ubm6ubm7ubm3t7e4t7e4uLi1tbW3tra0s7OysbGysrKzsbGvr6+wr6+urq6vrq6sq6utrKypqamrqqqsqqqop6epp6eqqKimpaWnpaWnpqako6OkpKSlo6OioaGjoqKkoqKgn5+goKChn5+hoKCenp6fnp6cm5ucnJydm5udnJyenJx+fX1/fHx/fX2Afn6Af3+Bf3+BgICCgICCgYGDgYGDgoKEgoKFgoKFg4OGhISGhYWHhYWIh4eJh4eJiIiJiYmKiIiLiYmLioqMioqMi4uNi4uNjIyOjIyPjY2Qjo6Qj4+SkJCSkZGTkZGTkpKUk5OVk5OVlJSWlZWXlZWYlpaYl5eYmJiZmJiZmZmamJibmZmbmpqcm5uenp6fn5+op6erqqouFWZ5AAAAXHRSTlMADw8PFhYWHR0kJCwsLDMzMzo6OkJCSUlJUFBQV1dXX19fX19mZmZtbW11fHx8g4OKioqSkpKZmaCoqKivr7a2vb3FxcXMzMzT09Pb29vi4uLp6enp8PD4+Pj4+E8lS9YAAAEjSURBVCjPY2DACYRtrDhwywralTZL45RldymLb5LEJctkWBOXHiSAS9q8OSOzxRiXrGJPWmqdNTcOWY3ggtQyN15cXvIpTCnxVcIhK+pYnZwZLYdDls22Nj2rywiHLKNeW2p8oxaMK6vGgiKt05edVGXPDOWJB7S6KyPJSgVnxZRb8sC4Co2JJV4icFkJl6KYXB8hOJ8/KiW5wl8TyuN0KonP81BEMs24Iye10A8S+Kwm1bGZESoobtHszkwtDtUGMS0aUjM7TdG8YhRZmJIdLc/IpNqWktpoxonuVeWw3OTC3gkT+/NTK525MINCN6QsNSuvND+93FUGW1CJeZekJiSn5wSKYw9KNc/q9IS8cH1cCYDPoT63XR13ypQwczbAEAQAnkJCZAp/V+QAAAAASUVORK5CYII='); + background-repeat: no-repeat; + background-size: 16px; + background-position: 2px 4px; +} + +.dropdown-menu .dropdown-multi-menu-selections ul a.selected.dropdown-multi-menu-item-with-subtitle, +.dropdown-menu .dropdown-multi-menu-selections ul a.selected.dropdown-multi-menu-with-image { + background-position: 2px 12px; +} + +.dropdown-menu .dropdown-multi-menu-image { + background-size: cover; + /*crop from the center*/ + background-position: center; + width: 30px; + height: 30px; + display: inline-block; + position: relative; + border-radius: 15px; + float: left; + margin-top: 3px; + margin-right: 11px; +} + +.dropdown-menu .dropdown-multi-menu-tip-area{ + margin: 10px; + text-align: left; + max-height: 40px; + overflow-y: scroll; +} + + .dropdown-menu .dropdown-multi-menu-header-area { + + } + +.dropdown-menu .dropdown-multi-menu-tip-area p { +} + +.dropdown-menu .input-with-icon { + position: relative; + margin: 10px 0; +} + +.dropdown-menu .input-with-icon i { + position: absolute; + right: 0; + padding: 10px 12px; + color: #817F7F; + pointer-events: none; +} diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb index 15506bd677a..5c999a40a53 100644 --- a/app/controllers/projects/milestones_controller.rb +++ b/app/controllers/projects/milestones_controller.rb @@ -19,6 +19,10 @@ class Projects::MilestonesController < Projects::ApplicationController @milestones = @milestones.includes(:project) @milestones = @milestones.page(params[:page]).per(PER_PAGE) + respond_to do |format| + format.html # show.html.erb + format.json { render :json => @milestones } + end end def new diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb index a42cbcff182..91ffc1aab6b 100644 --- a/app/helpers/milestones_helper.rb +++ b/app/helpers/milestones_helper.rb @@ -20,7 +20,7 @@ module MilestonesHelper end end - def projects_milestones_options + def projects_milestones_data_options milestones = if @project @project.milestones @@ -28,12 +28,11 @@ module MilestonesHelper Milestone.where(project_id: @projects) end.active - epoch = DateTime.parse('1970-01-01') - grouped_milestones = GlobalMilestone.build_collection(milestones) - grouped_milestones = grouped_milestones.sort_by { |x| x.due_date.nil? ? epoch : x.due_date } - grouped_milestones.unshift(Milestone::None) - grouped_milestones.unshift(Milestone::Any) + milestones.as_json only: [:title, :id] + end - options_from_collection_for_select(grouped_milestones, 'name', 'title', params[:milestone_title]) + def projects_milestones_header_options + grouped_milestones = [Milestone::Any, Milestone::None].as_json end + end diff --git a/app/helpers/selects_helper.rb b/app/helpers/selects_helper.rb index 05386d790ca..875acfd719c 100644 --- a/app/helpers/selects_helper.rb +++ b/app/helpers/selects_helper.rb @@ -36,6 +36,41 @@ module SelectsHelper hidden_field_tag(id, value, html) end + def multi_select_tag(name, opts = {}) + css_class = "dropdown-menu " + css_class << (opts[:class] || '') + header = opts[:header] || '' + ul_html = { + class: css_class, + data: { + header: header, + data: opts[:header_url], + "multi-awesome" => '', + "data-object" => { + label: "title", + data: "id" + } + } + } + + button_html = { + class: ["btn", "btn-default", "dropdown-toggle"], + type: "button", + data: { + toggle: "dropdown" + } + } + + button_class = "btn btn-default dropdown-toggle" + + content_tag :div, :class => "button-group" do + content_tag(:button, content_tag(:span, name) + + content_tag(:span,nil ,:class => "caret"), + button_html) + + content_tag(:ul, nil, ul_html) + end + end + def groups_select_tag(id, opts = {}) opts[:class] ||= '' opts[:class] << ' ajax-groups-select' diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml index be06738eac9..9e800ffb37c 100644 --- a/app/views/shared/issuable/_filter.html.haml +++ b/app/views/shared/issuable/_filter.html.haml @@ -38,9 +38,7 @@ placeholder: 'Assignee', class: 'trigger-submit', any_user: "Any Assignee", null_user: true, first_user: true, current_user: true) .filter-item.inline.milestone-filter - = select_tag('milestone_title', projects_milestones_options, - class: 'select2 trigger-submit', include_blank: true, - data: {placeholder: 'Milestone'}) + = multi_select_tag('Milestone', class: 'milestone-filter', header: projects_milestones_header_options) .filter-item.inline.labels-filter = select_tag('label_name', projects_labels_options, |