outpost-cms 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (168) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.md +295 -0
  3. data/Rakefile +26 -0
  4. data/app/assets/images/glyphicons-halflings-red.png +0 -0
  5. data/app/assets/javascripts/outpost/application.js +1 -0
  6. data/app/assets/javascripts/outpost/auto_slug_field.js.coffee +53 -0
  7. data/app/assets/javascripts/outpost/base.js.coffee +1 -0
  8. data/app/assets/javascripts/outpost/date_time_input.js.coffee +108 -0
  9. data/app/assets/javascripts/outpost/field_counter.js.coffee +93 -0
  10. data/app/assets/javascripts/outpost/field_manager.js.coffee +37 -0
  11. data/app/assets/javascripts/outpost/global_plugins.js.coffee +87 -0
  12. data/app/assets/javascripts/outpost/index_manager.js.coffee +88 -0
  13. data/app/assets/javascripts/outpost/notification.js.coffee +46 -0
  14. data/app/assets/javascripts/outpost/preview.js.coffee +60 -0
  15. data/app/assets/javascripts/outpost/templates/date_field.jst.eco +3 -0
  16. data/app/assets/javascripts/outpost/templates/loading.jst.eco +11 -0
  17. data/app/assets/javascripts/outpost/templates/slug_generate_button.jst.eco +1 -0
  18. data/app/assets/javascripts/outpost/templates/time_field.jst.eco +3 -0
  19. data/app/assets/javascripts/outpost/templates.js +1 -0
  20. data/app/assets/javascripts/outpost.js +32 -0
  21. data/app/assets/stylesheets/outpost/_base.css.scss +127 -0
  22. data/app/assets/stylesheets/outpost/_edit.css.scss +13 -0
  23. data/app/assets/stylesheets/outpost/_forms.css.scss +116 -0
  24. data/app/assets/stylesheets/outpost/_index.css.scss +68 -0
  25. data/app/assets/stylesheets/outpost/_utility.css.scss +16 -0
  26. data/app/assets/stylesheets/outpost/application.css.scss +1 -0
  27. data/app/assets/stylesheets/outpost/bootstrap/bootstrap.css.scss +49 -0
  28. data/app/assets/stylesheets/outpost/bootstrap/datepicker.css.scss +301 -0
  29. data/app/assets/stylesheets/outpost.css.scss +14 -0
  30. data/app/controllers/outpost/application_controller.rb +40 -0
  31. data/app/controllers/outpost/base_controller.rb +3 -0
  32. data/app/controllers/outpost/errors_controller.rb +9 -0
  33. data/app/controllers/outpost/home_controller.rb +2 -0
  34. data/app/controllers/outpost/resource_controller.rb +12 -0
  35. data/app/controllers/outpost/sessions_controller.rb +36 -0
  36. data/app/helpers/authorization_helper.rb +44 -0
  37. data/app/helpers/list_helper.rb +243 -0
  38. data/app/helpers/outpost_helper.rb +49 -0
  39. data/app/helpers/render_helper.rb +41 -0
  40. data/app/helpers/utility_helper.rb +136 -0
  41. data/app/inputs/date_time_input.rb +12 -0
  42. data/app/models/permission.rb +18 -0
  43. data/app/models/user_permission.rb +4 -0
  44. data/app/views/kaminari/bootstrap/_first_page.html.erb +3 -0
  45. data/app/views/kaminari/bootstrap/_gap.html.erb +3 -0
  46. data/app/views/kaminari/bootstrap/_last_page.html.erb +3 -0
  47. data/app/views/kaminari/bootstrap/_next_page.html.erb +3 -0
  48. data/app/views/kaminari/bootstrap/_page.html.erb +3 -0
  49. data/app/views/kaminari/bootstrap/_paginator.html.erb +17 -0
  50. data/app/views/kaminari/bootstrap/_prev_page.html.erb +3 -0
  51. data/app/views/layouts/outpost/application.html.erb +101 -0
  52. data/app/views/layouts/outpost/minimal.html.erb +26 -0
  53. data/app/views/outpost/errors/error_404.html.erb +1 -0
  54. data/app/views/outpost/errors/error_500.html.erb +8 -0
  55. data/app/views/outpost/home/dashboard.html.erb +1 -0
  56. data/app/views/outpost/resource/_errors.html.erb +11 -0
  57. data/app/views/outpost/resource/_extra_fields.html.erb +1 -0
  58. data/app/views/outpost/resource/_form_fields.html.erb +9 -0
  59. data/app/views/outpost/resource/edit.html.erb +44 -0
  60. data/app/views/outpost/resource/index.html.erb +22 -0
  61. data/app/views/outpost/resource/new.html.erb +21 -0
  62. data/app/views/outpost/resource/search.html.erb +1 -0
  63. data/app/views/outpost/resource/show.html.erb +1 -0
  64. data/app/views/outpost/sessions/new.html.erb +16 -0
  65. data/app/views/outpost/shared/_add_link.html.erb +1 -0
  66. data/app/views/outpost/shared/_breadcrumbs.html.erb +15 -0
  67. data/app/views/outpost/shared/_cancel_link.html.erb +1 -0
  68. data/app/views/outpost/shared/_columns.html.erb +5 -0
  69. data/app/views/outpost/shared/_filters.html.erb +16 -0
  70. data/app/views/outpost/shared/_flash_messages.html.erb +6 -0
  71. data/app/views/outpost/shared/_form_block.html.erb +18 -0
  72. data/app/views/outpost/shared/_form_nav.html.erb +12 -0
  73. data/app/views/outpost/shared/_headers.html.erb +16 -0
  74. data/app/views/outpost/shared/_index_header.html.erb +4 -0
  75. data/app/views/outpost/shared/_list_table.html.erb +7 -0
  76. data/app/views/outpost/shared/_modal.html.erb +16 -0
  77. data/app/views/outpost/shared/_navigation.html.erb +31 -0
  78. data/app/views/outpost/shared/_notice.html.erb +1 -0
  79. data/app/views/outpost/shared/_pagination.html.erb +2 -0
  80. data/app/views/outpost/shared/_preview_errors.html.erb +9 -0
  81. data/app/views/outpost/shared/_submit_row.html.erb +50 -0
  82. data/config/routes.rb +4 -0
  83. data/lib/action_view/helpers/form_builder.rb +71 -0
  84. data/lib/outpost/breadcrumbs.rb +73 -0
  85. data/lib/outpost/config.rb +63 -0
  86. data/lib/outpost/controller/actions.rb +72 -0
  87. data/lib/outpost/controller/authentication.rb +34 -0
  88. data/lib/outpost/controller/authorization.rb +28 -0
  89. data/lib/outpost/controller/callbacks.rb +14 -0
  90. data/lib/outpost/controller/custom_errors.rb +41 -0
  91. data/lib/outpost/controller/filtering.rb +22 -0
  92. data/lib/outpost/controller/helpers.rb +52 -0
  93. data/lib/outpost/controller/ordering.rb +46 -0
  94. data/lib/outpost/controller/preferences.rb +71 -0
  95. data/lib/outpost/controller.rb +123 -0
  96. data/lib/outpost/engine.rb +10 -0
  97. data/lib/outpost/helpers/naming.rb +22 -0
  98. data/lib/outpost/helpers.rb +6 -0
  99. data/lib/outpost/hook.rb +35 -0
  100. data/lib/outpost/list/base.rb +78 -0
  101. data/lib/outpost/list/column.rb +24 -0
  102. data/lib/outpost/list/filter.rb +37 -0
  103. data/lib/outpost/list.rb +15 -0
  104. data/lib/outpost/model/authentication.rb +34 -0
  105. data/lib/outpost/model/authorization.rb +32 -0
  106. data/lib/outpost/model/identifier.rb +39 -0
  107. data/lib/outpost/model/methods.rb +23 -0
  108. data/lib/outpost/model/naming.rb +63 -0
  109. data/lib/outpost/model/routing.rb +138 -0
  110. data/lib/outpost/model/serializer.rb +27 -0
  111. data/lib/outpost/model.rb +22 -0
  112. data/lib/outpost/test.rb +21 -0
  113. data/lib/outpost/version.rb +3 -0
  114. data/lib/outpost-cms.rb +2 -0
  115. data/lib/outpost.rb +80 -0
  116. data/lib/tasks/outpost_tasks.rake +7 -0
  117. data/spec/controllers/authentication_spec.rb +62 -0
  118. data/spec/controllers/sessions_controller_spec.rb +99 -0
  119. data/spec/factories.rb +31 -0
  120. data/spec/helpers/authorization_helper_spec.rb +47 -0
  121. data/spec/helpers/list_helper_spec.rb +74 -0
  122. data/spec/helpers/outpost_helper_spec.rb +5 -0
  123. data/spec/helpers/render_helper_spec.rb +19 -0
  124. data/spec/helpers/utility_helper_spec.rb +53 -0
  125. data/spec/internal/app/controllers/application_controller.rb +3 -0
  126. data/spec/internal/app/controllers/outpost/people_controller.rb +23 -0
  127. data/spec/internal/app/controllers/outpost/pidgeons_controller.rb +5 -0
  128. data/spec/internal/app/controllers/people_controller.rb +9 -0
  129. data/spec/internal/app/controllers/pidgeons_controller.rb +3 -0
  130. data/spec/internal/app/models/person.rb +10 -0
  131. data/spec/internal/app/models/pidgeon.rb +3 -0
  132. data/spec/internal/app/models/post.rb +4 -0
  133. data/spec/internal/app/models/user.rb +4 -0
  134. data/spec/internal/app/views/people/index.html.erb +7 -0
  135. data/spec/internal/app/views/people/show.html.erb +1 -0
  136. data/spec/internal/config/database.yml +3 -0
  137. data/spec/internal/config/initializers/configuration.rb +3 -0
  138. data/spec/internal/config/initializers/outpost.rb +6 -0
  139. data/spec/internal/config/routes.rb +16 -0
  140. data/spec/internal/db/combustion_test.sqlite +0 -0
  141. data/spec/internal/db/schema.rb +44 -0
  142. data/spec/internal/db/seeds.rb +14 -0
  143. data/spec/internal/log/test.log +59277 -0
  144. data/spec/internal/public/favicon.ico +0 -0
  145. data/spec/lib/breadcrumbs_spec.rb +54 -0
  146. data/spec/lib/config_spec.rb +76 -0
  147. data/spec/lib/controller/actions_spec.rb +5 -0
  148. data/spec/lib/controller/authorization_spec.rb +4 -0
  149. data/spec/lib/controller/callbacks_spec.rb +31 -0
  150. data/spec/lib/controller/helpers_spec.rb +33 -0
  151. data/spec/lib/controller_spec.rb +25 -0
  152. data/spec/lib/helpers/naming_spec.rb +10 -0
  153. data/spec/lib/hook_spec.rb +13 -0
  154. data/spec/lib/list/base_spec.rb +96 -0
  155. data/spec/lib/list/column_spec.rb +46 -0
  156. data/spec/lib/list/filter_spec.rb +44 -0
  157. data/spec/lib/model/authentication_spec.rb +29 -0
  158. data/spec/lib/model/authorization_spec.rb +66 -0
  159. data/spec/lib/model/identifier_spec.rb +51 -0
  160. data/spec/lib/model/methods_spec.rb +8 -0
  161. data/spec/lib/model/naming_spec.rb +55 -0
  162. data/spec/lib/model/routing_spec.rb +166 -0
  163. data/spec/lib/model/serializer_spec.rb +13 -0
  164. data/spec/lib/outpost_spec.rb +34 -0
  165. data/spec/models/permission_spec.rb +10 -0
  166. data/spec/models/user_permission_spec.rb +4 -0
  167. data/spec/spec_helper.rb +22 -0
  168. metadata +411 -0
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2013 Bryan Ricker
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,295 @@
1
+ # Outpost
2
+ [![Build Status](https://travis-ci.org/SCPR/outpost.png)](https://travis-ci.org/SCPR/outpost)
3
+
4
+ A Rails Engine for quickly standing up a CMS for a Newsroom.
5
+
6
+ ## Dependencies
7
+ * `rails >= 3.2`
8
+ * `ruby >= 1.9.3`
9
+
10
+ See `.travis.yml` to see which Ruby versions are officially supported.
11
+
12
+ ## Installation
13
+ Add `gem 'outpost-cms'` to your Gemfile. The module you interact with is just
14
+ `Outpost`.
15
+
16
+ **A note about the gem/repository/module name discrepancy**
17
+ There is [another gem](http://rubygems.org/gems/outpost) called "Outpost"
18
+ which occupies the same namespace as this gem. However, the other Outpost
19
+ is meant for service monitoring, and I can't imagine a scenario where
20
+ these two gems would be used together in the same application. Therefore,
21
+ I'm keeping the module name, and just renaming the gem to `outpost-cms`
22
+ so we can both exist on RubyGems.
23
+
24
+
25
+ This gem also has some hard dependencies that aren't in the gemspec.
26
+ My goal is to reduce these dependencies as much as possible, but as this was
27
+ extracted from the KPCC application, these are fairly strict at this point.
28
+
29
+ * `simple_form` - for Rails 3.2, use `~> 2.1.0`.
30
+ For Rails 4.0, you'll need to use `~> 3.0.0.beta1`
31
+ * `kaminari` - You need to use the
32
+ [kaminari master branch](https://github.com/amatsuda/kaminari).
33
+ * `eco`
34
+ * `sass-rails`
35
+ * `bootstrap-sass`
36
+ * `coffee-rails`
37
+
38
+ ## Usage
39
+ ### Authentication
40
+ Much like Devise, Outpost provides a basic `SessionsController` and
41
+ corresponding views. To use these, just add them to your routes:
42
+
43
+ ```ruby
44
+ namespace :outpost do
45
+ resources :sessions, only: [:create, :destroy]
46
+ get 'login' => "sessions#new", as: :login
47
+ get 'logout' => "sessions#destroy", as: :logout
48
+ end
49
+ ```
50
+
51
+ Outpost also provides the `Outpost::Model::Authentication` module,
52
+ which you should include into your User model to work with the provided
53
+ `SessionsController`:
54
+
55
+ ```ruby
56
+ class User < ActiveRecord::Base
57
+ include Outpost::Model::Authentication
58
+ end
59
+ ```
60
+
61
+ Your User class should have at least the following methods:
62
+ * `password_digest` (string)
63
+ * `last_login` (datetime)
64
+ * `can_login` (boolean)
65
+ * `is_superuser` (boolean)
66
+ * `name` (string)
67
+
68
+
69
+ #### Configuration
70
+
71
+ You can set a different User class, or the attribute which the user
72
+ should use to login:
73
+
74
+ ```
75
+ Outpost::Config.configure do |config|
76
+ config.user_class = "AdminUser"
77
+ config.authentication_attribute = :username
78
+ end
79
+ ```
80
+
81
+
82
+ ##### Routes
83
+
84
+ Note that this gem doesn't provide any routes for you. It is expected that you'll want to use the path globbing to render Outpost-style 404's inside of Outpost (rather than your application's 404 page), so it gets too messy and complicated to try to combine your routes with path globbing.
85
+
86
+ You'll want to put this at the bottom of you `outpost` namespace in your routes:
87
+
88
+ ```ruby
89
+ root to: 'home#dashboard'
90
+
91
+ resources :sessions, only: [:create, :destroy]
92
+ get 'login' => "sessions#new", as: :login
93
+ get 'logout' => "sessions#destroy", as: :logout
94
+
95
+ get "*path" => 'errors#not_found'
96
+ ```
97
+
98
+
99
+ ### Authorization
100
+ Outpost comes with a built-in `Permission` model, whose only attribute is
101
+ a String `resource`, which stores a class name which you want to be
102
+ authorized throughout the application. Run this migration to set it up:
103
+
104
+
105
+ ```ruby
106
+ create_table :permissions do |t|
107
+ t.string :resource
108
+ t.timestamps
109
+ end
110
+
111
+ create_table :user_permissions do |t|
112
+ t.integer :user_id
113
+ t.integer :permission_id
114
+ t.timestamps
115
+ end
116
+
117
+ add_index :permissions, :resource
118
+ add_index :user_permissions, :user_id
119
+ add_index :user_permissions, :permission_id
120
+ ```
121
+
122
+ You can include `Outpost::Model::Authorization` into your User model
123
+ to provide the Permission association, and also add the `can_manage?`
124
+ method:
125
+
126
+ ```ruby
127
+ if !current_user.can_manage?(Post)
128
+ redirect_to outpost_root_path, alert: "Not Authorized"
129
+ end
130
+ ```
131
+
132
+ Authorization is "All-or-None"... in other words, a user can either
133
+ manage a resource or not - A user with permission for a particular model
134
+ is able to Create, Read, Update, and Delete any of those objects.
135
+
136
+ Outpost controllers will automatically authorize their resource. Within
137
+ views, you can use one of the provided helpers to guard a block of text
138
+ or a link:
139
+
140
+ ```erb
141
+ <%= guard Post do %>
142
+ Only users who are authorized for Posts will see this.
143
+ <% end %>
144
+
145
+ <%= guarded_link_to Post, "Linked if authorized, plaintext if not", posts_path %>
146
+ ```
147
+
148
+
149
+ ### User Preferences
150
+ Preferences are stored in the session, and on a per-resource basis.
151
+ Outpost provides built-in hooks in the controller and views for
152
+ Order (attribute) and Sort Mode ("asc", "desc"). In order to manage other
153
+ preferences, you'll want to make use of a handful of methods that get
154
+ mixed-in to your Outpost controllers:
155
+
156
+ * `preference` - Access a preference's value.
157
+ * `set_preference` - Set a preference's value.
158
+ * `unset_preference` - Unset a preference's value.
159
+
160
+ The key for a preference needs to follow the convention:
161
+
162
+ ```ruby
163
+ "#{model.content_key}_#{preference}"
164
+ ```
165
+
166
+ For example:
167
+
168
+ ```ruby
169
+ set_preference("blog_entries_color", "ff0000")
170
+ ```
171
+
172
+ You also need to add the parameter that the preference is using to
173
+ `config.preferences`:
174
+
175
+ ```ruby
176
+ Outpost::Config.configure do |config|
177
+ # ...
178
+ config.preferences += [:color]
179
+ end
180
+ ```
181
+
182
+ A resource-based preference is automatically cleared if its param is an empty string (not `nil`). For example:
183
+
184
+ ```ruby
185
+ # GET /outpost/posts?color=ff0000
186
+ set_preference('posts_color', params[:color])
187
+ preference('posts_color') # => ff0000
188
+
189
+ # GET /outpost/posts?color=
190
+ preference('posts_color') # => nil
191
+ ```
192
+
193
+ If you have a preference for a non-resourceful page, you need to manage its
194
+ cleanup manually.
195
+
196
+
197
+
198
+ ## Javascripts
199
+
200
+ Outpost comes with a bunch of useful scripts built-in. Some of them are automatically used, and some are provided as "opt-in" functionality.
201
+
202
+
203
+ ### Field Counter
204
+
205
+ ![Field Counter](http://i.imgur.com/MUPrplL.png)
206
+
207
+ This will add a counter above any field which will show the number of characters entered into that field, the target length, and the +/- fuzziness, as well as a color indicating where in that range they are.
208
+
209
+ #### Use
210
+
211
+ Add the class `field-counter` to a div wrapping the input field, and two data-attributes containing integers:
212
+
213
+ * `data-target` - The target length (default: 145)
214
+ * `data-fuzziness` - The fuzziness allowed (default: 20)
215
+
216
+ If you're using `simple_form`, it might look like this:
217
+
218
+ ```ruby
219
+ f.input :title, wrapper_html; { class: "field-counter", data: { target: 50, fuzziness: 10} }
220
+ ```
221
+
222
+
223
+ ### Preview
224
+
225
+ ![Preview](http://i.imgur.com/OZhlIOd.png)
226
+
227
+ The Javascript for Preview is what handles sending the form data to the server, but you'll need to handle the server-side stuff yourself. The "Preview" button will show up once you've added a `preview` action to that controller.
228
+
229
+ #### Use
230
+
231
+ The `preview` action needs to do a few things:
232
+
233
+ * Find the object from the passed-in `obj_key` (You can use `Outpost::obj_by_key`). You'll also need to handle what happens if the record hasn't been saved yet.
234
+ * Merge in the changed attributes.
235
+ * Render the proper template/layout, or any validation errors (using `render_preview_validation_errors`).
236
+ * Make sure you don't save anything. For this, I recommend doing any object updating inside of a database transaction, because assigning associations to a persisted object will save the object. Outpost provides a controller method, `with_rollback`, which will perform the block inside of a database transaction and force an `ActiveRecord::Rollback` at the end.
237
+
238
+ Here is a full example of what your `preview` action could look like:
239
+
240
+ ```ruby
241
+ def preview
242
+ @post = ContentBase.obj_by_key(params[:obj_key]) || Post.new
243
+
244
+ with_rollback @post do
245
+ @post.assign_attributes(form_params)
246
+
247
+ if @post.valid?
248
+ render "/posts/_post", layout: "application", locals: { post: @post }
249
+ else
250
+ render_preview_validation_errors(@post)
251
+ end
252
+ end
253
+ end
254
+ ```
255
+
256
+ You'll also need to add two routes for the preview action:
257
+
258
+ ```ruby
259
+ resources :posts do
260
+ put "preview", on: :member
261
+ post "preview", on: :collection
262
+ end
263
+ ```
264
+
265
+ You need both `post` and `put` to allow the preview to happen from either the New or Edit pages. If you're using Rails 4, use `patch` instead of `put`. In fact, if you're using Rails 4 (or the `routing_concerns` gem), then you can use Routing Concerns:
266
+
267
+ ```ruby
268
+ concern :previewable do
269
+ patch "preview", on: :member
270
+ post "preview", on: :collection
271
+ end
272
+
273
+ resources :posts, concerns: [:previewable]
274
+ resources :reporters, concerns: [:previewable]
275
+ resources :stories, concerns: [:previewable]
276
+ ```
277
+
278
+
279
+ #### More documentation to come.
280
+
281
+ ## Todo
282
+ A ton of stuff. Here is a sampler:
283
+
284
+ * Generators for resources (models, controllers).
285
+ * Add record versioning (needs to be extracted from the SCPRv4 app).
286
+ * Documentation... oh man, the documentation...
287
+
288
+ ## Contributing
289
+ Pull Requests are encouraged! This engine was built specifically for KPCC,
290
+ so its flexibility is limited... if you have improvements to make, please
291
+ make them.
292
+
293
+ Fork it, make your changes, and send me a pull request.
294
+
295
+ Run tests with `bundle exec rake test`
data/Rakefile ADDED
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env rake
2
+ RAKED = true
3
+
4
+ require 'bundler/setup'
5
+ #require 'rdoc/task'
6
+ require 'rspec/core/rake_task'
7
+ require 'combustion'
8
+
9
+ # RDoc::Task.new(:rdoc) do |rdoc|
10
+ # rdoc.rdoc_dir = 'rdoc'
11
+ # rdoc.title = 'Outpost'
12
+ # rdoc.markup = 'tomdoc'
13
+ # rdoc.options << '--line-numbers'
14
+ # rdoc.rdoc_files.include('README.md')
15
+ # rdoc.rdoc_files.include('lib/**/*.rb')
16
+ # rdoc.rdoc_files.include('app/helpers/**/*.rb')
17
+ # rdoc.rdoc_files.include('app/models/**/*.rb')
18
+ # end
19
+
20
+ Dir[File.join(File.dirname(__FILE__), 'tasks/**/*.rake')].each { |f| load f }
21
+
22
+ Bundler.require :default, :test
23
+ Combustion.initialize! :active_record, :action_controller
24
+ Combustion::Application.load_tasks
25
+
26
+ RSpec::Core::RakeTask.new(:test)
@@ -0,0 +1 @@
1
+ //= require outpost
@@ -0,0 +1,53 @@
1
+ # Find slug fields and load them up
2
+ $ ->
3
+ for field in $("form input[name*='[slug]']")
4
+ new outpost.AutoSlugField(field: field)
5
+
6
+ ##
7
+ # AutoSlugField
8
+ #
9
+ # Takes a field and turns it into a slug on-the-fly
10
+ #
11
+ class outpost.AutoSlugField
12
+ DefaultOptions:
13
+ titleClass: ".sluggable"
14
+ maxLength: 50
15
+
16
+ constructor: (options={}) ->
17
+ @options = _.defaults options, @DefaultOptions
18
+
19
+ # Find the sluggable title field - if it doesn't
20
+ # exist then we can't auto-generate a slug
21
+ @titleField = $(@options.titleClass)[0]
22
+
23
+ if @titleField
24
+ @slugField = $ @options.field
25
+ @maxLength = @options.maxLength
26
+ @button = $ JST['outpost/templates/slug_generate_button']()
27
+
28
+ # If we found a matching field,
29
+ # render the generate button and add it after the slug field
30
+ @slugField.after(@button)
31
+ @button.on
32
+ click: (event) =>
33
+ @updateSlug($(@titleField).val())
34
+ event.preventDefault()
35
+ false
36
+
37
+ true
38
+
39
+ #------------------
40
+
41
+ updateSlug: (value) ->
42
+ @slugField.val @slugify(value)
43
+
44
+ #------------------
45
+
46
+ slugify: (str) ->
47
+ str.toLowerCase()
48
+ .replace(/\s+/g, "-") # Spaces -> `-`
49
+ .replace(/-{2,}/g, '-') # Fix accidental double-hyphens
50
+ .replace(/[^\w\-]+/g, '') # Remove non-word characters/hyphen
51
+ .replace(/^-+/, '') # Trim hyphens from beginning
52
+ .substring(0, @maxLength) # Just the first 50 characters
53
+ .replace(/-+$/, '') # Trim hyphens from end
@@ -0,0 +1 @@
1
+ window.outpost ?= {}
@@ -0,0 +1,108 @@
1
+ # Find the inputs and load them.
2
+ $ ->
3
+ outpost.DateTimeInput.buildDateTimeInputs($("form"))
4
+ outpost.DateTimeInput.buildDateInputs($("form"))
5
+
6
+ ##
7
+ # DateTimeInput
8
+ #
9
+ # Turns simple textfields (for datetime attributes) into
10
+ # awesome timetime picker things.
11
+ #
12
+ # Does both Date and Time inputs (separately)
13
+ #
14
+ class outpost.DateTimeInput
15
+ @buildDateTimeInputs: (els) ->
16
+ for wrapper in $("div.datetime", els)
17
+ new outpost.DateTimeInput(wrapper: wrapper)
18
+
19
+ @buildDateInputs: (els) ->
20
+ for wrapper in $("div.date", els)
21
+ new outpost.DateTimeInput(wrapper: wrapper, time: false, field: "input.date")
22
+
23
+ #-----------------------
24
+
25
+ DefaultOptions:
26
+ time: true
27
+ dateTemplate: JST["outpost/templates/date_field"]
28
+ timeTemplate: JST["outpost/templates/time_field"]
29
+ timestampEls: ".timestamp-el"
30
+ populateIcons: "span.populate"
31
+ controls: "div.controls"
32
+ field: "input.datetime"
33
+ dateFormat: "YYYY-MM-DD"
34
+ timeFormat: "HH:mm"
35
+ dbFormat: "YYYY-MM-DD HH:mm:ss"
36
+
37
+ constructor: (options={}) ->
38
+ @options = _.defaults options, @DefaultOptions
39
+ @time = @options.time
40
+
41
+ # Elements
42
+ @wrapper = $ @options.wrapper
43
+ @controls = $ @options.controls, @wrapper
44
+ @field = $ @options.field, @wrapper
45
+
46
+ # Attributes
47
+ @id = @field.attr("id")
48
+ @dateId = "#{@id}_date"
49
+ @timeId = "#{@id}_time"
50
+
51
+ # Hide the field since we don't want anybody editing it directly
52
+ @field.hide()
53
+
54
+ # Render the templates
55
+ # Prepend time first so it's second
56
+ @controls.prepend(@options.timeTemplate(time_id: @timeId, time_format: @options.timeFormat)) if @time
57
+ @controls.prepend(@options.dateTemplate(date_id: @dateId, date_format: @options.dateFormat))
58
+
59
+ # Register the newly-created elements
60
+ @timestampEls = $ @options.timestampEls, @wrapper
61
+ @dateEl = $ "##{@dateId}"
62
+ @timeEl = $ "##{@timeId}"
63
+ @populateIcons = $ @options.populateIcons, @wrapper
64
+
65
+ # Fill in the new fields with the correct date/time
66
+ # Only if the field has a value (i.e. we're editing the object)
67
+ if @field.val()
68
+ @dateEl.val @getDate(@options.dateFormat)
69
+ @timeEl.val @getDate(@options.timeFormat) if @time
70
+
71
+ # Make the dateEl a datepicker
72
+ @dateEl.datepicker(autoclose: true, format: @options.dateFormat.toLowerCase())
73
+
74
+ # Fill in hidden text field when visible field is changed
75
+ @timestampEls.on
76
+ change: (event) => (@field.trigger "update")
77
+
78
+ @field.on
79
+ update: => (@setDate(@dateEl.val(), @timeEl.val() if @time))
80
+
81
+ # Fill in visible fields with right now time,
82
+ # and trigger the "update" event on field hidden field
83
+ @populateIcons.on
84
+ click: (event) =>
85
+ @populateDate(@dateEl, @options.dateFormat)
86
+ @populateDate(@timeEl, @options.timeFormat) if @time
87
+ @field.trigger "update"
88
+
89
+ # Populate a visible field with a date human-readable date string
90
+ populateDate: (el, format) ->
91
+ date = moment().format(format)
92
+ el.val(date)
93
+
94
+ # Get timestamp from hidden field
95
+ getDate: (format) ->
96
+ date = Date.parse(@field.val())
97
+ moment(date).format(format) if date
98
+
99
+ # Set value of hidden field to a real date
100
+ setDate: (date, time="") ->
101
+ date = moment(Date.parse("#{date} #{time}"))
102
+ console.log "date set to", date
103
+
104
+ if date
105
+ formatted = date.format(@options.dbFormat)
106
+ @field.val(formatted)
107
+ else
108
+ @field.val("")
@@ -0,0 +1,93 @@
1
+
2
+ $ ->
3
+ # Initialize a FieldCounter for any field that asks for it.
4
+ for field in $("form .field-counter")
5
+ el = $(field)
6
+ target = el.attr("data-target")
7
+ fuzziness = el.attr("data-fuzziness")
8
+ new outpost.FieldCounter(el, target: target, fuzziness: fuzziness)
9
+
10
+ ##
11
+ # FieldCounter
12
+ # To turn a field into a field counter, add three attributes:
13
+ #
14
+ # class="field-counter"
15
+ # data-target="50" # Perfect length
16
+ # data-fuzziness="10" # Lee-way in either direction (inclusive)
17
+ #
18
+ # Anything spanning the range of
19
+ #
20
+ # `(target - fuzziness) through (target + fuzziness)`
21
+ #
22
+ # (inclusive) will be considered "in-range". Everything else is
23
+ # "out of range". By default, this class will use the Twitter Bootstrap
24
+ # notification classes, but that can be overridden.
25
+ #
26
+ class outpost.FieldCounter
27
+ DefaultOptions:
28
+ target: 145 # Perfect length
29
+ fuzziness: 20 # Lee-way (in either direction, inclusive)
30
+ inRangeClass: "alert alert-success"
31
+ outOfRangeClass: "alert alert-warning"
32
+ counterClass: "counter-notify"
33
+ counterWrapper: ".controls" # The element to which the counter will be prepended
34
+ counterStyle: "padding: 3px; margin: 0 0 2px 0;"
35
+
36
+ constructor: (@el, options={}) ->
37
+ @options = _.defaults options, @DefaultOptions
38
+
39
+ # Setup elements
40
+ @field = $("input, textarea", @el)
41
+ @counterEl = $("<div />", class: @options.counterClass, style: @options.counterStyle)
42
+ $(@options.counterWrapper, @el).prepend @counterEl
43
+
44
+ # Setup attributes
45
+ @count = 0
46
+ @target = parseInt(@options.target)
47
+ @fuzziness = parseInt(@options.fuzziness)
48
+ @rangeLow = @target - @fuzziness
49
+ @rangeHigh = @target + @fuzziness
50
+
51
+ @inRangeClass = @options.inRangeClass
52
+ @outOfRangeClass = @options.outOfRangeClass
53
+
54
+ # Register listeners
55
+ @field.on
56
+ keyup: (event) =>
57
+ @updateCount($(event.target).val().length)
58
+
59
+ @el.on
60
+ updateCounter: (event, count) =>
61
+ @updateText(count)
62
+ @updateColor(count)
63
+
64
+ # Set the count on initialize
65
+ @updateCount(@field.val().length)
66
+
67
+ #--------------
68
+
69
+ inRange: ->
70
+ @rangeLow <= @count and @count <= @rangeHigh
71
+
72
+ #--------------
73
+
74
+ updateCount: (length) ->
75
+ @count = length
76
+ @el.trigger "updateCounter", @count
77
+
78
+ #--------------
79
+
80
+ updateText: (count) ->
81
+ @counterEl.html("<strong>Optimal Length:</strong> #{count} of #{@target} (+/- #{@fuzziness})")
82
+
83
+ #--------------
84
+
85
+ updateColor: (count) ->
86
+ if @inRange()
87
+ @counterEl.removeClass(@outOfRangeClass)
88
+ @counterEl.addClass(@inRangeClass)
89
+ else
90
+ @counterEl.removeClass(@inRangeClass)
91
+ @counterEl.addClass(@outOfRangeClass)
92
+
93
+
@@ -0,0 +1,37 @@
1
+ ##
2
+ # FieldManager
3
+ #
4
+ # A simple class that listens for certain events
5
+ # and does things to forms
6
+ #
7
+ class outpost.FieldManager
8
+ constructor: ->
9
+
10
+ $("fieldset.form-block legend").on
11
+ click: (event) ->
12
+ target = $(@)
13
+ target.siblings(".fields").toggle()
14
+ target.siblings(".notification").toggle()
15
+
16
+ # Add fields
17
+ $(".js-add-fields").on
18
+ click: (event) ->
19
+ event.preventDefault()
20
+
21
+ target = $(@)
22
+ time = new Date().getTime()
23
+ regexp = new RegExp(target.data('id'), 'g')
24
+ fields = $(target.data('fields').trim().replace(regexp, time))
25
+
26
+ if buildTarget = target.data('build-target')
27
+ $(buildTarget).append fields
28
+ else
29
+ target.before(fields)
30
+
31
+ # Build any special fields.
32
+ # TODO: Can we accomplish this with triggers?
33
+ outpost.DateTimeInput.buildDateTimeInputs(fields)
34
+ outpost.DateTimeInput.buildDateInputs(fields)
35
+ $("select", fields).select2
36
+ placeholder: " "
37
+ allowClear: true