Commit 0a857a08 authored by David Stutz's avatar David Stutz

Fixed #109, #113.

Moved CSS declarations into separate LESS file. Filter will now be
rebuild, too.
parent 29f1ab30
This source diff could not be displayed because it is too large. You can view the blob instead.
.multiselect-container{position:absolute;list-style-type:none;margin:0;padding:0}.multiselect-container input[type="text"]{width:70%}.multiselect-container .input-prepend{padding:3px}.multiselect-container>li{padding:0}.multiselect-container>li>label{margin:0;padding:3px 20px 3px 20px;height:100%;cursor:pointer}.multiselect-container>li>label.multiselect-header{margin:0;padding:3px 20px 3px 20px;height:100%}.multiselect-container>li>label>input[type="checkbox"]{margin-bottom:5px}
\ No newline at end of file
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
<meta name="copyright" content="David Stutz" /> <meta name="copyright" content="David Stutz" />
<link rel="stylesheet" href="css/bootstrap-2.3.2.min.css" type="text/css"> <link rel="stylesheet" href="css/bootstrap-2.3.2.min.css" type="text/css">
<link rel="stylesheet" href="css/bootstrap-multiselect.css" type="text/css">
<link rel="stylesheet" href="css/prettify.css" type="text/css"> <link rel="stylesheet" href="css/prettify.css" type="text/css">
<script type="text/javascript" src="js/jquery-2.0.1.min.js"></script> <script type="text/javascript" src="js/jquery-2.0.1.min.js"></script>
...@@ -587,7 +588,8 @@ ...@@ -587,7 +588,8 @@
}); });
$('#example12').multiselect({ $('#example12').multiselect({
buttonContainer: '<span />' buttonContainer: '<span />',
enableFiltering: true
}); });
$('#example12-rebuild').on('click', function() { $('#example12-rebuild').on('click', function() {
$('#example12').multiselect('rebuild'); $('#example12').multiselect('rebuild');
...@@ -1297,7 +1299,7 @@ ...@@ -1297,7 +1299,7 @@
<div class="add-styling"> <div class="add-styling">
<table class="table table-striped"> <table class="table table-striped">
<tr> <tr>
<td> <td width="30%">
<select id="example15" multiple="multiple"> <select id="example15" multiple="multiple">
<optgroup label="Mathematics"> <optgroup label="Mathematics">
<option value="analysis">Analysis</option> <option value="analysis">Analysis</option>
...@@ -1314,10 +1316,10 @@ ...@@ -1314,10 +1316,10 @@
</optgroup> </optgroup>
</select> </select>
</td> </td>
<td> <td width="30%">
Text alignment combined with fixed width and bold, underlined text for option group headers. Text alignment combined with fixed width and bold, underlined text for option group headers.
</td> </td>
<td> <td width="40%">
<pre class="prettyprint linenums"> <pre class="prettyprint linenums">
.multiselect { .multiselect {
text-align: left; text-align: left;
......
...@@ -16,19 +16,19 @@ ...@@ -16,19 +16,19 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
!function ($) { !function($) {"use strict";// jshint ;_;
"use strict"; // jshint ;_; if ( typeof ko != 'undefined' && ko.bindingHandlers && !ko.bindingHandlers.multiselect) {
if(typeof ko != 'undefined' && ko.bindingHandlers && !ko.bindingHandlers.multiselect){
ko.bindingHandlers.multiselect = { ko.bindingHandlers.multiselect = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {}, init : function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) { },
update : function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var ms = $(element).data('multiselect'); var ms = $(element).data('multiselect');
if (!ms) { if (!ms) {
$(element).multiselect(ko.utils.unwrapObservable(valueAccessor())); $(element).multiselect(ko.utils.unwrapObservable(valueAccessor()));
} }
else if (allBindingsAccessor().options && allBindingsAccessor().options().length !== ms.originalOptions.length) { else
if (allBindingsAccessor().options && allBindingsAccessor().options().length !== ms.originalOptions.length) {
ms.updateOriginalOptions(); ms.updateOriginalOptions();
$(element).multiselect('rebuild'); $(element).multiselect('rebuild');
} }
...@@ -40,101 +40,55 @@ ...@@ -40,101 +40,55 @@
this.options = this.getOptions(options); this.options = this.getOptions(options);
this.$select = $(select); this.$select = $(select);
this.originalOptions = this.$select.clone()[0].options; //we have to clone to create a new reference this.originalOptions = this.$select.clone()[0].options;
//we have to clone to create a new reference
this.query = ''; this.query = '';
this.searchTimeout = null; this.searchTimeout = null;
this.options.multiple = this.$select.attr('multiple') == "multiple"; this.options.multiple = this.$select.attr('multiple') == "multiple";
this.$container = $(this.options.buttonContainer) this.$container = $(this.options.buttonContainer).append('<button type="button" class="multiselect dropdown-toggle ' + this.options.buttonClass + '" data-toggle="dropdown">' + this.options.buttonText(this.getSelected(), this.$select) + '</button>')
.append('<button type="button" class="multiselect dropdown-toggle ' + this.options.buttonClass + '" data-toggle="dropdown">' + this.options.buttonText(this.getSelected(), this.$select) + '</button>') .append('<ul class="multiselect-container dropdown-menu' + (this.options.dropRight ? ' pull-right' : '') + '"></ul>');
.append('<ul class="multiselect-container dropdown-menu' + (this.options.dropRight ? ' pull-right' : '') + '" style="position:absolute; list-style-type: none;margin:0;padding:0;"></ul>');
if (this.options.buttonWidth) { if (this.options.buttonWidth) {
$('button', this.$container).css({ $('button', this.$container).css({
'width': this.options.buttonWidth 'width' : this.options.buttonWidth
}); });
} }
// Set max height of dropdown menu to activate auto scrollbar. // Set max height of dropdown menu to activate auto scrollbar.
if (this.options.maxHeight) { if (this.options.maxHeight) {
// TODO: Add a class for this option to move the css declarations.
$('.multiselect-container', this.$container).css({ $('.multiselect-container', this.$container).css({
'max-height': this.options.maxHeight + 'px', 'max-height' : this.options.maxHeight + 'px',
'overflow-y': 'auto', 'overflow-y' : 'auto',
'overflow-x': 'hidden' 'overflow-x' : 'hidden'
}); });
$('input[type="text"]', this.$container).width('75%');
} }
// Enable filtering. // Enable filtering.
if (this.options.enableFiltering || this.options.enableCaseInsensitiveFiltering) { if (this.options.enableFiltering || this.options.enableCaseInsensitiveFiltering) {
$('.multiselect-container', this.$container).prepend('<div class="input-prepend" style="padding:3px;"><span class="add-on"><i class="icon-search"></i></span><input class="multiselect-search" type="text" placeholder="' + this.options.filterPlaceholder + '"></div>'); this.buildFilter();
$('.multiselect-search', this.$container).val(this.query).on('click', function (event) {
event.stopPropagation();
}).on('keydown', $.proxy(function (event) {
// This is useful to catch "keydown" events after the browser has updated the control.
clearTimeout(this.searchTimeout);
this.searchTimeout = this.asyncFunction($.proxy(function () {
if (this.query != event.target.value) {
this.query = event.target.value;
$.each($('.multiselect-container li', this.$container), $.proxy(function(index, element) {
var value = $('input', element).val();
if (value != this.options.selectAllValue) {
var text = $('label', element).text();
var value = $('input', element).val();
if (value && text && value != this.options.selectAllValue ) {
// by default lets assume that element is not interesting for this search
var showElement = false;
var filterCandidate = '';
if ( (this.options.filterBehavior == 'text' || this.options.filterBehavior == 'both')) {
filterCandidate = text;
}
if ( (this.options.filterBehavior == 'value' || this.options.filterBehavior == 'both')) {
filterCandidate = value;
}
if(this.options.enableCaseInsensitiveFiltering && filterCandidate.toLowerCase().indexOf(this.query.toLowerCase()) > -1) {
showElement = true;
} else if(filterCandidate.indexOf(this.query) > -1) {
showElement = true;
}
if (showElement) {
$(element).show();
} else {
$(element).hide();
}
}
}
}, this));
}
}, this), 300, this);
}, this));
} }
this.buildDropdown(); this.buildDropdown();
this.updateButtonText(); this.updateButtonText();
this.$select this.$select.hide().after(this.$container);
.hide()
.after(this.$container);
}; };
Multiselect.prototype = { Multiselect.prototype = {
defaults: { defaults : {
// Default text function will either print 'None selected' in case no option is selected, // Default text function will either print 'None selected' in case no
// or a list of the selected options up to a length of 3 selected options. // option is selected, or a list of the selected options up to a length of 3 selected options.
// If more than 3 options are selected, the number of selected options is printed. // If more than 3 options are selected, the number of selected options is printed.
buttonText: function(options, select) { buttonText : function(options, select) {
if (options.length == 0) { if (options.length == 0) {
return 'None selected <b class="caret"></b>'; return 'None selected <b class="caret"></b>';
} }
else if (options.length > 3) { else
if (options.length > 3) {
return options.length + ' selected <b class="caret"></b>'; return options.length + ' selected <b class="caret"></b>';
} }
else { else {
...@@ -144,34 +98,35 @@ ...@@ -144,34 +98,35 @@
selected += label + ', '; selected += label + ', ';
}); });
return selected.substr(0, selected.length -2) + ' <b class="caret"></b>'; return selected.substr(0, selected.length - 2) + ' <b class="caret"></b>';
} }
}, },
// Is triggered on change of the selected options. // Is triggered on change of the selected options.
onChange: function(option, checked) { onChange : function(option, checked) {
}, },
buttonClass: 'btn', buttonClass : 'btn',
dropRight: false, dropRight : false,
selectedClass: 'active', selectedClass : 'active',
buttonWidth: 'auto', buttonWidth : 'auto',
buttonContainer: '<div class="btn-group" />', buttonContainer : '<div class="btn-group" />',
// Maximum height of the dropdown menu. // Maximum height of the dropdown menu.
// If maximum height is exceeded a scrollbar will be displayed. // If maximum height is exceeded a scrollbar will be displayed.
maxHeight: false, maxHeight : false,
includeSelectAllOption: false, includeSelectAllOption : false,
selectAllText: ' Select all', selectAllText : ' Select all',
selectAllValue: 'multiselect-all', selectAllValue : 'multiselect-all',
enableFiltering: false, enableFiltering : false,
enableCaseInsensitiveFiltering: false, enableCaseInsensitiveFiltering : false,
filterPlaceholder: 'Search', filterPlaceholder : 'Search',
filterBehavior: 'text' // possible options: 'text', 'value', 'both' // possible options: 'text', 'value', 'both'
filterBehavior : 'text'
}, },
constructor: Multiselect, constructor : Multiselect,
// Will build an dropdown element for the given option. // Will build an dropdown element for the given option.
createOptionValue: function(element) { createOptionValue : function(element) {
if ($(element).is(':selected')) { if ($(element).is(':selected')) {
$(element).attr('selected', 'selected').prop('selected', true); $(element).attr('selected', 'selected').prop('selected', true);
} }
...@@ -181,7 +136,7 @@ ...@@ -181,7 +136,7 @@
var value = $(element).val(); var value = $(element).val();
var inputType = this.options.multiple ? "checkbox" : "radio"; var inputType = this.options.multiple ? "checkbox" : "radio";
var $li = $('<li><a href="javascript:void(0);" style="padding:0;"><label style="margin:0;padding:3px 20px 3px 20px;height:100%;cursor:pointer;"><input style="margin-bottom:5px;" type="' + inputType + '" /></label></a></li>'); var $li = $('<li><a href="javascript:void(0);"><label><input type="' + inputType + '" /></label></a></li>');
var selected = $(element).prop('selected') || false; var selected = $(element).prop('selected') || false;
var $checkbox = $('input', $li); var $checkbox = $('input', $li);
...@@ -206,7 +161,7 @@ ...@@ -206,7 +161,7 @@
} }
}, },
toggleActiveState: function (shouldBeActive) { toggleActiveState : function(shouldBeActive) {
if (this.$select.attr('disabled') == undefined) { if (this.$select.attr('disabled') == undefined) {
$('button.multiselect.dropdown-toggle', this.$container).removeClass('disabled'); $('button.multiselect.dropdown-toggle', this.$container).removeClass('disabled');
} }
...@@ -216,17 +171,18 @@ ...@@ -216,17 +171,18 @@
}, },
// Build the dropdown and bind event handling. // Build the dropdown and bind event handling.
buildDropdown: function () { buildDropdown : function() {
var alreadyHasSelectAll = this.$select[0][0] ? this.$select[0][0].value == this.options.selectAllValue : false; var alreadyHasSelectAll = this.$select[0][0] ? this.$select[0][0].value == this.options.selectAllValue : false;
// If options.includeSelectAllOption === true, add the include all checkbox. // If options.includeSelectAllOption === true, add the include all
// checkbox.
if (this.options.includeSelectAllOption && this.options.multiple && !alreadyHasSelectAll) { if (this.options.includeSelectAllOption && this.options.multiple && !alreadyHasSelectAll) {
this.$select.prepend('<option value="' + this.options.selectAllValue + '">' + this.options.selectAllText + '</option>'); this.$select.prepend('<option value="' + this.options.selectAllValue + '">' + this.options.selectAllText + '</option>');
} }
this.toggleActiveState(); this.toggleActiveState();
this.$select.children().each($.proxy(function (index, element) { this.$select.children().each($.proxy(function(index, element) {
// Support optgroups and options without a group simultaneously. // Support optgroups and options without a group simultaneously.
var tag = $(element).prop('tagName').toLowerCase(); var tag = $(element).prop('tagName').toLowerCase();
if (tag == 'optgroup') { if (tag == 'optgroup') {
...@@ -234,16 +190,17 @@ ...@@ -234,16 +190,17 @@
var groupName = $(group).prop('label'); var groupName = $(group).prop('label');
// Add a header for the group. // Add a header for the group.
var $li = $('<li><label style="margin:0;padding:3px 20px 3px 20px;height:100%;" class="multiselect-group"></label></li>'); var $li = $('<li><label class="multiselect-group"></label></li>');
$('label', $li).text(groupName); $('label', $li).text(groupName);
$('.multiselect-container', this.$container).append($li); $('.multiselect-container', this.$container).append($li);
// Add the options of the group. // Add the options of the group.
$('option', group).each($.proxy(function (index, element) { $('option', group).each($.proxy(function(index, element) {
this.createOptionValue(element); this.createOptionValue(element);
}, this)); }, this));
} }
else if (tag == 'option') { else
if (tag == 'option') {
this.createOptionValue(element); this.createOptionValue(element);
} }
else { else {
...@@ -252,7 +209,7 @@ ...@@ -252,7 +209,7 @@
}, this)); }, this));
// Bind the change event on the dropdown elements. // Bind the change event on the dropdown elements.
$('.multiselect-container li input', this.$container).on('change', $.proxy(function (event) { $('.multiselect-container li input', this.$container).on('change', $.proxy(function(event) {
var checked = $(event.target).prop('checked') || false; var checked = $(event.target).prop('checked') || false;
var isSelectAllOption = $(event.target).val() == this.options.selectAllValue; var isSelectAllOption = $(event.target).val() == this.options.selectAllValue;
...@@ -275,7 +232,9 @@ ...@@ -275,7 +232,9 @@
// Toggle all options if the select all option was changed. // Toggle all options if the select all option was changed.
if (isSelectAllOption) { if (isSelectAllOption) {
$checkboxesNotThis.filter(function () { return $(this).is(':checked') != checked; }).trigger('click'); $checkboxesNotThis.filter(function() {
return $(this).is(':checked') != checked;
}).trigger('click');
} }
if (checked) { if (checked) {
...@@ -313,14 +272,15 @@ ...@@ -313,14 +272,15 @@
this.$select.change(); this.$select.change();
}, this)); }, this));
$('.multiselect-container li a', this.$container).on('touchstart click', function (event) { $('.multiselect-container li a', this.$container).on('touchstart click', function(event) {
event.stopPropagation(); event.stopPropagation();
$(event.target).blur(); $(event.target).blur();
}); });
// Keyboard support. // Keyboard support.
this.$container.on('keydown', $.proxy(function (event) { this.$container.on('keydown', $.proxy(function(event) {
if ($('input[type="text"]', this.$container).is(':focus')) return; if ($('input[type="text"]', this.$container).is(':focus'))
return;
if ((event.keyCode == 9 || event.keyCode == 27) && this.$container.hasClass('open')) { if ((event.keyCode == 9 || event.keyCode == 27) && this.$container.hasClass('open')) {
// Close on tab or escape. // Close on tab or escape.
$(this.$container).find(".multiselect.dropdown-toggle").click(); $(this.$container).find(".multiselect.dropdown-toggle").click();
...@@ -339,10 +299,12 @@ ...@@ -339,10 +299,12 @@
index--; index--;
} }
// Navigate down. // Navigate down.
else if (event.keyCode == 40 && index < $items.length - 1) { else
if (event.keyCode == 40 && index < $items.length - 1) {
index++; index++;
} }
else if (!~index) { else
if (!~index) {
index = 0; index = 0;
} }
...@@ -369,16 +331,71 @@ ...@@ -369,16 +331,71 @@
}, this)); }, this));
}, },
// Build and bind filter.
buildFilter: function() {
$('.multiselect-container', this.$container).prepend('<div class="input-prepend"><span class="add-on"><i class="icon-search"></i></span><input class="multiselect-search" type="text" placeholder="' + this.options.filterPlaceholder + '"></div>');
$('.multiselect-search', this.$container).val(this.query).on('click', function(event) {
event.stopPropagation();
}).on('keydown', $.proxy(function(event) {
// This is useful to catch "keydown" events after the browser has
// updated the control.
clearTimeout(this.searchTimeout);
this.searchTimeout = this.asyncFunction($.proxy(function() {
if (this.query != event.target.value) {
this.query = event.target.value;
$.each($('.multiselect-container li', this.$container), $.proxy(function(index, element) {
var value = $('input', element).val();
if (value != this.options.selectAllValue) {
var text = $('label', element).text();
var value = $('input', element).val();
if (value && text && value != this.options.selectAllValue) {
// by default lets assume that element is not
// interesting for this search
var showElement = false;
var filterCandidate = '';
if ((this.options.filterBehavior == 'text' || this.options.filterBehavior == 'both')) {
filterCandidate = text;
}
if ((this.options.filterBehavior == 'value' || this.options.filterBehavior == 'both')) {
filterCandidate = value;
}
if (this.options.enableCaseInsensitiveFiltering && filterCandidate.toLowerCase().indexOf(this.query.toLowerCase()) > -1) {
showElement = true;
}
else if (filterCandidate.indexOf(this.query) > -1) {
showElement = true;
}
if (showElement) {
$(element).show();
}
else {
$(element).hide();
}
}
}
}, this));
}
}, this), 300, this);
}, this));
},
// Destroy - unbind - the plugin. // Destroy - unbind - the plugin.
destroy: function() { destroy : function() {
this.$container.remove(); this.$container.remove();
this.$select.show(); this.$select.show();
}, },
// Refreshs the checked options based on the current state of the select. // Refreshs the checked options based on the current state of the select.
refresh: function() { refresh : function() {
$('option', this.$select).each($.proxy(function(index, element) { $('option', this.$select).each($.proxy(function(index, element) {
var $input = $('.multiselect-container li input', this.$container).filter(function () { var $input = $('.multiselect-container li input', this.$container).filter(function() {
return $(this).val() == $(element).val(); return $(this).val() == $(element).val();
}); });
...@@ -409,11 +426,11 @@ ...@@ -409,11 +426,11 @@
}, },
// Select an option by its value. // Select an option by its value.
select: function(value) { select : function(value) {
var $option = $('option', this.$select).filter(function () { var $option = $('option', this.$select).filter(function() {
return $(this).val() == value; return $(this).val() == value;
}); });
var $checkbox = $('.multiselect-container li input', this.$container).filter(function () { var $checkbox = $('.multiselect-container li input', this.$container).filter(function() {
return $(this).val() == value; return $(this).val() == value;
}); });
...@@ -430,11 +447,11 @@ ...@@ -430,11 +447,11 @@
}, },
// Deselect an option by its value. // Deselect an option by its value.
deselect: function(value) { deselect : function(value) {
var $option = $('option', this.$select).filter(function () { var $option = $('option', this.$select).filter(function() {
return $(this).val() == value; return $(this).val() == value;
}); });
var $checkbox = $('.multiselect-container li input', this.$container).filter(function () { var $checkbox = $('.multiselect-container li input', this.$container).filter(function() {
return $(this).val() == value; return $(this).val() == value;
}); });
...@@ -450,34 +467,39 @@ ...@@ -450,34 +467,39 @@
}, },
// Rebuild the whole dropdown menu. // Rebuild the whole dropdown menu.
rebuild: function() { rebuild : function() {
$('.multiselect-container', this.$container).html(''); $('.multiselect-container', this.$container).html('');
this.buildDropdown(this.$select, this.options); this.buildDropdown(this.$select, this.options);
this.updateButtonText(); this.updateButtonText();
// Enable filtering.
if (this.options.enableFiltering || this.options.enableCaseInsensitiveFiltering) {
this.buildFilter();
}
}, },
// Get options by merging defaults and given options. // Get options by merging defaults and given options.
getOptions: function(options) { getOptions : function(options) {
return $.extend({}, this.defaults, options); return $.extend({}, this.defaults, options);
}, },
updateButtonText: function() { updateButtonText : function() {
var options = this.getSelected(); var options = this.getSelected();
$('button', this.$container).html(this.options.buttonText(options, this.$select)); $('button', this.$container).html(this.options.buttonText(options, this.$select));
}, },
// Get all selected options. // Get all selected options.
getSelected: function () { getSelected : function() {
return $('option:selected[value!="' + this.options.selectAllValue + '"]', this.$select); return $('option:selected[value!="' + this.options.selectAllValue + '"]', this.$select);
}, },
updateOriginalOptions: function() { updateOriginalOptions : function() {
this.originalOptions = this.$select.clone()[0].options; this.originalOptions = this.$select.clone()[0].options;
}, },
asyncFunction: function (callback, timeout, self) { asyncFunction : function(callback, timeout, self) {
var args = Array.prototype.slice.call(arguments, 3); var args = Array.prototype.slice.call(arguments, 3);
return setTimeout(function () { return setTimeout(function() {
callback.apply(self || window, args); callback.apply(self || window, args);
}, timeout); }, timeout);
} }
...@@ -485,16 +507,15 @@ ...@@ -485,16 +507,15 @@
$.fn.multiselect = function(option, parameter) { $.fn.multiselect = function(option, parameter) {
return this.each(function() { return this.each(function() {
var data = $(this).data('multiselect'), var data = $(this).data('multiselect'), options = typeof option == 'object' && option;
options = typeof option == 'object' && option;
// Initialize the multiselect. // Initialize the multiselect.
if (!data) { if (!data) {
$(this).data('multiselect', (data = new Multiselect(this, options))); $(this).data('multiselect', ( data = new Multiselect(this, options)));
} }
// Call multiselect method. // Call multiselect method.
if (typeof option == 'string') { if ( typeof option == 'string') {
data[option](parameter); data[option](parameter);
} }
}); });
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
<meta name="copyright" content="David Stutz" /> <meta name="copyright" content="David Stutz" />
<link rel="stylesheet" href="css/bootstrap-3.3.2.min.css" type="text/css"> <link rel="stylesheet" href="css/bootstrap-3.3.2.min.css" type="text/css">
<link rel="stylesheet" href="css/bootstrap-multiselect.css" type="text/css">
<link rel="stylesheet" href="css/prettify.css" type="text/css"> <link rel="stylesheet" href="css/prettify.css" type="text/css">
<script type="text/javascript" src="js/jquery-2.0.1.min.js"></script> <script type="text/javascript" src="js/jquery-2.0.1.min.js"></script>
......
.multiselect-container {
position: absolute;
list-style-type: none;
margin: 0;
padding: 0;
input[type="text"] {
width: 70%;
}
.input-prepend {
padding: 3px;
}
> li {
padding: 0;
> label {
margin: 0;
padding: 3px 20px 3px 20px;
height: 100%;
cursor: pointer;
&.multiselect-header {
margin: 0;
padding: 3px 20px 3px 20px;
height: 100%;
}
> input[type="checkbox"] {
margin-bottom:5px;
}
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment