chr 0.2.4 → 0.2.5
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Slate Studio](https://slate-git-images.s3-us-west-1.amazonaws.com/slate.png)](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
|