occams 1.0.0

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 (471) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +4 -0
  3. data/.gitattributes +2 -0
  4. data/.github/issue_template.md +22 -0
  5. data/.github/pull_request_template.md +6 -0
  6. data/.gitignore +21 -0
  7. data/.rubocop.yml +87 -0
  8. data/.travis.yml +44 -0
  9. data/CHANGELOG.md +7 -0
  10. data/CONTRIBUTING.md +35 -0
  11. data/Gemfile +30 -0
  12. data/LICENSE +20 -0
  13. data/LICENSE.comfy +20 -0
  14. data/README.md +121 -0
  15. data/Rakefile +5 -0
  16. data/app/assets/config/manifest.js +0 -0
  17. data/app/assets/fonts/occams/admin/cms/lib/redactor-font.eot +0 -0
  18. data/app/assets/javascripts/occams/admin/cms/application.js +39 -0
  19. data/app/assets/javascripts/occams/admin/cms/base.js +46 -0
  20. data/app/assets/javascripts/occams/admin/cms/categories.js +17 -0
  21. data/app/assets/javascripts/occams/admin/cms/codemirror.js +31 -0
  22. data/app/assets/javascripts/occams/admin/cms/custom.js +1 -0
  23. data/app/assets/javascripts/occams/admin/cms/diff.js +10 -0
  24. data/app/assets/javascripts/occams/admin/cms/file_link.js +67 -0
  25. data/app/assets/javascripts/occams/admin/cms/file_upload.js +194 -0
  26. data/app/assets/javascripts/occams/admin/cms/files_modal.js +40 -0
  27. data/app/assets/javascripts/occams/admin/cms/page_fragments.js +22 -0
  28. data/app/assets/javascripts/occams/admin/cms/slugify.js +34 -0
  29. data/app/assets/javascripts/occams/admin/cms/sortable_list.js +40 -0
  30. data/app/assets/javascripts/occams/admin/cms/timepicker.js +30 -0
  31. data/app/assets/javascripts/occams/admin/cms/wysiwyg.js +72 -0
  32. data/app/assets/javascripts/occams/vendor/Moxie.swf +0 -0
  33. data/app/assets/javascripts/occams/vendor/Moxie.xap +0 -0
  34. data/app/assets/javascripts/occams/vendor/bootstrap.bundle.min.js +7 -0
  35. data/app/assets/javascripts/occams/vendor/bootstrap.bundle.min.js.map +1 -0
  36. data/app/assets/javascripts/occams/vendor/codemirror/addon/edit/closetag.js +169 -0
  37. data/app/assets/javascripts/occams/vendor/codemirror/mode/css/css.js +832 -0
  38. data/app/assets/javascripts/occams/vendor/codemirror/mode/htmlmixed/htmlmixed.js +152 -0
  39. data/app/assets/javascripts/occams/vendor/codemirror/mode/javascript/javascript.js +875 -0
  40. data/app/assets/javascripts/occams/vendor/codemirror/mode/markdown/markdown.js +861 -0
  41. data/app/assets/javascripts/occams/vendor/codemirror/mode/xml/xml.js +394 -0
  42. data/app/assets/javascripts/occams/vendor/codemirror.js +9653 -0
  43. data/app/assets/javascripts/occams/vendor/diff/diff_match_patch.min.js +49 -0
  44. data/app/assets/javascripts/occams/vendor/diff/pretty_text_diff.js +76 -0
  45. data/app/assets/javascripts/occams/vendor/flatpickr/i18n/ar.js +52 -0
  46. data/app/assets/javascripts/occams/vendor/flatpickr/i18n/bg.js +65 -0
  47. data/app/assets/javascripts/occams/vendor/flatpickr/i18n/bn.js +65 -0
  48. data/app/assets/javascripts/occams/vendor/flatpickr/i18n/ca.js +83 -0
  49. data/app/assets/javascripts/occams/vendor/flatpickr/i18n/cs.js +70 -0
  50. data/app/assets/javascripts/occams/vendor/flatpickr/i18n/cy.js +92 -0
  51. data/app/assets/javascripts/occams/vendor/flatpickr/i18n/da.js +71 -0
  52. data/app/assets/javascripts/occams/vendor/flatpickr/i18n/de.js +70 -0
  53. data/app/assets/javascripts/occams/vendor/flatpickr/i18n/default.js +80 -0
  54. data/app/assets/javascripts/occams/vendor/flatpickr/i18n/eo.js +73 -0
  55. data/app/assets/javascripts/occams/vendor/flatpickr/i18n/es.js +69 -0
  56. data/app/assets/javascripts/occams/vendor/flatpickr/i18n/et.js +73 -0
  57. data/app/assets/javascripts/occams/vendor/flatpickr/i18n/fa.js +68 -0
  58. data/app/assets/javascripts/occams/vendor/flatpickr/i18n/fi.js +69 -0
  59. data/app/assets/javascripts/occams/vendor/flatpickr/i18n/fr.js +75 -0
  60. data/app/assets/javascripts/occams/vendor/flatpickr/i18n/gr.js +74 -0
  61. data/app/assets/javascripts/occams/vendor/flatpickr/i18n/he.js +57 -0
  62. data/app/assets/javascripts/occams/vendor/flatpickr/i18n/hi.js +65 -0
  63. data/app/assets/javascripts/occams/vendor/flatpickr/i18n/hr.js +66 -0
  64. data/app/assets/javascripts/occams/vendor/flatpickr/i18n/hu.js +72 -0
  65. data/app/assets/javascripts/occams/vendor/flatpickr/i18n/id.js +61 -0
  66. data/app/assets/javascripts/occams/vendor/flatpickr/i18n/it.js +70 -0
  67. data/app/assets/javascripts/occams/vendor/flatpickr/i18n/ja.js +57 -0
  68. data/app/assets/javascripts/occams/vendor/flatpickr/i18n/ko.js +60 -0
  69. data/app/assets/javascripts/occams/vendor/flatpickr/i18n/lt.js +72 -0
  70. data/app/assets/javascripts/occams/vendor/flatpickr/i18n/lv.js +67 -0
  71. data/app/assets/javascripts/occams/vendor/flatpickr/i18n/mk.js +69 -0
  72. data/app/assets/javascripts/occams/vendor/flatpickr/i18n/mn.js +67 -0
  73. data/app/assets/javascripts/occams/vendor/flatpickr/i18n/ms.js +68 -0
  74. data/app/assets/javascripts/occams/vendor/flatpickr/i18n/my.js +69 -0
  75. data/app/assets/javascripts/occams/vendor/flatpickr/i18n/nl.js +75 -0
  76. data/app/assets/javascripts/occams/vendor/flatpickr/i18n/no.js +73 -0
  77. data/app/assets/javascripts/occams/vendor/flatpickr/i18n/pa.js +65 -0
  78. data/app/assets/javascripts/occams/vendor/flatpickr/i18n/pl.js +68 -0
  79. data/app/assets/javascripts/occams/vendor/flatpickr/i18n/pt-BR.js +66 -0
  80. data/app/assets/javascripts/occams/vendor/flatpickr/i18n/pt.js +66 -0
  81. data/app/assets/javascripts/occams/vendor/flatpickr/i18n/ro.js +69 -0
  82. data/app/assets/javascripts/occams/vendor/flatpickr/i18n/ru.js +66 -0
  83. data/app/assets/javascripts/occams/vendor/flatpickr/i18n/si.js +65 -0
  84. data/app/assets/javascripts/occams/vendor/flatpickr/i18n/sk.js +70 -0
  85. data/app/assets/javascripts/occams/vendor/flatpickr/i18n/sl.js +70 -0
  86. data/app/assets/javascripts/occams/vendor/flatpickr/i18n/sq.js +65 -0
  87. data/app/assets/javascripts/occams/vendor/flatpickr/i18n/sr.js +69 -0
  88. data/app/assets/javascripts/occams/vendor/flatpickr/i18n/sv.js +70 -0
  89. data/app/assets/javascripts/occams/vendor/flatpickr/i18n/th.js +72 -0
  90. data/app/assets/javascripts/occams/vendor/flatpickr/i18n/tr.js +66 -0
  91. data/app/assets/javascripts/occams/vendor/flatpickr/i18n/uk.js +66 -0
  92. data/app/assets/javascripts/occams/vendor/flatpickr/i18n/vn.js +66 -0
  93. data/app/assets/javascripts/occams/vendor/flatpickr/i18n/zh-CN.js +61 -0
  94. data/app/assets/javascripts/occams/vendor/flatpickr/i18n/zh-TW.js +61 -0
  95. data/app/assets/javascripts/occams/vendor/flatpickr.min.js +2 -0
  96. data/app/assets/javascripts/occams/vendor/fontawesome.js +5 -0
  97. data/app/assets/javascripts/occams/vendor/moxie.min.js +16 -0
  98. data/app/assets/javascripts/occams/vendor/plupload.dev.js +2497 -0
  99. data/app/assets/javascripts/occams/vendor/redactor/definedlinks.js +53 -0
  100. data/app/assets/javascripts/occams/vendor/redactor/filemanager.js +64 -0
  101. data/app/assets/javascripts/occams/vendor/redactor/i18n/ar.js +76 -0
  102. data/app/assets/javascripts/occams/vendor/redactor/i18n/az.js +75 -0
  103. data/app/assets/javascripts/occams/vendor/redactor/i18n/ba.js +79 -0
  104. data/app/assets/javascripts/occams/vendor/redactor/i18n/bg.js +77 -0
  105. data/app/assets/javascripts/occams/vendor/redactor/i18n/by.js +77 -0
  106. data/app/assets/javascripts/occams/vendor/redactor/i18n/ca.js +75 -0
  107. data/app/assets/javascripts/occams/vendor/redactor/i18n/cs.js +87 -0
  108. data/app/assets/javascripts/occams/vendor/redactor/i18n/da.js +75 -0
  109. data/app/assets/javascripts/occams/vendor/redactor/i18n/de.js +80 -0
  110. data/app/assets/javascripts/occams/vendor/redactor/i18n/el.js +76 -0
  111. data/app/assets/javascripts/occams/vendor/redactor/i18n/en.js +75 -0
  112. data/app/assets/javascripts/occams/vendor/redactor/i18n/eo.js +76 -0
  113. data/app/assets/javascripts/occams/vendor/redactor/i18n/es.js +75 -0
  114. data/app/assets/javascripts/occams/vendor/redactor/i18n/es_AR.js +76 -0
  115. data/app/assets/javascripts/occams/vendor/redactor/i18n/fa.js +76 -0
  116. data/app/assets/javascripts/occams/vendor/redactor/i18n/fi.js +75 -0
  117. data/app/assets/javascripts/occams/vendor/redactor/i18n/fr.js +75 -0
  118. data/app/assets/javascripts/occams/vendor/redactor/i18n/ge.js +75 -0
  119. data/app/assets/javascripts/occams/vendor/redactor/i18n/gr.js +75 -0
  120. data/app/assets/javascripts/occams/vendor/redactor/i18n/he.js +75 -0
  121. data/app/assets/javascripts/occams/vendor/redactor/i18n/hr.js +76 -0
  122. data/app/assets/javascripts/occams/vendor/redactor/i18n/hu.js +77 -0
  123. data/app/assets/javascripts/occams/vendor/redactor/i18n/id.js +76 -0
  124. data/app/assets/javascripts/occams/vendor/redactor/i18n/it.js +78 -0
  125. data/app/assets/javascripts/occams/vendor/redactor/i18n/ja.js +76 -0
  126. data/app/assets/javascripts/occams/vendor/redactor/i18n/kn.js +75 -0
  127. data/app/assets/javascripts/occams/vendor/redactor/i18n/ko.js +76 -0
  128. data/app/assets/javascripts/occams/vendor/redactor/i18n/lt.js +75 -0
  129. data/app/assets/javascripts/occams/vendor/redactor/i18n/lv.js +75 -0
  130. data/app/assets/javascripts/occams/vendor/redactor/i18n/mk.js +75 -0
  131. data/app/assets/javascripts/occams/vendor/redactor/i18n/nb.js +75 -0
  132. data/app/assets/javascripts/occams/vendor/redactor/i18n/nl.js +80 -0
  133. data/app/assets/javascripts/occams/vendor/redactor/i18n/pl.js +76 -0
  134. data/app/assets/javascripts/occams/vendor/redactor/i18n/pt-BR.js +75 -0
  135. data/app/assets/javascripts/occams/vendor/redactor/i18n/pt.js +75 -0
  136. data/app/assets/javascripts/occams/vendor/redactor/i18n/ro.js +76 -0
  137. data/app/assets/javascripts/occams/vendor/redactor/i18n/ru.js +76 -0
  138. data/app/assets/javascripts/occams/vendor/redactor/i18n/sk.js +76 -0
  139. data/app/assets/javascripts/occams/vendor/redactor/i18n/sl.js +79 -0
  140. data/app/assets/javascripts/occams/vendor/redactor/i18n/sq.js +79 -0
  141. data/app/assets/javascripts/occams/vendor/redactor/i18n/sr-CIR.js +79 -0
  142. data/app/assets/javascripts/occams/vendor/redactor/i18n/sr-LAT.js +79 -0
  143. data/app/assets/javascripts/occams/vendor/redactor/i18n/sv.js +74 -0
  144. data/app/assets/javascripts/occams/vendor/redactor/i18n/th.js +74 -0
  145. data/app/assets/javascripts/occams/vendor/redactor/i18n/tr.js +75 -0
  146. data/app/assets/javascripts/occams/vendor/redactor/i18n/ua.js +77 -0
  147. data/app/assets/javascripts/occams/vendor/redactor/i18n/uk.js +77 -0
  148. data/app/assets/javascripts/occams/vendor/redactor/i18n/vi.js +74 -0
  149. data/app/assets/javascripts/occams/vendor/redactor/i18n/zh-CN.js +75 -0
  150. data/app/assets/javascripts/occams/vendor/redactor/i18n/zh-TW.js +75 -0
  151. data/app/assets/javascripts/occams/vendor/redactor/imagemanager.js +57 -0
  152. data/app/assets/javascripts/occams/vendor/redactor/table.js +337 -0
  153. data/app/assets/javascripts/occams/vendor/redactor/video.js +77 -0
  154. data/app/assets/javascripts/occams/vendor/redactor.js +9561 -0
  155. data/app/assets/javascripts/occams/vendor/sortable.min.js +5 -0
  156. data/app/assets/stylesheets/occams/admin/cms/application.sass +11 -0
  157. data/app/assets/stylesheets/occams/admin/cms/base.sass +256 -0
  158. data/app/assets/stylesheets/occams/admin/cms/codemirror_overrides.sass +19 -0
  159. data/app/assets/stylesheets/occams/admin/cms/custom.sass +1 -0
  160. data/app/assets/stylesheets/occams/admin/cms/redactor_overrides.sass +22 -0
  161. data/app/assets/stylesheets/occams/vendor/bootstrap/_alert.scss +51 -0
  162. data/app/assets/stylesheets/occams/vendor/bootstrap/_badge.scss +54 -0
  163. data/app/assets/stylesheets/occams/vendor/bootstrap/_breadcrumb.scss +41 -0
  164. data/app/assets/stylesheets/occams/vendor/bootstrap/_button-group.scss +163 -0
  165. data/app/assets/stylesheets/occams/vendor/bootstrap/_buttons.scss +137 -0
  166. data/app/assets/stylesheets/occams/vendor/bootstrap/_card.scss +289 -0
  167. data/app/assets/stylesheets/occams/vendor/bootstrap/_carousel.scss +197 -0
  168. data/app/assets/stylesheets/occams/vendor/bootstrap/_close.scss +41 -0
  169. data/app/assets/stylesheets/occams/vendor/bootstrap/_code.scss +48 -0
  170. data/app/assets/stylesheets/occams/vendor/bootstrap/_custom-forms.scss +507 -0
  171. data/app/assets/stylesheets/occams/vendor/bootstrap/_dropdown.scss +191 -0
  172. data/app/assets/stylesheets/occams/vendor/bootstrap/_forms.scss +330 -0
  173. data/app/assets/stylesheets/occams/vendor/bootstrap/_functions.scss +86 -0
  174. data/app/assets/stylesheets/occams/vendor/bootstrap/_grid.scss +52 -0
  175. data/app/assets/stylesheets/occams/vendor/bootstrap/_images.scss +42 -0
  176. data/app/assets/stylesheets/occams/vendor/bootstrap/_input-group.scss +193 -0
  177. data/app/assets/stylesheets/occams/vendor/bootstrap/_jumbotron.scss +17 -0
  178. data/app/assets/stylesheets/occams/vendor/bootstrap/_list-group.scss +149 -0
  179. data/app/assets/stylesheets/occams/vendor/bootstrap/_media.scss +8 -0
  180. data/app/assets/stylesheets/occams/vendor/bootstrap/_mixins.scss +47 -0
  181. data/app/assets/stylesheets/occams/vendor/bootstrap/_modal.scss +229 -0
  182. data/app/assets/stylesheets/occams/vendor/bootstrap/_nav.scss +120 -0
  183. data/app/assets/stylesheets/occams/vendor/bootstrap/_navbar.scss +294 -0
  184. data/app/assets/stylesheets/occams/vendor/bootstrap/_pagination.scss +73 -0
  185. data/app/assets/stylesheets/occams/vendor/bootstrap/_popover.scss +171 -0
  186. data/app/assets/stylesheets/occams/vendor/bootstrap/_print.scss +141 -0
  187. data/app/assets/stylesheets/occams/vendor/bootstrap/_progress.scss +43 -0
  188. data/app/assets/stylesheets/occams/vendor/bootstrap/_reboot.scss +483 -0
  189. data/app/assets/stylesheets/occams/vendor/bootstrap/_root.scss +19 -0
  190. data/app/assets/stylesheets/occams/vendor/bootstrap/_spinners.scss +55 -0
  191. data/app/assets/stylesheets/occams/vendor/bootstrap/_tables.scss +185 -0
  192. data/app/assets/stylesheets/occams/vendor/bootstrap/_toasts.scss +44 -0
  193. data/app/assets/stylesheets/occams/vendor/bootstrap/_tooltip.scss +115 -0
  194. data/app/assets/stylesheets/occams/vendor/bootstrap/_transitions.scss +20 -0
  195. data/app/assets/stylesheets/occams/vendor/bootstrap/_type.scss +125 -0
  196. data/app/assets/stylesheets/occams/vendor/bootstrap/_utilities.scss +17 -0
  197. data/app/assets/stylesheets/occams/vendor/bootstrap/_variables.scss +1123 -0
  198. data/app/assets/stylesheets/occams/vendor/bootstrap/bootstrap-grid.scss +29 -0
  199. data/app/assets/stylesheets/occams/vendor/bootstrap/bootstrap-reboot.scss +12 -0
  200. data/app/assets/stylesheets/occams/vendor/bootstrap/bootstrap.scss +44 -0
  201. data/app/assets/stylesheets/occams/vendor/bootstrap/mixins/_alert.scss +13 -0
  202. data/app/assets/stylesheets/occams/vendor/bootstrap/mixins/_background-variant.scss +21 -0
  203. data/app/assets/stylesheets/occams/vendor/bootstrap/mixins/_badge.scss +17 -0
  204. data/app/assets/stylesheets/occams/vendor/bootstrap/mixins/_border-radius.scss +63 -0
  205. data/app/assets/stylesheets/occams/vendor/bootstrap/mixins/_box-shadow.scss +20 -0
  206. data/app/assets/stylesheets/occams/vendor/bootstrap/mixins/_breakpoints.scss +123 -0
  207. data/app/assets/stylesheets/occams/vendor/bootstrap/mixins/_buttons.scss +107 -0
  208. data/app/assets/stylesheets/occams/vendor/bootstrap/mixins/_caret.scss +62 -0
  209. data/app/assets/stylesheets/occams/vendor/bootstrap/mixins/_clearfix.scss +7 -0
  210. data/app/assets/stylesheets/occams/vendor/bootstrap/mixins/_deprecate.scss +10 -0
  211. data/app/assets/stylesheets/occams/vendor/bootstrap/mixins/_float.scss +14 -0
  212. data/app/assets/stylesheets/occams/vendor/bootstrap/mixins/_forms.scss +192 -0
  213. data/app/assets/stylesheets/occams/vendor/bootstrap/mixins/_gradients.scss +45 -0
  214. data/app/assets/stylesheets/occams/vendor/bootstrap/mixins/_grid-framework.scss +66 -0
  215. data/app/assets/stylesheets/occams/vendor/bootstrap/mixins/_grid.scss +51 -0
  216. data/app/assets/stylesheets/occams/vendor/bootstrap/mixins/_hover.scss +37 -0
  217. data/app/assets/stylesheets/occams/vendor/bootstrap/mixins/_image.scss +36 -0
  218. data/app/assets/stylesheets/occams/vendor/bootstrap/mixins/_list-group.scss +21 -0
  219. data/app/assets/stylesheets/occams/vendor/bootstrap/mixins/_lists.scss +7 -0
  220. data/app/assets/stylesheets/occams/vendor/bootstrap/mixins/_nav-divider.scss +10 -0
  221. data/app/assets/stylesheets/occams/vendor/bootstrap/mixins/_pagination.scss +22 -0
  222. data/app/assets/stylesheets/occams/vendor/bootstrap/mixins/_reset-text.scss +17 -0
  223. data/app/assets/stylesheets/occams/vendor/bootstrap/mixins/_resize.scss +6 -0
  224. data/app/assets/stylesheets/occams/vendor/bootstrap/mixins/_screen-reader.scss +33 -0
  225. data/app/assets/stylesheets/occams/vendor/bootstrap/mixins/_size.scss +7 -0
  226. data/app/assets/stylesheets/occams/vendor/bootstrap/mixins/_table-row.scss +39 -0
  227. data/app/assets/stylesheets/occams/vendor/bootstrap/mixins/_text-emphasis.scss +16 -0
  228. data/app/assets/stylesheets/occams/vendor/bootstrap/mixins/_text-hide.scss +11 -0
  229. data/app/assets/stylesheets/occams/vendor/bootstrap/mixins/_text-truncate.scss +8 -0
  230. data/app/assets/stylesheets/occams/vendor/bootstrap/mixins/_transition.scss +16 -0
  231. data/app/assets/stylesheets/occams/vendor/bootstrap/mixins/_visibility.scss +8 -0
  232. data/app/assets/stylesheets/occams/vendor/bootstrap/utilities/_align.scss +8 -0
  233. data/app/assets/stylesheets/occams/vendor/bootstrap/utilities/_background.scss +19 -0
  234. data/app/assets/stylesheets/occams/vendor/bootstrap/utilities/_borders.scss +75 -0
  235. data/app/assets/stylesheets/occams/vendor/bootstrap/utilities/_clearfix.scss +3 -0
  236. data/app/assets/stylesheets/occams/vendor/bootstrap/utilities/_display.scss +26 -0
  237. data/app/assets/stylesheets/occams/vendor/bootstrap/utilities/_embed.scss +39 -0
  238. data/app/assets/stylesheets/occams/vendor/bootstrap/utilities/_flex.scss +51 -0
  239. data/app/assets/stylesheets/occams/vendor/bootstrap/utilities/_float.scss +11 -0
  240. data/app/assets/stylesheets/occams/vendor/bootstrap/utilities/_overflow.scss +5 -0
  241. data/app/assets/stylesheets/occams/vendor/bootstrap/utilities/_position.scss +32 -0
  242. data/app/assets/stylesheets/occams/vendor/bootstrap/utilities/_screenreaders.scss +11 -0
  243. data/app/assets/stylesheets/occams/vendor/bootstrap/utilities/_shadows.scss +6 -0
  244. data/app/assets/stylesheets/occams/vendor/bootstrap/utilities/_sizing.scss +20 -0
  245. data/app/assets/stylesheets/occams/vendor/bootstrap/utilities/_spacing.scss +73 -0
  246. data/app/assets/stylesheets/occams/vendor/bootstrap/utilities/_stretched-link.scss +19 -0
  247. data/app/assets/stylesheets/occams/vendor/bootstrap/utilities/_text.scss +72 -0
  248. data/app/assets/stylesheets/occams/vendor/bootstrap/utilities/_visibility.scss +13 -0
  249. data/app/assets/stylesheets/occams/vendor/bootstrap/vendor/_rfs.scss +204 -0
  250. data/app/assets/stylesheets/occams/vendor/codemirror.css +346 -0
  251. data/app/assets/stylesheets/occams/vendor/flatpickr.min.css +13 -0
  252. data/app/assets/stylesheets/occams/vendor/fontawesome.css +343 -0
  253. data/app/assets/stylesheets/occams/vendor/redactor.css +948 -0
  254. data/app/controllers/application_controller.rb +7 -0
  255. data/app/controllers/concerns/occams/paginate.rb +16 -0
  256. data/app/controllers/concerns/occams/reorder_action.rb +19 -0
  257. data/app/controllers/occams/admin/base_controller.rb +22 -0
  258. data/app/controllers/occams/admin/cms/base_controller.rb +55 -0
  259. data/app/controllers/occams/admin/cms/categories_controller.rb +40 -0
  260. data/app/controllers/occams/admin/cms/files_controller.rb +142 -0
  261. data/app/controllers/occams/admin/cms/layouts_controller.rb +69 -0
  262. data/app/controllers/occams/admin/cms/pages_controller.rb +156 -0
  263. data/app/controllers/occams/admin/cms/revisions/base_controller.rb +50 -0
  264. data/app/controllers/occams/admin/cms/revisions/layout_controller.rb +18 -0
  265. data/app/controllers/occams/admin/cms/revisions/page_controller.rb +30 -0
  266. data/app/controllers/occams/admin/cms/revisions/snippet_controller.rb +18 -0
  267. data/app/controllers/occams/admin/cms/revisions/translation_controller.rb +31 -0
  268. data/app/controllers/occams/admin/cms/sites_controller.rb +69 -0
  269. data/app/controllers/occams/admin/cms/snippets_controller.rb +66 -0
  270. data/app/controllers/occams/admin/cms/translations_controller.rb +107 -0
  271. data/app/controllers/occams/cms/assets_controller.rb +40 -0
  272. data/app/controllers/occams/cms/base_controller.rb +33 -0
  273. data/app/controllers/occams/cms/content_controller.rb +87 -0
  274. data/app/helpers/occams/admin/cms_helper.rb +49 -0
  275. data/app/helpers/occams/cms_helper.rb +73 -0
  276. data/app/models/concerns/occams/cms/with_categories.rb +53 -0
  277. data/app/models/concerns/occams/cms/with_fragments.rb +125 -0
  278. data/app/models/occams/cms/categorization.rb +16 -0
  279. data/app/models/occams/cms/category.rb +24 -0
  280. data/app/models/occams/cms/file.rb +70 -0
  281. data/app/models/occams/cms/fragment.rb +63 -0
  282. data/app/models/occams/cms/layout.rb +113 -0
  283. data/app/models/occams/cms/page.rb +170 -0
  284. data/app/models/occams/cms/revision.rb +12 -0
  285. data/app/models/occams/cms/site.rb +94 -0
  286. data/app/models/occams/cms/snippet.rb +45 -0
  287. data/app/models/occams/cms/translation.rb +43 -0
  288. data/app/views/kaminari/occams/_first_page.html.haml +3 -0
  289. data/app/views/kaminari/occams/_gap.html.haml +2 -0
  290. data/app/views/kaminari/occams/_last_page.html.haml +3 -0
  291. data/app/views/kaminari/occams/_next_page.html.haml +2 -0
  292. data/app/views/kaminari/occams/_page.html.haml +2 -0
  293. data/app/views/kaminari/occams/_paginator.html.haml +11 -0
  294. data/app/views/kaminari/occams/_prev_page.html.haml +2 -0
  295. data/app/views/layouts/occams/admin/cms/_body.html.haml +14 -0
  296. data/app/views/layouts/occams/admin/cms/_flash.html.haml +4 -0
  297. data/app/views/layouts/occams/admin/cms/_footer_js.html.haml +6 -0
  298. data/app/views/layouts/occams/admin/cms/_head.html.haml +19 -0
  299. data/app/views/layouts/occams/admin/cms/_left.html.haml +39 -0
  300. data/app/views/layouts/occams/admin/cms/_right.html.haml +5 -0
  301. data/app/views/layouts/occams/admin/cms.html.haml +4 -0
  302. data/app/views/occams/admin/cms/categories/_categories.html.haml +4 -0
  303. data/app/views/occams/admin/cms/categories/_edit.html.haml +7 -0
  304. data/app/views/occams/admin/cms/categories/_form.html.haml +5 -0
  305. data/app/views/occams/admin/cms/categories/_index.html.haml +28 -0
  306. data/app/views/occams/admin/cms/categories/_show.html.haml +15 -0
  307. data/app/views/occams/admin/cms/categories/create.js.erb +7 -0
  308. data/app/views/occams/admin/cms/categories/destroy.js.erb +8 -0
  309. data/app/views/occams/admin/cms/categories/edit.js.erb +3 -0
  310. data/app/views/occams/admin/cms/categories/update.js.erb +6 -0
  311. data/app/views/occams/admin/cms/files/_file.html.haml +31 -0
  312. data/app/views/occams/admin/cms/files/_form.html.haml +26 -0
  313. data/app/views/occams/admin/cms/files/_modal.html.haml +3 -0
  314. data/app/views/occams/admin/cms/files/destroy.js.erb +4 -0
  315. data/app/views/occams/admin/cms/files/edit.html.haml +5 -0
  316. data/app/views/occams/admin/cms/files/index.html.haml +31 -0
  317. data/app/views/occams/admin/cms/files/new.html.haml +5 -0
  318. data/app/views/occams/admin/cms/fragments/_form_fragment_attachments.html.haml +23 -0
  319. data/app/views/occams/admin/cms/fragments/_form_fragments.html.haml +38 -0
  320. data/app/views/occams/admin/cms/layouts/_form.html.haml +20 -0
  321. data/app/views/occams/admin/cms/layouts/_index_branch.html.haml +26 -0
  322. data/app/views/occams/admin/cms/layouts/edit.html.haml +9 -0
  323. data/app/views/occams/admin/cms/layouts/index.html.haml +10 -0
  324. data/app/views/occams/admin/cms/layouts/new.html.haml +5 -0
  325. data/app/views/occams/admin/cms/pages/_form.html.haml +31 -0
  326. data/app/views/occams/admin/cms/pages/_index_branch.html.haml +44 -0
  327. data/app/views/occams/admin/cms/pages/edit.html.haml +10 -0
  328. data/app/views/occams/admin/cms/pages/index.html.haml +13 -0
  329. data/app/views/occams/admin/cms/pages/new.html.haml +5 -0
  330. data/app/views/occams/admin/cms/pages/toggle_branch.js.erb +13 -0
  331. data/app/views/occams/admin/cms/revisions/_sidebar.html.haml +2 -0
  332. data/app/views/occams/admin/cms/revisions/show.html.haml +36 -0
  333. data/app/views/occams/admin/cms/sites/_form.html.haml +15 -0
  334. data/app/views/occams/admin/cms/sites/edit.html.haml +5 -0
  335. data/app/views/occams/admin/cms/sites/index.html.haml +28 -0
  336. data/app/views/occams/admin/cms/sites/new.html.haml +5 -0
  337. data/app/views/occams/admin/cms/snippets/_form.html.haml +18 -0
  338. data/app/views/occams/admin/cms/snippets/edit.html.haml +9 -0
  339. data/app/views/occams/admin/cms/snippets/index.html.haml +33 -0
  340. data/app/views/occams/admin/cms/snippets/new.html.haml +5 -0
  341. data/app/views/occams/admin/cms/translations/_form.html.haml +20 -0
  342. data/app/views/occams/admin/cms/translations/_sidebar.html.haml +19 -0
  343. data/app/views/occams/admin/cms/translations/edit.html.haml +10 -0
  344. data/app/views/occams/admin/cms/translations/new.html.haml +5 -0
  345. data/bin/bundle +3 -0
  346. data/bin/rails +4 -0
  347. data/bin/rake +4 -0
  348. data/bin/setup +36 -0
  349. data/bin/update +31 -0
  350. data/bin/yarn +11 -0
  351. data/config/application.rb +39 -0
  352. data/config/boot.rb +7 -0
  353. data/config/cms_routes.rb +8 -0
  354. data/config/database.yml +11 -0
  355. data/config/environment.rb +7 -0
  356. data/config/environments/development.rb +59 -0
  357. data/config/environments/test.rb +50 -0
  358. data/config/initializers/occams.rb +122 -0
  359. data/config/locales/ar.yml +262 -0
  360. data/config/locales/ca.yml +262 -0
  361. data/config/locales/cs.yml +262 -0
  362. data/config/locales/da.yml +262 -0
  363. data/config/locales/de.yml +262 -0
  364. data/config/locales/en.yml +262 -0
  365. data/config/locales/es.yml +262 -0
  366. data/config/locales/fi.yml +262 -0
  367. data/config/locales/fr.yml +262 -0
  368. data/config/locales/gr.yml +262 -0
  369. data/config/locales/hr.yml +262 -0
  370. data/config/locales/it.yml +262 -0
  371. data/config/locales/ja.yml +262 -0
  372. data/config/locales/nb.yml +262 -0
  373. data/config/locales/nl.yml +262 -0
  374. data/config/locales/pl.yml +262 -0
  375. data/config/locales/pt-BR.yml +262 -0
  376. data/config/locales/ru.yml +262 -0
  377. data/config/locales/sk.yml +262 -0
  378. data/config/locales/sv.yml +262 -0
  379. data/config/locales/tr.yml +262 -0
  380. data/config/locales/uk.yml +262 -0
  381. data/config/locales/zh-CN.yml +262 -0
  382. data/config/locales/zh-TW.yml +262 -0
  383. data/config/storage.yml +35 -0
  384. data/config.ru +6 -0
  385. data/db/cms_seeds/sample-site/files/_default.jpg.yml +5 -0
  386. data/db/cms_seeds/sample-site/files/default.jpg +0 -0
  387. data/db/cms_seeds/sample-site/layouts/default/content.html +16 -0
  388. data/db/cms_seeds/sample-site/layouts/default/nested/content.html +14 -0
  389. data/db/cms_seeds/sample-site/pages/index/child_a/content.html +7 -0
  390. data/db/cms_seeds/sample-site/pages/index/child_b/content.html +6 -0
  391. data/db/cms_seeds/sample-site/pages/index/cms logo.png +0 -0
  392. data/db/cms_seeds/sample-site/pages/index/content.es.html +6 -0
  393. data/db/cms_seeds/sample-site/pages/index/content.fr.html +6 -0
  394. data/db/cms_seeds/sample-site/pages/index/content.html +25 -0
  395. data/db/cms_seeds/sample-site/pages/index/footer.png +0 -0
  396. data/db/cms_seeds/sample-site/pages/index/header.png +0 -0
  397. data/db/cms_seeds/sample-site/snippets/default.html +8 -0
  398. data/db/migrate/00_create_active_storage_tables.rb +34 -0
  399. data/db/migrate/01_create_cms.rb +143 -0
  400. data/lib/generators/occams/cms/README +17 -0
  401. data/lib/generators/occams/cms/assets_generator.rb +18 -0
  402. data/lib/generators/occams/cms/cms_generator.rb +69 -0
  403. data/lib/generators/occams/cms/controllers_generator.rb +17 -0
  404. data/lib/generators/occams/cms/models_generator.rb +17 -0
  405. data/lib/generators/occams/cms/views_generator.rb +17 -0
  406. data/lib/generators/occams/scaffold/scaffold_generator.rb +80 -0
  407. data/lib/generators/occams/scaffold/templates/controller.rb.tt +62 -0
  408. data/lib/generators/occams/scaffold/templates/migration.rb.tt +11 -0
  409. data/lib/generators/occams/scaffold/templates/model.rb.tt +21 -0
  410. data/lib/generators/occams/scaffold/templates/tests/controller.rb.tt +115 -0
  411. data/lib/generators/occams/scaffold/templates/tests/fixture.yml.tt +4 -0
  412. data/lib/generators/occams/scaffold/templates/tests/model.rb.tt +26 -0
  413. data/lib/generators/occams/scaffold/templates/views/_form.haml.tt +7 -0
  414. data/lib/generators/occams/scaffold/templates/views/edit.haml.tt +5 -0
  415. data/lib/generators/occams/scaffold/templates/views/index.haml.tt +21 -0
  416. data/lib/generators/occams/scaffold/templates/views/new.haml.tt +5 -0
  417. data/lib/generators/occams/scaffold/templates/views/show.haml.tt +4 -0
  418. data/lib/occams/access_control/admin_authentication.rb +24 -0
  419. data/lib/occams/access_control/admin_authorization.rb +12 -0
  420. data/lib/occams/access_control/public_authentication.rb +12 -0
  421. data/lib/occams/access_control/public_authorization.rb +12 -0
  422. data/lib/occams/configuration.rb +140 -0
  423. data/lib/occams/content/block.rb +14 -0
  424. data/lib/occams/content/params_parser.rb +151 -0
  425. data/lib/occams/content/renderer.rb +149 -0
  426. data/lib/occams/content/tag.rb +50 -0
  427. data/lib/occams/content/tags/asset.rb +59 -0
  428. data/lib/occams/content/tags/checkbox.rb +29 -0
  429. data/lib/occams/content/tags/date.rb +21 -0
  430. data/lib/occams/content/tags/datetime.rb +44 -0
  431. data/lib/occams/content/tags/file.rb +72 -0
  432. data/lib/occams/content/tags/file_link.rb +63 -0
  433. data/lib/occams/content/tags/files.rb +43 -0
  434. data/lib/occams/content/tags/fragment.rb +66 -0
  435. data/lib/occams/content/tags/helper.rb +55 -0
  436. data/lib/occams/content/tags/markdown.rb +24 -0
  437. data/lib/occams/content/tags/mixins/file_content.rb +43 -0
  438. data/lib/occams/content/tags/number.rb +20 -0
  439. data/lib/occams/content/tags/page_file_link.rb +84 -0
  440. data/lib/occams/content/tags/partial.rb +49 -0
  441. data/lib/occams/content/tags/snippet.rb +38 -0
  442. data/lib/occams/content/tags/template.rb +44 -0
  443. data/lib/occams/content/tags/text.rb +20 -0
  444. data/lib/occams/content/tags/textarea.rb +20 -0
  445. data/lib/occams/content/tags/wysiwyg.rb +21 -0
  446. data/lib/occams/content.rb +31 -0
  447. data/lib/occams/engine.rb +31 -0
  448. data/lib/occams/error.rb +32 -0
  449. data/lib/occams/extensions/acts_as_tree.rb +120 -0
  450. data/lib/occams/extensions/has_revisions.rb +72 -0
  451. data/lib/occams/form_builder.rb +47 -0
  452. data/lib/occams/render_methods.rb +112 -0
  453. data/lib/occams/routes/cms.rb +18 -0
  454. data/lib/occams/routes/cms_admin.rb +58 -0
  455. data/lib/occams/routing.rb +12 -0
  456. data/lib/occams/seeds/file/exporter.rb +43 -0
  457. data/lib/occams/seeds/file/importer.rb +63 -0
  458. data/lib/occams/seeds/layout/exporter.rb +40 -0
  459. data/lib/occams/seeds/layout/importer.rb +62 -0
  460. data/lib/occams/seeds/page/exporter.rb +92 -0
  461. data/lib/occams/seeds/page/importer.rb +219 -0
  462. data/lib/occams/seeds/snippet/exporter.rb +34 -0
  463. data/lib/occams/seeds/snippet/importer.rb +51 -0
  464. data/lib/occams/seeds.rb +118 -0
  465. data/lib/occams/version.rb +7 -0
  466. data/lib/occams/view_hooks.rb +35 -0
  467. data/lib/occams.rb +60 -0
  468. data/lib/tasks/cms_seeds.rake +40 -0
  469. data/occams.gemspec +33 -0
  470. data/public/favicon.ico +0 -0
  471. metadata +666 -0
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Occams::Cms::Categorization < ActiveRecord::Base
4
+
5
+ self.table_name = "occams_cms_categorizations"
6
+
7
+ # -- Relationships -----------------------------------------------------------
8
+ belongs_to :category
9
+ belongs_to :categorized,
10
+ polymorphic: true
11
+
12
+ # -- Validations -------------------------------------------------------------
13
+ validates :category_id,
14
+ uniqueness: { scope: %i[categorized_type categorized_id] }
15
+
16
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Occams::Cms::Category < ActiveRecord::Base
4
+
5
+ self.table_name = "occams_cms_categories"
6
+
7
+ # -- Relationships --------------------------------------------------------
8
+ belongs_to :site
9
+ has_many :categorizations,
10
+ dependent: :destroy
11
+
12
+ # -- Validations ----------------------------------------------------------
13
+ validates :label,
14
+ presence: true,
15
+ uniqueness: { scope: %i[categorized_type site_id] }
16
+ validates :categorized_type,
17
+ presence: true
18
+
19
+ # -- Scopes ---------------------------------------------------------------
20
+ scope :of_type, ->(type) {
21
+ where(categorized_type: type)
22
+ }
23
+
24
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Occams::Cms::File < ActiveRecord::Base
4
+
5
+ self.table_name = "occams_cms_files"
6
+
7
+ include Occams::Cms::WithCategories
8
+
9
+ VARIANT_SIZE = {
10
+ redactor: { resize: "100x75^", gravity: "center", crop: "100x75+0+0" },
11
+ thumb: { resize: "200x150^", gravity: "center", crop: "200x150+0+0" },
12
+ icon: { resize: "28x28^", gravity: "center", crop: "28x28+0+0" }
13
+ }.freeze
14
+
15
+ # temporary place to store attachment
16
+ attr_accessor :file
17
+
18
+ has_one_attached :attachment
19
+
20
+ # -- Relationships -----------------------------------------------------------
21
+ belongs_to :site
22
+
23
+ # -- Callbacks ---------------------------------------------------------------
24
+ before_validation :assign_label, on: :create
25
+ before_create :assign_position
26
+ # active_storage attachment behavior changed in rails 6 - see PR#892 for details
27
+ if Rails::VERSION::MAJOR >= 6
28
+ before_save :process_attachment
29
+ else
30
+ after_save :process_attachment
31
+ end
32
+
33
+ after_save :clear_page_content_cache
34
+
35
+ # -- Validations -------------------------------------------------------------
36
+ validates :label, presence: true
37
+ validates :file, presence: true, on: :create
38
+
39
+ # -- Scopes ------------------------------------------------------------------
40
+ # When we need to grab only files with image attachments.
41
+ # Don't forget to include `with_attached_attachment` before calling this
42
+ scope :with_images, -> {
43
+ where("active_storage_blobs.content_type LIKE 'image/%'").references(:blob)
44
+ }
45
+
46
+ private
47
+
48
+ def clear_page_content_cache
49
+ Occams::Cms::Page.where(id: site.pages.pluck(:id)).update_all(content_cache: nil)
50
+ end
51
+
52
+ protected
53
+
54
+ def assign_position
55
+ max = Occams::Cms::File.maximum(:position)
56
+ self.position = max ? max + 1 : 0
57
+ end
58
+
59
+ # TODO: Change db schema not to set blank string
60
+ def assign_label
61
+ return if label.present?
62
+ self.label = file&.original_filename
63
+ end
64
+
65
+ def process_attachment
66
+ return if @file.blank?
67
+ self.attachment = @file
68
+ end
69
+
70
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Occams::Cms::Fragment < ActiveRecord::Base
4
+
5
+ self.table_name = "occams_cms_fragments"
6
+
7
+ has_many_attached :attachments
8
+
9
+ serialize :content
10
+
11
+ attr_reader :files
12
+
13
+ # -- Callbacks ---------------------------------------------------------------
14
+ # active_storage attachment behavior changed in rails 6 - see PR#892 for details
15
+ if Rails::VERSION::MAJOR >= 6
16
+ before_save :remove_attachments, :add_attachments
17
+ else
18
+ after_save :remove_attachments, :add_attachments
19
+ end
20
+
21
+ # -- Relationships -----------------------------------------------------------
22
+ belongs_to :record, polymorphic: true, touch: true
23
+
24
+ # -- Validations -------------------------------------------------------------
25
+ validates :identifier,
26
+ presence: true,
27
+ uniqueness: { scope: :record }
28
+
29
+ # -- Instance Methods --------------------------------------------------------
30
+
31
+ # Temporary accessor for uploaded files. We can only attach to persisted
32
+ # records so we are deffering it to the after_save callback.
33
+ # Note: hijacking dirty tracking to force trigger callbacks later.
34
+ def files=(files)
35
+ @files = [files].flatten.compact
36
+ content_will_change! if @files.present?
37
+ end
38
+
39
+ def file_ids_destroy=(ids)
40
+ @file_ids_destroy = [ids].flatten.compact
41
+ content_will_change! if @file_ids_destroy.present?
42
+ end
43
+
44
+ protected
45
+
46
+ def remove_attachments
47
+ return unless @file_ids_destroy.present?
48
+ attachments.where(id: @file_ids_destroy).destroy_all
49
+ end
50
+
51
+ def add_attachments
52
+ return if @files.blank?
53
+
54
+ # If we're dealing with a single file
55
+ if tag == "file"
56
+ @files = [@files.first]
57
+ attachments&.purge_later
58
+ end
59
+
60
+ attachments.attach(@files)
61
+ end
62
+
63
+ end
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Occams::Cms::Layout < ActiveRecord::Base
4
+
5
+ self.table_name = "occams_cms_layouts"
6
+
7
+ cms_acts_as_tree
8
+ cms_has_revisions_for :content, :css, :js
9
+
10
+ # -- Relationships --------------------------------------------------------
11
+ belongs_to :site
12
+ has_many :pages, dependent: :nullify
13
+
14
+ # -- Callbacks ---------------------------------------------------------------
15
+ before_validation :assign_label
16
+ before_create :assign_position
17
+ after_save :clear_page_content_cache
18
+ after_destroy :clear_page_content_cache
19
+
20
+ # -- Validations -------------------------------------------------------------
21
+ validates :site_id,
22
+ presence: true
23
+ validates :label,
24
+ presence: true
25
+ validates :identifier,
26
+ presence: true,
27
+ uniqueness: { scope: :site_id },
28
+ format: { with: %r{\A\w[a-z0-9_-]*\z}i }
29
+
30
+ # -- Class Methods -----------------------------------------------------------
31
+ # Tree-like structure for layouts
32
+ def self.options_for_select(site, current_layout = nil)
33
+ options = []
34
+
35
+ options_for_layout = ->(layout, depth = 0) do
36
+ return if layout == current_layout
37
+
38
+ options << ["#{'. . ' * depth}#{layout.label}", layout.id]
39
+
40
+ layout.children.order(:position).each do |child_layout|
41
+ options_for_layout.call(child_layout, depth + 1)
42
+ end
43
+ end
44
+
45
+ site.layouts.roots.order(:position).each do |layout|
46
+ options_for_layout.call(layout)
47
+ end
48
+
49
+ options
50
+ end
51
+
52
+ # List of available application layouts
53
+ def self.app_layouts_for_select(view_paths)
54
+ view_paths.map(&:to_s).select { |path| path.start_with?(Rails.root.to_s) }.flat_map do |full_path|
55
+ Dir.glob("#{full_path}/layouts/**/*.html.*").collect do |filename|
56
+ filename.gsub!("#{full_path}/layouts/", "")
57
+ filename.split("/").last[0...1] == "_" ? nil : filename.split(".").first
58
+ end.compact.sort
59
+ end.compact.uniq.sort
60
+ end
61
+
62
+ # -- Instance Methods --------------------------------------------------------
63
+ # Tokenized layout content that also pulls in parent layout (if there's one)
64
+ # and merges on the {{cms:tag_type content}} tag (if parent layout has that).
65
+ # Returns a list of tokens that can be fed into the renderer.
66
+ def content_tokens
67
+ renderer = Occams::Content::Renderer.new(nil)
68
+ tokens = renderer.tokenize(content)
69
+
70
+ if parent
71
+ fragment_tags = Occams::Content::Tag::Fragment.subclasses.map do |c|
72
+ Occams::Content::Renderer.tags.key(c)
73
+ end
74
+
75
+ parent_tokens = parent.content_tokens
76
+ replacement_position = parent_tokens.index do |n|
77
+ n.is_a?(Hash) &&
78
+ fragment_tags.member?(n[:tag_class]) &&
79
+ n[:tag_params].split(%r{\s}).first == "content"
80
+ end
81
+
82
+ if replacement_position
83
+ parent_tokens[replacement_position] = tokens
84
+ tokens = parent_tokens.flatten
85
+ end
86
+ end
87
+
88
+ tokens
89
+ end
90
+
91
+ def cache_buster
92
+ updated_at.to_i
93
+ end
94
+
95
+ # Forcing page content reload
96
+ def clear_page_content_cache
97
+ Occams::Cms::Page.where(id: pages.pluck(:id)).update_all(content_cache: nil)
98
+ children.each(&:clear_page_content_cache)
99
+ end
100
+
101
+ protected
102
+
103
+ def assign_label
104
+ self.label = label.blank? ? identifier.try(:titleize) : label
105
+ end
106
+
107
+ def assign_position
108
+ return if position.to_i.positive?
109
+ max = site.layouts.where(parent_id: parent_id).maximum(:position)
110
+ self.position = max ? max + 1 : 0
111
+ end
112
+
113
+ end
@@ -0,0 +1,170 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Occams::Cms::Page < ActiveRecord::Base
4
+
5
+ self.table_name = "occams_cms_pages"
6
+
7
+ include Occams::Cms::WithFragments
8
+ include Occams::Cms::WithCategories
9
+
10
+ cms_acts_as_tree counter_cache: :children_count, order: :position
11
+ cms_has_revisions_for :fragments_attributes
12
+
13
+ attr_accessor :content
14
+
15
+ # -- Relationships -----------------------------------------------------------
16
+ belongs_to :site
17
+ belongs_to :target_page,
18
+ class_name: "Occams::Cms::Page",
19
+ optional: true
20
+
21
+ has_many :translations,
22
+ dependent: :destroy
23
+
24
+ # -- Callbacks ---------------------------------------------------------------
25
+ before_validation :assigns_label,
26
+ :assign_parent,
27
+ :escape_slug,
28
+ :assign_full_path
29
+ before_create :assign_position
30
+ after_save :sync_child_full_paths!
31
+ after_find :unescape_slug_and_path
32
+
33
+ # -- Validations -------------------------------------------------------------
34
+ validates :label,
35
+ presence: true
36
+ validates :slug,
37
+ presence: true,
38
+ uniqueness: { scope: :parent_id },
39
+ unless: ->(p) {
40
+ p.site && (p.site.pages.count.zero? || p.site.pages.root == self)
41
+ }
42
+ validate :validate_target_page
43
+ validate :validate_format_of_unescaped_slug
44
+
45
+ # -- Scopes ------------------------------------------------------------------
46
+ scope :published, -> { where(is_published: true) }
47
+
48
+ # -- Class Methods -----------------------------------------------------------
49
+ # Tree-like structure for pages
50
+ def self.options_for_select(site:, current_page: nil, exclude_self: false)
51
+ options = []
52
+
53
+ options_for_page = ->(page, depth = 0) do
54
+ return if page.nil?
55
+ return if exclude_self && page == current_page
56
+
57
+ options << ["#{'. . ' * depth}#{page.label}", page.id]
58
+
59
+ page.children.order(:position).each do |child_page|
60
+ options_for_page.call(child_page, depth + 1)
61
+ end
62
+ end
63
+
64
+ options_for_page.call(site.pages.root)
65
+
66
+ options
67
+ end
68
+
69
+ # -- Instance Methods --------------------------------------------------------
70
+ # For previewing purposes sometimes we need to have full_path set. This
71
+ # full path take care of the pages and its childs but not of the site path
72
+ def full_path
73
+ read_attribute(:full_path) || assign_full_path
74
+ end
75
+
76
+ # Somewhat unique method of identifying a page that is not a full_path
77
+ def identifier
78
+ parent_id.blank? ? "index" : full_path[1..-1].parameterize
79
+ end
80
+
81
+ # Full url for a page
82
+ def url(relative: false)
83
+ [site.url(relative: relative), full_path].compact.join
84
+ end
85
+
86
+ # This method will mutate page object by transfering attributes from translation
87
+ # for a given locale.
88
+ def translate!
89
+ # If site locale same as page's or there's no translastions, we do nothing
90
+ if site.locale == I18n.locale.to_s || translations.blank?
91
+ return
92
+ end
93
+
94
+ translation = translations.published.find_by!(locale: I18n.locale)
95
+ self.layout = translation.layout
96
+ self.label = translation.label
97
+ self.content_cache = translation.content_cache
98
+
99
+ # We can't just assign fragments as it's a relation and will write to DB
100
+ # This has odd side-effect of preserving page's fragments and just replacing
101
+ # them from the translation. Not an issue if all fragments match.
102
+ self.fragments_attributes = translation.fragments_attributes
103
+ readonly!
104
+
105
+ self
106
+ end
107
+
108
+ protected
109
+
110
+ def assigns_label
111
+ self.label = label.blank? ? slug.try(:titleize) : label
112
+ end
113
+
114
+ def assign_parent
115
+ return unless site
116
+ self.parent ||= site.pages.root unless self == site.pages.root || site.pages.count.zero?
117
+ end
118
+
119
+ def assign_full_path
120
+ self.full_path =
121
+ if self.parent
122
+ [CGI.escape(self.parent.full_path).gsub("%2F", "/"), slug].join("/").squeeze("/")
123
+ else
124
+ "/"
125
+ end
126
+ end
127
+
128
+ def assign_position
129
+ return unless self.parent
130
+ return if position.to_i.positive?
131
+ max = self.parent.children.maximum(:position)
132
+ self.position = max ? max + 1 : 0
133
+ end
134
+
135
+ def validate_target_page
136
+ return unless target_page
137
+ p = self
138
+ while p.target_page
139
+ if (p = p.target_page) == self
140
+ return errors.add(:target_page_id, "Invalid Redirect")
141
+ end
142
+ end
143
+ end
144
+
145
+ def validate_format_of_unescaped_slug
146
+ return unless slug.present?
147
+ unescaped_slug = CGI.unescape(slug)
148
+ errors.add(:slug, :invalid) unless unescaped_slug =~ %r{^\p{Alnum}[\.\p{Alnum}\p{Mark}_-]*$}i
149
+ end
150
+
151
+ # Forcing re-saves for child pages so they can update full_paths
152
+ def sync_child_full_paths!
153
+ return unless full_path_previously_changed?
154
+ children.each do |p|
155
+ p.update_attribute(:full_path, p.send(:assign_full_path))
156
+ end
157
+ end
158
+
159
+ # Escape slug unless it's nonexistent (root)
160
+ def escape_slug
161
+ self.slug = CGI.escape(slug) unless slug.nil?
162
+ end
163
+
164
+ # Unescape the slug and full path back into their original forms unless they're nonexistent
165
+ def unescape_slug_and_path
166
+ self.slug = CGI.unescape(slug) unless slug.nil?
167
+ self.full_path = CGI.unescape(full_path) unless full_path.nil?
168
+ end
169
+
170
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Occams::Cms::Revision < ActiveRecord::Base
4
+
5
+ self.table_name = "occams_cms_revisions"
6
+
7
+ serialize :data
8
+
9
+ # -- Relationships --------------------------------------------------------
10
+ belongs_to :record, polymorphic: true
11
+
12
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Occams::Cms::Site < ActiveRecord::Base
4
+
5
+ self.table_name = "occams_cms_sites"
6
+
7
+ # -- Relationships -----------------------------------------------------------
8
+ with_options dependent: :destroy do |site|
9
+ site.has_many :layouts
10
+ site.has_many :pages
11
+ site.has_many :snippets
12
+ site.has_many :files
13
+ site.has_many :categories
14
+ end
15
+
16
+ # -- Callbacks ---------------------------------------------------------------
17
+ before_validation :assign_identifier,
18
+ :assign_hostname,
19
+ :assign_label,
20
+ :clean_path
21
+
22
+ # -- Validations -------------------------------------------------------------
23
+ validates :identifier,
24
+ presence: true,
25
+ uniqueness: true,
26
+ format: { with: %r{\A\w[a-z0-9_-]*\z}i }
27
+ validates :label,
28
+ presence: true
29
+ validates :hostname,
30
+ presence: true,
31
+ uniqueness: { scope: :path },
32
+ format: { with: %r{\A[\w.-]+(?:\:\d+)?\z} }
33
+
34
+ # -- Class Methods -----------------------------------------------------------
35
+ # returning the Occams::Cms::Site instance based on host and path
36
+ def self.find_site(host, path = nil)
37
+ return Occams::Cms::Site.first if Occams::Cms::Site.count == 1
38
+ cms_site = nil
39
+
40
+ public_cms_path = Occams.configuration.public_cms_path
41
+ if path && public_cms_path != "/"
42
+ path = path.sub(%r{\A#{public_cms_path}}, "")
43
+ end
44
+
45
+ Occams::Cms::Site.where(hostname: real_host_from_aliases(host)).each do |site|
46
+ if site.path.blank?
47
+ cms_site = site
48
+ elsif "#{path.to_s.split('?')[0]}/" =~ %r{^/#{Regexp.escape(site.path.to_s)}/}
49
+ cms_site = site
50
+ break
51
+ end
52
+ end
53
+ cms_site
54
+ end
55
+
56
+ def self.real_host_from_aliases(host)
57
+ if (aliases = Occams.config.hostname_aliases)
58
+ aliases.each do |alias_host, hosts|
59
+ return alias_host if hosts.include?(host)
60
+ end
61
+ end
62
+ host
63
+ end
64
+
65
+ # -- Instance Methods --------------------------------------------------------
66
+ def url(relative: false)
67
+ public_cms_path = Occams.config.public_cms_path || "/"
68
+ host = "//#{hostname}"
69
+ path = ["/", public_cms_path, self.path].compact.join("/").squeeze("/").chomp("/")
70
+ relative ? path.presence : [host, path].join
71
+ end
72
+
73
+ protected
74
+
75
+ def assign_identifier
76
+ self.identifier = identifier.blank? ? hostname.try(:parameterize) : identifier
77
+ end
78
+
79
+ def assign_hostname
80
+ self.hostname ||= identifier
81
+ end
82
+
83
+ def assign_label
84
+ self.label = label.blank? ? identifier.try(:titleize) : label
85
+ end
86
+
87
+ def clean_path
88
+ self.path ||= ""
89
+ self.path.squeeze!("/")
90
+ self.path.gsub!(%r{/$}, "")
91
+ self.path = nil if self.path.blank?
92
+ end
93
+
94
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Occams::Cms::Snippet < ActiveRecord::Base
4
+
5
+ self.table_name = "occams_cms_snippets"
6
+
7
+ include Occams::Cms::WithCategories
8
+
9
+ cms_has_revisions_for :content
10
+
11
+ # -- Relationships -----------------------------------------------------------
12
+ belongs_to :site
13
+
14
+ # -- Callbacks ---------------------------------------------------------------
15
+ before_validation :assign_label
16
+ before_create :assign_position
17
+ after_save :clear_page_content_cache
18
+ after_destroy :clear_page_content_cache
19
+
20
+ # -- Validations -------------------------------------------------------------
21
+ validates :label,
22
+ presence: true
23
+ validates :identifier,
24
+ presence: true,
25
+ uniqueness: { scope: :site_id },
26
+ format: { with: %r{\A\w[a-z0-9_-]*\z}i }
27
+
28
+ protected
29
+
30
+ def assign_label
31
+ self.label = label.blank? ? identifier.try(:titleize) : label
32
+ end
33
+
34
+ # When snippet is changed or removed we need to blow away all page caches as
35
+ # we don't know where it was used.
36
+ def clear_page_content_cache
37
+ Occams::Cms::Page.where(id: site.pages.pluck(:id)).update_all(content_cache: nil)
38
+ end
39
+
40
+ def assign_position
41
+ max = site.snippets.maximum(:position)
42
+ self.position = max ? max + 1 : 0
43
+ end
44
+
45
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Occams::Cms::Translation < ActiveRecord::Base
4
+
5
+ self.table_name = "occams_cms_translations"
6
+
7
+ include Occams::Cms::WithFragments
8
+
9
+ cms_has_revisions_for :fragments_attributes
10
+
11
+ delegate :site, to: :page
12
+
13
+ # -- Relationships -----------------------------------------------------------
14
+ belongs_to :page
15
+
16
+ # -- Callbacks ---------------------------------------------------------------
17
+ before_validation :assign_layout
18
+
19
+ # -- Scopes ------------------------------------------------------------------
20
+ scope :published, -> { where(is_published: true) }
21
+
22
+ # -- Validations -------------------------------------------------------------
23
+ validates :label,
24
+ presence: true
25
+
26
+ validates :locale,
27
+ presence: true,
28
+ uniqueness: { scope: :page_id }
29
+
30
+ validate :validate_locale
31
+
32
+ private
33
+
34
+ def validate_locale
35
+ return unless page
36
+ errors.add(:locale) if locale == page.site.locale
37
+ end
38
+
39
+ def assign_layout
40
+ self.layout ||= page.layout if page.present?
41
+ end
42
+
43
+ end
@@ -0,0 +1,3 @@
1
+ - unless current_page.first?
2
+ %li.page-item
3
+ = link_to_unless current_page.first?, raw(t 'views.pagination.first'), url, class: 'page-link', remote: remote
@@ -0,0 +1,2 @@
1
+ %li.page.item.disabled
2
+ = link_to raw(t 'views.pagination.truncate'), '#', class: 'page-link'
@@ -0,0 +1,3 @@
1
+ - unless current_page.last?
2
+ %li.page-item
3
+ = link_to_unless current_page.last?, raw(t 'views.pagination.last'), url, class: 'page-link', remote: remote
@@ -0,0 +1,2 @@
1
+ %li.page-item
2
+ = link_to_unless current_page.last?, raw(t 'views.pagination.next'), url, rel: 'next', remote: remote, class: 'page-link'
@@ -0,0 +1,2 @@
1
+ %li{class: "page-item#{' active' if page.current?}"}
2
+ = link_to page, url, opts = {remote: remote, class: 'page-link', rel: page.next? ? 'next' : page.prev? ? 'prev' : nil}
@@ -0,0 +1,11 @@
1
+ = paginator.render do
2
+ %ul.pagination.pagination-sm
3
+ = first_page_tag unless current_page.first?
4
+ = prev_page_tag unless current_page.first?
5
+ - each_page do |page|
6
+ - if page.left_outer? || page.right_outer? || page.inside_window?
7
+ = page_tag page
8
+ - elsif !page.was_truncated?
9
+ = gap_tag
10
+ = next_page_tag unless current_page.last?
11
+ = last_page_tag unless current_page.last?
@@ -0,0 +1,2 @@
1
+ %li
2
+ = link_to_unless current_page.first?, raw(t 'views.pagination.previous'), url, :rel => 'prev', :remote => remote, class: 'page-link'