octodmin 0.1.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 (153) hide show
  1. checksums.yaml +7 -0
  2. data/.bowerrc +3 -0
  3. data/.gitignore +24 -0
  4. data/.rspec +2 -0
  5. data/.travis.yml +9 -0
  6. data/CHANGELOG.md +4 -0
  7. data/Gemfile +17 -0
  8. data/LICENSE.md +22 -0
  9. data/README.md +117 -0
  10. data/Rakefile +38 -0
  11. data/_config.yml +27 -0
  12. data/_deploy.yml +13 -0
  13. data/app/.DS_Store +0 -0
  14. data/app/assets/.DS_Store +0 -0
  15. data/app/assets/javascripts/.DS_Store +0 -0
  16. data/app/assets/javascripts/app.js.coffee +13 -0
  17. data/app/assets/javascripts/components/header.js.cjsx +69 -0
  18. data/app/assets/javascripts/components/posts.js.cjsx +309 -0
  19. data/app/assets/javascripts/components/router.js.cjsx +56 -0
  20. data/app/assets/stylesheets/.DS_Store +0 -0
  21. data/app/assets/stylesheets/app.scss +75 -0
  22. data/app/config/environment.rb +7 -0
  23. data/app/config/routes.rb +19 -0
  24. data/app/config/sprockets.rb +36 -0
  25. data/app/controllers/deploys/create.rb +20 -0
  26. data/app/controllers/frontend/index.rb +8 -0
  27. data/app/controllers/posts/create.rb +18 -0
  28. data/app/controllers/posts/destroy.rb +14 -0
  29. data/app/controllers/posts/index.rb +13 -0
  30. data/app/controllers/posts/restore.rb +14 -0
  31. data/app/controllers/posts/revert.rb +14 -0
  32. data/app/controllers/posts/show.rb +13 -0
  33. data/app/controllers/posts/update.rb +23 -0
  34. data/app/controllers/site/show.rb +12 -0
  35. data/app/controllers/syncs/create.rb +62 -0
  36. data/app/controllers/version/show.rb +12 -0
  37. data/app/octodmin.rb +56 -0
  38. data/app/public/.DS_Store +0 -0
  39. data/app/public/assets/app.css +7960 -0
  40. data/app/public/assets/app.css.gz +7960 -0
  41. data/app/public/assets/app.js +39181 -0
  42. data/app/public/assets/app.js.gz +39181 -0
  43. data/app/public/assets/bootstrap/dist/fonts/glyphicons-halflings-regular.eot +0 -0
  44. data/app/public/assets/bootstrap/dist/fonts/glyphicons-halflings-regular.eot.gz +0 -0
  45. data/app/public/assets/bootstrap/dist/fonts/glyphicons-halflings-regular.svg +288 -0
  46. data/app/public/assets/bootstrap/dist/fonts/glyphicons-halflings-regular.svg.gz +288 -0
  47. data/app/public/assets/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf +0 -0
  48. data/app/public/assets/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf.gz +0 -0
  49. data/app/public/assets/bootstrap/dist/fonts/glyphicons-halflings-regular.woff +0 -0
  50. data/app/public/assets/bootstrap/dist/fonts/glyphicons-halflings-regular.woff.gz +0 -0
  51. data/app/public/assets/bootstrap/dist/fonts/glyphicons-halflings-regular.woff2 +0 -0
  52. data/app/public/assets/bootstrap/dist/fonts/glyphicons-halflings-regular.woff2.gz +0 -0
  53. data/app/public/assets/bootstrap/fonts/glyphicons-halflings-regular.eot +0 -0
  54. data/app/public/assets/bootstrap/fonts/glyphicons-halflings-regular.eot.gz +0 -0
  55. data/app/public/assets/bootstrap/fonts/glyphicons-halflings-regular.svg +288 -0
  56. data/app/public/assets/bootstrap/fonts/glyphicons-halflings-regular.svg.gz +288 -0
  57. data/app/public/assets/bootstrap/fonts/glyphicons-halflings-regular.ttf +0 -0
  58. data/app/public/assets/bootstrap/fonts/glyphicons-halflings-regular.ttf.gz +0 -0
  59. data/app/public/assets/bootstrap/fonts/glyphicons-halflings-regular.woff +0 -0
  60. data/app/public/assets/bootstrap/fonts/glyphicons-halflings-regular.woff.gz +0 -0
  61. data/app/public/assets/bootstrap/fonts/glyphicons-halflings-regular.woff2 +0 -0
  62. data/app/public/assets/bootstrap/fonts/glyphicons-halflings-regular.woff2.gz +0 -0
  63. data/app/public/assets/bootstrap-sass/assets/fonts/bootstrap/glyphicons-halflings-regular.eot +0 -0
  64. data/app/public/assets/bootstrap-sass/assets/fonts/bootstrap/glyphicons-halflings-regular.eot.gz +0 -0
  65. data/app/public/assets/bootstrap-sass/assets/fonts/bootstrap/glyphicons-halflings-regular.svg +288 -0
  66. data/app/public/assets/bootstrap-sass/assets/fonts/bootstrap/glyphicons-halflings-regular.svg.gz +288 -0
  67. data/app/public/assets/bootstrap-sass/assets/fonts/bootstrap/glyphicons-halflings-regular.ttf +0 -0
  68. data/app/public/assets/bootstrap-sass/assets/fonts/bootstrap/glyphicons-halflings-regular.ttf.gz +0 -0
  69. data/app/public/assets/bootstrap-sass/assets/fonts/bootstrap/glyphicons-halflings-regular.woff +0 -0
  70. data/app/public/assets/bootstrap-sass/assets/fonts/bootstrap/glyphicons-halflings-regular.woff.gz +0 -0
  71. data/app/public/assets/bootstrap-sass/assets/fonts/bootstrap/glyphicons-halflings-regular.woff2 +0 -0
  72. data/app/public/assets/bootstrap-sass/assets/fonts/bootstrap/glyphicons-halflings-regular.woff2.gz +0 -0
  73. data/app/public/assets/bootswatch/fonts/glyphicons-halflings-regular.eot +0 -0
  74. data/app/public/assets/bootswatch/fonts/glyphicons-halflings-regular.eot.gz +0 -0
  75. data/app/public/assets/bootswatch/fonts/glyphicons-halflings-regular.svg +288 -0
  76. data/app/public/assets/bootswatch/fonts/glyphicons-halflings-regular.svg.gz +288 -0
  77. data/app/public/assets/bootswatch/fonts/glyphicons-halflings-regular.ttf +0 -0
  78. data/app/public/assets/bootswatch/fonts/glyphicons-halflings-regular.ttf.gz +0 -0
  79. data/app/public/assets/bootswatch/fonts/glyphicons-halflings-regular.woff +0 -0
  80. data/app/public/assets/bootswatch/fonts/glyphicons-halflings-regular.woff.gz +0 -0
  81. data/app/public/assets/bootswatch/fonts/glyphicons-halflings-regular.woff2 +0 -0
  82. data/app/public/assets/bootswatch/fonts/glyphicons-halflings-regular.woff2.gz +0 -0
  83. data/app/public/assets/bootswatch-dist/fonts/glyphicons-halflings-regular.eot +0 -0
  84. data/app/public/assets/bootswatch-dist/fonts/glyphicons-halflings-regular.eot.gz +0 -0
  85. data/app/public/assets/bootswatch-dist/fonts/glyphicons-halflings-regular.svg +229 -0
  86. data/app/public/assets/bootswatch-dist/fonts/glyphicons-halflings-regular.svg.gz +229 -0
  87. data/app/public/assets/bootswatch-dist/fonts/glyphicons-halflings-regular.ttf +0 -0
  88. data/app/public/assets/bootswatch-dist/fonts/glyphicons-halflings-regular.ttf.gz +0 -0
  89. data/app/public/assets/bootswatch-dist/fonts/glyphicons-halflings-regular.woff +0 -0
  90. data/app/public/assets/bootswatch-dist/fonts/glyphicons-halflings-regular.woff.gz +0 -0
  91. data/app/public/assets/fontawesome/fonts/fontawesome-webfont.eot +0 -0
  92. data/app/public/assets/fontawesome/fonts/fontawesome-webfont.eot.gz +0 -0
  93. data/app/public/assets/fontawesome/fonts/fontawesome-webfont.svg +565 -0
  94. data/app/public/assets/fontawesome/fonts/fontawesome-webfont.svg.gz +565 -0
  95. data/app/public/assets/fontawesome/fonts/fontawesome-webfont.ttf +0 -0
  96. data/app/public/assets/fontawesome/fonts/fontawesome-webfont.ttf.gz +0 -0
  97. data/app/public/assets/fontawesome/fonts/fontawesome-webfont.woff +0 -0
  98. data/app/public/assets/fontawesome/fonts/fontawesome-webfont.woff.gz +0 -0
  99. data/app/public/assets/fontawesome/fonts/fontawesome-webfont.woff2 +0 -0
  100. data/app/public/assets/fontawesome/fonts/fontawesome-webfont.woff2.gz +0 -0
  101. data/app/public/assets/manifest.json +1 -0
  102. data/app/templates/application.html.erb +13 -0
  103. data/app/templates/frontend/index.html.erb +1 -0
  104. data/app/views/application_layout.rb +5 -0
  105. data/app/views/deploys/create.rb +10 -0
  106. data/app/views/frontend/index.rb +5 -0
  107. data/app/views/posts/create.rb +10 -0
  108. data/app/views/posts/destroy.rb +10 -0
  109. data/app/views/posts/index.rb +10 -0
  110. data/app/views/posts/restore.rb +10 -0
  111. data/app/views/posts/revert.rb +10 -0
  112. data/app/views/posts/show.rb +10 -0
  113. data/app/views/posts/update.rb +10 -0
  114. data/app/views/site/show.rb +10 -0
  115. data/app/views/syncs/create.rb +10 -0
  116. data/app/views/version/show.rb +10 -0
  117. data/bower.json +16 -0
  118. data/config.ru +5 -0
  119. data/lib/octodmin/app.rb +2 -0
  120. data/lib/octodmin/post.rb +161 -0
  121. data/lib/octodmin/site.rb +61 -0
  122. data/lib/octodmin/version.rb +3 -0
  123. data/lib/octodmin.rb +7 -0
  124. data/octodmin.gemspec +24 -0
  125. data/sample/.gitignore +2 -0
  126. data/sample/_includes/footer.html +55 -0
  127. data/sample/_includes/head.html +12 -0
  128. data/sample/_includes/header.html +27 -0
  129. data/sample/_layouts/default.html +20 -0
  130. data/sample/_layouts/page.html +14 -0
  131. data/sample/_layouts/post.html +15 -0
  132. data/sample/_posts/2015-01-29-welcome-to-jekyll.markdown +25 -0
  133. data/sample/_posts/2015-01-30-test.markdown +10 -0
  134. data/sample/_sass/_base.scss +204 -0
  135. data/sample/_sass/_layout.scss +236 -0
  136. data/sample/_sass/_syntax-highlighting.scss +67 -0
  137. data/sample/_templates/draft +4 -0
  138. data/sample/_templates/page +4 -0
  139. data/sample/_templates/post +8 -0
  140. data/sample/about.md +11 -0
  141. data/sample/css/main.scss +52 -0
  142. data/sample/feed.xml +30 -0
  143. data/sample/index.html +23 -0
  144. data/spec/api/deploys_spec.rb +36 -0
  145. data/spec/api/posts_spec.rb +411 -0
  146. data/spec/api/site_spec.rb +18 -0
  147. data/spec/api/syncs_spec.rb +81 -0
  148. data/spec/api/version_spec.rb +14 -0
  149. data/spec/frontend/assets_spec.rb +23 -0
  150. data/spec/frontend/home_spec.rb +16 -0
  151. data/spec/spec_helper.rb +31 -0
  152. data/tmp/.keep +0 -0
  153. metadata +259 -0
@@ -0,0 +1,309 @@
1
+ # @cjsx React.DOM
2
+
3
+ @Posts = React.createClass(
4
+ propTypes:
5
+ site: React.PropTypes.object.isRequired
6
+
7
+ getInitialState: ->
8
+ { alert: null, loading: false, posts: null }
9
+
10
+ fetchPosts: ->
11
+ return if @state.loading
12
+ @setState(loading: true)
13
+ $.get("/api/posts").always(@handleResponse).done(@handleSuccess).fail(@handleError)
14
+
15
+ handleResponse: ->
16
+ @setState(loading: false) if @isMounted()
17
+
18
+ handleSuccess: (response) ->
19
+ @setState(alert: null, posts: response.posts) if @isMounted()
20
+
21
+ handleError: (error) ->
22
+ @setState(alert: "Could not load posts: #{error.statusText} (#{error.status})") if @isMounted()
23
+
24
+ componentWillMount: ->
25
+ @fetchPosts()
26
+ $(document).on("fetchPosts", @fetchPosts)
27
+ @timer = setInterval(@fetchPosts, 5000)
28
+
29
+ componentWillUnmount: ->
30
+ $(document).off("fetchPosts", @fetchPosts)
31
+ clearInterval(@timer)
32
+
33
+ render: ->
34
+ <div>
35
+ {<div className="alert alert-danger">{@state.alert}</div> if @state.alert}
36
+ <NewPostPartial site={@props.site} />
37
+
38
+ <Loader loaded={!!@state.posts}>
39
+ {@state.posts?.map(((post) ->
40
+ <PostPartial key={post.identifier} site={@props.site} post={post} />
41
+ ).bind(this))}
42
+ </Loader>
43
+
44
+ <footer className="row">
45
+ <div className="col-sm-3">
46
+ <iframe src="http://ghbtns.com/github-btn.html?user=krasnoukhov&repo=octodmin&type=watch&count=true" allowTransparency="true" frameBorder="0" scrolling="no" style={border: "none", overflow: "hidden", width: "170px", height: "20px"}></iframe>
47
+ </div>
48
+ <div className="col-sm-9" style={textAlign: "right"}>
49
+ <a href="https://github.com/krasnoukhov/octodmin" target="_blank">Octodmin</a>, open source project by <a href="http://www.krasnoukhov.com" target="_blank">Dmitry Krasnoukhov</a>
50
+ </div>
51
+ </footer>
52
+ </div>
53
+ )
54
+
55
+ @NewPostPartial = React.createClass(
56
+ mixins: [ReactRouter.Navigation]
57
+
58
+ propTypes:
59
+ site: React.PropTypes.object.isRequired
60
+
61
+ getInitialState: ->
62
+ { alert: null, loading: false }
63
+
64
+ form: ->
65
+ $(@refs.form.getDOMNode())
66
+
67
+ handleSubmit: (event) ->
68
+ event.preventDefault()
69
+ return if @state.loading
70
+
71
+ @setState(loading: true)
72
+ $.post("/api/posts", @form().serialize()).always(@handleResponse).done(@handleSuccess).fail(@handleError)
73
+
74
+ handleResponse: ->
75
+ @setState(loading: false)
76
+
77
+ handleSuccess: (response) ->
78
+ @setState(alert: null)
79
+ @form()[0].reset()
80
+ $(document).trigger("fetchPosts")
81
+ @transitionTo("post_edit", post_id: response.posts.identifier)
82
+
83
+ handleError: (error) ->
84
+ @setState(alert: error.responseJSON?.errors.join(", "))
85
+
86
+ render: ->
87
+ <div className="panel panel-default">
88
+ <div className="panel-body">
89
+ {<div className="alert alert-danger">{@state.alert}</div> if @state.alert}
90
+
91
+ <form ref="form" className="form-inline" onSubmit={@handleSubmit}>
92
+ <fieldset className="row" disabled={@state.loading}>
93
+ <div className="col-sm-9 form-group form-group-lg">
94
+ <input className="form-control" style={width: '100%'} name="title" placeholder="Type the title of your new post here" required />
95
+ </div>
96
+ <div className="col-sm-3 buttons">
97
+ <button type="submit" className="btn btn-lg btn-default">Create</button>
98
+ </div>
99
+ </fieldset>
100
+ </form>
101
+ </div>
102
+ </div>
103
+ )
104
+
105
+ @PostPartial = React.createClass(
106
+ mixins: [ReactRouter.Navigation]
107
+
108
+ propTypes:
109
+ site: React.PropTypes.object.isRequired
110
+ post: React.PropTypes.object.isRequired
111
+
112
+ getInitialState: ->
113
+ { alert: null, loading: false }
114
+
115
+ panelClass: ->
116
+ if @props.post.added
117
+ "success"
118
+ else if @props.post.changed
119
+ "warning"
120
+ else if @props.post.deleted
121
+ "danger"
122
+ else
123
+ "default"
124
+
125
+ handleEdit: ->
126
+ @transitionTo("post_edit", post_id: @props.post.identifier)
127
+
128
+ handleRevert: ->
129
+ return if @state.loading
130
+ @setState(loading: true)
131
+
132
+ $.ajax(type: "PATCH", url: "/api/posts/#{@props.post.identifier}/revert").
133
+ always(@handleResponse).
134
+ done(@handleSuccess).
135
+ fail(@handleError)
136
+
137
+ handleDelete: ->
138
+ if @props.post.added
139
+ return unless confirm("Are you sure? This can't be undone")
140
+
141
+ return if @state.loading
142
+ @setState(loading: true)
143
+
144
+ $.ajax(type: "DELETE", url: "/api/posts/#{@props.post.identifier}").
145
+ always(@handleResponse).
146
+ done(@handleSuccess).
147
+ fail(@handleError)
148
+
149
+ handleRestore: ->
150
+ return if @state.loading
151
+ @setState(loading: true)
152
+
153
+ $.ajax(type: "PATCH", url: "/api/posts/#{@props.post.identifier}/restore").
154
+ always(@handleResponse).
155
+ done(@handleSuccess).
156
+ fail(@handleError)
157
+
158
+ handleResponse: ->
159
+ @setState(loading: false)
160
+
161
+ handleSuccess: (response) ->
162
+ @setState(alert: null)
163
+ $(document).trigger("fetchPosts")
164
+
165
+ handleError: (error) ->
166
+ @setState(alert: "Could not load post: #{error.statusText} (#{error.status})")
167
+
168
+ render: ->
169
+ <div className="panel panel-#{@panelClass()}">
170
+ <div className="panel-heading clearfix">
171
+ <h3 className="panel-title pull-left">{@props.post.title}</h3>
172
+ <div className="pull-right">
173
+ {moment((new Date(@props.post.date)).toISOString()).format("LLL")}
174
+ </div>
175
+ </div>
176
+ <div className="panel-body">
177
+ {<div className="alert alert-danger">{@state.alert}</div> if @state.alert}
178
+
179
+ <div className="row">
180
+ <div className="col-sm-9 excerpt" dangerouslySetInnerHTML={{__html: @props.post.excerpt }} />
181
+ <div className="col-sm-3 buttons">
182
+ {if @props.post.deleted
183
+ <div className="btn-group btn-group-sm">
184
+ <button className="btn btn-default #{'disabled' if @state.loading}" onClick={@handleRestore}>Restore</button>
185
+ </div>
186
+ else
187
+ <div className="btn-group btn-group-sm">
188
+ <button className="btn btn-default #{'disabled' if @state.loading}" onClick={@handleEdit}>Edit</button>
189
+ {if @props.post.changed
190
+ <button className="btn btn-warning #{'disabled' if @state.loading}" onClick={@handleRevert}>Revert</button>
191
+ }
192
+ <button className="btn btn-danger #{'disabled' if @state.loading}" onClick={@handleDelete}>Delete</button>
193
+ </div>
194
+ }
195
+ </div>
196
+ </div>
197
+ </div>
198
+ </div>
199
+ )
200
+
201
+ @PostEdit = React.createClass(
202
+ mixins: [ReactRouter.State, ReactRouter.Navigation]
203
+
204
+ propTypes:
205
+ site: React.PropTypes.object.isRequired
206
+
207
+ getInitialState: ->
208
+ { alert: null, success: null, loading: false, post: null }
209
+
210
+ form: ->
211
+ $(@refs.form.getDOMNode())
212
+
213
+ frontMatter: ->
214
+ @props.site.octodmin.front_matter
215
+
216
+ fetchPost: ->
217
+ return if @state.loading
218
+ @setState(loading: true)
219
+ $.get("/api/posts/#{@getParams().post_id}").always(@handleResponse).done(@handleSuccess).fail(@handleError)
220
+
221
+ handleBack: (event) ->
222
+ event.preventDefault()
223
+ @transitionTo("app")
224
+
225
+ handleSubmit: (event) ->
226
+ event.preventDefault()
227
+ return if @state.loading
228
+ @setState(loading: true)
229
+
230
+ data = @form().serializeObject()
231
+ $.ajax(type: "PATCH", url: "/api/posts/#{@state.post.identifier}", data: data).
232
+ always(@handleResponse).
233
+ done(@handleFormSuccess).
234
+ fail(@handleError)
235
+
236
+ handleResponse: ->
237
+ @setState(loading: false)
238
+
239
+ handleSuccess: (response) ->
240
+ @setState(alert: null, post: response.posts)
241
+
242
+ handleFormSuccess: (response) ->
243
+ @setState(alert: null, success: "Post is updated")
244
+ setTimeout(@removeSuccess, 5000)
245
+ @transitionTo("post_edit", post_id: response.posts.identifier)
246
+
247
+ handleError: (error) ->
248
+ @setState(alert: error.responseJSON?.errors.join(", "))
249
+
250
+ removeSuccess: ->
251
+ @setState(success: null) if @isMounted()
252
+
253
+ componentWillMount: ->
254
+ @fetchPost()
255
+
256
+ componentDidUpdate: ->
257
+ return if !@state.post || !@refs.editor
258
+ return if !/markdown$/.test(@state.post.path) && !/md$/.test(@state.post.path)
259
+
260
+ $(@refs.editor.getDOMNode()).markdown(
261
+ autofocus: false
262
+ savable: false
263
+ iconlibrary: "fa"
264
+ resize: "vertical"
265
+ fullscreen:
266
+ enable: false
267
+ )
268
+
269
+ render: ->
270
+ <Loader loaded={!!@state.post}>
271
+ {<div className="alert alert-danger">{@state.alert}</div> if @state.alert}
272
+ {<div className="alert alert-success">{@state.success}</div> if @state.success}
273
+
274
+ {if @state.post
275
+ <form ref="form" className="form-horizontal post-edit" onSubmit={@handleSubmit}>
276
+ <fieldset disabled={@state.loading}>
277
+ <div className="form-group">
278
+ <div className="col-sm-8">
279
+ <input type={@frontMatter().title.type} className="form-control" name="title" placeholder="Title" defaultValue={@state.post.title} required />
280
+ </div>
281
+ <div className="col-sm-4">
282
+ <input type={@frontMatter().date.type} className="form-control" name="date" placeholder="Date" defaultValue={@state.post.date} required />
283
+ </div>
284
+ </div>
285
+
286
+ <textarea ref="editor" className="md-content" name="content" rows="15" defaultValue={@state.post.content} placeholder="Post content" required></textarea>
287
+
288
+ {Object.keys(@frontMatter()).map(((key) ->
289
+ if key != "title" && key != "date"
290
+ config = @frontMatter()[key]
291
+ title = key.charAt(0).toUpperCase() + key.slice(1)
292
+
293
+ <div key={key} className="form-group">
294
+ <label htmlFor={key} className="col-sm-1 control-label">{title}</label>
295
+ <div className="col-sm-11">
296
+ <input type={config.type} className="form-control" name={key} placeholder={title} defaultValue={@state.post[key]} />
297
+ </div>
298
+ </div>
299
+ ).bind(this))}
300
+
301
+ <div className="buttons">
302
+ <button type="submit" className="btn btn-lg btn-success pull-right">Save</button>
303
+ <button className="btn btn-lg btn-default pull-right" onClick={@handleBack}>Back</button>
304
+ </div>
305
+ </fieldset>
306
+ </form>
307
+ }
308
+ </Loader>
309
+ )
@@ -0,0 +1,56 @@
1
+ # @cjsx React.DOM
2
+
3
+ @Router = ReactRouter
4
+ @Link = Router.Link
5
+ @Routes = Router.Routes
6
+ @Route = Router.Route
7
+ @DefaultRoute = Router.DefaultRoute
8
+ @RouteHandler = Router.RouteHandler
9
+
10
+ @Container = React.createClass(
11
+ render: ->
12
+ <div className="container">
13
+ {@props.children}
14
+ </div>
15
+ )
16
+
17
+ @App = React.createClass(
18
+ getInitialState: ->
19
+ { site: null }
20
+
21
+ fetchSite: ->
22
+ $.get("/api/site").done(@handleSuccess).fail(@handleError)
23
+
24
+ handleSuccess: (response) ->
25
+ @setState(site: response.sites)
26
+ $("title").text("Octodmin – #{response.sites.title}")
27
+
28
+ handleError: (error) ->
29
+ alert("Could not load site: #{error.statusText} (#{error.status})")
30
+
31
+ componentWillMount: ->
32
+ @fetchSite()
33
+ $(document).on("fetchSite", @fetchSite)
34
+
35
+ componentWillUnmount: ->
36
+ $(document).off("fetchSite", @fetchSite)
37
+
38
+ render: ->
39
+ <Loader loaded={!!@state.site}>
40
+ <Container>
41
+ <Header site={@state.site} />
42
+ <RouteHandler site={@state.site} />
43
+ </Container>
44
+ </Loader>
45
+ )
46
+
47
+ routes =
48
+ <Route path="/" name="app" handler={@App}>
49
+ <Route path="/posts/:post_id/edit" name="post_edit" handler={@PostEdit} />
50
+ <DefaultRoute handler={@Posts} />
51
+ </Route>
52
+
53
+
54
+ Router.run(routes, Router.HistoryLocation, (Handler) ->
55
+ React.render(<Handler />, document.getElementById("app"))
56
+ )
Binary file
@@ -0,0 +1,75 @@
1
+ @charset "utf-8";
2
+ $fa-font-path: "/assets/fontawesome/fonts";
3
+ @import "bootswatch/paper/variables";
4
+ $icon-font-path: "/assets/bootstrap/fonts/";
5
+ //= require bootstrap-markdown
6
+ @import
7
+ "bootstrap-sass/assets/stylesheets/bootstrap",
8
+ "bootswatch/paper/bootswatch",
9
+ "fontawesome/scss/font-awesome"
10
+ ;
11
+
12
+ body {
13
+ padding-bottom: 20px;
14
+ }
15
+
16
+ a, button, input, textarea, *:focus {
17
+ outline: 0 !important;
18
+ outline-style: none !important;
19
+ }
20
+
21
+ .navbar {
22
+ margin-bottom: 20px;
23
+
24
+ .navbar-right {
25
+ margin-right: 0;
26
+
27
+ .btn {
28
+ margin-left: 10px;
29
+ }
30
+ }
31
+ }
32
+
33
+ .panel {
34
+ .panel-heading {
35
+ line-height: 26px;
36
+
37
+ h3 {
38
+ font-size: 26px;
39
+ }
40
+ }
41
+
42
+ .panel-body {
43
+ .excerpt {
44
+ :first-child {
45
+ margin-top: 0;
46
+ }
47
+
48
+ :last-child {
49
+ margin-bottom: 0;
50
+ }
51
+ }
52
+
53
+ .buttons {
54
+ text-align: center;
55
+ }
56
+ }
57
+ }
58
+
59
+ .post-edit {
60
+ .md-editor {
61
+ .md-header {
62
+ margin-bottom: 15px;
63
+ }
64
+ }
65
+
66
+ .md-content {
67
+ width: 100%;
68
+ }
69
+
70
+ .buttons {
71
+ .btn {
72
+ margin-left: 10px;
73
+ }
74
+ }
75
+ }
@@ -0,0 +1,7 @@
1
+ require "lotus/setup"
2
+ ENV["RACK_ENV"] = ENV["LOTUS_ENV"] ||= "development"
3
+
4
+ require_relative "../octodmin"
5
+ Lotus::Container.configure do
6
+ mount Octodmin::App, at: "/"
7
+ end
@@ -0,0 +1,19 @@
1
+ namespace "api" do
2
+ resource :version, only: [:show]
3
+ resource :site, only: [:show]
4
+ resources :posts do
5
+ member do
6
+ patch :restore
7
+ patch :revert
8
+ end
9
+ end
10
+ resources :syncs, only: [:create]
11
+ resources :deploys, only: [:create]
12
+ end
13
+
14
+ if ENV["LOTUS_ENV"] != "production"
15
+ mount Octodmin.sprockets, at: "/assets"
16
+ end
17
+
18
+ get "/posts*", to: "frontend#index"
19
+ get "/", to: "frontend#index", as: :home
@@ -0,0 +1,36 @@
1
+ require "sprockets"
2
+ require "coffee_script"
3
+ require "coffee-react"
4
+ require "bower"
5
+
6
+ module Octodmin
7
+ module CjsxProcessor
8
+ VERSION = '1'
9
+ SOURCE_VERSION = ::CoffeeScript::Source.version
10
+
11
+ def self.cache_key
12
+ @cache_key ||= [name, SOURCE_VERSION, VERSION].freeze
13
+ end
14
+
15
+ def self.call(input)
16
+ data = input[:data]
17
+ input[:cache].fetch(self.cache_key + [data]) do
18
+ ::CoffeeScript.compile(::CoffeeReact.transform(data))
19
+ end
20
+ end
21
+ end
22
+
23
+ def self.sprockets
24
+ assets_path = File.expand_path("../../assets", __FILE__)
25
+ tmp_path = File.expand_path("../../../tmp", __FILE__)
26
+
27
+ sprockets = ::Sprockets::Environment.new
28
+ sprockets.append_path "#{assets_path}/stylesheets"
29
+ sprockets.append_path "#{assets_path}/javascripts"
30
+ sprockets.append_path "#{assets_path}/fonts"
31
+ sprockets.append_path Bower.environment.directory
32
+ sprockets.register_engine ".cjsx", CjsxProcessor
33
+ sprockets.cache = Sprockets::Cache::FileStore.new(tmp_path)
34
+ sprockets
35
+ end
36
+ end
@@ -0,0 +1,20 @@
1
+ module Octodmin::Controllers::Deploys
2
+ class Create
3
+ include Octodmin::Action
4
+ expose :message
5
+
6
+ def call(params)
7
+ self.format = :json
8
+
9
+ site = Octodmin::Site.new
10
+ site.process
11
+
12
+ options = site.config["octodmin"]["deploys"].first
13
+ Octopress::Deploy.push(options)
14
+
15
+ @message = "Deployed successfully"
16
+ rescue SystemExit => e
17
+ halt 400, JSON.dump(errors: [e.message])
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,8 @@
1
+ module Octodmin::Controllers::Frontend
2
+ class Index
3
+ include Octodmin::Action
4
+
5
+ def call(params)
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,18 @@
1
+ module Octodmin::Controllers::Posts
2
+ class Create
3
+ include Octodmin::Action
4
+ expose :post
5
+
6
+ params do
7
+ param :title, presence: true
8
+ end
9
+
10
+ def call(params)
11
+ self.format = :json
12
+ halt 400, JSON.dump(errors: ["Required param `title` is not specified"]) unless params.valid?
13
+
14
+ @post = Octodmin::Post.create(title: params[:title])
15
+ halt 400, JSON.dump(errors: ["Post with specified `title` already exists"]) unless @post
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,14 @@
1
+ module Octodmin::Controllers::Posts
2
+ class Destroy
3
+ include Octodmin::Action
4
+ expose :post
5
+
6
+ def call(params)
7
+ self.format = :json
8
+
9
+ @post = Octodmin::Post.find(params[:id])
10
+ halt 400, JSON.dump(errors: ["Could not find post"]) unless @post
11
+ @post.delete
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,13 @@
1
+ module Octodmin::Controllers::Posts
2
+ class Index
3
+ include Octodmin::Action
4
+ expose :posts
5
+
6
+ def call(params)
7
+ self.format = :json
8
+
9
+ site = Octodmin::Site.new
10
+ @posts = site.posts
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,14 @@
1
+ module Octodmin::Controllers::Posts
2
+ class Restore
3
+ include Octodmin::Action
4
+ expose :post
5
+
6
+ def call(params)
7
+ self.format = :json
8
+
9
+ @post = Octodmin::Post.find(params[:id])
10
+ halt 400, JSON.dump(errors: ["Could not find post"]) unless @post
11
+ @post.restore
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ module Octodmin::Controllers::Posts
2
+ class Revert
3
+ include Octodmin::Action
4
+ expose :post
5
+
6
+ def call(params)
7
+ self.format = :json
8
+
9
+ @post = Octodmin::Post.find(params[:id])
10
+ halt 400, JSON.dump(errors: ["Could not find post"]) unless @post
11
+ @post.revert
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,13 @@
1
+ module Octodmin::Controllers::Posts
2
+ class Show
3
+ include Octodmin::Action
4
+ expose :post
5
+
6
+ def call(params)
7
+ self.format = :json
8
+
9
+ @post = Octodmin::Post.find(params[:id])
10
+ halt 400, JSON.dump(errors: ["Could not find post"]) unless @post
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,23 @@
1
+ module Octodmin::Controllers::Posts
2
+ class Update
3
+ include Octodmin::Action
4
+ expose :post
5
+
6
+ params do
7
+ param :layout, presence: true
8
+ param :title, presence: true
9
+ param :slug, presence: true
10
+ param :date, presence: true
11
+ param :content, presence: true
12
+ end
13
+
14
+ def call(params)
15
+ self.format = :json
16
+
17
+ @post = Octodmin::Post.find(params.env["router.params"][:id])
18
+ halt 400, JSON.dump(errors: ["Could not find post"]) unless @post
19
+ halt 400, JSON.dump(errors: ["Required params are not specified"]) unless params.valid?
20
+ @post.update(params.env["rack.request.form_hash"].dup)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,12 @@
1
+ module Octodmin::Controllers::Site
2
+ class Show
3
+ include Octodmin::Action
4
+ expose :site
5
+
6
+ def call(params)
7
+ self.format = :json
8
+
9
+ @site = Octodmin::Site.new
10
+ end
11
+ end
12
+ end