chr 0.2.4 → 0.2.5
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.
- checksums.yaml +4 -4
- data/README.md +29 -255
- data/app/assets/javascripts/chr/core/chr.coffee +1 -1
- data/app/assets/javascripts/chr/core/item.coffee +3 -3
- data/app/assets/javascripts/chr/core/list.coffee +7 -14
- data/app/assets/javascripts/chr/core/module.coffee +23 -30
- data/app/assets/javascripts/chr/core/view.coffee +5 -18
- data/app/assets/javascripts/chr/vendor/redactor.fixedtoolbar.js +10 -13
- data/app/assets/javascripts/input-redactor.coffee +5 -10
- data/bower.json +1 -1
- data/dist/chr.js +33 -135
- data/dist/input-redactor.js +17 -16
- data/docs/bootstrap.md +23 -0
- data/docs/rails.md +255 -0
- data/lib/chr/version.rb +1 -1
- data/package.json +1 -1
- metadata +3 -2
- data/docs/bootstrap-data.md +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bc38de2cb0b82aa71968cd48127d83146daa2bfd
|
4
|
+
data.tar.gz: 9a373db5e5f9d4e1ce08ef020b8691b4b3c5d473
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b13c5bdb285b264cd540f236b6c55aa8c5671c66122798a5c1f840b8055d18c1a775d60c7fb9305c10cb3e8e06d20c616ae1fe6d296739c198418929bf1f06ca
|
7
|
+
data.tar.gz: 8223dd3c23d34d4da1cfbba9d22f7089e5ebe201a26ad6e58f6d08d482803c0613e794607f348a1e0ba7ef8dd587ce9e680bb6ce16ab1db72992f4920ea2dce3
|
data/README.md
CHANGED
@@ -3,283 +3,53 @@
|
|
3
3
|
*Powerful responsive javascript CMS for apps.*
|
4
4
|
|
5
5
|
|
6
|
-
##
|
6
|
+
## Quick Start
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
#### Gems
|
12
|
-
|
13
|
-
Add to following gems to ```Gemfile```:
|
14
|
-
|
15
|
-
gem "devise"
|
16
|
-
gem "mongosteen"
|
17
|
-
gem "chr"
|
18
|
-
|
19
|
-
This example uses ```devise``` for admins authentication.
|
20
|
-
|
21
|
-
|
22
|
-
#### Admin authentication
|
23
|
-
|
24
|
-
Start with running [devise](https://github.com/plataformatec/devise) generator:
|
25
|
-
|
26
|
-
rails generate devise:install
|
27
|
-
|
28
|
-
Setup ```Admin``` model with devise generator:
|
29
|
-
|
30
|
-
rails generate devise admin
|
31
|
-
|
32
|
-
Here is an example of basic ```app/models/admin.rb``` model that provides email/password authentication:
|
33
|
-
|
34
|
-
```ruby
|
35
|
-
class Admin
|
36
|
-
include Mongoid::Document
|
37
|
-
include Mongoid::Timestamps
|
38
|
-
include Mongoid::SerializableId
|
39
|
-
|
40
|
-
devise :database_authenticatable,
|
41
|
-
:rememberable,
|
42
|
-
:authentication_keys => [ :email ]
|
43
|
-
|
44
|
-
## Database authenticatable
|
45
|
-
field :email, type: String, default: ""
|
46
|
-
field :encrypted_password, type: String, default: ""
|
47
|
-
|
48
|
-
## Rememberable
|
49
|
-
field :remember_created_at, type: Time
|
50
|
-
end
|
51
|
-
```
|
52
|
-
|
53
|
-
When models are ready, setup controllers, views and configure routes.
|
54
|
-
|
55
|
-
Base admin controller ```app/controllers/admin/base_controller.rb``` looks like this:
|
56
|
-
|
57
|
-
```ruby
|
58
|
-
class Admin::BaseController < ActionController::Base
|
59
|
-
protect_from_forgery
|
60
|
-
|
61
|
-
if Rails.env.production?
|
62
|
-
before_action :authenticate_admin!
|
63
|
-
end
|
64
|
-
|
65
|
-
def index
|
66
|
-
render '/admin/index', layout: 'admin'
|
67
|
-
end
|
68
|
-
|
69
|
-
def bootstrap_data
|
70
|
-
render json: {}
|
71
|
-
end
|
72
|
-
end
|
73
|
-
```
|
74
|
-
|
75
|
-
Notes on code above:
|
76
|
-
|
77
|
-
1. Authentication is not required when running in development or testing environment;
|
78
|
-
2. Need to setup ```index``` view and ```admin``` layout to render admin app;
|
79
|
-
3. ```bootstrap_data``` is a placeholder for objects that might be required to be loaded when app starts.
|
80
|
-
|
81
|
-
Devise would require a custom ```SessionController``` implementation in ```app/controllers/admin/devise_overrides/session_controller.rb```. ```SessionController``` sets ```admin``` layout to be used for devise views rendering and enables login by email (*looks like workaround*).
|
82
|
-
|
83
|
-
```ruby
|
84
|
-
class Admin::DeviseOverrides::SessionsController < Devise::SessionsController
|
85
|
-
layout 'admin'
|
86
|
-
|
87
|
-
protected
|
88
|
-
|
89
|
-
def configure_permitted_parameters
|
90
|
-
devise_parameter_sanitizer.for(:sign_in) << :email
|
91
|
-
end
|
92
|
-
end
|
93
|
-
```
|
94
|
-
|
95
|
-
Admin app layout ```app/views/layouts/admin.html.erb```:
|
96
|
-
|
97
|
-
```erb
|
98
|
-
<!doctype html>
|
99
|
-
<html>
|
100
|
-
<head>
|
101
|
-
<meta charset="utf-8">
|
102
|
-
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
103
|
-
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
|
104
|
-
<meta name="apple-mobile-web-app-capable" content="yes">
|
105
|
-
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
106
|
-
<title>Admin</title>
|
107
|
-
<%= csrf_meta_tags %>
|
108
|
-
<%= stylesheet_link_tag :admin, media: "all" %>
|
109
|
-
<%= javascript_include_tag :admin %>
|
110
|
-
</head>
|
111
|
-
|
112
|
-
<%= yield %>
|
113
|
-
</html>
|
114
|
-
```
|
115
|
-
|
116
|
-
Admin index view ```app/views/admin/index.html.erb```:
|
117
|
-
|
118
|
-
```erb
|
119
|
-
<body class='loading'>
|
120
|
-
<%= link_to 'Sign Out', destroy_admin_session_path, method: :delete, style: 'display:none;' %>
|
121
|
-
</body>
|
122
|
-
```
|
123
|
-
|
124
|
-
New session view for devise ```app/views/admin/devise_overrides/sessions/new.html.erb```:
|
125
|
-
|
126
|
-
```erb
|
127
|
-
<body class='sign-in'>
|
128
|
-
<h2>Sign In</h2>
|
129
|
-
|
130
|
-
<%= simple_form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
|
131
|
-
<% if alert %>
|
132
|
-
<p class="error"><%= alert.gsub('username', 'email').gsub('or sign up', '') %></p>
|
133
|
-
<% end %>
|
134
|
-
|
135
|
-
<div class="form-inputs">
|
136
|
-
<%= f.input :email, required: true, autofocus: true %>
|
137
|
-
<%= f.input :password, required: true %>
|
138
|
-
|
139
|
-
<%= f.input :remember_me, as: :boolean if devise_mapping.rememberable? %>
|
140
|
-
</div>
|
141
|
-
|
142
|
-
<div class="form-actions">
|
143
|
-
<%= f.button :submit, "Sign In" %>
|
144
|
-
</div>
|
145
|
-
<% end %>
|
146
|
-
</body>
|
147
|
-
```
|
148
|
-
|
149
|
-
Now connect admin and devise in ```config/routes.rb``` with:
|
150
|
-
|
151
|
-
```ruby
|
152
|
-
devise_for :admins, path: "admin", controllers: { sessions: "admin/devise_overrides/sessions" }
|
153
|
-
namespace :admin do
|
154
|
-
get '/' => 'base#index'
|
155
|
-
get '/bootstrap.json' => 'base#bootstrap_data'
|
156
|
-
end
|
157
|
-
```
|
158
|
-
|
159
|
-
|
160
|
-
#### Character setup
|
161
|
-
|
162
|
-
Three pieces to be configured here.
|
163
|
-
|
164
|
-
**First**: create ```app/assets/javascripts/admin.coffee``` with empty ```modules``` configuration:
|
8
|
+
Application setup:
|
165
9
|
|
166
10
|
```coffee
|
167
11
|
#= require jquery
|
168
|
-
#= require jquery_ujs
|
169
12
|
#= require chr
|
170
13
|
|
14
|
+
postsConfig = (data) ->
|
15
|
+
itemTitleField: 'title'
|
16
|
+
arrayStore: new RestArrayStore({
|
17
|
+
resource: 'post'
|
18
|
+
path: '/admin/posts'
|
19
|
+
sortBy: 'title'
|
20
|
+
})
|
21
|
+
formSchema:
|
22
|
+
title { type: 'string' }
|
23
|
+
body: { type: 'text' }
|
24
|
+
|
171
25
|
$ ->
|
172
|
-
|
173
|
-
|
174
|
-
|
26
|
+
config =
|
27
|
+
modules:
|
28
|
+
posts: postsConfig()
|
175
29
|
|
176
|
-
$('body').removeClass('loading')
|
177
30
|
chr.start(config)
|
178
|
-
|
179
|
-
# append signout button to the end of sidebar menu
|
180
|
-
$('a[data-method=delete]').appendTo(".sidebar .menu").show()
|
181
31
|
```
|
182
32
|
|
183
|
-
|
33
|
+
Styles setup:
|
184
34
|
|
185
35
|
```scss
|
186
|
-
@charset "utf-8";
|
187
|
-
|
188
36
|
@import "normalize-rails";
|
189
37
|
@import "chr";
|
190
|
-
@import "admin/signin";
|
191
38
|
```
|
192
39
|
|
193
|
-
Last import in the code above is optional. But here is a default source for it as well ```app/assets/stylesheets/admin/chr/_signin.scss```:
|
194
|
-
|
195
|
-
```scss
|
196
|
-
.sign-in {
|
197
|
-
font-size: 14px;
|
198
|
-
color: #555;
|
199
|
-
margin: 3em 0 0 3em;
|
200
40
|
|
201
|
-
|
202
|
-
text-transform: uppercase;
|
203
|
-
font-size: 1em;
|
204
|
-
font-size: 16px;
|
205
|
-
color: $black;
|
206
|
-
margin-bottom: 1.5em;
|
207
|
-
}
|
41
|
+
## Documentation
|
208
42
|
|
209
|
-
|
210
|
-
|
211
|
-
color: $positiveColor;
|
212
|
-
}
|
43
|
+
* [Start with Rails](docs/rails.md)
|
44
|
+
* [Bootstrap Data](docs/bootstrap.md)
|
213
45
|
|
214
|
-
|
215
|
-
max-width: 280px;
|
216
|
-
}
|
46
|
+
More documentation and samples comming soon...
|
217
47
|
|
218
|
-
.input {
|
219
|
-
margin-bottom: 1.5em;
|
220
|
-
}
|
221
48
|
|
222
|
-
|
223
|
-
float: right;
|
224
|
-
margin-top: -.45em;
|
225
|
-
padding: .25em .5em;
|
226
|
-
width: 13.5em;
|
227
|
-
}
|
49
|
+
## Character family:
|
228
50
|
|
229
|
-
|
230
|
-
|
231
|
-
}
|
232
|
-
|
233
|
-
.form-actions input {
|
234
|
-
width: 100%;
|
235
|
-
padding: 1em 2em;
|
236
|
-
background-color: $positiveColor;
|
237
|
-
border: 0;
|
238
|
-
color: $white;
|
239
|
-
}
|
240
|
-
}
|
241
|
-
```
|
242
|
-
|
243
|
-
**Third**: make sure admin assets are precompiled on production, include ```admin.js``` and ```admin.css``` in ```config/initializers/assets.rb```:
|
244
|
-
|
245
|
-
```ruby
|
246
|
-
Rails.application.config.assets.precompile += %w( admin.js admin.css )
|
247
|
-
```
|
248
|
-
|
249
|
-
At this point initial setup for admin app is finished and it could be accessed via: ```localhost:3000/admin```.
|
250
|
-
|
251
|
-
|
252
|
-
#### Add models
|
253
|
-
|
254
|
-
To be continued...
|
255
|
-
|
256
|
-
|
257
|
-
#### Bootstrap data
|
258
|
-
|
259
|
-
Bootstrapped data configuration example with disabled item updates and pagination:
|
260
|
-
|
261
|
-
```coffee
|
262
|
-
postsConfig = (data) ->
|
263
|
-
itemTitleField: 'title'
|
264
|
-
disableUpdateItems: true
|
265
|
-
objects: data.posts
|
266
|
-
arrayStore: new MongosteenArrayStore({
|
267
|
-
resource: 'post'
|
268
|
-
path: '/admin/posts'
|
269
|
-
sortBy: 'title'
|
270
|
-
pagination: false
|
271
|
-
})
|
272
|
-
formSchema:
|
273
|
-
title: { type: 'string' }
|
274
|
-
body: { type: 'text' }
|
275
|
-
```
|
276
|
-
|
277
|
-
```disableUpdateItems``` — do not update items in the list while navigation, ```objects``` — provides initial (bootstrapped) array of objects to be added to the list, ```pagination``` — disable pagination for list. If attached as modules root list, you can access store data with: ```chr.modules.posts.arrayStore.data()```.
|
278
|
-
|
279
|
-
|
280
|
-
## Character tastes better with:
|
281
|
-
|
282
|
-
- [Mongosteen](https://github.com/slate-studio/mongosteen): An easy way to add restful actions for Mongoid models
|
51
|
+
- [Character](https://github.com/slate-studio/chr): Powerful responsive javascript CMS for apps
|
52
|
+
- [Mongosteen](https://github.com/slate-studio/mongosteen): An easy way to add RESTful actions for Mongoid models
|
283
53
|
- [Inverter](https://github.com/slate-studio/inverter): An easy way to connect Rails templates content to Character CMS
|
284
54
|
- [Loft](https://github.com/slate-studio/loft): Media assets manager for Character CMS
|
285
55
|
|
@@ -293,4 +63,8 @@ Copyright © 2015 [Slate Studio, LLC](http://slatestudio.com). Character is free
|
|
293
63
|
|
294
64
|
[](http://slatestudio.com)
|
295
65
|
|
296
|
-
Character is maintained and funded by [Slate Studio, LLC](http://slatestudio.com). Tweet your questions or suggestions to [@slatestudio](https://twitter.com/slatestudio) and while you’re at it follow us too.
|
66
|
+
Character is maintained and funded by [Slate Studio, LLC](http://slatestudio.com). Tweet your questions or suggestions to [@slatestudio](https://twitter.com/slatestudio) and while you’re at it follow us too.
|
67
|
+
|
68
|
+
|
69
|
+
|
70
|
+
|
@@ -74,12 +74,12 @@ class @Item
|
|
74
74
|
|
75
75
|
# show view for a arrayStore item
|
76
76
|
if crumbs[crumbs.length - 2] == 'view'
|
77
|
-
return @module.showViewByObjectId(id, @config, title
|
77
|
+
return @module.showViewByObjectId(id, @config, title)
|
78
78
|
# show objectStore item view
|
79
79
|
if @config.objectStore
|
80
|
-
return @module.showViewByObjectId('', @config, title
|
80
|
+
return @module.showViewByObjectId('', @config, title)
|
81
81
|
# show nested list
|
82
|
-
@module.showNestedList(_last(crumbs)
|
82
|
+
@module.showNestedList(_last(crumbs))
|
83
83
|
|
84
84
|
|
85
85
|
# PUBLIC ================================================
|
@@ -149,35 +149,28 @@ class @List
|
|
149
149
|
@module.destroyView()
|
150
150
|
|
151
151
|
if @showWithParent
|
152
|
-
@hide(
|
152
|
+
@hide()
|
153
153
|
else
|
154
|
-
@module.hideActiveList(
|
154
|
+
@module.hideActiveList()
|
155
155
|
|
156
156
|
|
157
157
|
_new: (e) ->
|
158
158
|
chr.updateHash($(e.currentTarget).attr('href'), true)
|
159
|
-
@module.showView(null, @config, 'New'
|
159
|
+
@module.showView(null, @config, 'New')
|
160
160
|
|
161
161
|
|
162
162
|
# PUBLIC ================================================
|
163
163
|
|
164
|
-
hide:
|
165
|
-
|
164
|
+
hide: ->
|
165
|
+
@$el.hide()
|
166
166
|
|
167
167
|
|
168
|
-
show: (
|
169
|
-
|
168
|
+
show: (callback) ->
|
169
|
+
@$el.show 0, =>
|
170
170
|
@$items.scrollTop(0)
|
171
171
|
@config.onListShow?(@)
|
172
172
|
callback?()
|
173
173
|
|
174
|
-
if animate
|
175
|
-
# z-index workaround to remove blink effect
|
176
|
-
@$el.css({ 'z-index': 1, 'box-shadow': 'none' })
|
177
|
-
@$el.fadeIn $.fx.speeds._default, => @$el.css({ 'z-index': '', 'box-shadow': '' }) ; onShow()
|
178
|
-
else
|
179
|
-
@$el.show() ; onShow()
|
180
|
-
|
181
174
|
|
182
175
|
updateItems: ->
|
183
176
|
if not @config.disableUpdateItems
|
@@ -16,16 +16,16 @@
|
|
16
16
|
#
|
17
17
|
# Public methods:
|
18
18
|
# addNestedList (listName, config, parentList)
|
19
|
-
# showNestedList (listName
|
19
|
+
# showNestedList (listName)
|
20
20
|
# hideNestedLists (exceptList)
|
21
21
|
# visibleNestedListShownWithParent ()
|
22
|
-
# showRootList()
|
23
|
-
# hideActiveList (
|
24
|
-
# showView (object, config, title
|
25
|
-
# showViewByObjectId (objectId, config, title
|
22
|
+
# showRootList ()
|
23
|
+
# hideActiveList ()
|
24
|
+
# showView (object, config, title)
|
25
|
+
# showViewByObjectId (objectId, config, title)
|
26
26
|
# destroyView ()
|
27
27
|
# show ()
|
28
|
-
# hide (
|
28
|
+
# hide ()
|
29
29
|
#
|
30
30
|
# -----------------------------------------------------------------------------
|
31
31
|
class @Module
|
@@ -77,22 +77,21 @@ class @Module
|
|
77
77
|
@nestedLists[listName] = new List(this, listName, config, parentList)
|
78
78
|
|
79
79
|
|
80
|
-
# shows one of nested lists
|
81
|
-
showNestedList: (listName
|
80
|
+
# shows one of nested lists
|
81
|
+
showNestedList: (listName) ->
|
82
82
|
listToShow = @nestedLists[listName]
|
83
83
|
|
84
84
|
if listToShow.showWithParent
|
85
85
|
# list works as view, never becomes active
|
86
86
|
listToShow.updateItems()
|
87
|
-
listToShow.show
|
87
|
+
listToShow.show => @hideNestedLists(exceptList=listName)
|
88
88
|
|
89
89
|
else
|
90
90
|
@activeList = listToShow
|
91
91
|
@_update_active_list_items()
|
92
|
-
@activeList.show(
|
92
|
+
@activeList.show()
|
93
93
|
|
94
|
-
|
95
|
-
if animate and @view then @view.$el.fadeOut $.fx.speeds._default, => @destroyView()
|
94
|
+
@destroyView()
|
96
95
|
|
97
96
|
|
98
97
|
hideNestedLists: (exceptList) ->
|
@@ -105,28 +104,28 @@ class @Module
|
|
105
104
|
if list.isVisible() && list.showWithParent then return list
|
106
105
|
|
107
106
|
|
108
|
-
showRootList:
|
107
|
+
showRootList: ->
|
109
108
|
@destroyView()
|
110
109
|
while @activeList != @rootList
|
111
|
-
@hideActiveList(
|
110
|
+
@hideActiveList()
|
112
111
|
|
113
112
|
|
114
|
-
hideActiveList:
|
115
|
-
|
113
|
+
hideActiveList: ->
|
114
|
+
@activeList.$el.hide()
|
116
115
|
@activeList = @activeList.parentList
|
117
116
|
|
118
117
|
|
119
|
-
showView: (object, config, title
|
118
|
+
showView: (object, config, title) ->
|
120
119
|
newView = new View(this, config, @_view_path(), object, title)
|
121
120
|
@chr.$el.append(newView.$el)
|
122
121
|
|
123
|
-
newView.show
|
122
|
+
newView.show =>
|
124
123
|
@destroyView()
|
125
124
|
@view = newView
|
126
125
|
|
127
126
|
|
128
|
-
showViewByObjectId: (objectId, config, title
|
129
|
-
onSuccess = (object) => @showView(object, config, title
|
127
|
+
showViewByObjectId: (objectId, config, title) ->
|
128
|
+
onSuccess = (object) => @showView(object, config, title)
|
130
129
|
onError = -> chr.showError("can\'t show view for requested object")
|
131
130
|
|
132
131
|
if objectId == ''
|
@@ -142,19 +141,13 @@ class @Module
|
|
142
141
|
show: ->
|
143
142
|
@_update_active_list_items()
|
144
143
|
@$el.show()
|
145
|
-
@activeList.show(
|
144
|
+
@activeList.show()
|
146
145
|
|
147
146
|
|
148
|
-
hide:
|
147
|
+
hide: ->
|
149
148
|
@hideNestedLists()
|
150
|
-
|
151
|
-
|
152
|
-
# TODO: move animation to the view class
|
153
|
-
if @view then @view.$el.fadeOut $.fx.speeds._default, => @destroyView()
|
154
|
-
@$el.fadeOut()
|
155
|
-
else
|
156
|
-
@destroyView()
|
157
|
-
@$el.hide()
|
149
|
+
@destroyView()
|
150
|
+
@$el.hide()
|
158
151
|
|
159
152
|
|
160
153
|
|
@@ -16,11 +16,9 @@
|
|
16
16
|
# disableSave - do not add save button in header
|
17
17
|
# fullsizeView — use fullsize layout in desktop mode
|
18
18
|
# onViewShow - on show callback
|
19
|
-
# onShowAnimation - animation method to be used on show
|
20
|
-
# onCloseAnimation - animation method to be used on close
|
21
19
|
#
|
22
20
|
# public methods:
|
23
|
-
# show(
|
21
|
+
# show(callback)
|
24
22
|
# destroy()
|
25
23
|
#
|
26
24
|
# -----------------------------------------------------------------------------
|
@@ -34,12 +32,6 @@ class @View
|
|
34
32
|
if @config.fullsizeView
|
35
33
|
@$el.addClass 'fullsize'
|
36
34
|
|
37
|
-
# animations
|
38
|
-
@onShowAnimation = @config.onShowAnimation
|
39
|
-
@onCloseAnimation = @config.onCloseAnimation
|
40
|
-
@onShowAnimation ?= (callback) => @$el.fadeIn $.fx.speeds._default, -> callback()
|
41
|
-
@onCloseAnimation ?= (callback) => @$el.fadeOut $.fx.speeds._default, -> callback()
|
42
|
-
|
43
35
|
# header
|
44
36
|
@$header =$ "<header></header>"
|
45
37
|
@$title =$ "<div class='title'></div>"
|
@@ -96,7 +88,7 @@ class @View
|
|
96
88
|
# EVENTS ================================================
|
97
89
|
|
98
90
|
_close: (e) ->
|
99
|
-
@
|
91
|
+
@destroy()
|
100
92
|
|
101
93
|
|
102
94
|
_save: (e) ->
|
@@ -122,23 +114,18 @@ class @View
|
|
122
114
|
e.preventDefault()
|
123
115
|
if confirm("Are you sure?")
|
124
116
|
@store.remove @object._id,
|
125
|
-
onSuccess: =>
|
117
|
+
onSuccess: => chr.updateHash("#/#{ @closePath }", true) ; @destroy()
|
126
118
|
onError: -> chr.showError('Can\'t delete object.')
|
127
119
|
|
128
120
|
|
129
121
|
# PUBLIC ================================================
|
130
122
|
|
131
|
-
show: (
|
132
|
-
|
123
|
+
show: (callback) ->
|
124
|
+
@$el.show 0, =>
|
133
125
|
callback?()
|
134
126
|
@form.initializePlugins()
|
135
127
|
@config.onViewShow?(@)
|
136
128
|
|
137
|
-
if animate
|
138
|
-
@onShowAnimation(=> after_show())
|
139
|
-
else
|
140
|
-
@$el.show(0, => after_show())
|
141
|
-
|
142
129
|
|
143
130
|
destroy: ->
|
144
131
|
@form.destroy()
|
@@ -31,19 +31,16 @@
|
|
31
31
|
isFocused: false,
|
32
32
|
|
33
33
|
checkOffset: function () {
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
} else {
|
45
|
-
this.unfix();
|
46
|
-
}
|
34
|
+
var boxOffset = this.redactor.$box.offset();
|
35
|
+
|
36
|
+
var isBelowBoxTop = boxOffset.top - this.viewHeaderHeight <= 0;
|
37
|
+
//var isAboveBoxBottom = boxOffset.top + this.redactor.$box.outerHeight() - this.redactor.$toolbar.outerHeight() - this.$window.scrollTop() >= 0;
|
38
|
+
var isAboveBoxBottom = this.redactor.$box.outerHeight() + boxOffset.top - this.viewHeaderHeight - this.redactor.$toolbar.outerHeight() >= 0;
|
39
|
+
|
40
|
+
if (isBelowBoxTop && isAboveBoxBottom) {
|
41
|
+
this.fix();
|
42
|
+
} else {
|
43
|
+
this.unfix();
|
47
44
|
}
|
48
45
|
},
|
49
46
|
|
@@ -29,21 +29,16 @@ class @InputRedactor extends InputString
|
|
29
29
|
# PUBLIC ================================================
|
30
30
|
|
31
31
|
initialize: ->
|
32
|
+
plugins = [ 'fixedtoolbar' ]
|
33
|
+
if Loft then plugins.push('loft')
|
34
|
+
|
32
35
|
redactor_options =
|
33
36
|
focus: false
|
34
37
|
imageFloatMargin: '20px'
|
35
38
|
buttonSource: true
|
36
39
|
pastePlainText: true
|
37
|
-
plugins:
|
38
|
-
buttons:
|
39
|
-
'formatting',
|
40
|
-
'bold',
|
41
|
-
'italic',
|
42
|
-
'deleted',
|
43
|
-
'alignment',
|
44
|
-
'unorderedlist',
|
45
|
-
'orderedlist',
|
46
|
-
'link' ]
|
40
|
+
plugins: plugins
|
41
|
+
buttons: [ 'html', 'formatting', 'bold', 'italic', 'deleted', 'unorderedlist', 'orderedlist', 'link' ]
|
47
42
|
|
48
43
|
@config.redactorOptions ?= {}
|
49
44
|
$.extend(redactor_options, @config.redactorOptions)
|
data/bower.json
CHANGED
data/dist/chr.js
CHANGED
@@ -2762,7 +2762,7 @@ this.Chr = (function() {
|
|
2762
2762
|
crumbs = path.split('/');
|
2763
2763
|
if (this.module !== this.modules[crumbs[1]]) {
|
2764
2764
|
if ((ref = this.module) != null) {
|
2765
|
-
ref.hide(
|
2765
|
+
ref.hide();
|
2766
2766
|
}
|
2767
2767
|
}
|
2768
2768
|
this.module = this.modules[crumbs[1]];
|
@@ -2912,15 +2912,12 @@ this.Module = (function() {
|
|
2912
2912
|
return this.nestedLists[listName] = new List(this, listName, config, parentList);
|
2913
2913
|
};
|
2914
2914
|
|
2915
|
-
Module.prototype.showNestedList = function(listName
|
2915
|
+
Module.prototype.showNestedList = function(listName) {
|
2916
2916
|
var listToShow;
|
2917
|
-
if (animate == null) {
|
2918
|
-
animate = false;
|
2919
|
-
}
|
2920
2917
|
listToShow = this.nestedLists[listName];
|
2921
2918
|
if (listToShow.showWithParent) {
|
2922
2919
|
listToShow.updateItems();
|
2923
|
-
listToShow.show(
|
2920
|
+
listToShow.show((function(_this) {
|
2924
2921
|
return function() {
|
2925
2922
|
var exceptList;
|
2926
2923
|
return _this.hideNestedLists(exceptList = listName);
|
@@ -2929,15 +2926,9 @@ this.Module = (function() {
|
|
2929
2926
|
} else {
|
2930
2927
|
this.activeList = listToShow;
|
2931
2928
|
this._update_active_list_items();
|
2932
|
-
this.activeList.show(
|
2933
|
-
}
|
2934
|
-
if (animate && this.view) {
|
2935
|
-
return this.view.$el.fadeOut($.fx.speeds._default, (function(_this) {
|
2936
|
-
return function() {
|
2937
|
-
return _this.destroyView();
|
2938
|
-
};
|
2939
|
-
})(this));
|
2929
|
+
this.activeList.show();
|
2940
2930
|
}
|
2931
|
+
return this.destroyView();
|
2941
2932
|
};
|
2942
2933
|
|
2943
2934
|
Module.prototype.hideNestedLists = function(exceptList) {
|
@@ -2970,31 +2961,21 @@ this.Module = (function() {
|
|
2970
2961
|
this.destroyView();
|
2971
2962
|
results = [];
|
2972
2963
|
while (this.activeList !== this.rootList) {
|
2973
|
-
results.push(this.hideActiveList(
|
2964
|
+
results.push(this.hideActiveList());
|
2974
2965
|
}
|
2975
2966
|
return results;
|
2976
2967
|
};
|
2977
2968
|
|
2978
|
-
Module.prototype.hideActiveList = function(
|
2979
|
-
|
2980
|
-
animate = false;
|
2981
|
-
}
|
2982
|
-
if (animate) {
|
2983
|
-
this.activeList.$el.fadeOut();
|
2984
|
-
} else {
|
2985
|
-
this.activeList.$el.hide();
|
2986
|
-
}
|
2969
|
+
Module.prototype.hideActiveList = function() {
|
2970
|
+
this.activeList.$el.hide();
|
2987
2971
|
return this.activeList = this.activeList.parentList;
|
2988
2972
|
};
|
2989
2973
|
|
2990
|
-
Module.prototype.showView = function(object, config, title
|
2974
|
+
Module.prototype.showView = function(object, config, title) {
|
2991
2975
|
var newView;
|
2992
|
-
if (animate == null) {
|
2993
|
-
animate = false;
|
2994
|
-
}
|
2995
2976
|
newView = new View(this, config, this._view_path(), object, title);
|
2996
2977
|
this.chr.$el.append(newView.$el);
|
2997
|
-
return newView.show(
|
2978
|
+
return newView.show((function(_this) {
|
2998
2979
|
return function() {
|
2999
2980
|
_this.destroyView();
|
3000
2981
|
return _this.view = newView;
|
@@ -3002,14 +2983,11 @@ this.Module = (function() {
|
|
3002
2983
|
})(this));
|
3003
2984
|
};
|
3004
2985
|
|
3005
|
-
Module.prototype.showViewByObjectId = function(objectId, config, title
|
2986
|
+
Module.prototype.showViewByObjectId = function(objectId, config, title) {
|
3006
2987
|
var onError, onSuccess;
|
3007
|
-
if (animate == null) {
|
3008
|
-
animate = false;
|
3009
|
-
}
|
3010
2988
|
onSuccess = (function(_this) {
|
3011
2989
|
return function(object) {
|
3012
|
-
return _this.showView(object, config, title
|
2990
|
+
return _this.showView(object, config, title);
|
3013
2991
|
};
|
3014
2992
|
})(this);
|
3015
2993
|
onError = function() {
|
@@ -3036,27 +3014,13 @@ this.Module = (function() {
|
|
3036
3014
|
Module.prototype.show = function() {
|
3037
3015
|
this._update_active_list_items();
|
3038
3016
|
this.$el.show();
|
3039
|
-
return this.activeList.show(
|
3017
|
+
return this.activeList.show();
|
3040
3018
|
};
|
3041
3019
|
|
3042
|
-
Module.prototype.hide = function(
|
3043
|
-
if (animate == null) {
|
3044
|
-
animate = false;
|
3045
|
-
}
|
3020
|
+
Module.prototype.hide = function() {
|
3046
3021
|
this.hideNestedLists();
|
3047
|
-
|
3048
|
-
|
3049
|
-
this.view.$el.fadeOut($.fx.speeds._default, (function(_this) {
|
3050
|
-
return function() {
|
3051
|
-
return _this.destroyView();
|
3052
|
-
};
|
3053
|
-
})(this));
|
3054
|
-
}
|
3055
|
-
return this.$el.fadeOut();
|
3056
|
-
} else {
|
3057
|
-
this.destroyView();
|
3058
|
-
return this.$el.hide();
|
3059
|
-
}
|
3022
|
+
this.destroyView();
|
3023
|
+
return this.$el.hide();
|
3060
3024
|
};
|
3061
3025
|
|
3062
3026
|
return Module;
|
@@ -3392,31 +3356,23 @@ this.List = (function() {
|
|
3392
3356
|
this.module.chr.unsetActiveListItems();
|
3393
3357
|
this.module.destroyView();
|
3394
3358
|
if (this.showWithParent) {
|
3395
|
-
return this.hide(
|
3359
|
+
return this.hide();
|
3396
3360
|
} else {
|
3397
|
-
return this.module.hideActiveList(
|
3361
|
+
return this.module.hideActiveList();
|
3398
3362
|
}
|
3399
3363
|
};
|
3400
3364
|
|
3401
3365
|
List.prototype._new = function(e) {
|
3402
3366
|
chr.updateHash($(e.currentTarget).attr('href'), true);
|
3403
|
-
return this.module.showView(null, this.config, 'New'
|
3367
|
+
return this.module.showView(null, this.config, 'New');
|
3404
3368
|
};
|
3405
3369
|
|
3406
|
-
List.prototype.hide = function(
|
3407
|
-
|
3408
|
-
return this.$el.fadeOut();
|
3409
|
-
} else {
|
3410
|
-
return this.$el.hide();
|
3411
|
-
}
|
3370
|
+
List.prototype.hide = function() {
|
3371
|
+
return this.$el.hide();
|
3412
3372
|
};
|
3413
3373
|
|
3414
|
-
List.prototype.show = function(
|
3415
|
-
|
3416
|
-
if (animate == null) {
|
3417
|
-
animate = false;
|
3418
|
-
}
|
3419
|
-
onShow = (function(_this) {
|
3374
|
+
List.prototype.show = function(callback) {
|
3375
|
+
return this.$el.show(0, (function(_this) {
|
3420
3376
|
return function() {
|
3421
3377
|
var base;
|
3422
3378
|
_this.$items.scrollTop(0);
|
@@ -3425,25 +3381,7 @@ this.List = (function() {
|
|
3425
3381
|
}
|
3426
3382
|
return typeof callback === "function" ? callback() : void 0;
|
3427
3383
|
};
|
3428
|
-
})(this);
|
3429
|
-
if (animate) {
|
3430
|
-
this.$el.css({
|
3431
|
-
'z-index': 1,
|
3432
|
-
'box-shadow': 'none'
|
3433
|
-
});
|
3434
|
-
return this.$el.fadeIn($.fx.speeds._default, (function(_this) {
|
3435
|
-
return function() {
|
3436
|
-
_this.$el.css({
|
3437
|
-
'z-index': '',
|
3438
|
-
'box-shadow': ''
|
3439
|
-
});
|
3440
|
-
return onShow();
|
3441
|
-
};
|
3442
|
-
})(this));
|
3443
|
-
} else {
|
3444
|
-
this.$el.show();
|
3445
|
-
return onShow();
|
3446
|
-
}
|
3384
|
+
})(this));
|
3447
3385
|
};
|
3448
3386
|
|
3449
3387
|
List.prototype.updateItems = function() {
|
@@ -3548,12 +3486,12 @@ this.Item = (function() {
|
|
3548
3486
|
id = $(e.currentTarget).attr('data-id');
|
3549
3487
|
chr.updateHash(hash, true);
|
3550
3488
|
if (crumbs[crumbs.length - 2] === 'view') {
|
3551
|
-
return this.module.showViewByObjectId(id, this.config, title
|
3489
|
+
return this.module.showViewByObjectId(id, this.config, title);
|
3552
3490
|
}
|
3553
3491
|
if (this.config.objectStore) {
|
3554
|
-
return this.module.showViewByObjectId('', this.config, title
|
3492
|
+
return this.module.showViewByObjectId('', this.config, title);
|
3555
3493
|
}
|
3556
|
-
return this.module.showNestedList(_last(crumbs)
|
3494
|
+
return this.module.showNestedList(_last(crumbs));
|
3557
3495
|
};
|
3558
3496
|
|
3559
3497
|
Item.prototype.render = function() {
|
@@ -3599,26 +3537,6 @@ this.View = (function() {
|
|
3599
3537
|
if (this.config.fullsizeView) {
|
3600
3538
|
this.$el.addClass('fullsize');
|
3601
3539
|
}
|
3602
|
-
this.onShowAnimation = this.config.onShowAnimation;
|
3603
|
-
this.onCloseAnimation = this.config.onCloseAnimation;
|
3604
|
-
if (this.onShowAnimation == null) {
|
3605
|
-
this.onShowAnimation = (function(_this) {
|
3606
|
-
return function(callback) {
|
3607
|
-
return _this.$el.fadeIn($.fx.speeds._default, function() {
|
3608
|
-
return callback();
|
3609
|
-
});
|
3610
|
-
};
|
3611
|
-
})(this);
|
3612
|
-
}
|
3613
|
-
if (this.onCloseAnimation == null) {
|
3614
|
-
this.onCloseAnimation = (function(_this) {
|
3615
|
-
return function(callback) {
|
3616
|
-
return _this.$el.fadeOut($.fx.speeds._default, function() {
|
3617
|
-
return callback();
|
3618
|
-
});
|
3619
|
-
};
|
3620
|
-
})(this);
|
3621
|
-
}
|
3622
3540
|
this.$header = $("<header></header>");
|
3623
3541
|
this.$title = $("<div class='title'></div>");
|
3624
3542
|
this.$header.append(this.$title);
|
@@ -3690,11 +3608,7 @@ this.View = (function() {
|
|
3690
3608
|
};
|
3691
3609
|
|
3692
3610
|
View.prototype._close = function(e) {
|
3693
|
-
return this.
|
3694
|
-
return function() {
|
3695
|
-
return _this.destroy();
|
3696
|
-
};
|
3697
|
-
})(this));
|
3611
|
+
return this.destroy();
|
3698
3612
|
};
|
3699
3613
|
|
3700
3614
|
View.prototype._save = function(e) {
|
@@ -3741,10 +3655,8 @@ this.View = (function() {
|
|
3741
3655
|
return this.store.remove(this.object._id, {
|
3742
3656
|
onSuccess: (function(_this) {
|
3743
3657
|
return function() {
|
3744
|
-
|
3745
|
-
|
3746
|
-
return _this.destroy();
|
3747
|
-
});
|
3658
|
+
chr.updateHash("#/" + _this.closePath, true);
|
3659
|
+
return _this.destroy();
|
3748
3660
|
};
|
3749
3661
|
})(this),
|
3750
3662
|
onError: function() {
|
@@ -3754,9 +3666,8 @@ this.View = (function() {
|
|
3754
3666
|
}
|
3755
3667
|
};
|
3756
3668
|
|
3757
|
-
View.prototype.show = function(
|
3758
|
-
|
3759
|
-
after_show = (function(_this) {
|
3669
|
+
View.prototype.show = function(callback) {
|
3670
|
+
return this.$el.show(0, (function(_this) {
|
3760
3671
|
return function() {
|
3761
3672
|
var base;
|
3762
3673
|
if (typeof callback === "function") {
|
@@ -3765,20 +3676,7 @@ this.View = (function() {
|
|
3765
3676
|
_this.form.initializePlugins();
|
3766
3677
|
return typeof (base = _this.config).onViewShow === "function" ? base.onViewShow(_this) : void 0;
|
3767
3678
|
};
|
3768
|
-
})(this);
|
3769
|
-
if (animate) {
|
3770
|
-
return this.onShowAnimation((function(_this) {
|
3771
|
-
return function() {
|
3772
|
-
return after_show();
|
3773
|
-
};
|
3774
|
-
})(this));
|
3775
|
-
} else {
|
3776
|
-
return this.$el.show(0, (function(_this) {
|
3777
|
-
return function() {
|
3778
|
-
return after_show();
|
3779
|
-
};
|
3780
|
-
})(this));
|
3781
|
-
}
|
3679
|
+
})(this));
|
3782
3680
|
};
|
3783
3681
|
|
3784
3682
|
View.prototype.destroy = function() {
|
data/dist/input-redactor.js
CHANGED
@@ -31,19 +31,16 @@
|
|
31
31
|
isFocused: false,
|
32
32
|
|
33
33
|
checkOffset: function () {
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
} else {
|
45
|
-
this.unfix();
|
46
|
-
}
|
34
|
+
var boxOffset = this.redactor.$box.offset();
|
35
|
+
|
36
|
+
var isBelowBoxTop = boxOffset.top - this.viewHeaderHeight <= 0;
|
37
|
+
//var isAboveBoxBottom = boxOffset.top + this.redactor.$box.outerHeight() - this.redactor.$toolbar.outerHeight() - this.$window.scrollTop() >= 0;
|
38
|
+
var isAboveBoxBottom = this.redactor.$box.outerHeight() + boxOffset.top - this.viewHeaderHeight - this.redactor.$toolbar.outerHeight() >= 0;
|
39
|
+
|
40
|
+
if (isBelowBoxTop && isAboveBoxBottom) {
|
41
|
+
this.fix();
|
42
|
+
} else {
|
43
|
+
this.unfix();
|
47
44
|
}
|
48
45
|
},
|
49
46
|
|
@@ -126,14 +123,18 @@ this.InputRedactor = (function(superClass) {
|
|
126
123
|
};
|
127
124
|
|
128
125
|
InputRedactor.prototype.initialize = function() {
|
129
|
-
var base, base1, redactor_options;
|
126
|
+
var base, base1, plugins, redactor_options;
|
127
|
+
plugins = ['fixedtoolbar'];
|
128
|
+
if (Loft) {
|
129
|
+
plugins.push('loft');
|
130
|
+
}
|
130
131
|
redactor_options = {
|
131
132
|
focus: false,
|
132
133
|
imageFloatMargin: '20px',
|
133
134
|
buttonSource: true,
|
134
135
|
pastePlainText: true,
|
135
|
-
plugins:
|
136
|
-
buttons: ['html', 'formatting', 'bold', 'italic', 'deleted', '
|
136
|
+
plugins: plugins,
|
137
|
+
buttons: ['html', 'formatting', 'bold', 'italic', 'deleted', 'unorderedlist', 'orderedlist', 'link']
|
137
138
|
};
|
138
139
|
if ((base = this.config).redactorOptions == null) {
|
139
140
|
base.redactorOptions = {};
|
data/docs/bootstrap.md
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# Character
|
2
|
+
|
3
|
+
## Bootstrap Data
|
4
|
+
|
5
|
+
Bootstrapped data configuration example with disabled item updates and pagination:
|
6
|
+
|
7
|
+
```coffee
|
8
|
+
postsConfig = (data) ->
|
9
|
+
itemTitleField: 'title'
|
10
|
+
disableUpdateItems: true
|
11
|
+
objects: data.posts
|
12
|
+
arrayStore: new MongosteenArrayStore({
|
13
|
+
resource: 'post'
|
14
|
+
path: '/admin/posts'
|
15
|
+
sortBy: 'title'
|
16
|
+
pagination: false
|
17
|
+
})
|
18
|
+
formSchema:
|
19
|
+
title: { type: 'string' }
|
20
|
+
body: { type: 'text' }
|
21
|
+
```
|
22
|
+
|
23
|
+
```disableUpdateItems``` — do not update items in the list while navigation, ```objects``` — provides initial (bootstrapped) array of objects to be added to the list, ```pagination``` — disable pagination for list. If attached as modules root list, you can access store data with: ```chr.modules.posts.arrayStore.data()```.
|
data/docs/rails.md
ADDED
@@ -0,0 +1,255 @@
|
|
1
|
+
# Character
|
2
|
+
|
3
|
+
## Rails
|
4
|
+
|
5
|
+
An example of admin implementation setup for [Rails](https://github.com/rails/rails) app that uses [Mongoid](https://github.com/mongoid/mongoid) stack.
|
6
|
+
|
7
|
+
|
8
|
+
### Gems
|
9
|
+
|
10
|
+
Add to following gems to ```Gemfile```:
|
11
|
+
|
12
|
+
gem "devise"
|
13
|
+
gem "mongosteen"
|
14
|
+
gem "chr"
|
15
|
+
|
16
|
+
This example uses ```devise``` for admins authentication.
|
17
|
+
|
18
|
+
|
19
|
+
### Admin authentication
|
20
|
+
|
21
|
+
Start with running [devise](https://github.com/plataformatec/devise) generator:
|
22
|
+
|
23
|
+
rails generate devise:install
|
24
|
+
|
25
|
+
Setup ```Admin``` model with devise generator:
|
26
|
+
|
27
|
+
rails generate devise admin
|
28
|
+
|
29
|
+
Here is an example of basic ```app/models/admin.rb``` model that provides email/password authentication:
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
class Admin
|
33
|
+
include Mongoid::Document
|
34
|
+
include Mongoid::Timestamps
|
35
|
+
include Mongoid::SerializableId
|
36
|
+
|
37
|
+
devise :database_authenticatable,
|
38
|
+
:rememberable,
|
39
|
+
:authentication_keys => [ :email ]
|
40
|
+
|
41
|
+
## Database authenticatable
|
42
|
+
field :email, type: String, default: ""
|
43
|
+
field :encrypted_password, type: String, default: ""
|
44
|
+
|
45
|
+
## Rememberable
|
46
|
+
field :remember_created_at, type: Time
|
47
|
+
end
|
48
|
+
```
|
49
|
+
|
50
|
+
When models are ready, setup controllers, views and configure routes.
|
51
|
+
|
52
|
+
Base admin controller ```app/controllers/admin/base_controller.rb``` looks like this:
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
class Admin::BaseController < ActionController::Base
|
56
|
+
protect_from_forgery
|
57
|
+
|
58
|
+
if Rails.env.production?
|
59
|
+
before_action :authenticate_admin!
|
60
|
+
end
|
61
|
+
|
62
|
+
def index
|
63
|
+
render '/admin/index', layout: 'admin'
|
64
|
+
end
|
65
|
+
|
66
|
+
def bootstrap_data
|
67
|
+
render json: {}
|
68
|
+
end
|
69
|
+
end
|
70
|
+
```
|
71
|
+
|
72
|
+
Notes on code above:
|
73
|
+
|
74
|
+
1. Authentication is not required when running in development or testing environment;
|
75
|
+
2. Need to setup ```index``` view and ```admin``` layout to render admin app;
|
76
|
+
3. ```bootstrap_data``` is a placeholder for objects that might be required to be loaded when app starts.
|
77
|
+
|
78
|
+
Devise would require a custom ```SessionController``` implementation in ```app/controllers/admin/devise_overrides/session_controller.rb```. ```SessionController``` sets ```admin``` layout to be used for devise views rendering and enables login by email (*looks like workaround*).
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
class Admin::DeviseOverrides::SessionsController < Devise::SessionsController
|
82
|
+
layout 'admin'
|
83
|
+
|
84
|
+
protected
|
85
|
+
|
86
|
+
def configure_permitted_parameters
|
87
|
+
devise_parameter_sanitizer.for(:sign_in) << :email
|
88
|
+
end
|
89
|
+
end
|
90
|
+
```
|
91
|
+
|
92
|
+
Admin app layout ```app/views/layouts/admin.html.erb```:
|
93
|
+
|
94
|
+
```erb
|
95
|
+
<!doctype html>
|
96
|
+
<html>
|
97
|
+
<head>
|
98
|
+
<meta charset="utf-8">
|
99
|
+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
100
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
|
101
|
+
<meta name="apple-mobile-web-app-capable" content="yes">
|
102
|
+
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
103
|
+
<title>Admin</title>
|
104
|
+
<%= csrf_meta_tags %>
|
105
|
+
<%= stylesheet_link_tag :admin, media: "all" %>
|
106
|
+
<%= javascript_include_tag :admin %>
|
107
|
+
</head>
|
108
|
+
|
109
|
+
<%= yield %>
|
110
|
+
</html>
|
111
|
+
```
|
112
|
+
|
113
|
+
Admin index view ```app/views/admin/index.html.erb```:
|
114
|
+
|
115
|
+
```erb
|
116
|
+
<body class='loading'>
|
117
|
+
<%= link_to 'Sign Out', destroy_admin_session_path, method: :delete, style: 'display:none;' %>
|
118
|
+
</body>
|
119
|
+
```
|
120
|
+
|
121
|
+
New session view for devise ```app/views/admin/devise_overrides/sessions/new.html.erb```:
|
122
|
+
|
123
|
+
```erb
|
124
|
+
<body class='sign-in'>
|
125
|
+
<h2>Sign In</h2>
|
126
|
+
|
127
|
+
<%= simple_form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
|
128
|
+
<% if alert %>
|
129
|
+
<p class="error"><%= alert.gsub('username', 'email').gsub('or sign up', '') %></p>
|
130
|
+
<% end %>
|
131
|
+
|
132
|
+
<div class="form-inputs">
|
133
|
+
<%= f.input :email, required: true, autofocus: true %>
|
134
|
+
<%= f.input :password, required: true %>
|
135
|
+
|
136
|
+
<%= f.input :remember_me, as: :boolean if devise_mapping.rememberable? %>
|
137
|
+
</div>
|
138
|
+
|
139
|
+
<div class="form-actions">
|
140
|
+
<%= f.button :submit, "Sign In" %>
|
141
|
+
</div>
|
142
|
+
<% end %>
|
143
|
+
</body>
|
144
|
+
```
|
145
|
+
|
146
|
+
Now connect admin and devise in ```config/routes.rb``` with:
|
147
|
+
|
148
|
+
```ruby
|
149
|
+
devise_for :admins, path: "admin", controllers: { sessions: "admin/devise_overrides/sessions" }
|
150
|
+
namespace :admin do
|
151
|
+
get '/' => 'base#index'
|
152
|
+
get '/bootstrap.json' => 'base#bootstrap_data'
|
153
|
+
end
|
154
|
+
```
|
155
|
+
|
156
|
+
|
157
|
+
### Character setup
|
158
|
+
|
159
|
+
Three pieces to be configured here.
|
160
|
+
|
161
|
+
**First**: create ```app/assets/javascripts/admin.coffee``` with empty ```modules``` configuration:
|
162
|
+
|
163
|
+
```coffee
|
164
|
+
#= require jquery
|
165
|
+
#= require jquery_ujs
|
166
|
+
#= require chr
|
167
|
+
|
168
|
+
$ ->
|
169
|
+
$.get '/admin/bootstrap.json', (response) ->
|
170
|
+
config =
|
171
|
+
modules: {}
|
172
|
+
|
173
|
+
$('body').removeClass('loading')
|
174
|
+
chr.start(config)
|
175
|
+
|
176
|
+
# append signout button to the end of sidebar menu
|
177
|
+
$('a[data-method=delete]').appendTo(".sidebar .menu").show()
|
178
|
+
```
|
179
|
+
|
180
|
+
**Second**: create foundation for style customization in ```app/assets/stylesheets/admin.scss```:
|
181
|
+
|
182
|
+
```scss
|
183
|
+
@charset "utf-8";
|
184
|
+
|
185
|
+
@import "normalize-rails";
|
186
|
+
@import "chr";
|
187
|
+
@import "admin/signin";
|
188
|
+
```
|
189
|
+
|
190
|
+
Last import in the code above is optional. But here is a default source for it as well ```app/assets/stylesheets/admin/chr/_signin.scss```:
|
191
|
+
|
192
|
+
```scss
|
193
|
+
.sign-in {
|
194
|
+
font-size: 14px;
|
195
|
+
color: #555;
|
196
|
+
margin: 3em 0 0 3em;
|
197
|
+
|
198
|
+
h2 {
|
199
|
+
text-transform: uppercase;
|
200
|
+
font-size: 1em;
|
201
|
+
font-size: 16px;
|
202
|
+
color: $black;
|
203
|
+
margin-bottom: 1.5em;
|
204
|
+
}
|
205
|
+
|
206
|
+
p {
|
207
|
+
margin: -1.5em 0 2em;
|
208
|
+
color: $positiveColor;
|
209
|
+
}
|
210
|
+
|
211
|
+
.form-actions, .form-inputs {
|
212
|
+
max-width: 280px;
|
213
|
+
}
|
214
|
+
|
215
|
+
.input {
|
216
|
+
margin-bottom: 1.5em;
|
217
|
+
}
|
218
|
+
|
219
|
+
input.string, input.password {
|
220
|
+
float: right;
|
221
|
+
margin-top: -.45em;
|
222
|
+
padding: .25em .5em;
|
223
|
+
width: 13.5em;
|
224
|
+
}
|
225
|
+
|
226
|
+
label.boolean input {
|
227
|
+
margin-right: .25em;
|
228
|
+
}
|
229
|
+
|
230
|
+
.form-actions input {
|
231
|
+
width: 100%;
|
232
|
+
padding: 1em 2em;
|
233
|
+
background-color: $positiveColor;
|
234
|
+
border: 0;
|
235
|
+
color: $white;
|
236
|
+
}
|
237
|
+
}
|
238
|
+
```
|
239
|
+
|
240
|
+
**Third**: make sure admin assets are precompiled on production, include ```admin.js``` and ```admin.css``` in ```config/initializers/assets.rb```:
|
241
|
+
|
242
|
+
```ruby
|
243
|
+
Rails.application.config.assets.precompile += %w( admin.js admin.css )
|
244
|
+
```
|
245
|
+
|
246
|
+
At this point initial setup for admin app is finished and it could be accessed via: ```localhost:3000/admin```.
|
247
|
+
|
248
|
+
|
249
|
+
### Add models
|
250
|
+
|
251
|
+
To be continued...
|
252
|
+
|
253
|
+
|
254
|
+
|
255
|
+
|
data/lib/chr/version.rb
CHANGED
data/package.json
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: chr
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alexander Kravets
|
@@ -133,11 +133,12 @@ files:
|
|
133
133
|
- dist/input-redactor.js
|
134
134
|
- docs/assets.md
|
135
135
|
- docs/basics.md
|
136
|
-
- docs/bootstrap
|
136
|
+
- docs/bootstrap.md
|
137
137
|
- docs/custom-inputs.md
|
138
138
|
- docs/form.md
|
139
139
|
- docs/internals.md
|
140
140
|
- docs/nested-forms.md
|
141
|
+
- docs/rails.md
|
141
142
|
- docs/redactor-js.md
|
142
143
|
- docs/scopes.md
|
143
144
|
- lib/chr.rb
|
data/docs/bootstrap-data.md
DELETED
File without changes
|