tomify 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
@@ -0,0 +1,87 @@
|
|
1
|
+
Component.create "Index.Container",
|
2
|
+
componentWillInitialize: ->
|
3
|
+
@model = Model.findOrCreate @props.name
|
4
|
+
@store = Store.findOrCreate "#{@props.name}.Index"
|
5
|
+
@records = @store.findOrCreate "Records", []
|
6
|
+
@followStores.push records: @records
|
7
|
+
@follow @model.on "all", @modelAll
|
8
|
+
@follow @model.on "create", @modelCreate
|
9
|
+
@follow @model.on "update", @modelUpdate
|
10
|
+
@follow @model.on "destroy", @modelDestroy
|
11
|
+
@newForm = @props.newForm || @props.form.copy()
|
12
|
+
@editForm = @props.editForm || @props.form.copy()
|
13
|
+
componentDidMount: ->
|
14
|
+
@model.all()
|
15
|
+
modelAll: (response) ->
|
16
|
+
@records.set(response.data)
|
17
|
+
@setPage(1)
|
18
|
+
modelCreate: (response) ->
|
19
|
+
@model.all() if response.type == "success"
|
20
|
+
modelUpdate: (response) ->
|
21
|
+
@model.all() if response.type == "success"
|
22
|
+
modelDestroy: (response) ->
|
23
|
+
@model.all() if response.type == "danger"
|
24
|
+
setPage: (page) ->
|
25
|
+
@setState(page: 1, currentRecords: @state.records[(page*10 - 10)..page*10])
|
26
|
+
new: (e) ->
|
27
|
+
e.preventDefault()
|
28
|
+
@model.new()
|
29
|
+
false
|
30
|
+
edit: (id, e) ->
|
31
|
+
e.preventDefault()
|
32
|
+
@model.edit(id)
|
33
|
+
false
|
34
|
+
destroy: (id, e) ->
|
35
|
+
e.preventDefault()
|
36
|
+
@model.destroy(id).then (response) ->
|
37
|
+
message type: response.type, text: response.message
|
38
|
+
false
|
39
|
+
render: ->
|
40
|
+
<div>
|
41
|
+
<div className="row">
|
42
|
+
<div className="col-xs-12">
|
43
|
+
<div className="panel panel-default">
|
44
|
+
<div className="panel-heading">
|
45
|
+
<h4>
|
46
|
+
{@model.name.pluralize.titleize}
|
47
|
+
<a className="btn btn-primary btn-xs" href="#" onClick={@new}>New</a>
|
48
|
+
</h4>
|
49
|
+
<Pagination page="1" total={@state.records.length} setPage={@setPage} />
|
50
|
+
</div>
|
51
|
+
<div className="table-responsive">
|
52
|
+
<table className="table table-bordered table-hover">
|
53
|
+
<thead>
|
54
|
+
<tr>
|
55
|
+
{for field, i in @model.columns
|
56
|
+
<th key={i}>{field.name.titleize}</th>
|
57
|
+
}
|
58
|
+
</tr>
|
59
|
+
</thead>
|
60
|
+
<tbody>
|
61
|
+
{for record, i in @state.records
|
62
|
+
<tr key={i}>
|
63
|
+
{for field, j in @model.columns
|
64
|
+
if field.name == "actions"
|
65
|
+
<td key={j}>
|
66
|
+
{[
|
67
|
+
field.view && field.view(record)
|
68
|
+
field.view && field.edit && " | "
|
69
|
+
field.edit && <a key="edit" onClick={@edit.bind(null, record.id)} href="#">Edit</a>
|
70
|
+
field.destroy && " | "
|
71
|
+
field.destroy && <a key="destroy" onClick={@destroy.bind(null, record.id)} href="#" data-confirm="Are you sure you want to delete #{record.name}?">Delete</a>
|
72
|
+
]}
|
73
|
+
</td>
|
74
|
+
else
|
75
|
+
<td key={j}>{field.value?(record) ? record[field.name]}</td>
|
76
|
+
}
|
77
|
+
</tr>
|
78
|
+
}
|
79
|
+
</tbody>
|
80
|
+
</table>
|
81
|
+
</div>
|
82
|
+
</div>
|
83
|
+
</div>
|
84
|
+
</div>
|
85
|
+
<New.Container name={@props.name} form={@newForm} />
|
86
|
+
<Edit.Container name={@props.name} form={@editForm} />
|
87
|
+
</div>
|
@@ -0,0 +1,29 @@
|
|
1
|
+
Component.create "Layout.AdminNavbar",
|
2
|
+
followStores: ["user", pages: "navbar.admin"]
|
3
|
+
componentWillInitialize: ->
|
4
|
+
env = Store.find "Env"
|
5
|
+
Store.create "Navbar.Admin", env.get().navbar.admin || []
|
6
|
+
@follow env.on "change", -> pages.set(env.get().navbar.admin)
|
7
|
+
render: ->
|
8
|
+
return <div /> unless @state.user.admin
|
9
|
+
<div className="brand-navbar navbar navbar-default center">
|
10
|
+
<div className="container-fluid">
|
11
|
+
<div className="navbar-header">
|
12
|
+
<a href="/admin" className="navbar-brand visible-xs-block">Admin</a>
|
13
|
+
<button className="navbar-toggle collapsed" data-toggle="collapse" data-target="#admin-navbar">
|
14
|
+
<span className="icon-bar" />
|
15
|
+
<span className="icon-bar" />
|
16
|
+
<span className="icon-bar" />
|
17
|
+
</button>
|
18
|
+
</div>
|
19
|
+
<div id="admin-navbar" className="navbar-collapse collapse">
|
20
|
+
<ul className="nav navbar-nav">
|
21
|
+
{for page in @state.pages
|
22
|
+
<li key={page.name}>
|
23
|
+
<a href="/#{page.path}">{page.name}</a>
|
24
|
+
</li>
|
25
|
+
}
|
26
|
+
</ul>
|
27
|
+
</div>
|
28
|
+
</div>
|
29
|
+
</div>
|
@@ -0,0 +1,34 @@
|
|
1
|
+
Component.create "Layout.Analytics",
|
2
|
+
componentDidMount: ->
|
3
|
+
@google()
|
4
|
+
@itunes()
|
5
|
+
google: ->
|
6
|
+
return unless code = setting "google_analytics_code"
|
7
|
+
|
8
|
+
window.GoogleAnalyticsObject = "ga"
|
9
|
+
window.ga = (args...) -> window[window.GoogleAnalyticsObject] args...
|
10
|
+
window?[window.GoogleAnalyticsObject] ?= (args...) ->
|
11
|
+
api = window[window.GoogleAnalyticsObject]
|
12
|
+
(api.q or= []).push args
|
13
|
+
return
|
14
|
+
window.ga.l = new Date().getTime()
|
15
|
+
script = document.createElement "script"
|
16
|
+
script.type = "text/javascript"
|
17
|
+
script.src = "//www.google-analytics.com/analytics.js"
|
18
|
+
script.async = true
|
19
|
+
tag = document.getElementsByTagName("script")[0]
|
20
|
+
tag.parentNode.insertBefore script, tag
|
21
|
+
|
22
|
+
ga "create", code, "auto"
|
23
|
+
ga "send", "pageview"
|
24
|
+
itunes: ->
|
25
|
+
window._merchantSettings ?= []
|
26
|
+
window._merchantSettings.push ["AT", "1001lbag"]
|
27
|
+
script = document.createElement "script"
|
28
|
+
script.type = "text/javascript"
|
29
|
+
script.src = "//autolinkmaker.itunes.apple.com/js/itunes_autolinkmaker.js"
|
30
|
+
script.async = true
|
31
|
+
tag = document.getElementsByTagName("script")[0]
|
32
|
+
tag.parentNode.insertBefore script, tag
|
33
|
+
render: ->
|
34
|
+
<script />
|
@@ -0,0 +1,11 @@
|
|
1
|
+
Component.create "Layout.Footer",
|
2
|
+
render: ->
|
3
|
+
<footer>
|
4
|
+
<div className="container">
|
5
|
+
<div className="row tomify">
|
6
|
+
<div className="col-xs-12 text-center">
|
7
|
+
Website by <a href="http://www.tomify.me" target="_blank">Tomify</a>
|
8
|
+
</div>
|
9
|
+
</div>
|
10
|
+
</div>
|
11
|
+
</footer>
|
@@ -0,0 +1,28 @@
|
|
1
|
+
Component.create "Layout.Header",
|
2
|
+
render: ->
|
3
|
+
image = setting "header_image"
|
4
|
+
text = setting "header_text"
|
5
|
+
return <div /> unless image && text
|
6
|
+
|
7
|
+
<div className="navbar navbar-default center header">
|
8
|
+
<div className="container-fluid">
|
9
|
+
<div className="row">
|
10
|
+
<div className="col-lg-8 col-lg-offset-2">
|
11
|
+
<div className="row">
|
12
|
+
<div className="col-sm-3">
|
13
|
+
<div className="navbar-header">
|
14
|
+
<a href="/" class="navbar-brand">
|
15
|
+
<img src={image.url} alt="Logo" />
|
16
|
+
</a>
|
17
|
+
</div>
|
18
|
+
</div>
|
19
|
+
<div className="col-sm-9">
|
20
|
+
<div className="navbar-text">
|
21
|
+
<h3>{text}</h3>
|
22
|
+
</div>
|
23
|
+
</div>
|
24
|
+
</div>
|
25
|
+
</div>
|
26
|
+
</div>
|
27
|
+
</div>
|
28
|
+
</div>
|
@@ -0,0 +1,18 @@
|
|
1
|
+
Component.create "Layout.Messages",
|
2
|
+
followStores: ["messages"]
|
3
|
+
componentWillInitialize: ->
|
4
|
+
@store = Store.create "Messages", window.env.messages
|
5
|
+
@follow @store.on "push", -> $("body").scrollTop(0)
|
6
|
+
remove: (i, e) ->
|
7
|
+
e.preventDefault()
|
8
|
+
@store.remove(i)
|
9
|
+
false
|
10
|
+
render: ->
|
11
|
+
<div>
|
12
|
+
{for message, i in @state.messages
|
13
|
+
<div key={i} className="alert alert-#{message.type} text-center">
|
14
|
+
{message.text}
|
15
|
+
<a className="btn btn-danger btn-xs pull-right" href="#" onClick={@remove.bind(null, i)}><i className="fa fa-close" /></a>
|
16
|
+
</div>
|
17
|
+
}
|
18
|
+
</div>
|
@@ -0,0 +1,56 @@
|
|
1
|
+
Component.create "Layout.PublicNavbar",
|
2
|
+
followStores: ["user", pages: "navbar.public"]
|
3
|
+
componentWillInitialize: ->
|
4
|
+
env = Store.find "Env"
|
5
|
+
Store.create "Navbar.Public", env.get().navbar.public || []
|
6
|
+
@follow env.on "change", -> pages.set(env.get().navbar.public)
|
7
|
+
logout: (e) ->
|
8
|
+
e.preventDefault()
|
9
|
+
Model.find("Public.Session").destroy().then (response) ->
|
10
|
+
window.location.replace "/" if response.type == "success"
|
11
|
+
link: (page) ->
|
12
|
+
<li key={page.name}>
|
13
|
+
<a href="/#{page.path}">{page.name}</a>
|
14
|
+
</li>
|
15
|
+
render: ->
|
16
|
+
root = @state.pages.find (page) -> page.root
|
17
|
+
<div className="navbar navbar-default center">
|
18
|
+
<div className="container-fluid">
|
19
|
+
<div className="navbar-header">
|
20
|
+
<a href="/" className="navbar-brand visible-xs-block">{root?.name || "Home"}</a>
|
21
|
+
<button className="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar">
|
22
|
+
<span className="icon-bar" />
|
23
|
+
<span className="icon-bar" />
|
24
|
+
<span className="icon-bar" />
|
25
|
+
</button>
|
26
|
+
</div>
|
27
|
+
<div id="navbar" className="navbar-collapse collapse">
|
28
|
+
<ul className="nav navbar-nav">
|
29
|
+
{if @state.user.id
|
30
|
+
@link(name: "Profile", path: "user/edit")
|
31
|
+
else if setting "allow_signup"
|
32
|
+
@link(name: "Login", path: "session")
|
33
|
+
}
|
34
|
+
{for page in @state.pages when page.active
|
35
|
+
if page.children[0]
|
36
|
+
<li key={page.name} className="dropdown">
|
37
|
+
<a href="#" className="dropdown-toggle" data-toggle="dropdown">
|
38
|
+
{page.name} <i className="fa fa-caret-down" />
|
39
|
+
</a>
|
40
|
+
<ul className="dropdown-menu">
|
41
|
+
{@link(page)}
|
42
|
+
{@link(child) for child in page.children}
|
43
|
+
</ul>
|
44
|
+
</li>
|
45
|
+
else
|
46
|
+
@link(page)
|
47
|
+
}
|
48
|
+
{if @state.user.id
|
49
|
+
<li>
|
50
|
+
<a href="#" onClick={@logout}>Logout</a>
|
51
|
+
</li>
|
52
|
+
}
|
53
|
+
</ul>
|
54
|
+
</div>
|
55
|
+
</div>
|
56
|
+
</div>
|
@@ -0,0 +1,48 @@
|
|
1
|
+
Component.create "New.Container",
|
2
|
+
componentWillInitialize: ->
|
3
|
+
@model = Model.findOrCreate @props.name
|
4
|
+
@store = Store.findOrCreate "#{@props.name}.New"
|
5
|
+
@form = @props.form.setComponent @
|
6
|
+
@follow @model.on "new", @modelNew
|
7
|
+
@follow @model.on "create", @modelCreate
|
8
|
+
@follow @model.on "edit", @modelEdit
|
9
|
+
modelNew: ->
|
10
|
+
@form.record.set({})
|
11
|
+
@form.setDefaultValues()
|
12
|
+
@store.merge(show: true)
|
13
|
+
modelCreate: (response) ->
|
14
|
+
message type: response.type, text: response.message
|
15
|
+
@store.merge(show: false) if response.type == "success"
|
16
|
+
modelEdit: ->
|
17
|
+
@store.merge(show: false)
|
18
|
+
submit: (e) ->
|
19
|
+
e.preventDefault()
|
20
|
+
if @form.changes.empty()
|
21
|
+
message type: "warning", text: "#{@model.name.titleize} was not created"
|
22
|
+
else
|
23
|
+
@model.create @form.changes.get()
|
24
|
+
false
|
25
|
+
cancel: (e) ->
|
26
|
+
e.preventDefault()
|
27
|
+
@store.merge(show: false)
|
28
|
+
false
|
29
|
+
render: ->
|
30
|
+
return <div /> unless @state.store.show
|
31
|
+
<div className="row">
|
32
|
+
<div className="col-xs-12">
|
33
|
+
<div className="panel panel-default">
|
34
|
+
<div className="panel-heading">
|
35
|
+
<h4>New {@model.name.titleize}</h4>
|
36
|
+
<a className="btn btn-danger pull-right" href="#" onClick={@cancel}><i className="fa fa-close" /></a>
|
37
|
+
</div>
|
38
|
+
<div className="panel-body">
|
39
|
+
<form className="form-horizontal" onSubmit={@submit}>
|
40
|
+
{@form.render()}
|
41
|
+
<div className="col-sm-offset-2 col-sm-10">
|
42
|
+
<input type="submit" name="commit" value="Create #{@model.name.titleize}" className="btn btn-primary" />
|
43
|
+
</div>
|
44
|
+
</form>
|
45
|
+
</div>
|
46
|
+
</div>
|
47
|
+
</div>
|
48
|
+
</div>
|
@@ -0,0 +1,29 @@
|
|
1
|
+
Component.create "Pagination",
|
2
|
+
setPage: (page, e) ->
|
3
|
+
e.preventDefault()
|
4
|
+
@props.setPage(page)
|
5
|
+
false
|
6
|
+
render: ->
|
7
|
+
first = 1
|
8
|
+
prev = @props.page - 1
|
9
|
+
next = @props.page + 1
|
10
|
+
last = Math.ceil(@props.total / 10)
|
11
|
+
prevDisabled = first > prev
|
12
|
+
nextDisabled = next > last
|
13
|
+
<ul className="pagination">
|
14
|
+
<li className="pagination-first#{' disabled' if prevDisabled}">
|
15
|
+
<a href="#" onClick={@setPage.bind(null, first)} className="fa fa-angle-double-left" />
|
16
|
+
</li>
|
17
|
+
<li className="pagination-prev#{' disabled' if prevDisabled}">
|
18
|
+
<a href="#" onClick={@setPage.bind(null, prev)} className="fa fa-angle-left" />
|
19
|
+
</li>
|
20
|
+
<li className="pagination-link active">
|
21
|
+
<a href="#" onClick={@setPage.bind(null, @props.page)}>{@props.page}</a>
|
22
|
+
</li>
|
23
|
+
<li className="pagination-next#{' disabled' if nextDisabled}">
|
24
|
+
<a href="#" onClick={@setPage.bind(null, next)} className="fa fa-angle-right" />
|
25
|
+
</li>
|
26
|
+
<li className="pagination-last#{' disabled' if nextDisabled}">
|
27
|
+
<a href="#" onClick={@setPage.bind(null, @props.total)} className="fa fa-angle-double-right" />
|
28
|
+
</li>
|
29
|
+
</ul>
|
@@ -0,0 +1,38 @@
|
|
1
|
+
Model.create "Public.Password", path: "password"
|
2
|
+
|
3
|
+
form = new Form
|
4
|
+
form.add "email", "email"
|
5
|
+
|
6
|
+
Component.create "Public.Passwords.New",
|
7
|
+
componentWillInitialize: ->
|
8
|
+
@model = Model.find "Public.Password"
|
9
|
+
@store = Store.findOrCreate "Public.Passwords.New"
|
10
|
+
@form = form.setComponent @
|
11
|
+
@follow @model.on "create", @modelCreate
|
12
|
+
modelCreate: (response) ->
|
13
|
+
message type: response.type, text: response.message
|
14
|
+
submit: (e) ->
|
15
|
+
e.preventDefault()
|
16
|
+
if @form.changes.empty()
|
17
|
+
message type: "warning", text: "You shall not pass!"
|
18
|
+
else
|
19
|
+
@model.create @form.changes.get()
|
20
|
+
false
|
21
|
+
newSession: (e) ->
|
22
|
+
e.preventDefault()
|
23
|
+
Model.findOrCreate("Public.Session").new()
|
24
|
+
render: ->
|
25
|
+
<div>
|
26
|
+
<h3>Forgot Password</h3>
|
27
|
+
<form onSubmit={@submit}>
|
28
|
+
{@form.render()}
|
29
|
+
<div className="form-group">
|
30
|
+
<input type="submit" name="commit" value="Submit" className="btn btn-primary" />
|
31
|
+
</div>
|
32
|
+
<div className="form-group">
|
33
|
+
<small>
|
34
|
+
<a href="#" onClick={@newSession}>Back</a>
|
35
|
+
</small>
|
36
|
+
</div>
|
37
|
+
</form>
|
38
|
+
</div>
|
@@ -0,0 +1,40 @@
|
|
1
|
+
Model.create "Public.Session", path: "session"
|
2
|
+
|
3
|
+
form = new Form
|
4
|
+
form.add "email", "email"
|
5
|
+
form.add "password", "password"
|
6
|
+
|
7
|
+
Component.create "Public.Sessions.New",
|
8
|
+
componentWillInitialize: ->
|
9
|
+
@model = Model.find "Public.Session"
|
10
|
+
@store = Store.findOrCreate "Public.Sessions.New"
|
11
|
+
@form = form.setComponent @
|
12
|
+
@follow @model.on "create", @modelCreate
|
13
|
+
modelCreate: (response) ->
|
14
|
+
return window.location.replace "/" if response.type == "success"
|
15
|
+
message type: response.type, text: response.message
|
16
|
+
submit: (e) ->
|
17
|
+
e.preventDefault()
|
18
|
+
if @form.changes.empty()
|
19
|
+
message type: "warning", text: "You shall not pass!"
|
20
|
+
else
|
21
|
+
@model.create @form.changes.get()
|
22
|
+
false
|
23
|
+
newPassword: (e) ->
|
24
|
+
e.preventDefault()
|
25
|
+
Model.findOrCreate("Public.Password").new()
|
26
|
+
render: ->
|
27
|
+
<div>
|
28
|
+
<h3>Sign In</h3>
|
29
|
+
<form onSubmit={@submit}>
|
30
|
+
{@form.render()}
|
31
|
+
<div className="form-group">
|
32
|
+
<input type="submit" name="commit" value="Sign In" className="btn btn-primary" />
|
33
|
+
</div>
|
34
|
+
<div className="form-group">
|
35
|
+
<small>
|
36
|
+
<a href="#" onClick={@newPassword}>Forgot Password?</a>
|
37
|
+
</small>
|
38
|
+
</div>
|
39
|
+
</form>
|
40
|
+
</div>
|
@@ -0,0 +1,35 @@
|
|
1
|
+
Component.create "Public.Sessions.Show",
|
2
|
+
getInitialState: ->
|
3
|
+
{ forgotPassword: false }
|
4
|
+
componentWillInitialize: ->
|
5
|
+
@follow Model.find("Public.Session").on "new", @newSession
|
6
|
+
@follow Model.find("Public.Password").on "new", @newPassword
|
7
|
+
newSession: ->
|
8
|
+
@setState(newPassword: false)
|
9
|
+
newPassword: ->
|
10
|
+
@setState(newPassword: true)
|
11
|
+
render: ->
|
12
|
+
<div className="container-fluid">
|
13
|
+
<div className="row text-center">
|
14
|
+
{if @state.newPassword
|
15
|
+
<div className="col-md-4 col-md-offset-4">
|
16
|
+
<Public.Passwords.New />
|
17
|
+
</div>
|
18
|
+
else if setting "allow_signup"
|
19
|
+
<div className="col-md-8 col-md-offset-2">
|
20
|
+
<div className="row">
|
21
|
+
<div className="col-md-6">
|
22
|
+
<Public.Sessions.New />
|
23
|
+
</div>
|
24
|
+
<div className="col-md-6">
|
25
|
+
<Public.Users.New />
|
26
|
+
</div>
|
27
|
+
</div>
|
28
|
+
</div>
|
29
|
+
else
|
30
|
+
<div className="col-md-4 col-md-offset-4">
|
31
|
+
<Public.Sessions.New />
|
32
|
+
</div>
|
33
|
+
}
|
34
|
+
</div>
|
35
|
+
</div>
|
@@ -0,0 +1,42 @@
|
|
1
|
+
form = new Form
|
2
|
+
form.add "email", "email"
|
3
|
+
form.add "first_name", "text"
|
4
|
+
form.add "last_name", "text"
|
5
|
+
form.add "password", "password"
|
6
|
+
form.add "password_confirmation", "password"
|
7
|
+
|
8
|
+
Component.create "Public.Users.Edit",
|
9
|
+
componentWillInitialize: ->
|
10
|
+
@model = Model.find "Public.User"
|
11
|
+
@store = Store.findOrCreate "Public.Users.Edit"
|
12
|
+
@form = form.setComponent @
|
13
|
+
@follow @model.on "update", @modelUpdate
|
14
|
+
@follow Store.find("User").on "change", @userChange
|
15
|
+
@userChange()
|
16
|
+
userChange: ->
|
17
|
+
@form.record.set Store.find("User").get()
|
18
|
+
@form.setDefaultValues()
|
19
|
+
modelUpdate: (response) ->
|
20
|
+
message type: response.type, text: response.message
|
21
|
+
Store.find("User").merge @form.changes.get() if response.type == "success"
|
22
|
+
submit: (e) ->
|
23
|
+
e.preventDefault()
|
24
|
+
if @form.changes.empty()
|
25
|
+
message type: "warning", text: "Profile was not updated"
|
26
|
+
else
|
27
|
+
@model.update @form.changes.get()
|
28
|
+
false
|
29
|
+
render: ->
|
30
|
+
<div className="container-fluid">
|
31
|
+
<div className="row text-center">
|
32
|
+
<div className="col-md-4 col-md-offset-4">
|
33
|
+
<h3>Edit Profile</h3>
|
34
|
+
<form onSubmit={@submit}>
|
35
|
+
{@form.render()}
|
36
|
+
<div className="form-group">
|
37
|
+
<input type="submit" name="commit" value="Save" className="btn btn-primary" />
|
38
|
+
</div>
|
39
|
+
</form>
|
40
|
+
</div>
|
41
|
+
</div>
|
42
|
+
</div>
|
@@ -0,0 +1,35 @@
|
|
1
|
+
Model.create "Public.User", path: "user"
|
2
|
+
|
3
|
+
form = new Form
|
4
|
+
form.add "email", "email"
|
5
|
+
form.add "first_name", "text"
|
6
|
+
form.add "last_name", "text"
|
7
|
+
form.add "password", "password"
|
8
|
+
form.add "password_confirmation", "password"
|
9
|
+
|
10
|
+
Component.create "Public.Users.New",
|
11
|
+
componentWillInitialize: ->
|
12
|
+
@model = Model.find "Public.User"
|
13
|
+
@store = Store.findOrCreate "Public.Users.New"
|
14
|
+
@form = form.setComponent @
|
15
|
+
@follow @model.on "create", @modelCreate
|
16
|
+
modelCreate: (response) ->
|
17
|
+
return window.location.replace "/" if response.type == "success"
|
18
|
+
message type: response.type, text: response.message
|
19
|
+
submit: (e) ->
|
20
|
+
e.preventDefault()
|
21
|
+
if @form.changes.empty()
|
22
|
+
message type: "warning", text: "You shall not pass!"
|
23
|
+
else
|
24
|
+
@model.create @form.changes.get()
|
25
|
+
false
|
26
|
+
render: ->
|
27
|
+
<div>
|
28
|
+
<h3>Sign Up</h3>
|
29
|
+
<form onSubmit={@submit}>
|
30
|
+
{@form.render()}
|
31
|
+
<div className="form-group">
|
32
|
+
<input type="submit" name="commit" value="Sign Up" className="btn btn-primary" />
|
33
|
+
</div>
|
34
|
+
</form>
|
35
|
+
</div>
|
@@ -0,0 +1,51 @@
|
|
1
|
+
@FollowMixin = {
|
2
|
+
getInitialSetup: ->
|
3
|
+
@followModels ?= []
|
4
|
+
@followStores ?= []
|
5
|
+
getInitialState: ->
|
6
|
+
@events ?= []
|
7
|
+
@setupStores()
|
8
|
+
@setupModels()
|
9
|
+
@stores["store"] = @store if @store
|
10
|
+
state = {}
|
11
|
+
state[key] = store.get() for key, store of @stores
|
12
|
+
state
|
13
|
+
componentDidMount: ->
|
14
|
+
context = @
|
15
|
+
for key, store of @stores
|
16
|
+
do (key, store) ->
|
17
|
+
callback = ->
|
18
|
+
state = {}
|
19
|
+
state[key] = store.get()
|
20
|
+
context.setState(state)
|
21
|
+
context.events.push store.on("change", callback)
|
22
|
+
model.all() for key, model in @models when !model.requested("all")
|
23
|
+
componentWillUnmount: ->
|
24
|
+
event.off() for event in @events
|
25
|
+
follow: (event) ->
|
26
|
+
@events ?= []
|
27
|
+
@events.push(event)
|
28
|
+
setupStores: ->
|
29
|
+
@stores = @convertToHash(@followStores?() || @followStores || [])
|
30
|
+
for key, value of @stores when not (value instanceof Store)
|
31
|
+
store = @stores[key.lowercase] = Store.find(value.capitalize)
|
32
|
+
throw "Component: Invalid Store (#{value.capitalize})" unless store?
|
33
|
+
setupModels: ->
|
34
|
+
@models = @convertToHash(@followModels?() || @followModels || [])
|
35
|
+
for key, value of @models when not (value instanceof Store)
|
36
|
+
store = @stores[key.lowercase] = @store.findOrCreate value.capitalize, []
|
37
|
+
model = @models[key.lowercase] = Model.findOrCreate(value)
|
38
|
+
context = @
|
39
|
+
do (context, store) ->
|
40
|
+
context.follow model.on "all", (response) -> store.set(response.data)
|
41
|
+
convertToHash: (object) ->
|
42
|
+
return object unless object instanceof Array
|
43
|
+
hash = {}
|
44
|
+
for item in object
|
45
|
+
if item instanceof Object
|
46
|
+
key = Object.keys(item)[0]
|
47
|
+
hash[key] = item[key]
|
48
|
+
else
|
49
|
+
hash[item] = item
|
50
|
+
hash
|
51
|
+
}
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# From https://github.com/turbolinks/turbolinks/blob/master/src/turbolinks/compatibility.coffee
|
2
|
+
{defer, dispatch} = Turbolinks
|
3
|
+
|
4
|
+
handleEvent = (eventName, handler) ->
|
5
|
+
document.addEventListener(eventName, handler, false)
|
6
|
+
|
7
|
+
translateEvent = ({from, to}) ->
|
8
|
+
handler = (event) ->
|
9
|
+
event = dispatch(to, target: event.target, cancelable: event.cancelable, data: event.data)
|
10
|
+
event.preventDefault() if event.defaultPrevented
|
11
|
+
handleEvent(from, handler)
|
12
|
+
|
13
|
+
translateEvent from: "turbolinks:click", to: "page:before-change"
|
14
|
+
translateEvent from: "turbolinks:request-start", to: "page:fetch"
|
15
|
+
translateEvent from: "turbolinks:request-end", to: "page:receive"
|
16
|
+
translateEvent from: "turbolinks:before-cache", to: "page:before-unload"
|
17
|
+
translateEvent from: "turbolinks:render", to: "page:update"
|
18
|
+
translateEvent from: "turbolinks:load", to: "page:change"
|
19
|
+
translateEvent from: "turbolinks:load", to: "page:update"
|
20
|
+
|
21
|
+
loaded = false
|
22
|
+
handleEvent "DOMContentLoaded", ->
|
23
|
+
defer -> loaded = true
|
24
|
+
handleEvent "turbolinks:load", ->
|
25
|
+
dispatch("page:load") if loaded
|
26
|
+
|
27
|
+
jQuery?(document).on "ajaxSuccess", (event, xhr, settings) ->
|
28
|
+
if jQuery.trim(xhr.responseText).length > 0
|
29
|
+
dispatch("page:update")
|