outpost-cms 0.0.3

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 (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