the_role 1.5.1 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Bye_bye_CanCan_I_got_the_Role.png +0 -0
- data/README.md +82 -92
- data/app/assets/javascripts/admin_the_role.js +3 -0
- data/app/assets/javascripts/bootstrap-dropdown.js +100 -0
- data/app/assets/stylesheets/admin_the_role.css +7 -0
- data/app/assets/stylesheets/alerts.less +58 -0
- data/app/assets/stylesheets/button-groups.less +191 -0
- data/app/assets/stylesheets/buttons.less +191 -0
- data/app/assets/stylesheets/custom.scss +3 -0
- data/app/assets/stylesheets/dropdowns.less +143 -0
- data/app/assets/stylesheets/forms.less +583 -0
- data/app/assets/stylesheets/grid.less +5 -0
- data/app/assets/stylesheets/headers.scss +15 -0
- data/app/assets/stylesheets/layouts.less +17 -0
- data/app/assets/stylesheets/mix.scss +41 -0
- data/app/assets/stylesheets/mixins.less +646 -0
- data/app/assets/stylesheets/reset.css +117 -0
- data/app/assets/stylesheets/role_set.less +136 -0
- data/app/assets/stylesheets/scaffolding.less +29 -0
- data/app/assets/stylesheets/variables.less +206 -0
- data/app/assets/stylesheets/wells.less +27 -0
- data/app/controllers/admin/role_sections_controller.rb +40 -17
- data/app/controllers/admin/roles_controller.rb +8 -6
- data/app/views/admin/roles/_role.html.haml +53 -0
- data/app/views/admin/roles/_sidebar.html.haml +8 -0
- data/app/views/admin/roles/edit.html.haml +2 -42
- data/app/views/admin/roles/index.haml +2 -15
- data/app/views/admin/roles/new.html.haml +5 -4
- data/app/views/layouts/the_role.html.haml +24 -0
- data/config/routes.rb +5 -1
- data/lib/the_role/engine.rb +2 -2
- data/lib/the_role/hash.rb +50 -14
- data/lib/the_role/modules/base.rb +4 -4
- data/lib/the_role/modules/controller_requires.rb +4 -15
- data/lib/the_role/modules/role_model.rb +14 -13
- data/lib/the_role/param_helper.rb +15 -0
- data/lib/the_role/version.rb +1 -1
- data/lib/the_role.rb +13 -11
- data/pic.png +0 -0
- data/the_role.gemspec +5 -2
- metadata +58 -16
- data/app/assets/stylesheets/the_role/form.css +0 -57
- data/app/assets/stylesheets/the_role/headers.css.scss +0 -15
- data/app/assets/stylesheets/the_role/style.css.scss +0 -75
- data/lib/the_role/modules/param_helper.rb +0 -7
Binary file
|
data/README.md
CHANGED
@@ -1,10 +1,14 @@
|
|
1
|
-
# gem 'the_role'
|
1
|
+
# gem 'the_role' (alpha v0.1)
|
2
2
|
|
3
|
-
|
3
|
+
| Bye bye CanCan, I got The Role! | Description |
|
4
|
+
|:------------- |:-------------|
|
5
|
+
|  | TheRole is an authorization library for Ruby on Rails which restricts what resources a given user is allowed to access. All permissions are defined in with 2-level-hash, and store in database with JSON.<br><br>TheRole - Semantic, lightweight role system with an administrative interface.<br><br>Role is a two-level hash, consisting of the **sections** and nested **rules**.<br><br>**Section** may be associated with **controller** name.<br><br>**Rule** may be associated with **action** name.<br><br>Section can have many rules.<br><br>Rule can have **true** or **false** value<br><br>**Sections** and nested **Rules** provide **ACL** (**Access Control List**)<br><br>Role **stored in the database as JSON** string.<br><br>Using of hashes, makes role system extremely easy to configure and use.<br> |
|
4
6
|
|
5
|
-
|
7
|
+
### GUI
|
6
8
|
|
7
|
-
|
9
|
+
| TheRole management web interface |
|
10
|
+
|:-------------:|
|
11
|
+
||
|
8
12
|
|
9
13
|
## What does it mean semantic?
|
10
14
|
|
@@ -14,73 +18,55 @@ Look at hash. If you can understand access rules - this role system is semantica
|
|
14
18
|
|
15
19
|
``` ruby
|
16
20
|
role = {
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
21
|
+
'pages' => {
|
22
|
+
'index' => true,
|
23
|
+
'show' => true,
|
24
|
+
'new' => false,
|
25
|
+
'edit' => false,
|
26
|
+
'update' => false,
|
27
|
+
'destroy' => false
|
24
28
|
},
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
}
|
29
|
-
|
30
|
-
|
31
|
-
|
29
|
+
'articles' => {
|
30
|
+
'index' => true,
|
31
|
+
'show' => true
|
32
|
+
},
|
33
|
+
'twitter' => {
|
34
|
+
'button' => true,
|
35
|
+
'follow' => false
|
32
36
|
}
|
33
37
|
}
|
34
38
|
```
|
35
39
|
|
36
|
-
### How it works
|
37
|
-
|
38
|
-
Role - is a two-level hash, consisting of the sections and rules.
|
39
|
-
|
40
|
-
**Section** may be associated with the name of a **controller**.
|
41
|
-
|
42
|
-
**Rule** may be associated with the name of **action** in the controller.
|
43
|
-
|
44
|
-
Section may contain a set of rules.
|
45
|
-
|
46
|
-
**Rule in Section** can be set to **true** and **false**, this provide **ACL** (**Access Control List**)
|
47
|
-
|
48
|
-
Role hash **stored in the database as YAML** string.
|
49
|
-
Using of hashes, makes it extremely easy to configure access rules in the role.
|
50
|
-
|
51
40
|
### Virtual sections and rules
|
52
41
|
|
53
|
-
Usually, we use real names of controllers
|
54
|
-
|
55
|
-
Like this:
|
42
|
+
Usually, we use real names of controllers and actions for names of sections and rules:
|
56
43
|
|
57
44
|
``` ruby
|
58
45
|
current_user.has_role?(:pages, :show)
|
59
46
|
```
|
60
47
|
|
61
|
-
But you can
|
62
|
-
|
48
|
+
But, also, you can use virtual names of sections, and virtual names of section's rules.
|
63
49
|
|
64
50
|
``` ruby
|
65
51
|
current_user.has_role?(:twitter, :button)
|
66
52
|
current_user.has_role?(:facebook, :like)
|
67
53
|
```
|
68
54
|
|
69
|
-
These sections and the rules are not associated with real controllers and actions.
|
70
55
|
And you can use them as well as other access rules.
|
71
56
|
|
72
|
-
|
57
|
+
# Install
|
73
58
|
|
74
59
|
``` ruby
|
75
60
|
gem 'the_role'
|
76
61
|
```
|
77
62
|
|
78
63
|
``` ruby
|
79
|
-
bundle
|
64
|
+
bundle
|
80
65
|
```
|
81
66
|
|
82
|
-
|
67
|
+
### Migrate
|
83
68
|
|
69
|
+
Add **role_id:integer** to User Model Migration
|
84
70
|
|
85
71
|
``` ruby
|
86
72
|
rake the_role_engine:install:migrations
|
@@ -95,18 +81,16 @@ rails g model role --migration=false
|
|
95
81
|
rake db:create && rake db:migrate
|
96
82
|
```
|
97
83
|
|
98
|
-
|
84
|
+
### Fake roles for test (not required)
|
85
|
+
|
86
|
+
Creating roles for test
|
99
87
|
|
100
88
|
``` ruby
|
101
89
|
rake db:roles:test
|
102
90
|
>> Administrator, Moderator of pages, User, Demo
|
103
91
|
```
|
104
92
|
|
105
|
-
|
106
|
-
|
107
|
-
**authenticate_user!** or any other method from your auth system
|
108
|
-
|
109
|
-
**access_denied** or any other method for processing access denied situation
|
93
|
+
### Change your ApplicationController
|
110
94
|
|
111
95
|
**Example for Devise2**
|
112
96
|
|
@@ -118,55 +102,48 @@ class ApplicationController < ActionController::Base
|
|
118
102
|
render :text => 'access_denied: requires an role' and return
|
119
103
|
end
|
120
104
|
|
121
|
-
|
122
|
-
|
123
|
-
# *access_denied* - define it before alias_method
|
124
|
-
# before_filter :role_object_finder, :only => [:edit, :update, :rebuild, :destroy]
|
125
|
-
alias_method :role_login_required, :authenticate_user!
|
126
|
-
alias_method :role_access_denied, :access_denied
|
105
|
+
alias_method :login_required, :authenticate_user!
|
106
|
+
alias_method :role_access_denied, :access_denied
|
127
107
|
|
128
108
|
end
|
129
109
|
```
|
130
110
|
|
131
|
-
|
132
|
-
|
133
|
-
``` ruby
|
134
|
-
class PagesController < ApplicationController
|
135
|
-
# Devise2 and TheRole before_filters
|
136
|
-
before_filter :role_login_required, :except => [:index, :show]
|
137
|
-
before_filter :role_require, :except => [:index, :show]
|
138
|
-
|
139
|
-
before_filter :find_page, :only => [:edit, :update, :destroy]
|
140
|
-
before_filter :owner_require, :only => [:edit, :update, :destroy]
|
111
|
+
Define aliases method for correctly work TheRole's controllers
|
141
112
|
|
142
|
-
|
143
|
-
```
|
113
|
+
**authenticate_user!** or any other method from your auth system
|
144
114
|
|
145
|
-
|
115
|
+
**access_denied** or any other method for processing access denied situation
|
146
116
|
|
147
|
-
When you checking **owner_require** - you should before this to define variable **@object_for_ownership_checking** in finder method.
|
148
117
|
|
149
|
-
|
118
|
+
### Using with any controller
|
150
119
|
|
151
120
|
``` ruby
|
152
121
|
class PagesController < ApplicationController
|
153
|
-
|
154
|
-
before_filter :
|
155
|
-
|
122
|
+
# Devise2 and TheRole before_filters
|
123
|
+
before_filter :login_required, :except => [:index, :show]
|
124
|
+
before_filter :role_required, :except => [:index, :show]
|
125
|
+
|
126
|
+
before_filter :find_page, :only => [:edit, :update, :destroy]
|
127
|
+
before_filter :owner_required, :only => [:edit, :update, :destroy]
|
128
|
+
|
156
129
|
private
|
157
130
|
|
158
131
|
def find_page
|
159
132
|
@page = Page.find params[:id]
|
160
|
-
@
|
133
|
+
@ownership_checking_object = @page
|
161
134
|
end
|
162
135
|
end
|
163
136
|
```
|
164
137
|
|
165
|
-
|
138
|
+
**owner_required** method require **@ownership_checking_object** variable, with cheked object.
|
139
|
+
|
140
|
+
### Who is Administrator?
|
166
141
|
|
167
|
-
Administrator
|
168
|
-
|
169
|
-
Administrator
|
142
|
+
Administrator it's a user who can access any section and the rules of your application.
|
143
|
+
|
144
|
+
Administrator is the owner of any objects in your application.
|
145
|
+
|
146
|
+
Administrator it's a user, which has virtual section **system** and rule **administrator** in the role-hash.
|
170
147
|
|
171
148
|
|
172
149
|
``` ruby
|
@@ -177,13 +154,15 @@ admin_role_fragment = {
|
|
177
154
|
}
|
178
155
|
```
|
179
156
|
|
180
|
-
### Who is
|
157
|
+
### Who is Moderator?
|
158
|
+
|
159
|
+
Moderator it's a user, which has access to any actions of some section(s).
|
181
160
|
|
182
|
-
Moderator
|
183
|
-
Moderator is the owner of any objects of this class.
|
184
|
-
Moderator - user which has in a section **moderator** rule with name of real or virtual section (controller).
|
161
|
+
Moderator is's owner of any objects of some class.
|
185
162
|
|
186
|
-
|
163
|
+
Moderator it's a user, which has a virtual section **moderator**, with **section name** as rule name.
|
164
|
+
|
165
|
+
There is Moderator of Pages (controller) and Twitter (virtual section)
|
187
166
|
|
188
167
|
``` ruby
|
189
168
|
moderator_role_fragment = {
|
@@ -195,9 +174,17 @@ moderator_role_fragment = {
|
|
195
174
|
}
|
196
175
|
```
|
197
176
|
|
198
|
-
###
|
177
|
+
### Who is Owner?
|
178
|
+
|
179
|
+
Administrator is owner of any object in system.
|
180
|
+
|
181
|
+
Moderator of pages is owner of any page.
|
199
182
|
|
200
|
-
|
183
|
+
User is owner of object, when **Object#user_id == User#id**.
|
184
|
+
|
185
|
+
# User Model methods
|
186
|
+
|
187
|
+
Has a user an access to **rule** of **section** (action of controller)?
|
201
188
|
|
202
189
|
``` ruby
|
203
190
|
current_user.has_role?(:pages, :show) => true | false
|
@@ -227,22 +214,25 @@ current_user.owner?(@blog) => true | false
|
|
227
214
|
current_user.owner?(@article) => true | false
|
228
215
|
```
|
229
216
|
|
230
|
-
|
217
|
+
# Base Role methods
|
231
218
|
|
232
219
|
``` ruby
|
233
|
-
#
|
234
|
-
@role.
|
220
|
+
# User's role
|
221
|
+
@role = current_user.role
|
235
222
|
```
|
236
223
|
|
237
224
|
``` ruby
|
238
|
-
#
|
225
|
+
# Find a Role by name
|
226
|
+
@role = Role.find_by_name(:user)
|
227
|
+
```
|
239
228
|
|
229
|
+
``` ruby
|
240
230
|
@role.has?(:pages, :show) => true | false
|
241
231
|
@role.moderator?(:pages) => true | false
|
242
232
|
@role.admin? => true | false
|
243
233
|
```
|
244
234
|
|
245
|
-
|
235
|
+
# CRUD API (for console users)
|
246
236
|
|
247
237
|
#### CREATE
|
248
238
|
|
@@ -261,10 +251,10 @@ current_user.owner?(@article) => true | false
|
|
261
251
|
``` ruby
|
262
252
|
@role.to_hash => Hash
|
263
253
|
|
264
|
-
#
|
265
|
-
@role.
|
254
|
+
# JSON string
|
255
|
+
@role.to_json => String
|
266
256
|
|
267
|
-
#
|
257
|
+
# JSON string
|
268
258
|
@role.to_s => String
|
269
259
|
```
|
270
260
|
|
@@ -0,0 +1,100 @@
|
|
1
|
+
/* ============================================================
|
2
|
+
* bootstrap-dropdown.js v2.0.4
|
3
|
+
* http://twitter.github.com/bootstrap/javascript.html#dropdowns
|
4
|
+
* ============================================================
|
5
|
+
* Copyright 2012 Twitter, Inc.
|
6
|
+
*
|
7
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
8
|
+
* you may not use this file except in compliance with the License.
|
9
|
+
* You may obtain a copy of the License at
|
10
|
+
*
|
11
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
12
|
+
*
|
13
|
+
* Unless required by applicable law or agreed to in writing, software
|
14
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
15
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
16
|
+
* See the License for the specific language governing permissions and
|
17
|
+
* limitations under the License.
|
18
|
+
* ============================================================ */
|
19
|
+
|
20
|
+
|
21
|
+
!function ($) {
|
22
|
+
|
23
|
+
"use strict"; // jshint ;_;
|
24
|
+
|
25
|
+
|
26
|
+
/* DROPDOWN CLASS DEFINITION
|
27
|
+
* ========================= */
|
28
|
+
|
29
|
+
var toggle = '[data-toggle="dropdown"]'
|
30
|
+
, Dropdown = function (element) {
|
31
|
+
var $el = $(element).on('click.dropdown.data-api', this.toggle)
|
32
|
+
$('html').on('click.dropdown.data-api', function () {
|
33
|
+
$el.parent().removeClass('open')
|
34
|
+
})
|
35
|
+
}
|
36
|
+
|
37
|
+
Dropdown.prototype = {
|
38
|
+
|
39
|
+
constructor: Dropdown
|
40
|
+
|
41
|
+
, toggle: function (e) {
|
42
|
+
var $this = $(this)
|
43
|
+
, $parent
|
44
|
+
, selector
|
45
|
+
, isActive
|
46
|
+
|
47
|
+
if ($this.is('.disabled, :disabled')) return
|
48
|
+
|
49
|
+
selector = $this.attr('data-target')
|
50
|
+
|
51
|
+
if (!selector) {
|
52
|
+
selector = $this.attr('href')
|
53
|
+
selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
|
54
|
+
}
|
55
|
+
|
56
|
+
$parent = $(selector)
|
57
|
+
$parent.length || ($parent = $this.parent())
|
58
|
+
|
59
|
+
isActive = $parent.hasClass('open')
|
60
|
+
|
61
|
+
clearMenus()
|
62
|
+
|
63
|
+
if (!isActive) $parent.toggleClass('open')
|
64
|
+
|
65
|
+
return false
|
66
|
+
}
|
67
|
+
|
68
|
+
}
|
69
|
+
|
70
|
+
function clearMenus() {
|
71
|
+
$(toggle).parent().removeClass('open')
|
72
|
+
}
|
73
|
+
|
74
|
+
|
75
|
+
/* DROPDOWN PLUGIN DEFINITION
|
76
|
+
* ========================== */
|
77
|
+
|
78
|
+
$.fn.dropdown = function (option) {
|
79
|
+
return this.each(function () {
|
80
|
+
var $this = $(this)
|
81
|
+
, data = $this.data('dropdown')
|
82
|
+
if (!data) $this.data('dropdown', (data = new Dropdown(this)))
|
83
|
+
if (typeof option == 'string') data[option].call($this)
|
84
|
+
})
|
85
|
+
}
|
86
|
+
|
87
|
+
$.fn.dropdown.Constructor = Dropdown
|
88
|
+
|
89
|
+
|
90
|
+
/* APPLY TO STANDARD DROPDOWN ELEMENTS
|
91
|
+
* =================================== */
|
92
|
+
|
93
|
+
$(function () {
|
94
|
+
$('html').on('click.dropdown.data-api', clearMenus)
|
95
|
+
$('body')
|
96
|
+
.on('click.dropdown', '.dropdown form', function (e) { e.stopPropagation() })
|
97
|
+
.on('click.dropdown.data-api', toggle, Dropdown.prototype.toggle)
|
98
|
+
})
|
99
|
+
|
100
|
+
}(window.jQuery);
|
@@ -0,0 +1,58 @@
|
|
1
|
+
// ALERT STYLES
|
2
|
+
// ------------
|
3
|
+
|
4
|
+
// Base alert styles
|
5
|
+
.alert {
|
6
|
+
padding: 8px 35px 8px 14px;
|
7
|
+
margin-bottom: @baseLineHeight;
|
8
|
+
text-shadow: 0 1px 0 rgba(255,255,255,.5);
|
9
|
+
background-color: @warningBackground;
|
10
|
+
border: 1px solid @warningBorder;
|
11
|
+
.border-radius(4px);
|
12
|
+
color: @warningText;
|
13
|
+
}
|
14
|
+
.alert-heading {
|
15
|
+
color: inherit;
|
16
|
+
}
|
17
|
+
|
18
|
+
// Adjust close link position
|
19
|
+
.alert .close {
|
20
|
+
position: relative;
|
21
|
+
top: -2px;
|
22
|
+
right: -21px;
|
23
|
+
line-height: 18px;
|
24
|
+
}
|
25
|
+
|
26
|
+
// Alternate styles
|
27
|
+
// ----------------
|
28
|
+
|
29
|
+
.alert-success {
|
30
|
+
background-color: @successBackground;
|
31
|
+
border-color: @successBorder;
|
32
|
+
color: @successText;
|
33
|
+
}
|
34
|
+
.alert-danger,
|
35
|
+
.alert-error {
|
36
|
+
background-color: @errorBackground;
|
37
|
+
border-color: @errorBorder;
|
38
|
+
color: @errorText;
|
39
|
+
}
|
40
|
+
.alert-info {
|
41
|
+
background-color: @infoBackground;
|
42
|
+
border-color: @infoBorder;
|
43
|
+
color: @infoText;
|
44
|
+
}
|
45
|
+
|
46
|
+
// Block alerts
|
47
|
+
// ------------------------
|
48
|
+
.alert-block {
|
49
|
+
padding-top: 14px;
|
50
|
+
padding-bottom: 14px;
|
51
|
+
}
|
52
|
+
.alert-block > p,
|
53
|
+
.alert-block > ul {
|
54
|
+
margin-bottom: 0;
|
55
|
+
}
|
56
|
+
.alert-block p + p {
|
57
|
+
margin-top: 5px;
|
58
|
+
}
|
@@ -0,0 +1,191 @@
|
|
1
|
+
// BUTTON GROUPS
|
2
|
+
// -------------
|
3
|
+
|
4
|
+
|
5
|
+
// Make the div behave like a button
|
6
|
+
.btn-group {
|
7
|
+
position: relative;
|
8
|
+
.clearfix(); // clears the floated buttons
|
9
|
+
.ie7-restore-left-whitespace();
|
10
|
+
}
|
11
|
+
|
12
|
+
// Space out series of button groups
|
13
|
+
.btn-group + .btn-group {
|
14
|
+
margin-left: 5px;
|
15
|
+
}
|
16
|
+
|
17
|
+
// Optional: Group multiple button groups together for a toolbar
|
18
|
+
.btn-toolbar {
|
19
|
+
margin-top: @baseLineHeight / 2;
|
20
|
+
margin-bottom: @baseLineHeight / 2;
|
21
|
+
.btn-group {
|
22
|
+
display: inline-block;
|
23
|
+
.ie7-inline-block();
|
24
|
+
}
|
25
|
+
}
|
26
|
+
|
27
|
+
// Float them, remove border radius, then re-add to first and last elements
|
28
|
+
.btn-group > .btn {
|
29
|
+
position: relative;
|
30
|
+
float: left;
|
31
|
+
margin-left: -1px;
|
32
|
+
.border-radius(0);
|
33
|
+
}
|
34
|
+
// Set corners individual because sometimes a single button can be in a .btn-group and we need :first-child and :last-child to both match
|
35
|
+
.btn-group > .btn:first-child {
|
36
|
+
margin-left: 0;
|
37
|
+
-webkit-border-top-left-radius: 4px;
|
38
|
+
-moz-border-radius-topleft: 4px;
|
39
|
+
border-top-left-radius: 4px;
|
40
|
+
-webkit-border-bottom-left-radius: 4px;
|
41
|
+
-moz-border-radius-bottomleft: 4px;
|
42
|
+
border-bottom-left-radius: 4px;
|
43
|
+
}
|
44
|
+
// Need .dropdown-toggle since :last-child doesn't apply given a .dropdown-menu immediately after it
|
45
|
+
.btn-group > .btn:last-child,
|
46
|
+
.btn-group > .dropdown-toggle {
|
47
|
+
-webkit-border-top-right-radius: 4px;
|
48
|
+
-moz-border-radius-topright: 4px;
|
49
|
+
border-top-right-radius: 4px;
|
50
|
+
-webkit-border-bottom-right-radius: 4px;
|
51
|
+
-moz-border-radius-bottomright: 4px;
|
52
|
+
border-bottom-right-radius: 4px;
|
53
|
+
}
|
54
|
+
// Reset corners for large buttons
|
55
|
+
.btn-group > .btn.large:first-child {
|
56
|
+
margin-left: 0;
|
57
|
+
-webkit-border-top-left-radius: 6px;
|
58
|
+
-moz-border-radius-topleft: 6px;
|
59
|
+
border-top-left-radius: 6px;
|
60
|
+
-webkit-border-bottom-left-radius: 6px;
|
61
|
+
-moz-border-radius-bottomleft: 6px;
|
62
|
+
border-bottom-left-radius: 6px;
|
63
|
+
}
|
64
|
+
.btn-group > .btn.large:last-child,
|
65
|
+
.btn-group > .large.dropdown-toggle {
|
66
|
+
-webkit-border-top-right-radius: 6px;
|
67
|
+
-moz-border-radius-topright: 6px;
|
68
|
+
border-top-right-radius: 6px;
|
69
|
+
-webkit-border-bottom-right-radius: 6px;
|
70
|
+
-moz-border-radius-bottomright: 6px;
|
71
|
+
border-bottom-right-radius: 6px;
|
72
|
+
}
|
73
|
+
|
74
|
+
// On hover/focus/active, bring the proper btn to front
|
75
|
+
.btn-group > .btn:hover,
|
76
|
+
.btn-group > .btn:focus,
|
77
|
+
.btn-group > .btn:active,
|
78
|
+
.btn-group > .btn.active {
|
79
|
+
z-index: 2;
|
80
|
+
}
|
81
|
+
|
82
|
+
// On active and open, don't show outline
|
83
|
+
.btn-group .dropdown-toggle:active,
|
84
|
+
.btn-group.open .dropdown-toggle {
|
85
|
+
outline: 0;
|
86
|
+
}
|
87
|
+
|
88
|
+
|
89
|
+
|
90
|
+
// Split button dropdowns
|
91
|
+
// ----------------------
|
92
|
+
|
93
|
+
// Give the line between buttons some depth
|
94
|
+
.btn-group > .dropdown-toggle {
|
95
|
+
padding-left: 8px;
|
96
|
+
padding-right: 8px;
|
97
|
+
.box-shadow(~"inset 1px 0 0 rgba(255,255,255,.125), inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05)");
|
98
|
+
*padding-top: 4px;
|
99
|
+
*padding-bottom: 4px;
|
100
|
+
}
|
101
|
+
.btn-group > .btn-mini.dropdown-toggle {
|
102
|
+
padding-left: 5px;
|
103
|
+
padding-right: 5px;
|
104
|
+
}
|
105
|
+
.btn-group > .btn-small.dropdown-toggle {
|
106
|
+
*padding-top: 4px;
|
107
|
+
*padding-bottom: 4px;
|
108
|
+
}
|
109
|
+
.btn-group > .btn-large.dropdown-toggle {
|
110
|
+
padding-left: 12px;
|
111
|
+
padding-right: 12px;
|
112
|
+
}
|
113
|
+
|
114
|
+
.btn-group.open {
|
115
|
+
|
116
|
+
// The clickable button for toggling the menu
|
117
|
+
// Remove the gradient and set the same inset shadow as the :active state
|
118
|
+
.dropdown-toggle {
|
119
|
+
background-image: none;
|
120
|
+
.box-shadow(~"inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05)");
|
121
|
+
}
|
122
|
+
|
123
|
+
// Keep the hover's background when dropdown is open
|
124
|
+
.btn.dropdown-toggle {
|
125
|
+
background-color: @btnBackgroundHighlight;
|
126
|
+
}
|
127
|
+
.btn-primary.dropdown-toggle {
|
128
|
+
background-color: @btnPrimaryBackgroundHighlight;
|
129
|
+
}
|
130
|
+
.btn-warning.dropdown-toggle {
|
131
|
+
background-color: @btnWarningBackgroundHighlight;
|
132
|
+
}
|
133
|
+
.btn-danger.dropdown-toggle {
|
134
|
+
background-color: @btnDangerBackgroundHighlight;
|
135
|
+
}
|
136
|
+
.btn-success.dropdown-toggle {
|
137
|
+
background-color: @btnSuccessBackgroundHighlight;
|
138
|
+
}
|
139
|
+
.btn-info.dropdown-toggle {
|
140
|
+
background-color: @btnInfoBackgroundHighlight;
|
141
|
+
}
|
142
|
+
.btn-inverse.dropdown-toggle {
|
143
|
+
background-color: @btnInverseBackgroundHighlight;
|
144
|
+
}
|
145
|
+
}
|
146
|
+
|
147
|
+
|
148
|
+
// Reposition the caret
|
149
|
+
.btn .caret {
|
150
|
+
margin-top: 7px;
|
151
|
+
margin-left: 0;
|
152
|
+
}
|
153
|
+
.btn:hover .caret,
|
154
|
+
.open.btn-group .caret {
|
155
|
+
.opacity(100);
|
156
|
+
}
|
157
|
+
// Carets in other button sizes
|
158
|
+
.btn-mini .caret {
|
159
|
+
margin-top: 5px;
|
160
|
+
}
|
161
|
+
.btn-small .caret {
|
162
|
+
margin-top: 6px;
|
163
|
+
}
|
164
|
+
.btn-large .caret {
|
165
|
+
margin-top: 6px;
|
166
|
+
border-left-width: 5px;
|
167
|
+
border-right-width: 5px;
|
168
|
+
border-top-width: 5px;
|
169
|
+
}
|
170
|
+
// Upside down carets for .dropup
|
171
|
+
.dropup .btn-large .caret {
|
172
|
+
border-bottom: 5px solid @black;
|
173
|
+
border-top: 0;
|
174
|
+
}
|
175
|
+
|
176
|
+
|
177
|
+
|
178
|
+
// Account for other colors
|
179
|
+
.btn-primary,
|
180
|
+
.btn-warning,
|
181
|
+
.btn-danger,
|
182
|
+
.btn-info,
|
183
|
+
.btn-success,
|
184
|
+
.btn-inverse {
|
185
|
+
.caret {
|
186
|
+
border-top-color: @white;
|
187
|
+
border-bottom-color: @white;
|
188
|
+
.opacity(75);
|
189
|
+
}
|
190
|
+
}
|
191
|
+
|