tomify 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
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 = []