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,200 @@
1
+ # This module provides a search interface to the record using elasticsearch-rails.
2
+ # The easiest way to implement search in your host app deployed on heroku is to
3
+ # add the 'bonsai-elasticsearch-rails' gem, and include this Searchable concern
4
+ # in the models you want to index.
5
+ #
6
+ # Does not support Elasticsearch version > 5
7
+ # TODO: Once the elasticsearch/rails gems make it easier to use version 6, make this
8
+ # Searchable concern compatible.
9
+ #
10
+ # You'll want to look at the SearchIndexManager class next for information on how to customize
11
+ # the elasticsearch indices for your models.
12
+ #
13
+ # Once your model is configured, import documents with the rake task:
14
+ # `bin/rails forest:elasticsearch:rebuild`
15
+
16
+ begin
17
+ require 'elasticsearch/model'
18
+ require 'elasticsearch/rails'
19
+ rescue LoadError
20
+ end
21
+
22
+ module Searchable
23
+ extend ActiveSupport::Concern
24
+
25
+ included do
26
+ include Elasticsearch::Model
27
+
28
+ index_name SearchIndexManager::INDEX_NAME
29
+
30
+ # TODO: as an improvement implement Asynchronous Callbacks
31
+ # https://github.com/elastic/elasticsearch-rails/tree/master/elasticsearch-model#asynchronous-callbacks
32
+
33
+ after_commit on: [:create] do
34
+ begin
35
+ index_elasticsearch_document if indexable_for_elasticsearch?
36
+ rescue Exception => e
37
+ logger.warn { "[Forest][Searchable] Failed to index document #{self.class} #{self.id} #{e.inspect}" }
38
+ end
39
+ end
40
+
41
+ after_commit on: [:update] do
42
+ begin
43
+ if indexable_for_elasticsearch?
44
+ index_elasticsearch_document
45
+ else
46
+ delete_elasticsearch_document
47
+ end
48
+ rescue Exception => e
49
+ logger.warn { "[Forest][Searchable] Failed to index document #{self.class} #{self.id} #{e.inspect}" }
50
+ end
51
+ end
52
+
53
+ after_commit on: [:destroy] do
54
+ begin
55
+ delete_elasticsearch_document
56
+ rescue Exception => e
57
+ logger.warn { "[Forest][Searchable] Failed to delete document #{self.class} #{self.id} #{e.inspect}" }
58
+ end
59
+ end
60
+
61
+ def self.searchable?
62
+ true
63
+ end
64
+
65
+ # Override this in your host app with the scope method to use when importing elasticsearch documents. This
66
+ # should probably match the conditions of the indexable_for_elasticsearch? method.
67
+ def self.elasticsearch_import_model_scope
68
+ if self.respond_to?(:statusable?)
69
+ :published
70
+ end
71
+ end
72
+
73
+ def self.elasticsearch(query)
74
+ __elasticsearch__.search(query)
75
+ end
76
+
77
+ def self.non_indexed_attributes
78
+ %W(id slug path status scheduled_date created_at updated_at featured_image_id parent_page_id redirect)
79
+ end
80
+ def non_indexed_attributes
81
+ self.class.non_indexed_attributes
82
+ end
83
+
84
+ def self.indexed_attributes
85
+ columns.select { |c| %i(text string).include?(c.type) }
86
+ .collect(&:name)
87
+ .reject { |c| c =~ /_url$/ } - non_indexed_attributes
88
+ end
89
+ def indexed_attributes
90
+ self.class.indexed_attributes
91
+ end
92
+
93
+ def self.indexed_associations
94
+ if self.try(:blockable?)
95
+ {
96
+ block_slots: {
97
+ only: :block,
98
+ include: {
99
+ block: {
100
+ methods: [:as_indexed_json],
101
+ only: {
102
+ methods: [:as_indexed_json]
103
+ }
104
+ }
105
+ }
106
+ }
107
+ }
108
+ end
109
+ end
110
+ def indexed_associations
111
+ self.class.indexed_associations
112
+ end
113
+
114
+ # def self.fields_to_search
115
+ # indexed_attributes.collect { |a|
116
+ # a =~ /name$/ ? "#{a}^2" : a
117
+ # }.concat(indexed_associations_to_search)
118
+ # end
119
+
120
+ # Override in your host app model to specify field search and match behavior
121
+ def self.elasticsearch_query(query)
122
+ {
123
+ query: {
124
+ match: {
125
+ _all: sanitize_query(query)
126
+ }
127
+ # multi_match: {
128
+ # query: sanitize_query(query),
129
+ # fields: fields_to_search
130
+ # }
131
+ },
132
+ highlight: {
133
+ fields: { '*' => {} },
134
+ require_field_match: false,
135
+ pre_tags: ['|highlight|'],
136
+ post_tags: ['|/highlight|']
137
+ }
138
+ }
139
+ end
140
+
141
+ def self.sanitize_query(query)
142
+ return if query.blank?
143
+ memo_key = "@_sanitize_query_#{query.parameterize.underscore}"
144
+ return instance_variable_get(memo_key) if instance_variable_defined?(memo_key)
145
+ instance_variable_set memo_key, begin
146
+ # http://stackoverflow.com/a/16442439
147
+ # Escape special characters
148
+ # http://lucene.apache.org/core/old_versioned_docs/versions/2_9_1/queryparsersyntax.html#Escaping Special Characters
149
+ escaped_characters = Regexp.escape('\\\/+-&|!(){}[]^~*?:')
150
+ query = query.gsub(/([#{escaped_characters}])/, '\\\\\1')
151
+
152
+ # AND, OR and NOT are used by lucene as logical operators. We need
153
+ # to escape them
154
+ ['AND', 'OR', 'NOT'].each do |word|
155
+ escaped_word = word.split('').map {|char| "\\#{char}" }.join('')
156
+ query = query.gsub(/\s*\b(#{word.upcase})\b\s*/, " #{escaped_word} ")
157
+ end
158
+
159
+ # Escape odd quotes
160
+ quote_count = query.count '"'
161
+ query = query.gsub(/(.*)"(.*)/, '\1\"\3') if quote_count % 2 == 1
162
+
163
+ query
164
+ end
165
+ end
166
+
167
+ def searchable?
168
+ true
169
+ end
170
+
171
+ # Override this method if you need to customize the logic that determines
172
+ # if a record is published, or should be indexed for the public.
173
+ def indexable_for_elasticsearch?
174
+ if self.respond_to?(:statusable?)
175
+ self.try(:published?)
176
+ else
177
+ true
178
+ end
179
+ end
180
+
181
+ def as_indexed_json(options={})
182
+ self.as_json({
183
+ only: indexed_attributes,
184
+ include: indexed_associations
185
+ })
186
+ end
187
+ end
188
+
189
+ private
190
+
191
+ def index_elasticsearch_document
192
+ logger.debug { '[Forest][Searchable] Indexing document' }
193
+ __elasticsearch__.index_document
194
+ end
195
+
196
+ def delete_elasticsearch_document
197
+ logger.debug { '[Forest][Searchable] Deleting document' }
198
+ __elasticsearch__.delete_document ignore: 404
199
+ end
200
+ end
@@ -0,0 +1,70 @@
1
+ module Sluggable
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ before_validation :generate_slug
6
+ before_save :parameterize_slug, if: :will_save_change_to_slug?
7
+
8
+ # validates :slug, presence: true, uniqueness: true
9
+
10
+ scope :by_slug, -> (orderer = :asc) { order(slug: orderer) }
11
+
12
+ def self.sluggable?
13
+ true
14
+ end
15
+
16
+ def sluggable?
17
+ true
18
+ end
19
+
20
+ # Override this method to define which attribute the slug is created from, or
21
+ # have this method return false to use a random slug
22
+ def slug_attribute
23
+ if respond_to?(:title)
24
+ title
25
+ elsif respond_to?(:name)
26
+ name
27
+ else
28
+ raise Forest::Error.new("Slug attribute :title does not exist on #{self.class.name}. Define a `slug_attribute` method with a valid attribue.")
29
+ end
30
+ end
31
+
32
+ def generate_slug
33
+ return unless generate_slug?
34
+
35
+ if slug_attribute.present?
36
+ slug_attr = slug_attribute.parameterize
37
+ if slug_as_key?
38
+ slug_attr = slug_attribute.parameterize.underscore
39
+ end
40
+ else
41
+ slug_attr = SecureRandom.uuid
42
+ end
43
+
44
+ self.slug = slug_attr
45
+
46
+ if self.class.find_by_slug(slug_attr).present?
47
+ self.slug = "#{slug_attr}-#{SecureRandom.uuid}"
48
+ end
49
+ end
50
+
51
+ def generate_slug?
52
+ self.slug.blank?
53
+ end
54
+
55
+ # Override slug_as_key? in models like settings where the slug should be underscored, not dasherized
56
+ def slug_as_key?
57
+ false
58
+ end
59
+
60
+ def to_friendly_param
61
+ slug
62
+ end
63
+
64
+ private
65
+
66
+ def parameterize_slug
67
+ self.slug = slug.parameterize
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,71 @@
1
+ module Statusable
2
+ extend ActiveSupport::Concern
3
+
4
+ # To make a record schedulable, create a date column named published_date
5
+ # TODO: remove references to scheduled_date in favor of published_date
6
+
7
+ included do
8
+ parent_class = self
9
+
10
+ enum status: {
11
+ published: 1,
12
+ # draft: 2,
13
+ scheduled: 3,
14
+ # pending: 4,
15
+ hidden: 5
16
+ }
17
+
18
+ validates_presence_of :status
19
+
20
+ before_save :set_status_by_published_date
21
+
22
+ scope :by_status, -> (status) { where(status: status) }
23
+ scope :published, -> { by_status(:published) }
24
+ scope :published_or_scheduled, -> {
25
+ where("#{parent_class.model_name.plural}.status = :published OR (#{parent_class.model_name.plural}.status = :scheduled AND COALESCE(#{parent_class.model_name.plural}.#{attribute_for_scheduled_date}, :date_yesterday) <= :date_today)",
26
+ published: 1,
27
+ scheduled: 3,
28
+ date_today: Date.today,
29
+ date_yesterday: Date.yesterday)
30
+ }
31
+
32
+ def self.statusable?
33
+ true
34
+ end
35
+
36
+ def self.statuses_for_select
37
+ if (self.column_names & ['scheduled_date', 'published_date']).present?
38
+ self.statuses
39
+ else
40
+ self.statuses.except(:scheduled)
41
+ end
42
+ end
43
+
44
+ def self.attribute_for_scheduled_date
45
+ if column_names.include?('scheduled_date')
46
+ :scheduled_date
47
+ elsif column_names.include?('published_date')
48
+ :published_date
49
+ end
50
+ end
51
+
52
+ def statusable?
53
+ true
54
+ end
55
+
56
+ private
57
+
58
+ def set_status_by_published_date
59
+ return if self.class.attribute_for_scheduled_date.blank?
60
+ return if self.send(self.class.attribute_for_scheduled_date).blank?
61
+
62
+ if self.respond_to?(self.class.attribute_for_scheduled_date)
63
+ if self.send(self.class.attribute_for_scheduled_date).present? && self.send(self.class.attribute_for_scheduled_date).to_date <= Date.today
64
+ self.status = self.scheduled? ? 'published' : self.status
65
+ else
66
+ self.status = self.published? ? 'scheduled' : self.status
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,13 @@
1
+ module Versionable
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ def self.versionable?
6
+ true
7
+ end
8
+
9
+ def versionable?
10
+ true
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,111 @@
1
+ require 'csv'
2
+
3
+ module Forest
4
+ class ApplicationRecord < ActiveRecord::Base
5
+ self.abstract_class = true
6
+
7
+ after_commit :expire_application_cache_key
8
+ after_commit :expire_cache_key
9
+
10
+ scope :by_id, -> (orderer = :desc) { order(id: orderer) }
11
+ scope :by_title, -> (orderer = :asc) { order(title: orderer, id: :desc) }
12
+ scope :by_created_at, -> (orderer = :desc) { order(created_at: orderer, id: orderer) }
13
+ scope :by_updated_at, -> (orderer = :desc) { order(updated_at: orderer, id: orderer) }
14
+ scope :fuzzy_search, -> (query) {
15
+ columns_to_search = self.columns
16
+ .select { |x| [:string, :text].include?(x.type) }
17
+ .map(&:name).collect { |x| "#{self.model_name.plural}.#{x} ILIKE :query" }
18
+ columns_to_search << "CAST(#{self.model_name.plural}.id AS TEXT) ILIKE :query"
19
+ where(columns_to_search.join(' OR '), query: "%#{sanitize_sql_like(query)}%")
20
+ }
21
+
22
+ def self.cache_key_name
23
+ "forest_all_#{model_name.plural}_cache_key"
24
+ end
25
+
26
+ def self.cache_key
27
+ Rails.cache.fetch self.cache_key_name do
28
+ SecureRandom.uuid
29
+ end
30
+ end
31
+
32
+ def self.csv_columns
33
+ valid_columns = self.columns.select { |a| valid_csv_column_types.include?(a.type) }
34
+ valid_columns.collect(&:name) - invalid_csv_column_names
35
+ end
36
+
37
+ def self.to_csv_template
38
+ CSV.generate(headers: true) do |csv|
39
+ csv << csv_columns
40
+ end
41
+ end
42
+
43
+ # Statusable default
44
+ def self.statusable?
45
+ false
46
+ end
47
+
48
+ def statusable?
49
+ false
50
+ end
51
+
52
+ # Versionable default
53
+ def self.versionable
54
+ false
55
+ end
56
+
57
+ def versionable
58
+ false
59
+ end
60
+
61
+ def self.expire_cache_key
62
+ Rails.cache.delete cache_key_name
63
+ end
64
+
65
+ def expire_cache_key
66
+ self.class.expire_cache_key
67
+ end
68
+
69
+ # Override this to define a friendly param attribute, like `slug` or `path`
70
+ def to_friendly_param
71
+ to_param
72
+ end
73
+
74
+ def to_label
75
+ if try(:display_name).present?
76
+ display_name
77
+ elsif try(:name).present?
78
+ name
79
+ elsif try(:title).present?
80
+ title
81
+ else
82
+ "#{self.model_name.human} #{id}"
83
+ end
84
+ end
85
+
86
+ def to_select2_response
87
+ if respond_to?(:media_item) && media_item.try(:attachment).present?
88
+ img_tag = "<img src='#{media_item.attachment.url(:thumb)}' style='height: 20px; display: inline-block; vertical-align: top;'> "
89
+ end
90
+ "#{img_tag}<span class='select2-response__id'>#{id}</span> #{to_label}"
91
+ end
92
+
93
+ def to_select2_selection
94
+ to_select2_response
95
+ end
96
+
97
+ private
98
+
99
+ def self.valid_csv_column_types
100
+ %i(integer text string boolean)
101
+ end
102
+
103
+ def self.invalid_csv_column_names
104
+ %w(id created_at updated_at status slug)
105
+ end
106
+
107
+ def expire_application_cache_key
108
+ Setting.expire_application_cache_key!
109
+ end
110
+ end
111
+ end