forest_cms 0.98.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 (411) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +204 -0
  4. data/Rakefile +37 -0
  5. data/app/assets/config/forest_manifest.js +2 -0
  6. data/app/assets/fonts/FontAwesome.otf +0 -0
  7. data/app/assets/fonts/fontawesome-webfont.eot +0 -0
  8. data/app/assets/fonts/fontawesome-webfont.svg +2671 -0
  9. data/app/assets/fonts/fontawesome-webfont.ttf +0 -0
  10. data/app/assets/fonts/fontawesome-webfont.woff +0 -0
  11. data/app/assets/fonts/fontawesome-webfont.woff2 +0 -0
  12. data/app/assets/images/forest/favicons/android-chrome-192x192.png +0 -0
  13. data/app/assets/images/forest/favicons/android-chrome-512x512.png +0 -0
  14. data/app/assets/images/forest/favicons/apple-touch-icon.png +0 -0
  15. data/app/assets/images/forest/favicons/browserconfig.xml +9 -0
  16. data/app/assets/images/forest/favicons/favicon-16x16.png +0 -0
  17. data/app/assets/images/forest/favicons/favicon-32x32.png +0 -0
  18. data/app/assets/images/forest/favicons/favicon.ico +0 -0
  19. data/app/assets/images/forest/favicons/manifest.json.erb +18 -0
  20. data/app/assets/images/forest/favicons/mstile-150x150.png +0 -0
  21. data/app/assets/images/forest/favicons/safari-pinned-tab.svg +21 -0
  22. data/app/assets/images/forest/forest-low.jpg +0 -0
  23. data/app/assets/images/forest/forest.jpg +0 -0
  24. data/app/assets/javascripts/forest/admin/lib/jquery_plugins.js +32 -0
  25. data/app/assets/javascripts/forest/admin/main.js +59 -0
  26. data/app/assets/javascripts/forest/admin/partials/add_block.js +22 -0
  27. data/app/assets/javascripts/forest/admin/partials/block_collapser.js +49 -0
  28. data/app/assets/javascripts/forest/admin/partials/block_errors.js +11 -0
  29. data/app/assets/javascripts/forest/admin/partials/block_selector_inputs.js +61 -0
  30. data/app/assets/javascripts/forest/admin/partials/cocoon_events.js +3 -0
  31. data/app/assets/javascripts/forest/admin/partials/cocoon_sortable_field_sets.js +55 -0
  32. data/app/assets/javascripts/forest/admin/partials/collage_inputs.js +286 -0
  33. data/app/assets/javascripts/forest/admin/partials/color_inputs.js +24 -0
  34. data/app/assets/javascripts/forest/admin/partials/datepicker_init.js +70 -0
  35. data/app/assets/javascripts/forest/admin/partials/documentation_sidebar_affix.js +22 -0
  36. data/app/assets/javascripts/forest/admin/partials/dropdown_block_selector.js +106 -0
  37. data/app/assets/javascripts/forest/admin/partials/edit_header.js +22 -0
  38. data/app/assets/javascripts/forest/admin/partials/file_upload_init.js +111 -0
  39. data/app/assets/javascripts/forest/admin/partials/forest_tables.js +42 -0
  40. data/app/assets/javascripts/forest/admin/partials/infinite_loader.js +89 -0
  41. data/app/assets/javascripts/forest/admin/partials/markdown_text_editor_init.js +89 -0
  42. data/app/assets/javascripts/forest/admin/partials/media_item_chooser_modal.js.erb +259 -0
  43. data/app/assets/javascripts/forest/admin/partials/menus.js +79 -0
  44. data/app/assets/javascripts/forest/admin/partials/multi_checkbox_selector.js +27 -0
  45. data/app/assets/javascripts/forest/admin/partials/persisted_value_warnings.js +38 -0
  46. data/app/assets/javascripts/forest/admin/partials/repeater_inputs.js +24 -0
  47. data/app/assets/javascripts/forest/admin/partials/select2_init.js +165 -0
  48. data/app/assets/javascripts/forest/admin/partials/select_tag_form_submitter.js +40 -0
  49. data/app/assets/javascripts/forest/admin/partials/sortable_gallery.js +45 -0
  50. data/app/assets/javascripts/forest/admin/partials/sortable_page_block_position_setter.js +37 -0
  51. data/app/assets/javascripts/forest/admin/partials/status_scheduler.js +16 -0
  52. data/app/assets/javascripts/forest/admin/partials/tab_templates.js +93 -0
  53. data/app/assets/javascripts/forest/admin/partials/tables.js +7 -0
  54. data/app/assets/javascripts/forest/admin/partials/tooltips.js +7 -0
  55. data/app/assets/javascripts/forest/admin/partials/turboform_form_submitter.js +15 -0
  56. data/app/assets/javascripts/forest/admin/vendor/imagesloaded.pkgd.min.js +7 -0
  57. data/app/assets/javascripts/forest/admin/vendor/jquery-ui-timepicker-addon.js +2291 -0
  58. data/app/assets/javascripts/forest/admin/vendor/jquery.ba-throttle-debounce.min.js +9 -0
  59. data/app/assets/javascripts/forest/admin/vendor/jquery.nestable.js +756 -0
  60. data/app/assets/javascripts/forest/admin/vendor/select2.full.min.js +3 -0
  61. data/app/assets/javascripts/forest/admin/vendor/simplemde.min.js +15 -0
  62. data/app/assets/javascripts/forest/application_admin.js +29 -0
  63. data/app/assets/javascripts/forest/application_admin_from_host_app.js +1 -0
  64. data/app/assets/javascripts/forest/application_public.js +19 -0
  65. data/app/assets/javascripts/forest/cable.js +13 -0
  66. data/app/assets/javascripts/forest/lib/jquery-3.3.1.min.js +2 -0
  67. data/app/assets/javascripts/forest/main.js +59 -0
  68. data/app/assets/javascripts/forest/public/partials/analytics.js +16 -0
  69. data/app/assets/stylesheets/forest/admin/_bootstrap-variables.scss +877 -0
  70. data/app/assets/stylesheets/forest/admin/_variables.scss +13 -0
  71. data/app/assets/stylesheets/forest/admin/base/_foundation.scss +17 -0
  72. data/app/assets/stylesheets/forest/admin/mixins/_breakpoints.scss +26 -0
  73. data/app/assets/stylesheets/forest/admin/mixins/_glyphicons.scss +278 -0
  74. data/app/assets/stylesheets/forest/admin/mixins/_layout.scss +13 -0
  75. data/app/assets/stylesheets/forest/admin/mixins/_links.scss +10 -0
  76. data/app/assets/stylesheets/forest/admin/mixins/_lists.scss +10 -0
  77. data/app/assets/stylesheets/forest/admin/mixins/_mixins.scss +6 -0
  78. data/app/assets/stylesheets/forest/admin/mixins/_triangles.scss +21 -0
  79. data/app/assets/stylesheets/forest/admin/partials/_admin.scss +57 -0
  80. data/app/assets/stylesheets/forest/admin/partials/_affix.scss +5 -0
  81. data/app/assets/stylesheets/forest/admin/partials/_block_errors.scss +15 -0
  82. data/app/assets/stylesheets/forest/admin/partials/_block_layout.scss +48 -0
  83. data/app/assets/stylesheets/forest/admin/partials/_block_type_edit_ui.scss +15 -0
  84. data/app/assets/stylesheets/forest/admin/partials/_bootstrap_customizations.scss +12 -0
  85. data/app/assets/stylesheets/forest/admin/partials/_collage_inputs.scss +122 -0
  86. data/app/assets/stylesheets/forest/admin/partials/_documentation.scss +7 -0
  87. data/app/assets/stylesheets/forest/admin/partials/_dropdown_block_selector.scss +19 -0
  88. data/app/assets/stylesheets/forest/admin/partials/_edit_header.scss +16 -0
  89. data/app/assets/stylesheets/forest/admin/partials/_file_upload.scss +3 -0
  90. data/app/assets/stylesheets/forest/admin/partials/_forest_tables.scss +20 -0
  91. data/app/assets/stylesheets/forest/admin/partials/_forms_and_inputs.scss +72 -0
  92. data/app/assets/stylesheets/forest/admin/partials/_helper_classes.scss +24 -0
  93. data/app/assets/stylesheets/forest/admin/partials/_infinite_loader.scss +8 -0
  94. data/app/assets/stylesheets/forest/admin/partials/_markdown_editor.scss +12 -0
  95. data/app/assets/stylesheets/forest/admin/partials/_media_item_chooser.scss +168 -0
  96. data/app/assets/stylesheets/forest/admin/partials/_media_library.scss +109 -0
  97. data/app/assets/stylesheets/forest/admin/partials/_pages.scss +6 -0
  98. data/app/assets/stylesheets/forest/admin/partials/_pagination.scss +5 -0
  99. data/app/assets/stylesheets/forest/admin/partials/_repeater_inputs.scss +57 -0
  100. data/app/assets/stylesheets/forest/admin/partials/_resizable_ui.scss +1 -0
  101. data/app/assets/stylesheets/forest/admin/partials/_select2_custom.scss +20 -0
  102. data/app/assets/stylesheets/forest/admin/partials/_sessions.scss +13 -0
  103. data/app/assets/stylesheets/forest/admin/partials/_slug_attributes.scss +9 -0
  104. data/app/assets/stylesheets/forest/admin/partials/_sortable_field_sets.scss +22 -0
  105. data/app/assets/stylesheets/forest/admin/partials/_sortable_select2.scss +18 -0
  106. data/app/assets/stylesheets/forest/admin/partials/_sortable_ui.scss +15 -0
  107. data/app/assets/stylesheets/forest/admin/partials/_status_indicators.scss +33 -0
  108. data/app/assets/stylesheets/forest/admin/partials/_status_scheduler.scss +9 -0
  109. data/app/assets/stylesheets/forest/admin/partials/_tooltips.scss +5 -0
  110. data/app/assets/stylesheets/forest/admin/partials/_turbolinks.scss +5 -0
  111. data/app/assets/stylesheets/forest/admin/partials/_typography.scss +19 -0
  112. data/app/assets/stylesheets/forest/admin/partials/_ui.scss +32 -0
  113. data/app/assets/stylesheets/forest/admin/sprockets/font-awesome.scss +2337 -0
  114. data/app/assets/stylesheets/forest/admin/sprockets/main_bootstrap.scss +3 -0
  115. data/app/assets/stylesheets/forest/admin/vendor/_jquery-ui-timepicker-addon.scss +30 -0
  116. data/app/assets/stylesheets/forest/admin/vendor/_nestable.scss +347 -0
  117. data/app/assets/stylesheets/forest/admin/vendor/_select2_1_core.scss +484 -0
  118. data/app/assets/stylesheets/forest/admin/vendor/_select2_2_bootstrap.scss +612 -0
  119. data/app/assets/stylesheets/forest/admin/vendor/_simplemde.min.scss +7 -0
  120. data/app/assets/stylesheets/forest/admin_overrides.scss +1 -0
  121. data/app/assets/stylesheets/forest/application_admin.css +23 -0
  122. data/app/assets/stylesheets/forest/application_public.css +16 -0
  123. data/app/assets/stylesheets/forest/main_admin.scss +16 -0
  124. data/app/assets/stylesheets/forest/main_public.scss +4 -0
  125. data/app/assets/stylesheets/forest/public/base/_admin_error_messages.scss +15 -0
  126. data/app/assets/stylesheets/forest/public/base/_foundation.scss +24 -0
  127. data/app/assets/stylesheets/forest/public/base/_images.scss +14 -0
  128. data/app/assets/stylesheets/forest/scaffolds.scss +87 -0
  129. data/app/assets/stylesheets/jquery.fileupload.scss +37 -0
  130. data/app/classes/forest/block_set.rb +26 -0
  131. data/app/classes/forest/error.rb +15 -0
  132. data/app/classes/forest/markdown_renderer.rb +31 -0
  133. data/app/classes/search_index_manager.rb +83 -0
  134. data/app/controllers/admin/block_kinds_controller.rb +31 -0
  135. data/app/controllers/admin/block_layouts_controller.rb +47 -0
  136. data/app/controllers/admin/cache_purge_controller.rb +11 -0
  137. data/app/controllers/admin/dashboard_controller.rb +6 -0
  138. data/app/controllers/admin/documentation_controller.rb +5 -0
  139. data/app/controllers/admin/errors_controller.rb +9 -0
  140. data/app/controllers/admin/forest_controller.rb +96 -0
  141. data/app/controllers/admin/imports_controller.rb +34 -0
  142. data/app/controllers/admin/media_items_controller.rb +107 -0
  143. data/app/controllers/admin/menus_controller.rb +69 -0
  144. data/app/controllers/admin/pages_controller.rb +105 -0
  145. data/app/controllers/admin/settings_controller.rb +66 -0
  146. data/app/controllers/admin/translations_controller.rb +58 -0
  147. data/app/controllers/admin/user_groups_controller.rb +69 -0
  148. data/app/controllers/admin/users_controller.rb +88 -0
  149. data/app/controllers/application_controller.rb +2 -0
  150. data/app/controllers/errors_controller.rb +26 -0
  151. data/app/controllers/forest_controller.rb +39 -0
  152. data/app/controllers/media_items_controller.rb +12 -0
  153. data/app/controllers/pages_controller.rb +39 -0
  154. data/app/controllers/public_controller.rb +5 -0
  155. data/app/helpers/admin_helper.rb +95 -0
  156. data/app/helpers/admin_info_helper.rb +12 -0
  157. data/app/helpers/admin_settings_helper.rb +16 -0
  158. data/app/helpers/application_helper.rb +2 -0
  159. data/app/helpers/block_helper.rb +25 -0
  160. data/app/helpers/cache_helper.rb +23 -0
  161. data/app/helpers/forest_errors_helper.rb +34 -0
  162. data/app/helpers/form_helper.rb +93 -0
  163. data/app/helpers/image_helper.rb +65 -0
  164. data/app/helpers/markdown_helper.rb +25 -0
  165. data/app/helpers/meta_helper.rb +238 -0
  166. data/app/helpers/navigation_helper.rb +18 -0
  167. data/app/helpers/status_indicator_helper.rb +7 -0
  168. data/app/inputs/block_selector_input.rb +16 -0
  169. data/app/inputs/collage_input.rb +63 -0
  170. data/app/inputs/color_input.rb +30 -0
  171. data/app/inputs/currency_input.rb +22 -0
  172. data/app/inputs/datepicker_input.rb +19 -0
  173. data/app/inputs/gallery_input.rb +67 -0
  174. data/app/inputs/grouped_collection_select_input.rb +19 -0
  175. data/app/inputs/image_input.rb +82 -0
  176. data/app/inputs/repeater_input.rb +58 -0
  177. data/app/jobs/forest/application_job.rb +4 -0
  178. data/app/mailers/forest/application_mailer.rb +6 -0
  179. data/app/models/application_record.rb +13 -0
  180. data/app/models/block_kind.rb +54 -0
  181. data/app/models/block_layout.rb +19 -0
  182. data/app/models/block_slot.rb +49 -0
  183. data/app/models/blocks/base_block.rb +134 -0
  184. data/app/models/cache_record.rb +2 -0
  185. data/app/models/concerns/attachable.rb +126 -0
  186. data/app/models/concerns/blockable.rb +65 -0
  187. data/app/models/concerns/filter_controller_scopes.rb +13 -0
  188. data/app/models/concerns/forest/cache_buster.rb +112 -0
  189. data/app/models/concerns/searchable.rb +200 -0
  190. data/app/models/concerns/sluggable.rb +70 -0
  191. data/app/models/concerns/statusable.rb +71 -0
  192. data/app/models/concerns/versionable.rb +13 -0
  193. data/app/models/forest/application_record.rb +111 -0
  194. data/app/models/import.rb +43 -0
  195. data/app/models/media_item.rb +136 -0
  196. data/app/models/menu.rb +65 -0
  197. data/app/models/page.rb +154 -0
  198. data/app/models/setting.rb +142 -0
  199. data/app/models/translation.rb +97 -0
  200. data/app/models/user.rb +45 -0
  201. data/app/models/user_group.rb +17 -0
  202. data/app/policies/application_policy.rb +71 -0
  203. data/app/policies/block_kind_policy.rb +2 -0
  204. data/app/policies/block_layout_policy.rb +2 -0
  205. data/app/policies/block_record_policy.rb +13 -0
  206. data/app/policies/dashboard_policy.rb +19 -0
  207. data/app/policies/media_item_policy.rb +5 -0
  208. data/app/policies/menu_policy.rb +2 -0
  209. data/app/policies/page_policy.rb +9 -0
  210. data/app/policies/setting_policy.rb +2 -0
  211. data/app/policies/translation_policy.rb +2 -0
  212. data/app/policies/user_group_policy.rb +2 -0
  213. data/app/policies/user_policy.rb +17 -0
  214. data/app/views/admin/_file_upload.html.erb +75 -0
  215. data/app/views/admin/_flash_notifications.html.erb +20 -0
  216. data/app/views/admin/_navigation.html.erb +61 -0
  217. data/app/views/admin/api/index/_header.json.jbuilder +2 -0
  218. data/app/views/admin/api/index/_records.json.jbuilder +6 -0
  219. data/app/views/admin/block_kinds/_form.html.erb +34 -0
  220. data/app/views/admin/block_kinds/edit.html.erb +3 -0
  221. data/app/views/admin/block_kinds/index.html.erb +31 -0
  222. data/app/views/admin/block_layouts/_form.html.erb +33 -0
  223. data/app/views/admin/block_layouts/edit.html.erb +3 -0
  224. data/app/views/admin/block_layouts/index.html.erb +29 -0
  225. data/app/views/admin/block_layouts/new.html.erb +3 -0
  226. data/app/views/admin/block_layouts/show.html.erb +24 -0
  227. data/app/views/admin/block_record/_block_layout.html.erb +67 -0
  228. data/app/views/admin/block_record/_block_slot_field_buttons.html.erb +15 -0
  229. data/app/views/admin/block_record/_block_slot_fields.html.erb +37 -0
  230. data/app/views/admin/block_record/_block_slot_fields_template.html.erb +50 -0
  231. data/app/views/admin/dashboard/_panel.html.erb +30 -0
  232. data/app/views/admin/dashboard/index.html.erb +18 -0
  233. data/app/views/admin/documentation/_body.html.erb +9 -0
  234. data/app/views/admin/documentation/_sidebar.html.erb +17 -0
  235. data/app/views/admin/documentation/index.html.erb +87 -0
  236. data/app/views/admin/form_inputs/_slug.html.erb +11 -0
  237. data/app/views/admin/form_inputs/_status.html.erb +20 -0
  238. data/app/views/admin/form_inputs/collage/_media_item_fields.html.erb +41 -0
  239. data/app/views/admin/headers/_edit.html.erb +68 -0
  240. data/app/views/admin/headers/_index.html.erb +61 -0
  241. data/app/views/admin/headers/_new.html.erb +14 -0
  242. data/app/views/admin/headers/_show.html.erb +37 -0
  243. data/app/views/admin/imports/edit.html.erb +60 -0
  244. data/app/views/admin/media_items/_form.html.erb +54 -0
  245. data/app/views/admin/media_items/_media_item_grid_layout.html.erb +23 -0
  246. data/app/views/admin/media_items/_media_item_list_layout.html.erb +32 -0
  247. data/app/views/admin/media_items/_modal.html.erb +62 -0
  248. data/app/views/admin/media_items/edit.html.erb +3 -0
  249. data/app/views/admin/media_items/index.html.erb +58 -0
  250. data/app/views/admin/media_items/index.json.jbuilder +2 -0
  251. data/app/views/admin/media_items/new.html.erb +3 -0
  252. data/app/views/admin/menus/_form.html.erb +92 -0
  253. data/app/views/admin/menus/_list_item.html.erb +39 -0
  254. data/app/views/admin/menus/edit.html.erb +3 -0
  255. data/app/views/admin/menus/index.html.erb +29 -0
  256. data/app/views/admin/menus/new.html.erb +3 -0
  257. data/app/views/admin/menus/show.html.erb +3 -0
  258. data/app/views/admin/navigation/_resources_dropdown.html.erb +12 -0
  259. data/app/views/admin/pages/_additional_inputs.html.erb +1 -0
  260. data/app/views/admin/pages/_form.html.erb +52 -0
  261. data/app/views/admin/pages/_table_row.html.erb +14 -0
  262. data/app/views/admin/pages/edit.html.erb +3 -0
  263. data/app/views/admin/pages/index.html.erb +33 -0
  264. data/app/views/admin/pages/index.json.jbuilder +2 -0
  265. data/app/views/admin/pages/new.html.erb +3 -0
  266. data/app/views/admin/pages/show.html.erb +3 -0
  267. data/app/views/admin/settings/_form.html.erb +25 -0
  268. data/app/views/admin/settings/edit.html.erb +3 -0
  269. data/app/views/admin/settings/index.html.erb +38 -0
  270. data/app/views/admin/settings/new.html.erb +3 -0
  271. data/app/views/admin/settings/show.html.erb +19 -0
  272. data/app/views/admin/settings/types/_boolean.html.erb +1 -0
  273. data/app/views/admin/settings/types/_image.html.erb +10 -0
  274. data/app/views/admin/settings/types/_integer.html.erb +5 -0
  275. data/app/views/admin/settings/types/_string.html.erb +5 -0
  276. data/app/views/admin/settings/types/_text.html.erb +5 -0
  277. data/app/views/admin/shared/_admin_head_additions.html.erb +1 -0
  278. data/app/views/admin/shared/navigation/_navbar.html.erb +8 -0
  279. data/app/views/admin/translations/_form.html.erb +31 -0
  280. data/app/views/admin/translations/edit.html.erb +3 -0
  281. data/app/views/admin/translations/index.html.erb +41 -0
  282. data/app/views/admin/translations/new.html.erb +3 -0
  283. data/app/views/admin/translations/show.html.erb +19 -0
  284. data/app/views/admin/user_groups/_form.html.erb +11 -0
  285. data/app/views/admin/user_groups/edit.html.erb +3 -0
  286. data/app/views/admin/user_groups/index.html.erb +29 -0
  287. data/app/views/admin/user_groups/new.html.erb +3 -0
  288. data/app/views/admin/user_groups/show.html.erb +9 -0
  289. data/app/views/admin/users/_form.html.erb +41 -0
  290. data/app/views/admin/users/edit.html.erb +3 -0
  291. data/app/views/admin/users/index.html.erb +35 -0
  292. data/app/views/admin/users/new.html.erb +3 -0
  293. data/app/views/admin/users/show.html.erb +4 -0
  294. data/app/views/blocks/_show.html.erb +21 -0
  295. data/app/views/devise/confirmations/new.html.erb +28 -0
  296. data/app/views/devise/mailer/confirmation_instructions.html.erb +5 -0
  297. data/app/views/devise/mailer/password_change.html.erb +3 -0
  298. data/app/views/devise/mailer/reset_password_instructions.html.erb +8 -0
  299. data/app/views/devise/mailer/unlock_instructions.html.erb +7 -0
  300. data/app/views/devise/passwords/edit.html.erb +31 -0
  301. data/app/views/devise/passwords/new.html.erb +27 -0
  302. data/app/views/devise/registrations/edit.html.erb +39 -0
  303. data/app/views/devise/registrations/new.html.erb +1 -0
  304. data/app/views/devise/sessions/_default_login.html.erb +25 -0
  305. data/app/views/devise/sessions/_first_time_login.html.erb +22 -0
  306. data/app/views/devise/sessions/new.html.erb +12 -0
  307. data/app/views/devise/shared/_flash_notifications.html.erb +3 -0
  308. data/app/views/devise/shared/_links.html.erb +25 -0
  309. data/app/views/devise/unlocks/new.html.erb +28 -0
  310. data/app/views/errors/internal_server_error.html.erb +2 -0
  311. data/app/views/errors/internal_server_error.json.jbuilder +4 -0
  312. data/app/views/errors/not_found.html.erb +2 -0
  313. data/app/views/errors/not_found.json.jbuilder +4 -0
  314. data/app/views/errors/unprocessable.html.erb +2 -0
  315. data/app/views/errors/unprocessable.json.jbuilder +4 -0
  316. data/app/views/forest/_analytics.html.erb +10 -0
  317. data/app/views/forest/_favicons.html.erb +7 -0
  318. data/app/views/kaminari/_first_page.html.erb +11 -0
  319. data/app/views/kaminari/_gap.html.erb +8 -0
  320. data/app/views/kaminari/_last_page.html.erb +11 -0
  321. data/app/views/kaminari/_next_page.html.erb +11 -0
  322. data/app/views/kaminari/_page.html.erb +12 -0
  323. data/app/views/kaminari/_paginator.html.erb +25 -0
  324. data/app/views/kaminari/_prev_page.html.erb +11 -0
  325. data/app/views/layouts/_shared_head.html.erb +48 -0
  326. data/app/views/layouts/admin.html.erb +19 -0
  327. data/app/views/layouts/application.html.erb +15 -0
  328. data/app/views/layouts/devise.html.erb +12 -0
  329. data/app/views/layouts/mailer.html.erb +13 -0
  330. data/app/views/layouts/mailer.text.erb +1 -0
  331. data/app/views/media_items/show.html.erb +29 -0
  332. data/app/views/pages/show.html.erb +1 -0
  333. data/app/views/public/index.html.erb +2 -0
  334. data/app/views/shared/_footer.html.erb +9 -0
  335. data/app/views/shared/_header.html.erb +9 -0
  336. data/app/views/shared/_header_colophon.html.erb +7 -0
  337. data/app/views/shared/_menu.html.erb +5 -0
  338. data/app/views/shared/_menu_item.html.erb +30 -0
  339. data/app/views/shared/_subnav.html.erb +7 -0
  340. data/config/initializers/aws.rb +17 -0
  341. data/config/initializers/devise.rb +275 -0
  342. data/config/initializers/forest.rb +5 -0
  343. data/config/initializers/paperclip.rb +1 -0
  344. data/config/initializers/rails_extensions.rb +6 -0
  345. data/config/initializers/simple_form.rb +164 -0
  346. data/config/initializers/simple_form/markdown.rb +21 -0
  347. data/config/initializers/simple_form/remote.rb +38 -0
  348. data/config/initializers/simple_form/sortable.rb +28 -0
  349. data/config/initializers/simple_form_bootstrap.rb +155 -0
  350. data/config/locales/devise.en.yml +62 -0
  351. data/config/locales/en.yml +24 -0
  352. data/config/locales/simple_form.en.yml +31 -0
  353. data/config/routes.rb +61 -0
  354. data/db/migrate/20161217191000_devise_create_users.rb +48 -0
  355. data/db/migrate/20161217200300_create_pages.rb +21 -0
  356. data/db/migrate/20161217205353_create_menus.rb +12 -0
  357. data/db/migrate/20161219033747_create_settings.rb +14 -0
  358. data/db/migrate/20161219231741_create_media_items.rb +18 -0
  359. data/db/migrate/20161225212203_create_user_groups.rb +10 -0
  360. data/db/migrate/20161225212258_create_join_table_user_groups_users.rb +8 -0
  361. data/db/migrate/20170109175932_create_block_kinds.rb +14 -0
  362. data/db/migrate/20170109180818_create_block_slots.rb +17 -0
  363. data/db/migrate/20170109185414_create_base_blocks.rb +8 -0
  364. data/db/migrate/20170818025124_create_block_layouts.rb +13 -0
  365. data/db/migrate/20170818025229_add_block_layout_to_block_slots.rb +5 -0
  366. data/db/migrate/20170818025500_remove_layout_from_block_slots.rb +5 -0
  367. data/db/migrate/20170821161755_add_unique_index_to_block_layouts.rb +6 -0
  368. data/db/migrate/20170826173346_create_translations.rb +12 -0
  369. data/db/migrate/20170910204934_index_attachment_content_type_on_media_items.rb +5 -0
  370. data/db/migrate/20180206035546_create_cache_records.rb +14 -0
  371. data/db/migrate/20180415034427_add_redirect_to_pages.rb +5 -0
  372. data/db/migrate/20180618190248_add_index_created_at_to_media_items.rb +5 -0
  373. data/db/migrate/20180625172226_add_blockable_metadata_to_pages.rb +6 -0
  374. data/db/migrate/20180626182949_remove_attachable_from_media_items.rb +7 -0
  375. data/db/migrate/20180712162414_add_media_item_status_to_media_items.rb +6 -0
  376. data/db/migrate/20190223155013_add_type_to_block_kinds.rb +6 -0
  377. data/db/migrate/20190423173909_add_seo_title_to_pages.rb +5 -0
  378. data/lib/forest.rb +9 -0
  379. data/lib/forest/engine.rb +51 -0
  380. data/lib/forest/migrations.rb +86 -0
  381. data/lib/forest/rails/active_record/has_many_ordered.rb +30 -0
  382. data/lib/forest/rails/active_record/has_repeatable.rb +19 -0
  383. data/lib/forest/rails/core_ext/enumerable.rb +17 -0
  384. data/lib/forest/version.rb +3 -0
  385. data/lib/generators/forest/block/USAGE +8 -0
  386. data/lib/generators/forest/block/block_generator.rb +103 -0
  387. data/lib/generators/forest/block/templates/_block.html.erb +2 -0
  388. data/lib/generators/forest/block/templates/_block.scss.erb +5 -0
  389. data/lib/generators/forest/block/templates/_block_edit_fields.html.erb +21 -0
  390. data/lib/generators/forest/block/templates/create_table_migration.rb +31 -0
  391. data/lib/generators/forest/block/templates/model.rb +41 -0
  392. data/lib/generators/forest/install/USAGE +2 -0
  393. data/lib/generators/forest/install/install_generator.rb +72 -0
  394. data/lib/generators/forest/scaffold/USAGE +8 -0
  395. data/lib/generators/forest/scaffold/scaffold_generator.rb +133 -0
  396. data/lib/generators/forest/scaffold/templates/admin_controller.rb +67 -0
  397. data/lib/generators/forest/scaffold/templates/controller.rb +20 -0
  398. data/lib/generators/forest/scaffold/templates/create_table_migration.rb +35 -0
  399. data/lib/generators/forest/scaffold/templates/model.rb +21 -0
  400. data/lib/generators/forest/scaffold/templates/policy.rb +2 -0
  401. data/lib/generators/forest/scaffold/templates/views/admin/_form.html.erb +56 -0
  402. data/lib/generators/forest/scaffold/templates/views/admin/edit.html.erb +3 -0
  403. data/lib/generators/forest/scaffold/templates/views/admin/index.html.erb +58 -0
  404. data/lib/generators/forest/scaffold/templates/views/admin/index.json.jbuilder +2 -0
  405. data/lib/generators/forest/scaffold/templates/views/admin/new.html.erb +3 -0
  406. data/lib/generators/forest/scaffold/templates/views/admin/show.html.erb +12 -0
  407. data/lib/generators/forest/scaffold/templates/views/public/index.html.erb +1 -0
  408. data/lib/generators/forest/scaffold/templates/views/public/show.html.erb +17 -0
  409. data/lib/tasks/forest_db_tasks.rake +176 -0
  410. data/lib/tasks/forest_elasticsearch_tasks.rake +22 -0
  411. metadata +719 -0
@@ -0,0 +1,19 @@
1
+ class BlockLayout < Forest::ApplicationRecord
2
+ include Sluggable
3
+
4
+ has_many :block_slots, inverse_of: :block_layout
5
+
6
+ validates_presence_of :display_name
7
+
8
+ def self.resource_description
9
+ "A block layout is a grouping of blocks that may appear in a particular part of the page. For example, you may have block layouts that apply to the sidebar or header areas of a page."
10
+ end
11
+
12
+ def self.default_layout
13
+ BlockLayout.find_by_slug('default')
14
+ end
15
+
16
+ def slug_attribute
17
+ display_name
18
+ end
19
+ end
@@ -0,0 +1,49 @@
1
+ class BlockSlot < Forest::ApplicationRecord
2
+ parent_class = self
3
+
4
+ validates_presence_of :block_kind
5
+ validates :block_record, presence: true, on: :update
6
+
7
+ belongs_to :block, polymorphic: true, dependent: :destroy
8
+ belongs_to :block_record, polymorphic: true #, optional: true
9
+ belongs_to :block_kind, inverse_of: :block_slots
10
+ belongs_to :block_layout, inverse_of: :block_slots
11
+
12
+ before_validation :create_block_if_block_attributes_empty
13
+
14
+ accepts_nested_attributes_for :block, reject_if: :all_blank, allow_destroy: true
15
+
16
+ scope :by_layout, -> (block_layout) { where(block_layout_id: block_layout.id) }
17
+
18
+ # Merge these blockable params into the blockable record's strong params
19
+ def self.blockable_params
20
+ {
21
+ block_slots_attributes: [
22
+ :id, :_destroy, :position,
23
+ :block_type, :block_record_type,
24
+ :block_id, :block_kind_id, :block_layout_id, :block_record_id,
25
+ block_attributes: [*BlockKind.block_kind_params]
26
+ ]
27
+ }
28
+ end
29
+
30
+ def block_attributes=(attributes)
31
+ if BlockKind.where(name: self.block_kind.name).exists?
32
+ self.block ||= self.block_kind.name.constantize.new
33
+ self.block.assign_attributes(attributes)
34
+ end
35
+ end
36
+
37
+ def create_block_if_block_attributes_empty
38
+ if BlockKind.where(name: self.block_kind.name).exists? && BlockKind.where(name: self.block_kind.name).first.block.permitted_params.blank?
39
+ self.block ||= self.block_kind.name.constantize.new
40
+ end
41
+ end
42
+
43
+ protected
44
+
45
+ def build_block(kind = nil)
46
+ kind ||= self.block_kind.name.constantize
47
+ self.block = kind.new
48
+ end
49
+ end
@@ -0,0 +1,134 @@
1
+ class BaseBlock < Forest::ApplicationRecord
2
+ self.abstract_class = true
3
+
4
+ has_one :block_slot,
5
+ as: :block,
6
+ inverse_of: :block,
7
+ class_name: 'BlockSlot',
8
+ dependent: :destroy
9
+
10
+ # Override to customize the display name of the block.
11
+ def self.display_name
12
+ 'Base Block'
13
+ end
14
+
15
+ # Override to add a Bootstrap 3 glyphicon next to the block's title.
16
+ def self.display_icon
17
+ 'glyphicon glyphicon-tree-conifer'
18
+ end
19
+
20
+ def self.kind
21
+ BlockKind.find_by_name(name)
22
+ end
23
+
24
+ def display_name
25
+ self.class.display_name
26
+ end
27
+
28
+ def hidden?
29
+ false
30
+ end
31
+
32
+ def block?
33
+ true
34
+ end
35
+
36
+ # Override to add css classes to a block's wrapper.
37
+ def css_class
38
+ end
39
+
40
+ # Override to add css styles to a block's wrapper.
41
+ def css_styles
42
+ end
43
+
44
+ # Override to add data attributes to a block's wrapper. Make sure this is a hash.
45
+ def data_attributes
46
+ end
47
+
48
+ def display_icon
49
+ self.class.display_icon
50
+ end
51
+
52
+ def permitted_params
53
+ self.class.permitted_params
54
+ end
55
+
56
+ def block_id
57
+ "#{self.model_name.singular.dasherize}-#{self.id}"
58
+ end
59
+
60
+ def block_kind
61
+ @block_kind ||= BlockKind.find_by_name(self.class.name)
62
+ end
63
+
64
+ def block_record
65
+ @block_record ||= block_slot.block_record
66
+ end
67
+
68
+ def block_record_id
69
+ block_record.block_record.id
70
+ end
71
+
72
+ def block_layout
73
+ block_slot.block_layout
74
+ end
75
+
76
+ def blocks
77
+ block_record.blocks(block_layout: block_layout)
78
+ end
79
+
80
+ def index
81
+ blocks.index(self)
82
+ end
83
+
84
+ def first?
85
+ index == 0
86
+ end
87
+
88
+ def last?
89
+ index == blocks.length - 1
90
+ end
91
+
92
+ def first_of_kind?
93
+ blocks.first_of_kind(self.class) == self
94
+ end
95
+
96
+ def last_of_kind?
97
+ blocks.last_of_kind(self.class) == self
98
+ end
99
+
100
+ def even_of_kind?
101
+ blocks.kind(self.class).index(self).even?
102
+ end
103
+
104
+ def odd_of_kind?
105
+ blocks.kind(self.class).index(self).odd?
106
+ end
107
+
108
+ def previous
109
+ return nil if first?
110
+ blocks[index - 1]
111
+ end
112
+
113
+ def next
114
+ return nil if last?
115
+ blocks[index + 1]
116
+ end
117
+
118
+ def non_indexed_attributes
119
+ %W(id created_at updated_at)
120
+ end
121
+
122
+ # Override this in your block to define which attributes should be indexed for search
123
+ def indexed_attributes
124
+ self.class.columns.select { |c| %i(text string).include?(c.type) }
125
+ .collect(&:name)
126
+ .reject { |c| c =~ /_url$/ } - non_indexed_attributes
127
+ end
128
+
129
+ def as_indexed_json(options={})
130
+ self.as_json({
131
+ only: indexed_attributes
132
+ })
133
+ end
134
+ end
@@ -0,0 +1,2 @@
1
+ class CacheRecord < ApplicationRecord
2
+ end
@@ -0,0 +1,126 @@
1
+ module Attachable
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ has_attached_file :attachment,
6
+ styles: {
7
+ thumb: '200x200#',
8
+ small: '600x600>',
9
+ medium: '1200x1200>',
10
+ large: '2000x2000>'
11
+ },
12
+ convert_options: {
13
+ thumb: lambda { |r| r.gif? ? '' : '-unsharp 0x1' },
14
+ small: lambda { |r| r.gif? ? '' : '-unsharp 0x1' },
15
+ medium: lambda { |r| r.gif? ? '' : '-unsharp 1.5x1+0.7+0.02' },
16
+ large: lambda { |r| r.gif? ? '' : '-unsharp 1.5x1+0.7+0.02' },
17
+ all: lambda { |r| r.gif? ? '-strip -auto-orient -colorspace sRGB' : '-strip -auto-orient -quality 85 -colorspace sRGB -interlace Plane -sampling-factor 4:2:0' }
18
+ },
19
+ default_url: '/images/:style/missing.png'
20
+
21
+ do_not_validate_attachment_file_type :attachment
22
+
23
+ before_post_process :skip_for_non_images
24
+ before_post_process :rename_attachment
25
+ after_post_process :extract_dimensions
26
+ after_post_process :collect_garbage
27
+
28
+ serialize :dimensions
29
+
30
+ belongs_to :attachable, polymorphic: true, optional: true
31
+
32
+ scope :by_content_type, -> (content_type) { where(attachment_content_type: content_type) }
33
+ scope :images, -> { where('attachment_content_type LIKE ?', '%image%') }
34
+ scope :videos, -> { where('attachment_content_type LIKE ?', '%video%') }
35
+ scope :pdfs, -> { where('attachment_content_type LIKE ?', '%pdf%') }
36
+
37
+ def large_attachment_url
38
+ attachment.url(:large)
39
+ end
40
+
41
+ def image?
42
+ (attachment_content_type =~ %r{^(image|(x-)?application)/(bmp|gif|jpeg|jpg|pjpeg|png|x-png)$}).present?
43
+ end
44
+
45
+ def video?
46
+ (attachment_content_type =~ %r{^video\/}).present?
47
+ end
48
+
49
+ def file?
50
+ !image?
51
+ end
52
+
53
+ def gif?
54
+ attachment_content_type == 'image/gif'
55
+ end
56
+
57
+ def display_content_type
58
+ if image?
59
+ 'image'
60
+ elsif video?
61
+ 'video'
62
+ elsif file?
63
+ 'file'
64
+ else
65
+ 'media item'
66
+ end
67
+ end
68
+
69
+ def dimensions_at(size)
70
+ geometry = attachment.options[:styles][size].split('x')
71
+ w = geometry.first.to_f
72
+ ratio = dimensions[:width] / w
73
+
74
+ {
75
+ width: dimensions[:width] / ratio,
76
+ height: dimensions[:height] / ratio
77
+ }
78
+ end
79
+
80
+ def file_extension
81
+ File.extname(attachment_file_name).downcase
82
+ end
83
+
84
+ def display_file_name
85
+ attachment_file_name.sub(/(--\d*)?#{Regexp.quote(file_extension)}$/i, '')
86
+ end
87
+
88
+ private
89
+
90
+ def set_default_metadata
91
+ if self.title.blank?
92
+ self.title = display_file_name
93
+ end
94
+ end
95
+
96
+ # Append a timestamp to the end of the file name to avoid cache issues with a CDN, e.g. CloudFront.
97
+ def rename_attachment
98
+ self.attachment.instance_write(:file_name, "#{display_file_name}--#{Time.now.to_i}#{file_extension}")
99
+ end
100
+
101
+ def skip_for_non_images
102
+ image?
103
+ end
104
+
105
+ # Retrieves dimensions for image assets
106
+ # @note Do this after resize operations to account for auto-orientation.
107
+ # https://github.com/thoughtbot/paperclip/wiki/Extracting-image-dimensions
108
+ def extract_dimensions
109
+ return unless image?
110
+ tempfile = attachment.queued_for_write[:original]
111
+ unless tempfile.nil?
112
+ geometry = Paperclip::Geometry.from_file(tempfile)
113
+ self.dimensions = {
114
+ width: geometry.width.to_i,
115
+ height: geometry.height.to_i
116
+ }
117
+ end
118
+ end
119
+
120
+ # Not sure if this is a good idea, but Heroku's memory gets bogged down immediately
121
+ # when using paperclip and imagemagick.
122
+ def collect_garbage
123
+ GC.start
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,65 @@
1
+ module Blockable
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ parent_class = self
6
+
7
+ before_save :touch_self
8
+ after_save :set_blockable_metadata
9
+
10
+ has_many :block_slots, -> { order(:position) },
11
+ as: :block_record,
12
+ inverse_of: :block_record,
13
+ foreign_key: 'block_record_id',
14
+ dependent: :destroy
15
+
16
+ accepts_nested_attributes_for :block_slots, allow_destroy: true
17
+
18
+ def self.blockable?
19
+ true
20
+ end
21
+
22
+ def blockable?
23
+ true
24
+ end
25
+
26
+ def blocks(options = {})
27
+ block_layout = options.fetch(:block_layout, nil)
28
+ block_layout_name = block_layout.try(:slug).try(:underscore).presence || 'default'
29
+ block_type = options.fetch(:kind, nil)
30
+
31
+ instance_variable_get("@#{block_layout_name}_blocks") || instance_variable_set("@#{block_layout_name}_blocks", begin
32
+ if block_type
33
+ b = block_slots.includes(:block).where(block_kind_id: block_type.kind.id)
34
+ else
35
+ b = block_slots.includes(:block, :block_layout)
36
+ end
37
+ if block_layout.present?
38
+ b = b.select { |bs| bs.block_layout == block_layout }
39
+ end
40
+ Forest::BlockSet.new b.collect(&:block)
41
+ end)
42
+ end
43
+
44
+ def featured_image_from_blocks
45
+ blocks.find { |b| b.try(:media_item).present? }.try(:media_item)
46
+ end
47
+
48
+ private
49
+
50
+ def touch_self
51
+ if self.respond_to?(:updated_at)
52
+ self.updated_at = Time.now
53
+ end
54
+ end
55
+
56
+ # Blockable metadata is a way to cache and store data on the blockable record itself to avoid expensive database lookups
57
+ # when querying information about a record that may come from the reord's blocks.
58
+ def set_blockable_metadata
59
+ if self.respond_to?(:blockable_metadata)
60
+ self.blockable_metadata['featured_image_url'] = featured_image_from_blocks.try(:attachment).try(:url, :large)
61
+ self.update_column(:blockable_metadata, self.blockable_metadata)
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,13 @@
1
+ module FilterControllerScopes
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ has_scope :by_id
6
+ has_scope :by_title
7
+ has_scope :by_slug
8
+ has_scope :by_created_at
9
+ has_scope :by_updated_at
10
+ has_scope :by_status
11
+ has_scope :fuzzy_search
12
+ end
13
+ end
@@ -0,0 +1,112 @@
1
+ # Include the CacheBuster concern in a model to automatically update
2
+ # all of a model's associated records (hopefully).
3
+ #
4
+ # There are only two hard things in Computer Science: cache invalidation and naming things.
5
+ # -- Phil Karlton
6
+
7
+ module Forest::CacheBuster
8
+ extend ActiveSupport::Concern
9
+
10
+ included do
11
+ after_save :touch_associations
12
+ after_save :touch_dependent_records
13
+ after_destroy :touch_associations
14
+ after_destroy :touch_dependent_records
15
+
16
+ # A cached set of associations is stored in a polymorphic record after every save.
17
+ # This allows us to know what the previous set of associated records was during a
18
+ # record's after_save callback, which is important when associations are removed.
19
+ has_one :cache_record, as: :cacheable, dependent: :destroy
20
+
21
+ private
22
+
23
+ def touch_associations
24
+ # Find all relevant associations
25
+ has_many_associations = self.class.reflect_on_all_associations(:has_many)
26
+ habtm_many_associations = self.class.reflect_on_all_associations(:has_and_belongs_to_many)
27
+ all_associations = has_many_associations + habtm_many_associations
28
+
29
+ # Collect a set of associated records (ActiveRecord::Associations objects)
30
+ association_groups = all_associations.collect { |association| self.send(association.plural_name) }
31
+
32
+ to_update = {}
33
+ time_now = Time.now
34
+
35
+ # Find the cache_record or build a new one
36
+ cache_record = cache_record.presence || self.build_cache_record
37
+
38
+ # Extract the associated class name and ids from the cache_record
39
+ if cache_record.data.present?
40
+ cache_record.data.each do |cache_record_item|
41
+ klass = cache_record_item['klass']
42
+ id = cache_record_item['id']
43
+ to_update[klass] ||= []
44
+ to_update[klass] << id
45
+ end
46
+ end
47
+
48
+ # Update the associations currently attached to the record
49
+ association_groups.each do |records|
50
+ if records.try :any?
51
+ if records.class.parent.name == 'ActiveRecord::Associations'
52
+ if records.column_names.include?('updated_at')
53
+ logger.debug { "[Forest] CacheBuster is updating associations for #{records.length} #{records.first.model_name.plural}" }
54
+ records.unscope(:order).update_all updated_at: time_now
55
+ # Expire model cache_key
56
+ records.klass.name.safe_constantize&.try(:expire_cache_key)
57
+ end
58
+
59
+ # Delete record ids from the to_update hash if we update them here
60
+ records.to_a.flatten.each do |record|
61
+ to_update[record.class.name] ||= []
62
+ to_update[record.class.name].delete(record.id)
63
+ end
64
+ end
65
+ end
66
+ end
67
+
68
+ # Check all associations and see if they are blocks. If they are, collect the unique associated
69
+ # block records and touch each one.
70
+ associated_blocks = association_groups.flatten.select { |r| r.try(:block?) }
71
+ if associated_blocks.present?
72
+ associated_blocks.collect(&:block_record).uniq.each do |block_record|
73
+ logger.debug { "[Forest] CacheBuster is updating block_record #{block_record.class.to_s} that was associated with a block." }
74
+ block_record.touch
75
+ end
76
+ end
77
+
78
+ # Update any remaining associations
79
+ to_update.each do |class_name, ids|
80
+ klass = class_name.safe_constantize
81
+ next unless klass.present?
82
+ records = klass.where(id: ids)
83
+ if records.present?
84
+ if records.column_names.include?('updated_at')
85
+ logger.debug { "[Forest] CacheBuster is updating remaining associations for #{records.length} #{records.first.model_name.plural} inferred from the cache_record" }
86
+ records.unscope(:order).update_all(updated_at: time_now)
87
+ # Expire model cache_key
88
+ klass.try(:expire_cache_key)
89
+ end
90
+ end
91
+ end
92
+
93
+ # Store the new set of associations in the cache_record
94
+ cache_record.data = association_groups.flatten.collect do |record|
95
+ { klass: record.class.name, id: record.id }
96
+ end
97
+
98
+ # Save the cache_record
99
+ cache_record.save
100
+ end
101
+
102
+ # In some situations a block might rely on data that isn't associated via an ActiveRecord
103
+ # Association. For example, a CurrentExhibitions block depends on Exhibition, but doesn't rely
104
+ # on an association. In that case, override this method in the model and bust the associated data
105
+ # as necessary, using something like the commented out code here.
106
+ def touch_dependent_records
107
+ # For example:
108
+ # logger.debug { "[Forest] CacheBuster is touching dependent records for #{'CurrentExhibitionsBlock'.safe_constantize.length} CurrentExhibitionsBlocks" }
109
+ # 'CurrentExhibitionsBlock'.safe_constantize&.update_all(updated_at: Time.now)
110
+ end
111
+ end
112
+ end