virgo 0.1

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 (309) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/Rakefile +37 -0
  4. data/app/assets/javascripts/tinymce/manifest.coffee +1 -0
  5. data/app/assets/javascripts/tinymce/plugins/image_manager/plugin.coffee +234 -0
  6. data/app/assets/javascripts/virgo/admin/page_modules/edit_form.coffee +4 -0
  7. data/app/assets/javascripts/virgo/admin/page_modules/edit_subject_form.coffee +29 -0
  8. data/app/assets/javascripts/virgo/admin/posts/edit_locking.coffee +35 -0
  9. data/app/assets/javascripts/virgo/admin/posts/form.coffee +183 -0
  10. data/app/assets/javascripts/virgo/admin/slides/form.coffee +27 -0
  11. data/app/assets/javascripts/virgo/admin/slideshows/form.coffee +26 -0
  12. data/app/assets/javascripts/virgo/admin_only.coffee +1 -0
  13. data/app/assets/javascripts/virgo/application.coffee +51 -0
  14. data/app/assets/javascripts/virgo/common.coffee +83 -0
  15. data/app/assets/javascripts/virgo/common/character_counter.coffee +27 -0
  16. data/app/assets/javascripts/virgo/components/header.coffee +47 -0
  17. data/app/assets/javascripts/virgo/components/media_modal.coffee +151 -0
  18. data/app/assets/javascripts/virgo/components/share_bar.coffee +53 -0
  19. data/app/assets/javascripts/virgo/components/slideshow.coffee +35 -0
  20. data/app/assets/javascripts/virgo/lib/jquery-ui-multiselect.js +342 -0
  21. data/app/assets/javascripts/virgo/lib/jquery.browser.js +184 -0
  22. data/app/assets/javascripts/virgo/lib/jquery.cookie.js +114 -0
  23. data/app/assets/javascripts/virgo/lib/jquery.pjax.js +932 -0
  24. data/app/assets/javascripts/virgo/lib/jquery_ext.coffee +0 -0
  25. data/app/assets/javascripts/virgo/lib/manifest.coffee +5 -0
  26. data/app/assets/javascripts/virgo/lib/ouibounce.js +194 -0
  27. data/app/assets/javascripts/virgo/page_modules.coffee +175 -0
  28. data/app/assets/javascripts/virgo/posts/show.coffee +32 -0
  29. data/app/assets/stylesheets/tinymce/skin.sass +2 -0
  30. data/app/assets/stylesheets/tinymce/skins/railspress/content.min.sass +82 -0
  31. data/app/assets/stylesheets/tinymce/skins/railspress/fonts/tinymce-small.eot +0 -0
  32. data/app/assets/stylesheets/tinymce/skins/railspress/fonts/tinymce-small.svg +62 -0
  33. data/app/assets/stylesheets/tinymce/skins/railspress/fonts/tinymce-small.ttf +0 -0
  34. data/app/assets/stylesheets/tinymce/skins/railspress/fonts/tinymce-small.woff +0 -0
  35. data/app/assets/stylesheets/tinymce/skins/railspress/fonts/tinymce.eot +0 -0
  36. data/app/assets/stylesheets/tinymce/skins/railspress/fonts/tinymce.svg +63 -0
  37. data/app/assets/stylesheets/tinymce/skins/railspress/fonts/tinymce.ttf +0 -0
  38. data/app/assets/stylesheets/tinymce/skins/railspress/fonts/tinymce.woff +0 -0
  39. data/app/assets/stylesheets/tinymce/skins/railspress/img/anchor.gif +0 -0
  40. data/app/assets/stylesheets/tinymce/skins/railspress/img/loader.gif +0 -0
  41. data/app/assets/stylesheets/tinymce/skins/railspress/img/object.gif +0 -0
  42. data/app/assets/stylesheets/tinymce/skins/railspress/img/trans.gif +0 -0
  43. data/app/assets/stylesheets/tinymce/skins/railspress/skin.min.sass +1703 -0
  44. data/app/assets/stylesheets/virgo/_common.sass +65 -0
  45. data/app/assets/stylesheets/virgo/admin.sass +301 -0
  46. data/app/assets/stylesheets/virgo/admin/columns.sass +3 -0
  47. data/app/assets/stylesheets/virgo/admin/images.sass +14 -0
  48. data/app/assets/stylesheets/virgo/admin/media_modal.sass +72 -0
  49. data/app/assets/stylesheets/virgo/admin/post.sass +102 -0
  50. data/app/assets/stylesheets/virgo/admin/slideshow.sass +35 -0
  51. data/app/assets/stylesheets/virgo/admin/users.sass +2 -0
  52. data/app/assets/stylesheets/virgo/admin_only.sass +1 -0
  53. data/app/assets/stylesheets/virgo/application.sass +605 -0
  54. data/app/assets/stylesheets/virgo/columns.sass +51 -0
  55. data/app/assets/stylesheets/virgo/install.sass +15 -0
  56. data/app/assets/stylesheets/virgo/lib/bootstrap/bootstrap-imports.sass +9 -0
  57. data/app/assets/stylesheets/virgo/lib/bootstrap/bootstrap-overrides.sass +51 -0
  58. data/app/assets/stylesheets/virgo/lib/bootstrap/bootstrap-variables.sass +27 -0
  59. data/app/assets/stylesheets/virgo/lib/jquery-ui-multiselect.css +30 -0
  60. data/app/assets/stylesheets/virgo/lib/manifest.sass +3 -0
  61. data/app/assets/stylesheets/virgo/lib/ouibounce.css +222 -0
  62. data/app/assets/stylesheets/virgo/lib/select2-imports.sass +8 -0
  63. data/app/assets/stylesheets/virgo/page_modules.sass +54 -0
  64. data/app/assets/stylesheets/virgo/pages/authors.sass +30 -0
  65. data/app/assets/stylesheets/virgo/posts.sass +782 -0
  66. data/app/assets/stylesheets/virgo/search.sass +36 -0
  67. data/app/assets/stylesheets/virgo/ui.sass +434 -0
  68. data/app/assets/stylesheets/virgo/users.sass +9 -0
  69. data/app/controllers/virgo/admin/base_controller.rb +22 -0
  70. data/app/controllers/virgo/admin/categories_controller.rb +90 -0
  71. data/app/controllers/virgo/admin/columns_controller.rb +65 -0
  72. data/app/controllers/virgo/admin/images_controller.rb +89 -0
  73. data/app/controllers/virgo/admin/media_modal_controller.rb +101 -0
  74. data/app/controllers/virgo/admin/newsletters_controller.rb +37 -0
  75. data/app/controllers/virgo/admin/page_modules_controller.rb +62 -0
  76. data/app/controllers/virgo/admin/pages_controller.rb +6 -0
  77. data/app/controllers/virgo/admin/posts_controller.rb +173 -0
  78. data/app/controllers/virgo/admin/sites_controller.rb +31 -0
  79. data/app/controllers/virgo/admin/slides_controller.rb +71 -0
  80. data/app/controllers/virgo/admin/slideshows_controller.rb +66 -0
  81. data/app/controllers/virgo/admin/tags_controller.rb +98 -0
  82. data/app/controllers/virgo/admin/users/sessions_controller.rb +21 -0
  83. data/app/controllers/virgo/admin/users_controller.rb +84 -0
  84. data/app/controllers/virgo/application_controller.rb +151 -0
  85. data/app/controllers/virgo/categories_controller.rb +20 -0
  86. data/app/controllers/virgo/columns_controller.rb +24 -0
  87. data/app/controllers/virgo/images_controller.rb +9 -0
  88. data/app/controllers/virgo/install_controller.rb +46 -0
  89. data/app/controllers/virgo/page_modules_controller.rb +23 -0
  90. data/app/controllers/virgo/pages_controller.rb +29 -0
  91. data/app/controllers/virgo/posts_controller.rb +101 -0
  92. data/app/controllers/virgo/search_controller.rb +35 -0
  93. data/app/controllers/virgo/subscribers_controller.rb +54 -0
  94. data/app/controllers/virgo/tags_controller.rb +15 -0
  95. data/app/controllers/virgo/users/confirmations_controller.rb +18 -0
  96. data/app/controllers/virgo/users/passwords_controller.rb +31 -0
  97. data/app/controllers/virgo/users/registrations_controller.rb +41 -0
  98. data/app/controllers/virgo/users/sessions_controller.rb +32 -0
  99. data/app/controllers/virgo/users_controller.rb +15 -0
  100. data/app/helpers/virgo/admin/post_helper.rb +54 -0
  101. data/app/helpers/virgo/admin_helper.rb +15 -0
  102. data/app/helpers/virgo/application_helper.rb +124 -0
  103. data/app/helpers/virgo/hooks_helper.rb +30 -0
  104. data/app/helpers/virgo/page_modules_helper.rb +4 -0
  105. data/app/helpers/virgo/post_helper.rb +159 -0
  106. data/app/helpers/virgo/render_helper.rb +33 -0
  107. data/app/helpers/virgo/search_helper.rb +49 -0
  108. data/app/helpers/virgo/shortcode_helper.rb +7 -0
  109. data/app/helpers/virgo/text_helper.rb +12 -0
  110. data/app/helpers/virgo/video_helper.rb +34 -0
  111. data/app/models/concerns/virgo/common/slug_history.rb +36 -0
  112. data/app/models/concerns/virgo/common/uuid.rb +22 -0
  113. data/app/models/concerns/virgo/post/recommendations.rb +15 -0
  114. data/app/models/concerns/virgo/post/search.rb +100 -0
  115. data/app/models/concerns/virgo/user/search.rb +39 -0
  116. data/app/models/virgo/ability.rb +59 -0
  117. data/app/models/virgo/category.rb +44 -0
  118. data/app/models/virgo/column.rb +25 -0
  119. data/app/models/virgo/form_model.rb +19 -0
  120. data/app/models/virgo/image.rb +63 -0
  121. data/app/models/virgo/page_module.rb +42 -0
  122. data/app/models/virgo/page_module_post.rb +8 -0
  123. data/app/models/virgo/post.rb +391 -0
  124. data/app/models/virgo/post_category.rb +8 -0
  125. data/app/models/virgo/post_search.rb +7 -0
  126. data/app/models/virgo/post_tag.rb +10 -0
  127. data/app/models/virgo/site.rb +36 -0
  128. data/app/models/virgo/slide.rb +23 -0
  129. data/app/models/virgo/slideshow.rb +37 -0
  130. data/app/models/virgo/slug_history.rb +5 -0
  131. data/app/models/virgo/subscriber.rb +5 -0
  132. data/app/models/virgo/tag.rb +34 -0
  133. data/app/models/virgo/user.rb +145 -0
  134. data/app/uploaders/virgo/application_uploader.rb +11 -0
  135. data/app/uploaders/virgo/avatar_uploader.rb +21 -0
  136. data/app/uploaders/virgo/featured_image_uploader.rb +28 -0
  137. data/app/uploaders/virgo/image_uploader.rb +40 -0
  138. data/app/uploaders/virgo/slide_uploader.rb +19 -0
  139. data/app/views/layouts/virgo/admin.haml +16 -0
  140. data/app/views/layouts/virgo/admin/site_settings.haml +14 -0
  141. data/app/views/layouts/virgo/application.haml +63 -0
  142. data/app/views/layouts/virgo/errors.haml +42 -0
  143. data/app/views/layouts/virgo/main.haml +5 -0
  144. data/app/views/layouts/virgo/posts.haml +10 -0
  145. data/app/views/virgo/admin/categories/_form.haml +19 -0
  146. data/app/views/virgo/admin/categories/edit.haml +7 -0
  147. data/app/views/virgo/admin/categories/index.haml +38 -0
  148. data/app/views/virgo/admin/categories/modal_form.haml +11 -0
  149. data/app/views/virgo/admin/categories/new.haml +7 -0
  150. data/app/views/virgo/admin/categories/success_modal.haml +14 -0
  151. data/app/views/virgo/admin/columns/_form.haml +30 -0
  152. data/app/views/virgo/admin/columns/edit.haml +1 -0
  153. data/app/views/virgo/admin/columns/index.haml +30 -0
  154. data/app/views/virgo/admin/columns/new.haml +1 -0
  155. data/app/views/virgo/admin/images/_form.haml +46 -0
  156. data/app/views/virgo/admin/images/edit.haml +1 -0
  157. data/app/views/virgo/admin/images/index.haml +32 -0
  158. data/app/views/virgo/admin/images/new.haml +1 -0
  159. data/app/views/virgo/admin/media_modal/_image_settings.haml +17 -0
  160. data/app/views/virgo/admin/media_modal/_library_panel.haml +26 -0
  161. data/app/views/virgo/admin/media_modal/_upload_panel.haml +21 -0
  162. data/app/views/virgo/admin/media_modal/_upload_success.haml +7 -0
  163. data/app/views/virgo/admin/media_modal/index.haml +19 -0
  164. data/app/views/virgo/admin/newsletters/changelog.haml +25 -0
  165. data/app/views/virgo/admin/newsletters/edit.haml +6 -0
  166. data/app/views/virgo/admin/page_modules/edit.haml +33 -0
  167. data/app/views/virgo/admin/page_modules/edit_subject.haml +43 -0
  168. data/app/views/virgo/admin/pages/help.haml +114 -0
  169. data/app/views/virgo/admin/posts/_author_dropdown.haml +1 -0
  170. data/app/views/virgo/admin/posts/_categories_form.haml +7 -0
  171. data/app/views/virgo/admin/posts/_category.haml +22 -0
  172. data/app/views/virgo/admin/posts/_form.haml +201 -0
  173. data/app/views/virgo/admin/posts/_revisions.haml +40 -0
  174. data/app/views/virgo/admin/posts/edit.haml +9 -0
  175. data/app/views/virgo/admin/posts/index.haml +59 -0
  176. data/app/views/virgo/admin/posts/new.haml +1 -0
  177. data/app/views/virgo/admin/posts/revision_detail.haml +28 -0
  178. data/app/views/virgo/admin/posts/revisions.haml +3 -0
  179. data/app/views/virgo/admin/sites/edit.haml +12 -0
  180. data/app/views/virgo/admin/slides/_form.haml +41 -0
  181. data/app/views/virgo/admin/slides/edit.haml +1 -0
  182. data/app/views/virgo/admin/slides/new.haml +1 -0
  183. data/app/views/virgo/admin/slideshows/_form.haml +49 -0
  184. data/app/views/virgo/admin/slideshows/edit.haml +1 -0
  185. data/app/views/virgo/admin/slideshows/index.haml +43 -0
  186. data/app/views/virgo/admin/slideshows/new.haml +1 -0
  187. data/app/views/virgo/admin/tags/_form.haml +16 -0
  188. data/app/views/virgo/admin/tags/edit.haml +7 -0
  189. data/app/views/virgo/admin/tags/index.haml +33 -0
  190. data/app/views/virgo/admin/tags/modal_form.haml +12 -0
  191. data/app/views/virgo/admin/tags/new.haml +7 -0
  192. data/app/views/virgo/admin/tags/success_modal.haml +14 -0
  193. data/app/views/virgo/admin/users/_form.haml +62 -0
  194. data/app/views/virgo/admin/users/edit.haml +1 -0
  195. data/app/views/virgo/admin/users/index.haml +34 -0
  196. data/app/views/virgo/admin/users/new.haml +1 -0
  197. data/app/views/virgo/application/sitemap.xml.builder +0 -0
  198. data/app/views/virgo/authors/_profile_long.haml +13 -0
  199. data/app/views/virgo/authors/_profile_short.haml +10 -0
  200. data/app/views/virgo/categories/show.haml +24 -0
  201. data/app/views/virgo/columns/index.haml +19 -0
  202. data/app/views/virgo/columns/show.haml +18 -0
  203. data/app/views/virgo/common/_admin_navbar.haml +100 -0
  204. data/app/views/virgo/common/_alerts.haml +40 -0
  205. data/app/views/virgo/common/_analytics.html.erb +1 -0
  206. data/app/views/virgo/common/_base_errors.haml +8 -0
  207. data/app/views/virgo/common/_category_heading.haml +26 -0
  208. data/app/views/virgo/common/_disqus.html.erb +24 -0
  209. data/app/views/virgo/common/_disqus_comment_count.html.erb +12 -0
  210. data/app/views/virgo/common/_fb_api.html.erb +30 -0
  211. data/app/views/virgo/common/_fonts.html.erb +1 -0
  212. data/app/views/virgo/common/_footer.haml +13 -0
  213. data/app/views/virgo/common/_footer_javascript.html.erb +0 -0
  214. data/app/views/virgo/common/_header_analytics.html.erb +9 -0
  215. data/app/views/virgo/common/_list_signup.haml +20 -0
  216. data/app/views/virgo/common/_ouibounce_modal.haml +26 -0
  217. data/app/views/virgo/common/_public_nav.haml +58 -0
  218. data/app/views/virgo/common/_search_form.haml +8 -0
  219. data/app/views/virgo/common/_share_bar.haml +45 -0
  220. data/app/views/virgo/common/_user_vars.haml +2 -0
  221. data/app/views/virgo/errors/404.haml +10 -0
  222. data/app/views/virgo/errors/500.haml +10 -0
  223. data/app/views/virgo/images/_shortcode.haml +4 -0
  224. data/app/views/virgo/install/index.haml +58 -0
  225. data/app/views/virgo/install/success.haml +12 -0
  226. data/app/views/virgo/page_modules/_latest_posts.haml +29 -0
  227. data/app/views/virgo/page_modules/_popular_posts.haml +51 -0
  228. data/app/views/virgo/pages/authors.haml +8 -0
  229. data/app/views/virgo/pages/home.haml +0 -0
  230. data/app/views/virgo/pages/show.haml +6 -0
  231. data/app/views/virgo/posts/_author_info.haml +16 -0
  232. data/app/views/virgo/posts/_post_full.haml +63 -0
  233. data/app/views/virgo/posts/_post_short.haml +30 -0
  234. data/app/views/virgo/posts/_post_thumb_box.haml +13 -0
  235. data/app/views/virgo/posts/_recommendations.haml +12 -0
  236. data/app/views/virgo/posts/_sidebar_thumb.haml +4 -0
  237. data/app/views/virgo/posts/index.haml +22 -0
  238. data/app/views/virgo/posts/latest.haml +16 -0
  239. data/app/views/virgo/posts/more.haml +10 -0
  240. data/app/views/virgo/posts/rss.xml.builder +17 -0
  241. data/app/views/virgo/posts/show.haml +17 -0
  242. data/app/views/virgo/search/_form.haml +5 -0
  243. data/app/views/virgo/search/_item.haml +22 -0
  244. data/app/views/virgo/search/index.haml +11 -0
  245. data/app/views/virgo/search/new.haml +4 -0
  246. data/app/views/virgo/shortcode_templates/blockquote.haml +9 -0
  247. data/app/views/virgo/shortcode_templates/h1.haml +1 -0
  248. data/app/views/virgo/shortcode_templates/h2.haml +1 -0
  249. data/app/views/virgo/shortcode_templates/h3.haml +1 -0
  250. data/app/views/virgo/shortcode_templates/h4.haml +1 -0
  251. data/app/views/virgo/shortcode_templates/image.haml +17 -0
  252. data/app/views/virgo/shortcode_templates/pullquote.haml +3 -0
  253. data/app/views/virgo/shortcode_templates/slideshow.haml +35 -0
  254. data/app/views/virgo/shortcode_templates/tweet.haml +3 -0
  255. data/app/views/virgo/shortcode_templates/video.haml +8 -0
  256. data/app/views/virgo/subscribers/_success_modal.haml +13 -0
  257. data/app/views/virgo/subscribers/modal.haml +15 -0
  258. data/app/views/virgo/tags/show.haml +16 -0
  259. data/app/views/virgo/users/confirmations/new.html.haml +10 -0
  260. data/app/views/virgo/users/devise_mailer/confirmation_instructions.html.erb +7 -0
  261. data/app/views/virgo/users/devise_mailer/reset_password_instructions.html.haml +6 -0
  262. data/app/views/virgo/users/devise_mailer/unlock_instructions.html.haml +5 -0
  263. data/app/views/virgo/users/passwords/edit.html.haml +14 -0
  264. data/app/views/virgo/users/passwords/new.html.haml +14 -0
  265. data/app/views/virgo/users/registrations/edit.html.haml +30 -0
  266. data/app/views/virgo/users/registrations/new.html.haml +13 -0
  267. data/app/views/virgo/users/sessions/new.html.haml +12 -0
  268. data/app/views/virgo/users/shared/_links.haml +26 -0
  269. data/app/views/virgo/users/show.haml +11 -0
  270. data/app/views/virgo/users/unlocks/new.html.haml +9 -0
  271. data/config/initializers/action_mailer.rb +7 -0
  272. data/config/initializers/active_record.rb +72 -0
  273. data/config/initializers/active_record_symbolize.rb +18 -0
  274. data/config/initializers/assets.rb +17 -0
  275. data/config/initializers/backtrace_silencers.rb +7 -0
  276. data/config/initializers/carrierwave.rb +36 -0
  277. data/config/initializers/cookies_serializer.rb +3 -0
  278. data/config/initializers/devise.rb +257 -0
  279. data/config/initializers/filter_parameter_logging.rb +4 -0
  280. data/config/initializers/hash.rb +5 -0
  281. data/config/initializers/inflections.rb +16 -0
  282. data/config/initializers/mime_types.rb +4 -0
  283. data/config/initializers/nil_class.rb +13 -0
  284. data/config/initializers/non_digest_assets.rb +3 -0
  285. data/config/initializers/paper_trail.rb +30 -0
  286. data/config/initializers/schema_plus.rb +5 -0
  287. data/config/initializers/session_store.rb +3 -0
  288. data/config/initializers/shortcode.rb +12 -0
  289. data/config/initializers/simple_form.rb +170 -0
  290. data/config/initializers/simple_form_bootstrap.rb +136 -0
  291. data/config/initializers/string.rb +5 -0
  292. data/config/initializers/time_formatting.rb +23 -0
  293. data/config/initializers/wrap_parameters.rb +14 -0
  294. data/config/locales/devise.en.yml +60 -0
  295. data/config/routes.rb +96 -0
  296. data/config/schedule.rb +3 -0
  297. data/db/migrate/20150729174510_create_virgo_schema.rb +225 -0
  298. data/lib/generators/virgo/install/install_generator.rb +16 -0
  299. data/lib/generators/virgo/views/views_generator.rb +22 -0
  300. data/lib/tasks/virgo_tasks.rake +7 -0
  301. data/lib/virgo.rb +4 -0
  302. data/lib/virgo/development_mail_interceptor.rb +6 -0
  303. data/lib/virgo/email_validator.rb +6 -0
  304. data/lib/virgo/engine.rb +73 -0
  305. data/lib/virgo/reduce_validator.rb +6 -0
  306. data/lib/virgo/upload_helpers.rb +89 -0
  307. data/lib/virgo/version.rb +3 -0
  308. data/lib/virgo/view_help.rb +3 -0
  309. metadata +1051 -0
@@ -0,0 +1,39 @@
1
+ module Virgo
2
+ class User < ActiveRecord::Base
3
+ module Search
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ scope :search, ->(filters=nil) {
8
+ filters ||= {}
9
+
10
+ items = all
11
+
12
+ if filters[:term].present?
13
+ parts = filters[:term].split(" ")
14
+ queries = []
15
+ vals = {}
16
+
17
+ parts.each_with_index do |part, i|
18
+ vals[:"term_#{i}"] = "%#{part}%"
19
+
20
+ queries << \
21
+ "(users.email ILIKE :term_#{i} OR " +
22
+ "users.first_name ILIKE :term_#{i} OR " +
23
+ "users.last_name ILIKE :term_#{i} OR " +
24
+ "users.username ILIKE :term_#{i} OR " +
25
+ "users.byline ILIKE :term_#{i})"
26
+ end
27
+
28
+ query = queries.join(" OR ")
29
+
30
+ items = items.where(query, vals)
31
+ end
32
+
33
+
34
+ items
35
+ }
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,59 @@
1
+ # note: role inheritence implemented via Ryan Bates' method
2
+ # outlined in https://github.com/ryanb/cancan/wiki/Role-Based-Authorization
3
+ module Virgo
4
+ class Ability
5
+ include CanCan::Ability
6
+
7
+ def initialize(user)
8
+ @user = user || User.new # for guest
9
+
10
+ send(@user.role) if @user.role.present?
11
+ end
12
+
13
+ def anonymous
14
+ can :read, Post do |post|
15
+ post.published? && post.live
16
+ end
17
+ end
18
+
19
+ def normal
20
+ anonymous
21
+ end
22
+
23
+ def editor
24
+ can :manage, :all
25
+ can :manage, Virgo::User do |u|
26
+ u == @user
27
+ end
28
+ cannot :index, Virgo::User
29
+ end
30
+
31
+ def contributor
32
+ can :manage, Virgo::Post do |post|
33
+ post.author == @user
34
+ end
35
+
36
+ can :manage, Virgo::Slideshow do |slideshow|
37
+ slideshow.author == @user
38
+ end
39
+
40
+ can :manage, Virgo::Slide do |slide|
41
+ can?(:manage, slide.slideshow)
42
+ end
43
+
44
+ can :read, Virgo::Post
45
+
46
+ cannot :manage, Virgo::User
47
+ cannot :manage, Virgo::Column
48
+ cannot :manage, Virgo::Site
49
+ end
50
+
51
+ def admin
52
+ can :manage, :all
53
+ end
54
+
55
+ def superuser
56
+ can :manage, :all
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,44 @@
1
+ module Virgo
2
+ class Category < ActiveRecord::Base
3
+ enable_redis_tracking
4
+
5
+ extend FriendlyId
6
+
7
+ friendly_id :name, use: :slugged
8
+
9
+ has_many :post_categories, dependent: :destroy
10
+ has_many :posts, ->{ where(post_type: :post) }, through: :post_categories
11
+
12
+ belongs_to :parent, class_name: 'Virgo::Category'
13
+
14
+ belongs_to :created_by, class_name: 'Virgo::User'
15
+
16
+ has_many :children, class_name: 'Virgo::Category', foreign_key: :parent_id
17
+
18
+ validates :name, presence: true, uniqueness: true
19
+
20
+ scope :visible, ->{ where(visible: true) }
21
+
22
+ scope :by_weight, ->{ order(navbar_weight: :asc) }
23
+
24
+ scope :navbar, ->{ where(display_in_navbar: true).by_weight }
25
+
26
+ scope :bottom_level, ->{
27
+ where("virgo_categories.id NOT IN (SELECT DISTINCT virgo_categories.parent_id FROM virgo_categories WHERE virgo_categories.parent_id IS NOT NULL)")
28
+ }
29
+
30
+ scope :top_level, ->{
31
+ where(parent_id: nil)
32
+ }
33
+
34
+ scope :with_post_count, ->{
35
+ select("virgo_categories.*, " +
36
+ "(SELECT COUNT(*) FROM virgo_post_categories WHERE virgo_post_categories.category_id = virgo_categories.id) AS post_count"
37
+ )
38
+ }
39
+
40
+ def self.COLUMNS
41
+ find_by(name: "Columns")
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,25 @@
1
+ module Virgo
2
+ class Column < ActiveRecord::Base
3
+ enable_redis_tracking
4
+
5
+ extend FriendlyId
6
+
7
+ friendly_id :name, use: :slugged
8
+
9
+ mount_uploader :image, Virgo::FeaturedImageUploader
10
+
11
+ validates :name, presence: true
12
+
13
+ has_many :posts
14
+
15
+ after_save :expire_site_key
16
+
17
+ scope :with_post_count, ->{
18
+ select("virgo_columns.*, " +
19
+ "(SELECT COUNT(*) FROM virgo_posts WHERE virgo_posts.column_id = virgo_columns.id) AS post_count"
20
+ )
21
+ }
22
+
23
+ scope :by_weight, ->{ order(weight: :asc) }
24
+ end
25
+ end
@@ -0,0 +1,19 @@
1
+ module Virgo
2
+ class FormModel
3
+ include ActiveModel::Validations
4
+ include ActiveModel::Conversion
5
+ extend ActiveModel::Naming
6
+
7
+ def initialize(attributes = {})
8
+ if attributes
9
+ attributes.each do |name, value|
10
+ send("#{name}=", value)
11
+ end
12
+ end
13
+ end
14
+
15
+ def persisted?
16
+ false
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,63 @@
1
+ module Virgo
2
+ class Image < ActiveRecord::Base
3
+ extend FriendlyId
4
+
5
+ friendly_id :name, use: :slugged
6
+
7
+ mount_uploader :image, Virgo::ImageUploader
8
+
9
+ validates :name, presence: true
10
+ validates :image, presence: true
11
+
12
+ belongs_to :user
13
+
14
+ before_validation :derive_name
15
+
16
+ scope :search, ->(filters={}){
17
+ items = all
18
+
19
+ if filters[:term].present?
20
+ items = items.where("images.name ILIKE :term", term: "%#{filters[:term]}%")
21
+ end
22
+
23
+ items
24
+ }
25
+
26
+ def url(arg)
27
+ image.url
28
+ end
29
+
30
+ def redactor_json
31
+ {
32
+ thumb: image.url(:thumb),
33
+ image: image.url
34
+ }
35
+ end
36
+
37
+ def as_json(opts={})
38
+ {
39
+ id: id,
40
+ name: name,
41
+ alt_text: alt_text,
42
+ credit: credit,
43
+ description: description,
44
+ slug: slug,
45
+ image_url: image.try(:url),
46
+ small_thumb_image: image.try(:url, :small_thumb),
47
+ thumb_image: image.try(:url, :thumb),
48
+ med_thumb_image: image.try(:url, :med_thumb),
49
+ content_image: image.try(:url, :content),
50
+ featured_image: image.try(:url, :featured),
51
+ wide_image: image.try(:url, :wide)
52
+ }
53
+ end
54
+
55
+ private
56
+
57
+ def derive_name
58
+ if defined?(image_changed?) && image_changed? && !name_changed? && name.blank?
59
+ self.name = image.file.try(:filename)
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,42 @@
1
+ module Virgo
2
+ class PageModule < ActiveRecord::Base
3
+ mount_uploader :image, Virgo::ImageUploader
4
+
5
+ attr_accessor :subject_headline # form hook
6
+
7
+ before_save :derive_template_path
8
+
9
+ validates :name, presence: true
10
+
11
+ belongs_to :subject, polymorphic: true
12
+
13
+ has_many :page_module_posts
14
+ has_many :posts, through: :page_module_posts
15
+
16
+ scope :enabled, ->{ where(enabled: true) }
17
+
18
+ scope :visible_to_admins, ->{ where(hidden_from_admins: false) }
19
+
20
+ after_save :expire_site_key
21
+
22
+ def self.POPULAR
23
+ @_popular ||= find_by(name: 'popular posts')
24
+ end
25
+
26
+ def admin_image_path
27
+ "virgo/admin/page_modules/#{name.downcase.gsub(' ', '_').gsub('-', '_')}.png"
28
+ end
29
+
30
+ def subject_headline
31
+ subject.try :headline
32
+ end
33
+
34
+ private
35
+
36
+ def derive_template_path(opts={})
37
+ if name_changed? || opts[:force]
38
+ self.template_path = "/virgo/page_modules/#{name.downcase.gsub(' ', '_').gsub('-', '_')}"
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,8 @@
1
+ module Virgo
2
+ class PageModulePost < ActiveRecord::Base
3
+ belongs_to :page_module, touch: true
4
+ belongs_to :post
5
+
6
+ scope :by_position, ->{ order(position: :asc) }
7
+ end
8
+ end
@@ -0,0 +1,391 @@
1
+ module Virgo
2
+ class Post < ActiveRecord::Base
3
+ paginates_per 10
4
+
5
+ enable_redis_tracking
6
+
7
+ has_paper_trail ignore: [:updated_at, :editing_user_id, :editing_timestamp, :live, :view_count, :meta, :popularity]
8
+
9
+ include Post::Search, Post::Recommendations, ActionView::Helpers::SanitizeHelper, Common::Uuid, Common::SlugHistory
10
+
11
+ extend FriendlyId
12
+
13
+ attr_accessor :ordered_tag_ids
14
+
15
+ friendly_id :slug_candidates, use: :slugged
16
+
17
+ belongs_to :author, class_name: 'Virgo::User'
18
+
19
+ belongs_to :featured_image, class_name: 'Virgo::Image'
20
+ belongs_to :thumbnail_image, class_name: 'Virgo::Image'
21
+
22
+ belongs_to :created_by, class_name: 'Virgo::User'
23
+
24
+ belongs_to :editing_user, class_name: 'Virgo::User'
25
+
26
+ validates :author_id, presence: true
27
+
28
+ has_many :post_tags, dependent: :destroy
29
+ has_many :tags, through: :post_tags
30
+
31
+ has_many :post_categories, dependent: :destroy
32
+ has_many :categories, through: :post_categories
33
+
34
+ belongs_to :column, touch: true
35
+
36
+ validates :headline, presence: true, unless: :page?
37
+ validates :body, presence: true
38
+
39
+ validates :slug, presence: true, uniqueness: true
40
+
41
+ symbolize :status
42
+ symbolize :post_type
43
+
44
+ scope :with_relations, ->{ joins(:author).preload(:author, :tags, :categories) }
45
+
46
+ scope :publicly_viewable, ->{ where(status: :published, live: true).where.not(publish_at: nil) }
47
+ scope :published, ->{ where(status: :published) }
48
+ scope :draft, ->{ where(status: :draft) }
49
+ scope :pages, ->{ where(post_type: :page) }
50
+ scope :posts, ->{ where(post_type: :post) }
51
+ scope :for_month, ->(month){ where("posts.publish_at >= :month_start AND posts.publish_at <= :month_end",
52
+ month_start: month.beginning_of_month,
53
+ month_end: month.end_of_month) }
54
+
55
+ scope :for_weekly_email, ->{
56
+ publicly_viewable.where(include_in_weekly_email: true)
57
+ }
58
+
59
+ scope :exclude_front_page_feature, ->{
60
+ items = posts.publicly_viewable
61
+
62
+ if Post.front_page_feature
63
+ items = items.where.not(id: Post.front_page_feature.id)
64
+ end
65
+
66
+ items
67
+ }
68
+
69
+ scope :exclude_category_feature, ->(category){
70
+ items = posts.publicly_viewable
71
+
72
+ if Post.category_feature(category)
73
+ items = items.where.not(id: Post.category_feature(category).id)
74
+ end
75
+
76
+ items
77
+ }
78
+
79
+ scope :exclude, ->(item) {
80
+ if item.nil?
81
+ all
82
+ else
83
+ where.not(id: item.id)
84
+ end
85
+ }
86
+
87
+ scope :most_popular, ->{
88
+ posts.publicly_viewable.order(popularity: :desc)
89
+ }
90
+
91
+ scope :latest, ->{ posts.publicly_viewable.order(publish_at: :desc) }
92
+
93
+ scope :containing_slideshow, ->(slideshow){
94
+ where("virgo_posts.meta -> 'contains_slideshow' = 'true'").where("virgo_posts.meta -> 'slideshow_id' = ?", slideshow.id.to_s)
95
+ }
96
+
97
+ before_save :set_default_publish_at
98
+ before_save :set_live_status
99
+ before_save :generate_search_document
100
+ before_save :set_meta
101
+
102
+ after_save :expire_site_key
103
+ after_save :touch_dependent
104
+ after_save :set_columns_category
105
+ after_save :apply_tag_ordering!
106
+
107
+ if Rails.application.config.require_posts_to_have_category
108
+ validate :has_category
109
+ end
110
+
111
+ after_initialize :init
112
+
113
+ def self.category_feature(category)
114
+ category.posts.publicly_viewable.where(feature_on_category_page: true).order(publish_at: :asc).last
115
+ end
116
+
117
+ def self.front_page_feature
118
+ posts.publicly_viewable.where(feature_on_front_page: true).order(publish_at: :asc).last
119
+ end
120
+
121
+ def permalink
122
+ urls.post_detail_url(self, protocol: 'https')
123
+ end
124
+
125
+ def rendered_body
126
+ @rendered_body ||= Shortcode.process(body || '').html_safe
127
+ end
128
+
129
+ def draft?
130
+ status == :draft
131
+ end
132
+
133
+ def assigned?
134
+ status == :assigned
135
+ end
136
+
137
+ def published?
138
+ status == :published
139
+ end
140
+
141
+ def killed?
142
+ status == :killed
143
+ end
144
+
145
+ def hidden?
146
+ status == :hidden
147
+ end
148
+
149
+ def page?
150
+ post_type == :page
151
+ end
152
+
153
+ def post?
154
+ post_type == :post
155
+ end
156
+
157
+ def self.status_names
158
+ {
159
+ 'Draft' => :draft,
160
+ 'Assigned' => :assigned,
161
+ 'Published' => :published,
162
+ 'Killed' => :killed,
163
+ 'Hidden' => :hidden
164
+ }
165
+ end
166
+
167
+ def disqus_identifier
168
+ "post_#{id}"
169
+ end
170
+
171
+ def self.publish_scheduled!
172
+ candidates = Post.where.not(live: true).where("publish_at <= ?", Time.now.to_datetime)
173
+
174
+ candidates.each do |post|
175
+ post.update(live: true)
176
+ end
177
+ end
178
+
179
+ def has_citation?
180
+ citation_name.present? && citation_url.present?
181
+ end
182
+
183
+ # need to explicitly unscope this because papertrail
184
+ # imposes a forward chronological order with a default scope
185
+ def unscoped_versions
186
+ PaperTrail::Version.unscoped.where(item_id: id)
187
+ end
188
+
189
+ # i.e. if the lock timeout is "15 seconds" but the post's
190
+ # "editing_timestamp" has been bumped less than 15 seconds ago,
191
+ # we say the post is "mid-edit"
192
+ def is_mid_edit?
193
+ editing_timestamp && editing_timestamp > edit_lock_timeout.ago
194
+ end
195
+
196
+ def lock_for_editing!(user)
197
+ update!(editing_timestamp: Time.now.to_datetime, editing_user: user)
198
+ end
199
+
200
+ def attempt_edit_lock(user)
201
+ if is_mid_edit? && editing_user != user
202
+ false
203
+ else
204
+ lock_for_editing!(user) if persisted?
205
+ end
206
+ end
207
+
208
+ def edit_lock_timeout
209
+ Rails.application.config.edit_lock_timeout
210
+ end
211
+
212
+ def self.cache_key
213
+ "posts-#{maximum(:updated_at).try(:to_i)}-#{all.size}"
214
+ end
215
+
216
+ def track_view!
217
+
218
+ update_columns({
219
+ view_count: (view_count + 1),
220
+ popularity: calc_popularity(:increment)
221
+ })
222
+ end
223
+
224
+ def calc_popularity(opt=nil)
225
+ unless publish_at.nil?
226
+ # the number of days ago the very oldest post was made
227
+ days_ago_max = (Time.now - Post.minimum(:publish_at))/86400
228
+ days_ago = (Time.now - publish_at)/86400
229
+
230
+ # ex oldest post gets age_percentage 0.0, newest post gets 1.
231
+ age_percentage = (1 - (days_ago/(days_ago_max || 1.0)))
232
+
233
+ # boost from today to 10x...
234
+ age_percentage = 10 if publish_at.to_date == Date.today
235
+
236
+ # age factor is a number in the range of 0.1 - 1.00
237
+ # that is used to moderate the weight of votes for this post
238
+ age_factor = 0.1 + (0.9*age_percentage)
239
+
240
+ basis = opt == :increment ? (view_count + 1) : view_count
241
+
242
+ basis * age_factor
243
+ else
244
+ 0
245
+ end
246
+ end
247
+
248
+ def apply_tag_ordering!
249
+ if ordered_tag_ids.present?
250
+ if ordered_tag_ids.first.is_a?(String)
251
+ cleaned = ordered_tag_ids.first.gsub("[", "").gsub("]", "").split(",").map(&:to_i)
252
+ ordered_tag_ids = cleaned
253
+ end
254
+
255
+ ordered_tag_ids.each_with_index do |tag_id, index|
256
+ existing = post_tags.find_by(tag_id: tag_id)
257
+
258
+ if existing
259
+ existing.update!(position: index)
260
+ else
261
+ tag = Tag.find_by(id: tag_id)
262
+
263
+ if tag
264
+ post_tags.create!(tag_id: tag_id, position: index)
265
+ end
266
+ end
267
+ end
268
+
269
+ post_tags.where.not(tag_id: ordered_tag_ids).map &:destroy
270
+ end
271
+
272
+ true
273
+ end
274
+
275
+ def primary_category
276
+ categories.first
277
+ end
278
+
279
+ def description
280
+ if meta_description_tag_value.present?
281
+ meta_description_tag_value
282
+ elsif excerpt.present?
283
+ view_help.truncate(excerpt, length: 160)
284
+ else
285
+ view_help.truncate(view_help.strip_tags(rendered_body || ''), length: 160)
286
+ end
287
+ end
288
+
289
+ def thumb_image
290
+ if thumbnail_image.present?
291
+ thumbnail_image
292
+ else
293
+ featured_image
294
+ end
295
+ end
296
+
297
+
298
+ private
299
+
300
+ def set_default_publish_at
301
+ if status_changed? && status == :published && publish_at.blank?
302
+ self.publish_at = Time.now.to_datetime
303
+ end
304
+
305
+ true
306
+ end
307
+
308
+ def init
309
+ self.post_type = :post if post_type.nil?
310
+ end
311
+
312
+ def generate_search_document(opts={})
313
+ if headline_changed? || body_changed? || author_id_changed? || excerpt_changed? || opts[:force]
314
+ self.search_document = "#{headline} by #{author.byline}#{column ? (' ' + column.name) : ''}#{excerpt.present? ? (' ' + excerpt) : ''} #{strip_tags(rendered_body)}"
315
+ end
316
+
317
+ true
318
+ end
319
+
320
+ def set_meta
321
+ self.meta = {} if (meta.nil? || meta.is_a?(String))
322
+
323
+ # note: dirty tracking does not workin with hstore and meta_will_change! must be called
324
+ # for changes to an existing hash to persist: https://github.com/rails/rails/issues/6127
325
+
326
+ if has_slideshow?
327
+ slideshow_id = body.match(/\[slideshow id=\"(?<slideshow_id>\d+)\"\]/)['slideshow_id']
328
+ _will_change = (meta['contains_slideshow'] != 'true' || meta['slideshow_id'] != slideshow_id)
329
+ self.meta['contains_slideshow'] = 'true'
330
+ self.meta['slideshow_id'] = slideshow_id
331
+ meta_will_change! if _will_change
332
+ else
333
+ _will_change = meta['contains_slideshow'] != 'false'
334
+ self.meta['contains_slideshow'] = 'false'
335
+ meta_will_change! if _will_change
336
+ end
337
+
338
+ true
339
+ end
340
+
341
+ def set_columns_category
342
+ if column_id_changed?
343
+ if column_id.present? && !categories.include?(Category.COLUMNS)
344
+ self.categories << Category.COLUMNS
345
+ elsif column_id.blank? && categories.include?(Category.COLUMNS)
346
+ self.post_categories.where(category_id: Category.COLUMNS.id).map &:destroy
347
+ end
348
+ end
349
+
350
+ true
351
+ end
352
+
353
+ def has_slideshow?
354
+ body.present? && body.match(/\[slideshow id=\"\d+\"\]/).present?
355
+ end
356
+
357
+ def has_category
358
+ if categories.size < 1 && post?
359
+ errors[:base] << "Post must have at least one category"
360
+ end
361
+ end
362
+
363
+ # if a post has a publish_at value set and it is
364
+ # in the future, set the post to not be live
365
+ def set_live_status
366
+ if publish_at.present? && publish_at >= Time.now
367
+ self.live = false
368
+ else
369
+ self.live = true
370
+ end
371
+
372
+ true
373
+ end
374
+
375
+ def touch_dependent
376
+ categories.map &:touch
377
+ end
378
+
379
+ def should_generate_new_friendly_id?
380
+ slug.blank?
381
+ end
382
+
383
+ def slug_candidates
384
+ [
385
+ :headline,
386
+ [:headline, :uuid],
387
+ :uuid
388
+ ]
389
+ end
390
+ end
391
+ end