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.
Files changed (157) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +19 -0
  4. data/Rakefile +6 -0
  5. data/app/assets/images/tomify/gifs/basketball.gif +0 -0
  6. data/app/assets/images/tomify/gifs/laughing.gif +0 -0
  7. data/app/assets/images/tomify/gifs/sneer.gif +0 -0
  8. data/app/assets/images/tomify/gifs/wind.gif +0 -0
  9. data/app/assets/images/tomify/gifs/woods.gif +0 -0
  10. data/app/assets/javascripts/tomify/default/array.coffee +4 -0
  11. data/app/assets/javascripts/tomify/default/jquery.patch.coffee +9 -0
  12. data/app/assets/javascripts/tomify/default/object.coffee +4 -0
  13. data/app/assets/javascripts/tomify/default/string.coffee +40 -0
  14. data/app/assets/javascripts/tomify/dynamic/classes/component.coffee +7 -0
  15. data/app/assets/javascripts/tomify/dynamic/classes/field.coffee +24 -0
  16. data/app/assets/javascripts/tomify/dynamic/classes/form.coffee +72 -0
  17. data/app/assets/javascripts/tomify/dynamic/classes/model.coffee +41 -0
  18. data/app/assets/javascripts/tomify/dynamic/classes/namespace.coffee +24 -0
  19. data/app/assets/javascripts/tomify/dynamic/classes/observer.coffee +22 -0
  20. data/app/assets/javascripts/tomify/dynamic/classes/request.coffee +26 -0
  21. data/app/assets/javascripts/tomify/dynamic/classes/store.coffee +37 -0
  22. data/app/assets/javascripts/tomify/dynamic/global.coffee +16 -0
  23. data/app/assets/javascripts/tomify/dynamic/react/components/admin/pages.coffee.erb +30 -0
  24. data/app/assets/javascripts/tomify/dynamic/react/components/admin/settings.coffee +27 -0
  25. data/app/assets/javascripts/tomify/dynamic/react/components/admin/sidebars.coffee +16 -0
  26. data/app/assets/javascripts/tomify/dynamic/react/components/admin/uploads.coffee +13 -0
  27. data/app/assets/javascripts/tomify/dynamic/react/components/admin/users.coffee +18 -0
  28. data/app/assets/javascripts/tomify/dynamic/react/components/edit.coffee +51 -0
  29. data/app/assets/javascripts/tomify/dynamic/react/components/fields/checkbox.coffee +8 -0
  30. data/app/assets/javascripts/tomify/dynamic/react/components/fields/default.coffee +3 -0
  31. data/app/assets/javascripts/tomify/dynamic/react/components/fields/file.coffee +3 -0
  32. data/app/assets/javascripts/tomify/dynamic/react/components/fields/markdown.coffee +9 -0
  33. data/app/assets/javascripts/tomify/dynamic/react/components/fields/select.coffee +8 -0
  34. data/app/assets/javascripts/tomify/dynamic/react/components/fields/textarea.coffee +3 -0
  35. data/app/assets/javascripts/tomify/dynamic/react/components/index.coffee +87 -0
  36. data/app/assets/javascripts/tomify/dynamic/react/components/layout/admin_navbar.coffee +29 -0
  37. data/app/assets/javascripts/tomify/dynamic/react/components/layout/analytics.coffee +34 -0
  38. data/app/assets/javascripts/tomify/dynamic/react/components/layout/footer.coffee +11 -0
  39. data/app/assets/javascripts/tomify/dynamic/react/components/layout/header.coffee +28 -0
  40. data/app/assets/javascripts/tomify/dynamic/react/components/layout/messages.coffee +18 -0
  41. data/app/assets/javascripts/tomify/dynamic/react/components/layout/navbar.coffee +6 -0
  42. data/app/assets/javascripts/tomify/dynamic/react/components/layout/public_navbar.coffee +56 -0
  43. data/app/assets/javascripts/tomify/dynamic/react/components/new.coffee +48 -0
  44. data/app/assets/javascripts/tomify/dynamic/react/components/pagination.coffee +29 -0
  45. data/app/assets/javascripts/tomify/dynamic/react/components/public/passwords/new.coffee +38 -0
  46. data/app/assets/javascripts/tomify/dynamic/react/components/public/sessions/new.coffee +40 -0
  47. data/app/assets/javascripts/tomify/dynamic/react/components/public/sessions/show.coffee +35 -0
  48. data/app/assets/javascripts/tomify/dynamic/react/components/public/users/edit.coffee +42 -0
  49. data/app/assets/javascripts/tomify/dynamic/react/components/public/users/new.coffee +35 -0
  50. data/app/assets/javascripts/tomify/dynamic/react/mixins/follow.coffee +51 -0
  51. data/app/assets/javascripts/tomify/dynamic/react/mixins/will-initialize.coffee +7 -0
  52. data/app/assets/javascripts/tomify/dynamic/react/mount.coffee +9 -0
  53. data/app/assets/javascripts/tomify/turbolinks/compatibility.coffee +29 -0
  54. data/app/assets/javascripts/tomify/turbolinks/react.coffee +5 -0
  55. data/app/assets/javascripts/tomify-dynamic.js +8 -0
  56. data/app/assets/javascripts/tomify.js +1 -0
  57. data/app/assets/stylesheets/_tomify.scss +1 -0
  58. data/app/assets/stylesheets/tomify/_default.scss +26 -0
  59. data/app/assets/stylesheets/tomify/_footer.scss +1 -0
  60. data/app/assets/stylesheets/tomify/_navbar.scss +43 -0
  61. data/app/assets/stylesheets/tomify/_page.scss +31 -0
  62. data/app/assets/stylesheets/tomify/_pagination.scss +5 -0
  63. data/app/assets/stylesheets/tomify/_variables.scss +10 -0
  64. data/app/controllers/tomify/admin/controller.rb +7 -0
  65. data/app/controllers/tomify/admin/pages_controller.rb +2 -0
  66. data/app/controllers/tomify/admin/settings_controller.rb +2 -0
  67. data/app/controllers/tomify/admin/sidebars_controller.rb +2 -0
  68. data/app/controllers/tomify/admin/uploads_controller.rb +2 -0
  69. data/app/controllers/tomify/admin/users_controller.rb +2 -0
  70. data/app/controllers/tomify/api/admin/controller.rb +3 -0
  71. data/app/controllers/tomify/api/admin/pages_controller.rb +2 -0
  72. data/app/controllers/tomify/api/admin/settings_controller.rb +2 -0
  73. data/app/controllers/tomify/api/admin/sidebars_controller.rb +2 -0
  74. data/app/controllers/tomify/api/admin/uploads_controller.rb +2 -0
  75. data/app/controllers/tomify/api/admin/users_controller.rb +7 -0
  76. data/app/controllers/tomify/api/public/controller.rb +3 -0
  77. data/app/controllers/tomify/api/public/passwords_controller.rb +10 -0
  78. data/app/controllers/tomify/api/public/sessions_controller.rb +19 -0
  79. data/app/controllers/tomify/api/public/users_controller.rb +25 -0
  80. data/app/controllers/tomify/concerns/api/admin.rb +9 -0
  81. data/app/controllers/tomify/concerns/api/helpers.rb +57 -0
  82. data/app/controllers/tomify/concerns/api/json.rb +46 -0
  83. data/app/controllers/tomify/concerns/api/public.rb +9 -0
  84. data/app/controllers/tomify/concerns/default/auth_helper.rb +22 -0
  85. data/app/controllers/tomify/concerns/default/env_helper.rb +34 -0
  86. data/app/controllers/tomify/concerns/default/helper.rb +13 -0
  87. data/app/controllers/tomify/concerns/default/react_helper.rb +7 -0
  88. data/app/controllers/tomify/concerns/default.rb +17 -0
  89. data/app/controllers/tomify/public/controller.rb +2 -0
  90. data/app/controllers/tomify/public/pages_controller.rb +22 -0
  91. data/app/controllers/tomify/public/sessions_controller.rb +5 -0
  92. data/app/controllers/tomify/public/users_controller.rb +7 -0
  93. data/app/controllers/tomify_controller.rb +3 -0
  94. data/app/helpers/tomify/carrierwave_helper.rb +17 -0
  95. data/app/helpers/tomify/email_helper.rb +19 -0
  96. data/app/helpers/tomify/render_helper.rb +13 -0
  97. data/app/helpers/tomify/timezone_helper.rb +5 -0
  98. data/app/mailers/tomify/user_mailer.rb +13 -0
  99. data/app/mailers/tomify_mailer.rb +14 -0
  100. data/app/models/tomify/concerns/page.rb +74 -0
  101. data/app/models/tomify/concerns/sidebar.rb +18 -0
  102. data/app/models/tomify/concerns/token.rb +18 -0
  103. data/app/models/tomify/concerns/upload.rb +36 -0
  104. data/app/models/tomify/concerns/user.rb +52 -0
  105. data/app/models/tomify/page.rb +3 -0
  106. data/app/models/tomify/setting/boolean.rb +11 -0
  107. data/app/models/tomify/setting/json.rb +11 -0
  108. data/app/models/tomify/setting/text.rb +3 -0
  109. data/app/models/tomify/setting/uploader.rb +6 -0
  110. data/app/models/tomify/setting.rb +48 -0
  111. data/app/models/tomify/sidebar.rb +3 -0
  112. data/app/models/tomify/token.rb +3 -0
  113. data/app/models/tomify/upload.rb +3 -0
  114. data/app/models/tomify/user.rb +3 -0
  115. data/app/models/tomify_record.rb +33 -0
  116. data/app/uploaders/tomify/file_uploader.rb +5 -0
  117. data/app/uploaders/tomify/setting_uploader.rb +7 -0
  118. data/app/uploaders/tomify_uploader.rb +20 -0
  119. data/app/views/templates/contact.haml +24 -0
  120. data/app/views/templates/default.haml +4 -0
  121. data/app/views/tomify/defaults/_body.haml +8 -0
  122. data/app/views/tomify/defaults/_container.haml +3 -0
  123. data/app/views/tomify/defaults/_env.haml +2 -0
  124. data/app/views/tomify/defaults/_head.haml +14 -0
  125. data/app/views/tomify/defaults/_meta.haml +17 -0
  126. data/app/views/tomify/defaults/_sharing.haml +23 -0
  127. data/app/views/tomify/layouts/application.haml +4 -0
  128. data/app/views/tomify/layouts/mailer.haml +7 -0
  129. data/app/views/tomify/mailers/user_mailer/invite.haml +13 -0
  130. data/app/views/tomify/mailers/user_mailer/reset_password.haml +17 -0
  131. data/config/initializers/assets.rb +7 -0
  132. data/config/initializers/database.rb +3 -0
  133. data/config/initializers/flash_patch.rb +15 -0
  134. data/config/initializers/mailer.rb +4 -0
  135. data/config/initializers/renderers.rb +4 -0
  136. data/config/initializers/settings.rb +5 -0
  137. data/config/routes.rb +38 -0
  138. data/db/migrate/19900000000000_create_users.rb +13 -0
  139. data/db/migrate/19900000000001_create_tokens.rb +10 -0
  140. data/db/migrate/19900000000002_create_settings.rb +13 -0
  141. data/db/migrate/19900000000003_create_uploads.rb +13 -0
  142. data/db/migrate/19900000000004_create_pages.rb +22 -0
  143. data/db/migrate/19900000000005_create_sidebars.rb +12 -0
  144. data/db/seeds.rb +43 -0
  145. data/lib/generators/tomify/bundle/bundle_generator.rb +12 -0
  146. data/lib/generators/tomify/bundle/templates/default.js +2 -0
  147. data/lib/generators/tomify/bundle/templates/react.js +3 -0
  148. data/lib/previews/tomify/user_preview.rb +9 -0
  149. data/lib/previews/tomify_preview.rb +6 -0
  150. data/lib/tasks/package.rake +40 -0
  151. data/lib/tomify/constantly.rb +12 -0
  152. data/lib/tomify/engine.rb +11 -0
  153. data/lib/tomify/version.rb +3 -0
  154. data/lib/tomify.rb +40 -0
  155. data/vendor/tomify/development/react.js +20494 -0
  156. data/vendor/tomify/production/react.js +20352 -0
  157. 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,6 @@
1
+ Component.create "Layout.Navbar",
2
+ render: ->
3
+ <nav>
4
+ <Layout.AdminNavbar />
5
+ <Layout.PublicNavbar />
6
+ </nav>
@@ -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,7 @@
1
+ @WillInitializeMixin = {
2
+ getInitialState: ->
3
+ @getInitialSetup?()
4
+ @componentWillInitialize?()
5
+
6
+ {}
7
+ }
@@ -0,0 +1,9 @@
1
+ @reactNodes = []
2
+
3
+ $ ->
4
+ $(".react-component").each ->
5
+ reactNodes.push(@)
6
+ react = $(@).data("react")
7
+ component = Component.find(react.component)
8
+ props = JSON.parse(react.props) if react.props
9
+ ReactDOM.render(React.createElement(component, props), @)
@@ -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")
@@ -0,0 +1,5 @@
1
+ @reactNodes = []
2
+
3
+ $(document).on "turbolinks:before-render", ->
4
+ ReactDOM.unmountComponentAtNode(node) for node in window.reactNodes
5
+ window.reactNodes = []