the_role 1.5.1 → 1.6.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
| ![Bye bye CanCan, I got The Role!](https://github.com/the-teacher/the_role/raw/master/Bye_bye_CanCan_I_got_the_Role.png) | 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
|
+
|![TheRole](https://github.com/the-teacher/the_role/raw/master/pic.png)|
|
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
|
+
|