tomify 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +19 -0
- data/Rakefile +6 -0
- data/app/assets/images/tomify/gifs/basketball.gif +0 -0
- data/app/assets/images/tomify/gifs/laughing.gif +0 -0
- data/app/assets/images/tomify/gifs/sneer.gif +0 -0
- data/app/assets/images/tomify/gifs/wind.gif +0 -0
- data/app/assets/images/tomify/gifs/woods.gif +0 -0
- data/app/assets/javascripts/tomify/default/array.coffee +4 -0
- data/app/assets/javascripts/tomify/default/jquery.patch.coffee +9 -0
- data/app/assets/javascripts/tomify/default/object.coffee +4 -0
- data/app/assets/javascripts/tomify/default/string.coffee +40 -0
- data/app/assets/javascripts/tomify/dynamic/classes/component.coffee +7 -0
- data/app/assets/javascripts/tomify/dynamic/classes/field.coffee +24 -0
- data/app/assets/javascripts/tomify/dynamic/classes/form.coffee +72 -0
- data/app/assets/javascripts/tomify/dynamic/classes/model.coffee +41 -0
- data/app/assets/javascripts/tomify/dynamic/classes/namespace.coffee +24 -0
- data/app/assets/javascripts/tomify/dynamic/classes/observer.coffee +22 -0
- data/app/assets/javascripts/tomify/dynamic/classes/request.coffee +26 -0
- data/app/assets/javascripts/tomify/dynamic/classes/store.coffee +37 -0
- data/app/assets/javascripts/tomify/dynamic/global.coffee +16 -0
- data/app/assets/javascripts/tomify/dynamic/react/components/admin/pages.coffee.erb +30 -0
- data/app/assets/javascripts/tomify/dynamic/react/components/admin/settings.coffee +27 -0
- data/app/assets/javascripts/tomify/dynamic/react/components/admin/sidebars.coffee +16 -0
- data/app/assets/javascripts/tomify/dynamic/react/components/admin/uploads.coffee +13 -0
- data/app/assets/javascripts/tomify/dynamic/react/components/admin/users.coffee +18 -0
- data/app/assets/javascripts/tomify/dynamic/react/components/edit.coffee +51 -0
- data/app/assets/javascripts/tomify/dynamic/react/components/fields/checkbox.coffee +8 -0
- data/app/assets/javascripts/tomify/dynamic/react/components/fields/default.coffee +3 -0
- data/app/assets/javascripts/tomify/dynamic/react/components/fields/file.coffee +3 -0
- data/app/assets/javascripts/tomify/dynamic/react/components/fields/markdown.coffee +9 -0
- data/app/assets/javascripts/tomify/dynamic/react/components/fields/select.coffee +8 -0
- data/app/assets/javascripts/tomify/dynamic/react/components/fields/textarea.coffee +3 -0
- data/app/assets/javascripts/tomify/dynamic/react/components/index.coffee +87 -0
- data/app/assets/javascripts/tomify/dynamic/react/components/layout/admin_navbar.coffee +29 -0
- data/app/assets/javascripts/tomify/dynamic/react/components/layout/analytics.coffee +34 -0
- data/app/assets/javascripts/tomify/dynamic/react/components/layout/footer.coffee +11 -0
- data/app/assets/javascripts/tomify/dynamic/react/components/layout/header.coffee +28 -0
- data/app/assets/javascripts/tomify/dynamic/react/components/layout/messages.coffee +18 -0
- data/app/assets/javascripts/tomify/dynamic/react/components/layout/navbar.coffee +6 -0
- data/app/assets/javascripts/tomify/dynamic/react/components/layout/public_navbar.coffee +56 -0
- data/app/assets/javascripts/tomify/dynamic/react/components/new.coffee +48 -0
- data/app/assets/javascripts/tomify/dynamic/react/components/pagination.coffee +29 -0
- data/app/assets/javascripts/tomify/dynamic/react/components/public/passwords/new.coffee +38 -0
- data/app/assets/javascripts/tomify/dynamic/react/components/public/sessions/new.coffee +40 -0
- data/app/assets/javascripts/tomify/dynamic/react/components/public/sessions/show.coffee +35 -0
- data/app/assets/javascripts/tomify/dynamic/react/components/public/users/edit.coffee +42 -0
- data/app/assets/javascripts/tomify/dynamic/react/components/public/users/new.coffee +35 -0
- data/app/assets/javascripts/tomify/dynamic/react/mixins/follow.coffee +51 -0
- data/app/assets/javascripts/tomify/dynamic/react/mixins/will-initialize.coffee +7 -0
- data/app/assets/javascripts/tomify/dynamic/react/mount.coffee +9 -0
- data/app/assets/javascripts/tomify/turbolinks/compatibility.coffee +29 -0
- data/app/assets/javascripts/tomify/turbolinks/react.coffee +5 -0
- data/app/assets/javascripts/tomify-dynamic.js +8 -0
- data/app/assets/javascripts/tomify.js +1 -0
- data/app/assets/stylesheets/_tomify.scss +1 -0
- data/app/assets/stylesheets/tomify/_default.scss +26 -0
- data/app/assets/stylesheets/tomify/_footer.scss +1 -0
- data/app/assets/stylesheets/tomify/_navbar.scss +43 -0
- data/app/assets/stylesheets/tomify/_page.scss +31 -0
- data/app/assets/stylesheets/tomify/_pagination.scss +5 -0
- data/app/assets/stylesheets/tomify/_variables.scss +10 -0
- data/app/controllers/tomify/admin/controller.rb +7 -0
- data/app/controllers/tomify/admin/pages_controller.rb +2 -0
- data/app/controllers/tomify/admin/settings_controller.rb +2 -0
- data/app/controllers/tomify/admin/sidebars_controller.rb +2 -0
- data/app/controllers/tomify/admin/uploads_controller.rb +2 -0
- data/app/controllers/tomify/admin/users_controller.rb +2 -0
- data/app/controllers/tomify/api/admin/controller.rb +3 -0
- data/app/controllers/tomify/api/admin/pages_controller.rb +2 -0
- data/app/controllers/tomify/api/admin/settings_controller.rb +2 -0
- data/app/controllers/tomify/api/admin/sidebars_controller.rb +2 -0
- data/app/controllers/tomify/api/admin/uploads_controller.rb +2 -0
- data/app/controllers/tomify/api/admin/users_controller.rb +7 -0
- data/app/controllers/tomify/api/public/controller.rb +3 -0
- data/app/controllers/tomify/api/public/passwords_controller.rb +10 -0
- data/app/controllers/tomify/api/public/sessions_controller.rb +19 -0
- data/app/controllers/tomify/api/public/users_controller.rb +25 -0
- data/app/controllers/tomify/concerns/api/admin.rb +9 -0
- data/app/controllers/tomify/concerns/api/helpers.rb +57 -0
- data/app/controllers/tomify/concerns/api/json.rb +46 -0
- data/app/controllers/tomify/concerns/api/public.rb +9 -0
- data/app/controllers/tomify/concerns/default/auth_helper.rb +22 -0
- data/app/controllers/tomify/concerns/default/env_helper.rb +34 -0
- data/app/controllers/tomify/concerns/default/helper.rb +13 -0
- data/app/controllers/tomify/concerns/default/react_helper.rb +7 -0
- data/app/controllers/tomify/concerns/default.rb +17 -0
- data/app/controllers/tomify/public/controller.rb +2 -0
- data/app/controllers/tomify/public/pages_controller.rb +22 -0
- data/app/controllers/tomify/public/sessions_controller.rb +5 -0
- data/app/controllers/tomify/public/users_controller.rb +7 -0
- data/app/controllers/tomify_controller.rb +3 -0
- data/app/helpers/tomify/carrierwave_helper.rb +17 -0
- data/app/helpers/tomify/email_helper.rb +19 -0
- data/app/helpers/tomify/render_helper.rb +13 -0
- data/app/helpers/tomify/timezone_helper.rb +5 -0
- data/app/mailers/tomify/user_mailer.rb +13 -0
- data/app/mailers/tomify_mailer.rb +14 -0
- data/app/models/tomify/concerns/page.rb +74 -0
- data/app/models/tomify/concerns/sidebar.rb +18 -0
- data/app/models/tomify/concerns/token.rb +18 -0
- data/app/models/tomify/concerns/upload.rb +36 -0
- data/app/models/tomify/concerns/user.rb +52 -0
- data/app/models/tomify/page.rb +3 -0
- data/app/models/tomify/setting/boolean.rb +11 -0
- data/app/models/tomify/setting/json.rb +11 -0
- data/app/models/tomify/setting/text.rb +3 -0
- data/app/models/tomify/setting/uploader.rb +6 -0
- data/app/models/tomify/setting.rb +48 -0
- data/app/models/tomify/sidebar.rb +3 -0
- data/app/models/tomify/token.rb +3 -0
- data/app/models/tomify/upload.rb +3 -0
- data/app/models/tomify/user.rb +3 -0
- data/app/models/tomify_record.rb +33 -0
- data/app/uploaders/tomify/file_uploader.rb +5 -0
- data/app/uploaders/tomify/setting_uploader.rb +7 -0
- data/app/uploaders/tomify_uploader.rb +20 -0
- data/app/views/templates/contact.haml +24 -0
- data/app/views/templates/default.haml +4 -0
- data/app/views/tomify/defaults/_body.haml +8 -0
- data/app/views/tomify/defaults/_container.haml +3 -0
- data/app/views/tomify/defaults/_env.haml +2 -0
- data/app/views/tomify/defaults/_head.haml +14 -0
- data/app/views/tomify/defaults/_meta.haml +17 -0
- data/app/views/tomify/defaults/_sharing.haml +23 -0
- data/app/views/tomify/layouts/application.haml +4 -0
- data/app/views/tomify/layouts/mailer.haml +7 -0
- data/app/views/tomify/mailers/user_mailer/invite.haml +13 -0
- data/app/views/tomify/mailers/user_mailer/reset_password.haml +17 -0
- data/config/initializers/assets.rb +7 -0
- data/config/initializers/database.rb +3 -0
- data/config/initializers/flash_patch.rb +15 -0
- data/config/initializers/mailer.rb +4 -0
- data/config/initializers/renderers.rb +4 -0
- data/config/initializers/settings.rb +5 -0
- data/config/routes.rb +38 -0
- data/db/migrate/19900000000000_create_users.rb +13 -0
- data/db/migrate/19900000000001_create_tokens.rb +10 -0
- data/db/migrate/19900000000002_create_settings.rb +13 -0
- data/db/migrate/19900000000003_create_uploads.rb +13 -0
- data/db/migrate/19900000000004_create_pages.rb +22 -0
- data/db/migrate/19900000000005_create_sidebars.rb +12 -0
- data/db/seeds.rb +43 -0
- data/lib/generators/tomify/bundle/bundle_generator.rb +12 -0
- data/lib/generators/tomify/bundle/templates/default.js +2 -0
- data/lib/generators/tomify/bundle/templates/react.js +3 -0
- data/lib/previews/tomify/user_preview.rb +9 -0
- data/lib/previews/tomify_preview.rb +6 -0
- data/lib/tasks/package.rake +40 -0
- data/lib/tomify/constantly.rb +12 -0
- data/lib/tomify/engine.rb +11 -0
- data/lib/tomify/version.rb +3 -0
- data/lib/tomify.rb +40 -0
- data/vendor/tomify/development/react.js +20494 -0
- data/vendor/tomify/production/react.js +20352 -0
- metadata +219 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: d5d3338338ef1c5b06f88ce9df1f5456ce8484a8
|
|
4
|
+
data.tar.gz: 00481061072904e6dea701448bead33cf4cd0d5c
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 76c1403baf7268c7b334ecdd96f9766cc5644aa2949352bf2595ad8a16ce372e27d8c763cd8e1c61bb5e6643f05090873659d040535c22fe9af115e3329193ac
|
|
7
|
+
data.tar.gz: 006a9d347314ba2766fbf70cb1a1d175da13c2186cd837dc6fd9723c76ba83855a7991967958ce8f337732f4d9caa01c1c86d84d37271a36973619743a0b5af9
|
data/MIT-LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Copyright 2016 Tom Prats
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
4
|
+
a copy of this software and associated documentation files (the
|
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
9
|
+
the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be
|
|
12
|
+
included in all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Tomify
|
|
2
|
+
|
|
3
|
+
## Controllers
|
|
4
|
+
You can override a controller in an initializer
|
|
5
|
+
|
|
6
|
+
```ruby
|
|
7
|
+
Tomify.controllers.base = "ApplicationController"
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Views
|
|
11
|
+
You can override a default template in a layout file
|
|
12
|
+
|
|
13
|
+
```haml
|
|
14
|
+
- content_for :navbar_partial, "layouts/navbar"
|
|
15
|
+
= render "tomify/layouts/application"
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## License
|
|
19
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
String::assign = (options) ->
|
|
2
|
+
@replace /::.*::/, (replace) -> options[replace.slice(2, -2)] ? ""
|
|
3
|
+
|
|
4
|
+
String::break = ->
|
|
5
|
+
@replace(/(?!^)([A-Z])/g, "_$1").lowercase.split(/[\W_-]/)
|
|
6
|
+
|
|
7
|
+
String::date = ->
|
|
8
|
+
new Date(@).toLocaleDateString()
|
|
9
|
+
|
|
10
|
+
String::startsWith ?= (s) -> @slice(0, s.length) == s
|
|
11
|
+
String::endsWith ?= (s) -> s != "" && @slice(-s.length) == s
|
|
12
|
+
|
|
13
|
+
String::transform = (method) ->
|
|
14
|
+
string = @replace(/(?!^)([A-Z])/g, "_$1").lowercase
|
|
15
|
+
string = string.splitAndJoin "-", method
|
|
16
|
+
string = string.splitAndJoin "_", method
|
|
17
|
+
string = string.splitAndJoin ".", method
|
|
18
|
+
string
|
|
19
|
+
|
|
20
|
+
String::splitAnd = (separator, method) ->
|
|
21
|
+
method word for word in @split(separator)
|
|
22
|
+
|
|
23
|
+
String::splitAndJoin = (separator, method) ->
|
|
24
|
+
@splitAnd(separator, method).join(separator)
|
|
25
|
+
|
|
26
|
+
Object.defineProperties String.prototype, {
|
|
27
|
+
camelize: { get: ->
|
|
28
|
+
word = (word.capitalize for word in @break()).join()
|
|
29
|
+
word.charAt(0).lowercase + word.slice(1)
|
|
30
|
+
},
|
|
31
|
+
capitalize: { get: ->
|
|
32
|
+
@transform (word) -> word.charAt(0).toUpperCase() + word.slice(1)
|
|
33
|
+
},
|
|
34
|
+
dasherize: { get: -> @break().join("-") },
|
|
35
|
+
lowercase: { get: -> @toLowerCase() },
|
|
36
|
+
pluralize: { get: -> "#{@}s" },
|
|
37
|
+
titleize: { get: -> (word.capitalize for word in @break() when word isnt "id").join(" ") },
|
|
38
|
+
underscore: { get: -> @break().join("_") },
|
|
39
|
+
uppercase: { get: -> @toUpperCase() }
|
|
40
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
class @Component extends Namespace
|
|
2
|
+
@base: window
|
|
3
|
+
@create: (namespace, component) ->
|
|
4
|
+
component.displayName ?= namespace
|
|
5
|
+
component.mixins = @defaultMixins.concat(component.mixins || [])
|
|
6
|
+
@namespace namespace, React.createClass(component)
|
|
7
|
+
@defaultMixins: [WillInitializeMixin, FollowMixin]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
class @Field
|
|
2
|
+
@build: (options) ->
|
|
3
|
+
props = { name: options.name, type: options.type }
|
|
4
|
+
switch props.type
|
|
5
|
+
when "checkbox"
|
|
6
|
+
props.label = options.label
|
|
7
|
+
when "select"
|
|
8
|
+
props.allowBlank = options.allowBlank
|
|
9
|
+
props.model = options.model
|
|
10
|
+
props.options = []
|
|
11
|
+
for option in options.options || []
|
|
12
|
+
props.options.push { value: option?.value ? option, name: option?.name ? option.titleize }
|
|
13
|
+
field = new Field(options)
|
|
14
|
+
field.props = props
|
|
15
|
+
field.setForm(options.form)
|
|
16
|
+
field
|
|
17
|
+
copy: (form) ->
|
|
18
|
+
Field.build $.extend({}, @options, { form: form })
|
|
19
|
+
constructor: (options) ->
|
|
20
|
+
@options = options
|
|
21
|
+
setForm: (form) ->
|
|
22
|
+
@props.value = form.value.bind(form)
|
|
23
|
+
@props.onChange = form.onChange.bind(form)
|
|
24
|
+
@props.options = form.options.bind(form, @props) if @props.model
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
class @Form
|
|
2
|
+
copy: ->
|
|
3
|
+
form = new Form(@layout)
|
|
4
|
+
form.fields = (field.copy(form) for field in @fields)
|
|
5
|
+
form.models = @models
|
|
6
|
+
form
|
|
7
|
+
constructor: (layout) ->
|
|
8
|
+
@layout = layout
|
|
9
|
+
@fields = []
|
|
10
|
+
@models = []
|
|
11
|
+
value: (name) ->
|
|
12
|
+
@changes.get(name) ? @record.get(name) ? ""
|
|
13
|
+
onChange: (name, e) ->
|
|
14
|
+
change = {}
|
|
15
|
+
change[name] = switch e.target.type
|
|
16
|
+
when "checkbox" then e.target.checked
|
|
17
|
+
when "file" then e.target.files[0]
|
|
18
|
+
else e.target.value
|
|
19
|
+
return @changes.remove(name) if @record.get(name) == change[name]
|
|
20
|
+
@changes.merge(change)
|
|
21
|
+
options: (props) ->
|
|
22
|
+
options = []
|
|
23
|
+
options.push { value: "", name: "None" } if props.allowBlank
|
|
24
|
+
for record in @component.state[props.model.lowercase]
|
|
25
|
+
options.push { value: record.id, name: record.name }
|
|
26
|
+
options
|
|
27
|
+
add: (name, type, options = {}) ->
|
|
28
|
+
options = $.extend { type: type, name: name, form: @ }, options
|
|
29
|
+
@fields.push Field.build(options)
|
|
30
|
+
@models.push options.model if options.model
|
|
31
|
+
setComponent: (component) ->
|
|
32
|
+
@component = component
|
|
33
|
+
@record = component.store.findOrCreate "Record", {}
|
|
34
|
+
@changes = component.store.findOrCreate "Changes", {}
|
|
35
|
+
@stores = { record: @record, changes: @changes }
|
|
36
|
+
component.followModels.push model for model in @models
|
|
37
|
+
component.followStores.push BuildObject(key, value) for key, value of @stores
|
|
38
|
+
@
|
|
39
|
+
setDefaultValues: ->
|
|
40
|
+
record = @record.get()
|
|
41
|
+
changes = {}
|
|
42
|
+
for field in @fields
|
|
43
|
+
name = field.props.name
|
|
44
|
+
value = null
|
|
45
|
+
continue if record[name]?
|
|
46
|
+
switch field.props.type
|
|
47
|
+
when "checkbox"
|
|
48
|
+
value = true
|
|
49
|
+
when "select"
|
|
50
|
+
continue if field.allowBlank
|
|
51
|
+
options = field.props.options?() || field.props.options
|
|
52
|
+
value = options[0]?.value
|
|
53
|
+
continue unless value?
|
|
54
|
+
changes[name] = value
|
|
55
|
+
@changes.set(changes)
|
|
56
|
+
renderField: (field) ->
|
|
57
|
+
FieldComponent = Form.Field[field.props.type.capitalize] || Form.Field.Default
|
|
58
|
+
if @layout == "horizontal"
|
|
59
|
+
<div key={field.props.name} className="form-group">
|
|
60
|
+
<label className="col-sm-2 control-label" htmlFor={field.props.name}>
|
|
61
|
+
{field.props.name.titleize}
|
|
62
|
+
</label>
|
|
63
|
+
<div className="col-sm-10">
|
|
64
|
+
<FieldComponent {...field.props} />
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
else
|
|
68
|
+
<div key={field.props.name} className="form-group">
|
|
69
|
+
<FieldComponent {...field.props} />
|
|
70
|
+
</div>
|
|
71
|
+
render: ->
|
|
72
|
+
@renderField(field) for field in @fields
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
class @Model extends Observer
|
|
2
|
+
@base: Model
|
|
3
|
+
@create: (namespace, options) ->
|
|
4
|
+
@namespace namespace, new @(namespace, options)
|
|
5
|
+
constructor: (namespace, options = {}) ->
|
|
6
|
+
@name = namespace.split(".").last ? throw "Model: Requires Name"
|
|
7
|
+
@prefix = namespace.split(".").first.lowercase
|
|
8
|
+
@path = options.path ? @name.underscore.pluralize
|
|
9
|
+
@path = "/api/#{@prefix}/#{@path}"
|
|
10
|
+
@requests = {}
|
|
11
|
+
@setDefaultActions()
|
|
12
|
+
setAction: (name, request) ->
|
|
13
|
+
requests = @requests[name] = []
|
|
14
|
+
context = @
|
|
15
|
+
@[name] = (args...) ->
|
|
16
|
+
request.running = true
|
|
17
|
+
requests.push(request)
|
|
18
|
+
$.when(request(args...)).done (response) ->
|
|
19
|
+
request.running = false
|
|
20
|
+
context.trigger name, response
|
|
21
|
+
requested: (action) -> @requests[action].length > 0
|
|
22
|
+
request: (type, nest) ->
|
|
23
|
+
name = @name.underscore
|
|
24
|
+
basePath = @path
|
|
25
|
+
(path, params) ->
|
|
26
|
+
[path, params] = [params, path] if path instanceof Object
|
|
27
|
+
route = basePath
|
|
28
|
+
route += "/#{path}" if path
|
|
29
|
+
if nest
|
|
30
|
+
[params, nest] = [{}, params]
|
|
31
|
+
params[name] = nest
|
|
32
|
+
Request[type](route, params)
|
|
33
|
+
setDefaultActions: ->
|
|
34
|
+
@setAction "find", @request "get"
|
|
35
|
+
@setAction "edit", @request "get"
|
|
36
|
+
@setAction "update", @request "put", true
|
|
37
|
+
@setAction "new", Request.none
|
|
38
|
+
@setAction "all", @request "get"
|
|
39
|
+
@setAction "where", @request "get"
|
|
40
|
+
@setAction "create", @request "post", true
|
|
41
|
+
@setAction "destroy", @request "delete"
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
class @Namespace
|
|
2
|
+
@base: window,
|
|
3
|
+
@namespace: (namespace, value) ->
|
|
4
|
+
unless namespace?.length then throw "Namespace: Requires Keys"
|
|
5
|
+
unless value? then throw "Namespace: Requires Value"
|
|
6
|
+
keys = namespace.split(".")
|
|
7
|
+
value.namespace = namespace
|
|
8
|
+
scope = @base
|
|
9
|
+
for key in keys when key isnt keys.last
|
|
10
|
+
scope[key] ?= {}
|
|
11
|
+
scope = scope[key]
|
|
12
|
+
scope[keys.last] = value
|
|
13
|
+
value
|
|
14
|
+
@create: @namespace
|
|
15
|
+
@find: (namespace) ->
|
|
16
|
+
unless namespace?.length then throw "Namespace: Requires Keys"
|
|
17
|
+
scope = @base
|
|
18
|
+
scope = (scope ? {})[key] for key in namespace.split(".")
|
|
19
|
+
scope
|
|
20
|
+
@findOrCreate: (namespace, options) ->
|
|
21
|
+
@find(namespace, options) || @create(namespace, options)
|
|
22
|
+
findOrCreate: (namespace, options) ->
|
|
23
|
+
namespace = "#{@namespace}.#{namespace}"
|
|
24
|
+
@constructor.find(namespace, options) || @constructor.create(namespace, options)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
class @Observer extends Namespace
|
|
2
|
+
@base: Observer
|
|
3
|
+
@create: (namespace) ->
|
|
4
|
+
@namespace namespace, new @
|
|
5
|
+
callbacksFor: (action) ->
|
|
6
|
+
@observe ?= {}
|
|
7
|
+
@observe[action] ?= $.Callbacks()
|
|
8
|
+
on: (action, callback) ->
|
|
9
|
+
context = @
|
|
10
|
+
callback.off = -> context.off(action, callback)
|
|
11
|
+
@callbacksFor(action).add callback
|
|
12
|
+
callback
|
|
13
|
+
off: (action, callback) ->
|
|
14
|
+
@callbacksFor(action).remove(callback)
|
|
15
|
+
callback
|
|
16
|
+
one: (action, callback) ->
|
|
17
|
+
@on action, callback
|
|
18
|
+
@on action, -> callback.off()
|
|
19
|
+
callback
|
|
20
|
+
trigger: (action, data) ->
|
|
21
|
+
@callbacksFor(action).fire(data)
|
|
22
|
+
@
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
class @Request
|
|
2
|
+
@none: (data) -> data
|
|
3
|
+
@make: (type) ->
|
|
4
|
+
(path, params) -> @ajax(type: type, url: path, data: params)
|
|
5
|
+
@get: @make("GET")
|
|
6
|
+
@post: @make("POST")
|
|
7
|
+
@put: @make("PUT")
|
|
8
|
+
@delete: @make("DELETE")
|
|
9
|
+
@ajax: (options) ->
|
|
10
|
+
options.url = options.url.assign(options.data)
|
|
11
|
+
if options.type in ["POST", "PUT"]
|
|
12
|
+
$.extend options, {
|
|
13
|
+
data: @formData(new FormData(), options.data),
|
|
14
|
+
cache: false,
|
|
15
|
+
processData: false,
|
|
16
|
+
contentType: false
|
|
17
|
+
}
|
|
18
|
+
$.ajax(options)
|
|
19
|
+
@formData: (data, object, objectName) ->
|
|
20
|
+
for key, value of object
|
|
21
|
+
name = if objectName then "#{objectName}[#{key}]" else key
|
|
22
|
+
if value not instanceof Object || value instanceof File
|
|
23
|
+
data.append(name, value)
|
|
24
|
+
else
|
|
25
|
+
data = @formData(data, value, name)
|
|
26
|
+
data
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
class @Store extends Observer
|
|
2
|
+
@base: Store
|
|
3
|
+
@create: (namespace, data) ->
|
|
4
|
+
@namespace namespace, new @(data)
|
|
5
|
+
constructor: (data) ->
|
|
6
|
+
@data = data ? {}
|
|
7
|
+
set: (data) ->
|
|
8
|
+
@data = data
|
|
9
|
+
@trigger "change", data
|
|
10
|
+
@
|
|
11
|
+
merge: (data) ->
|
|
12
|
+
throw "Store: Invalid Data Type" unless @isObject()
|
|
13
|
+
@trigger "merge", data
|
|
14
|
+
@set($.extend({}, @get(), data))
|
|
15
|
+
push: (data) ->
|
|
16
|
+
throw "Store: Invalid Data Type" unless @isArray()
|
|
17
|
+
@trigger "push", data
|
|
18
|
+
@set(@get().concat([data]))
|
|
19
|
+
remove: (field) ->
|
|
20
|
+
throw "Store: Invalid Data Type" unless @isObject()
|
|
21
|
+
@trigger "remove", data
|
|
22
|
+
data = @get()
|
|
23
|
+
delete data[field]
|
|
24
|
+
@set(item for item in data when item?)
|
|
25
|
+
get: (field) ->
|
|
26
|
+
throw "Store: Invalid Data Type" unless @isDefined()
|
|
27
|
+
return @data if @isString() && !field
|
|
28
|
+
data = $.extend(@data.constructor(), @data)
|
|
29
|
+
if field then data[field] else data
|
|
30
|
+
empty: ->
|
|
31
|
+
return @data.length == 0 if @isArray()
|
|
32
|
+
return Object.keys(@data).length == 0 if @isObject()
|
|
33
|
+
@data?
|
|
34
|
+
isDefined: -> @data?
|
|
35
|
+
isObject: -> @data instanceof Object
|
|
36
|
+
isArray: -> @data instanceof Array
|
|
37
|
+
isString: -> $.type(@data) == "string"
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
messages = Store.create "Messages", []
|
|
2
|
+
settings = Store.create "Settings", []
|
|
3
|
+
user = Store.create "User", {}
|
|
4
|
+
env = Store.create "Env", {}
|
|
5
|
+
env.on "change", ->
|
|
6
|
+
current = env.get()
|
|
7
|
+
messages.set current.messages
|
|
8
|
+
settings.set current.settings
|
|
9
|
+
user.set current.user ? {}
|
|
10
|
+
|
|
11
|
+
$ -> env.set(window.env)
|
|
12
|
+
|
|
13
|
+
@message = (message) -> messages.push message
|
|
14
|
+
@setting = (name) ->
|
|
15
|
+
setting = settings.get().find (setting) -> setting.name == name
|
|
16
|
+
setting?.value
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
model = Model.findOrCreate "Admin.Page"
|
|
2
|
+
model.columns = [
|
|
3
|
+
{ name: "active", value: (r) -> if r.active then "Yes" else "No" },
|
|
4
|
+
{ name: "rank" },
|
|
5
|
+
{ name: "parent_id", value: (r) -> r.parent?.name ? "None" },
|
|
6
|
+
{ name: "sidebar_id", value: (r) -> r.sidebar?.name ? "None" },
|
|
7
|
+
{ name: "name" },
|
|
8
|
+
{ name: "path" },
|
|
9
|
+
{ name: "updated_at", value: (r) -> r.updated_at.date() },
|
|
10
|
+
{ name: "actions", edit: true, destroy: true, view: (r) ->
|
|
11
|
+
<a key="visit" href="/#{r.path}" target="_blank">Visit</a>
|
|
12
|
+
}
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
form = new Form("horizontal")
|
|
16
|
+
form.add "parent_id", "select", model: "Admin.Page", allowBlank: true
|
|
17
|
+
form.add "sidebar_id", "select", model: "Admin.Sidebar", allowBlank: true
|
|
18
|
+
form.add "active", "checkbox"
|
|
19
|
+
form.add "root", "checkbox", label: "If checked, this page will be available at <a href='/' target='_blank'>#{location.origin}</a>"
|
|
20
|
+
form.add "rank", "number"
|
|
21
|
+
form.add "name", "text"
|
|
22
|
+
form.add "template", "select", options: <%= Tomify::Page.templates %>
|
|
23
|
+
form.add "path", "text"
|
|
24
|
+
form.add "title", "text"
|
|
25
|
+
form.add "description", "textarea"
|
|
26
|
+
form.add "cover_image", "file"
|
|
27
|
+
form.add "share_image", "file"
|
|
28
|
+
form.add "text", "markdown"
|
|
29
|
+
|
|
30
|
+
Component.create "Admin.Pages.Index.Container", render: -> <Index.Container name="Admin.Page" form={form} />
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
model = Model.findOrCreate "Admin.Setting"
|
|
2
|
+
model.columns = [
|
|
3
|
+
{ name: "name" },
|
|
4
|
+
{ name: "type", value: (r) -> r.type.split("::").last },
|
|
5
|
+
{ name: "value", value: (r) ->
|
|
6
|
+
type = r.type.split("::").last
|
|
7
|
+
return "#{r.value}" if type == "Boolean"
|
|
8
|
+
return "JSON (Don't Edit)" if type == "Json"
|
|
9
|
+
r.value
|
|
10
|
+
},
|
|
11
|
+
{ name: "updated_at", value: (r) -> r.updated_at.date() },
|
|
12
|
+
{ name: "actions", edit: true, destroy: true }
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
options = ["Boolean", "Json", "Text", "Uploader"]
|
|
16
|
+
options = ({ name: option, value: "Tomify::Setting::#{option}" } for option in options)
|
|
17
|
+
|
|
18
|
+
newForm = new Form("horizontal")
|
|
19
|
+
newForm.add "type", "select", options: options
|
|
20
|
+
newForm.add "name", "text"
|
|
21
|
+
newForm.add "value", "text"
|
|
22
|
+
|
|
23
|
+
editForm = new Form("horizontal")
|
|
24
|
+
editForm.add "name", "text"
|
|
25
|
+
editForm.add "value", "text"
|
|
26
|
+
|
|
27
|
+
Component.create "Admin.Settings.Index.Container", render: -> <Index.Container name="Admin.Setting" newForm={newForm} editForm={editForm} />
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
model = Model.findOrCreate "Admin.Sidebar"
|
|
2
|
+
model.columns = [
|
|
3
|
+
{ name: "active", value: (r) -> if r.active then "Yes" else "No" },
|
|
4
|
+
{ name: "name" },
|
|
5
|
+
{ name: "heading" },
|
|
6
|
+
{ name: "updated_at", value: (r) -> r.updated_at.date() },
|
|
7
|
+
{ name: "actions", edit: true, destroy: true }
|
|
8
|
+
]
|
|
9
|
+
|
|
10
|
+
form = new Form("horizontal")
|
|
11
|
+
form.add "active", "checkbox"
|
|
12
|
+
form.add "name", "text"
|
|
13
|
+
form.add "heading", "text"
|
|
14
|
+
form.add "text", "markdown"
|
|
15
|
+
|
|
16
|
+
Component.create "Admin.Sidebars.Index.Container", render: -> <Index.Container name="Admin.Sidebar" form={form} />
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
model = Model.findOrCreate "Admin.Upload"
|
|
2
|
+
model.columns = [
|
|
3
|
+
{ name: "name" },
|
|
4
|
+
{ name: "file", value: (r) -> <a href={r.file.url} target="_blank">View</a> },
|
|
5
|
+
{ name: "updated_at", value: (r) -> r.updated_at.date() },
|
|
6
|
+
{ name: "actions", edit: true, destroy: true }
|
|
7
|
+
]
|
|
8
|
+
|
|
9
|
+
form = new Form("horizontal")
|
|
10
|
+
form.add "name", "text"
|
|
11
|
+
form.add "file", "file"
|
|
12
|
+
|
|
13
|
+
Component.create "Admin.Uploads.Index.Container", render: -> <Index.Container name="Admin.Upload" form={form} />
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
model = Model.findOrCreate "Admin.User"
|
|
2
|
+
model.columns = [
|
|
3
|
+
{ name: "admin", value: (r) -> if r.admin then "Yes" else "No" },
|
|
4
|
+
{ name: "email" },
|
|
5
|
+
{ name: "first_name" },
|
|
6
|
+
{ name: "last_name" },
|
|
7
|
+
{ name: "created_at", value: (r) -> r.created_at.date() },
|
|
8
|
+
{ name: "updated_at", value: (r) -> r.updated_at.date() },
|
|
9
|
+
{ name: "actions", edit: true, destroy: true }
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
form = new Form("horizontal")
|
|
13
|
+
form.add "admin", "checkbox"
|
|
14
|
+
form.add "email", "text"
|
|
15
|
+
form.add "first_name", "text"
|
|
16
|
+
form.add "last_name", "text"
|
|
17
|
+
|
|
18
|
+
Component.create "Admin.Users.Index.Container", render: -> <Index.Container name="Admin.User" form={form} />
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
Component.create "Edit.Container",
|
|
2
|
+
componentWillInitialize: ->
|
|
3
|
+
@model = Model.findOrCreate @props.name
|
|
4
|
+
@store = Store.findOrCreate "#{@props.name}.Edit"
|
|
5
|
+
@form = @props.form.setComponent @
|
|
6
|
+
@follow @model.on "new", @modelNew
|
|
7
|
+
@follow @model.on "edit", @modelEdit
|
|
8
|
+
@follow @model.on "update", @modelUpdate
|
|
9
|
+
@follow @model.on "destroy", @modelDestroy
|
|
10
|
+
modelNew: ->
|
|
11
|
+
@store.merge(show: false)
|
|
12
|
+
modelEdit: (response) ->
|
|
13
|
+
@form.record.set(response.data)
|
|
14
|
+
@form.setDefaultValues()
|
|
15
|
+
@store.merge(show: true)
|
|
16
|
+
modelUpdate: (response) ->
|
|
17
|
+
message type: response.type, text: response.message
|
|
18
|
+
@store.merge(show: false) if response.type == "success"
|
|
19
|
+
modelDestroy: ->
|
|
20
|
+
@store.merge(show: false)
|
|
21
|
+
submit: (e) ->
|
|
22
|
+
e.preventDefault()
|
|
23
|
+
return @update() && false unless @form.changes.empty()
|
|
24
|
+
message type: "warning", text: "#{@model.name.titleize} was not updated"
|
|
25
|
+
false
|
|
26
|
+
update: ->
|
|
27
|
+
@model.update @form.record.get("id"), @form.changes.get()
|
|
28
|
+
cancel: (e) ->
|
|
29
|
+
e.preventDefault()
|
|
30
|
+
@store.merge(show: false)
|
|
31
|
+
false
|
|
32
|
+
render: ->
|
|
33
|
+
return <div /> unless @state.store.show
|
|
34
|
+
<div className="row">
|
|
35
|
+
<div className="col-xs-12">
|
|
36
|
+
<div className="panel panel-default">
|
|
37
|
+
<div className="panel-heading">
|
|
38
|
+
<h4>Edit {@model.name.titleize}</h4>
|
|
39
|
+
<a className="btn btn-danger pull-right" href="#" onClick={@cancel}><i className="fa fa-close" /></a>
|
|
40
|
+
</div>
|
|
41
|
+
<div className="panel-body">
|
|
42
|
+
<form className="form-horizontal" onSubmit={@submit}>
|
|
43
|
+
{@form.render()}
|
|
44
|
+
<div className="col-sm-offset-2 col-sm-10">
|
|
45
|
+
<input type="submit" name="commit" value="Update #{@model.name.titleize}" className="btn btn-primary btn-disabled" />
|
|
46
|
+
</div>
|
|
47
|
+
</form>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
Component.create "Form.Field.Checkbox",
|
|
2
|
+
render: ->
|
|
3
|
+
<div className="checkbox">
|
|
4
|
+
<label>
|
|
5
|
+
<input type="checkbox" name={@props.name} id={@props.name} checked={@props.value(@props.name)} onChange={@props.onChange.bind(null, @props.name)} />
|
|
6
|
+
<span dangerouslySetInnerHTML={{__html: @props.label}} />
|
|
7
|
+
</label>
|
|
8
|
+
</div>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Component.create "Form.Field.Markdown",
|
|
2
|
+
render: ->
|
|
3
|
+
<div>
|
|
4
|
+
<p className="form-control-static">
|
|
5
|
+
You can use <a href="https://guides.github.com/features/mastering-markdown/#examples" target="_blank">markdown syntax</a> to bedazzle the content
|
|
6
|
+
</p>
|
|
7
|
+
<textarea placeholder="## Example Markdown\n\nTry to **be bold** or *maybe italic*.\n\nWrite a link [to somewhere](http://tomify.me) or even embed an image with ." rows=5 className="form-control" type="text" name={@props.name} id={@props.name} value={@props.value(@props.name)} onChange={@props.onChange.bind(null, @props.name)} />
|
|
8
|
+
<p className="help-block">Drag the bottom right corner down to enlarge the box</p>
|
|
9
|
+
</div>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
Component.create "Form.Field.Select",
|
|
2
|
+
render: ->
|
|
3
|
+
<select className="form-control" name={@props.name} id={@props.name} value={@props.value(@props.name)} onChange={@props.onChange.bind(null, @props.name)}>
|
|
4
|
+
{<option key="none">None</option> if @props.allow_blank}
|
|
5
|
+
{for option in @props.options?() || @props.options
|
|
6
|
+
<option key={option.value} value={option.value}>{option.name}</option>
|
|
7
|
+
}
|
|
8
|
+
</select>
|