comfy_middle_seat 3.2.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 (436) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +4 -0
  3. data/.gitattributes +3 -0
  4. data/.github/dependabot.yml +7 -0
  5. data/.github/issue_template.md +19 -0
  6. data/.github/pull_request_template.md +6 -0
  7. data/.github/workflows/claude-code-review.yml +46 -0
  8. data/.github/workflows/claude.yml +148 -0
  9. data/.github/workflows/coveralls.yml +53 -0
  10. data/.github/workflows/rubyonrails.yml +94 -0
  11. data/.gitignore +25 -0
  12. data/.rubocop.yml +82 -0
  13. data/.travis.yml +44 -0
  14. data/CHANGELOG.md +147 -0
  15. data/CONTRIBUTING.md +42 -0
  16. data/Gemfile +44 -0
  17. data/LICENSE +20 -0
  18. data/README.md +158 -0
  19. data/Rakefile +206 -0
  20. data/app/assets/builds/.keep +0 -0
  21. data/app/assets/config/manifest.js +1 -0
  22. data/app/assets/fonts/comfy/admin/cms/lib/redactor-font.eot +0 -0
  23. data/app/assets/javascripts/comfy/admin/cms/application.js +27 -0
  24. data/app/assets/javascripts/comfy/admin/cms/base.js +48 -0
  25. data/app/assets/javascripts/comfy/admin/cms/categories.js +17 -0
  26. data/app/assets/javascripts/comfy/admin/cms/codemirror.js +43 -0
  27. data/app/assets/javascripts/comfy/admin/cms/custom.js +1 -0
  28. data/app/assets/javascripts/comfy/admin/cms/diff.js +12 -0
  29. data/app/assets/javascripts/comfy/admin/cms/file_link.js +96 -0
  30. data/app/assets/javascripts/comfy/admin/cms/file_upload.js +229 -0
  31. data/app/assets/javascripts/comfy/admin/cms/files_modal.js +97 -0
  32. data/app/assets/javascripts/comfy/admin/cms/page_fragments.js +22 -0
  33. data/app/assets/javascripts/comfy/admin/cms/slugify.js +34 -0
  34. data/app/assets/javascripts/comfy/admin/cms/sortable_list.js +49 -0
  35. data/app/assets/javascripts/comfy/admin/cms/timepicker.js +38 -0
  36. data/app/assets/javascripts/comfy/admin/cms/wysiwyg.js +91 -0
  37. data/app/assets/javascripts/comfy/vendor/diff/diff_match_patch.min.js +49 -0
  38. data/app/assets/javascripts/comfy/vendor/diff/pretty_text_diff.js +88 -0
  39. data/app/assets/javascripts/comfy/vendor/flatpickr.min.js +2 -0
  40. data/app/assets/javascripts/comfy/vendor/fontawesome.js +5 -0
  41. data/app/assets/javascripts/comfy/vendor/redactor/definedlinks.js +50 -0
  42. data/app/assets/javascripts/comfy/vendor/redactor/filemanager.js +79 -0
  43. data/app/assets/javascripts/comfy/vendor/redactor/i18n/ar.js +76 -0
  44. data/app/assets/javascripts/comfy/vendor/redactor/i18n/az.js +75 -0
  45. data/app/assets/javascripts/comfy/vendor/redactor/i18n/ba.js +79 -0
  46. data/app/assets/javascripts/comfy/vendor/redactor/i18n/bg.js +77 -0
  47. data/app/assets/javascripts/comfy/vendor/redactor/i18n/by.js +77 -0
  48. data/app/assets/javascripts/comfy/vendor/redactor/i18n/ca.js +75 -0
  49. data/app/assets/javascripts/comfy/vendor/redactor/i18n/cs.js +87 -0
  50. data/app/assets/javascripts/comfy/vendor/redactor/i18n/da.js +75 -0
  51. data/app/assets/javascripts/comfy/vendor/redactor/i18n/de.js +80 -0
  52. data/app/assets/javascripts/comfy/vendor/redactor/i18n/el.js +76 -0
  53. data/app/assets/javascripts/comfy/vendor/redactor/i18n/en.js +75 -0
  54. data/app/assets/javascripts/comfy/vendor/redactor/i18n/eo.js +76 -0
  55. data/app/assets/javascripts/comfy/vendor/redactor/i18n/es.js +75 -0
  56. data/app/assets/javascripts/comfy/vendor/redactor/i18n/es_AR.js +76 -0
  57. data/app/assets/javascripts/comfy/vendor/redactor/i18n/fa.js +76 -0
  58. data/app/assets/javascripts/comfy/vendor/redactor/i18n/fi.js +75 -0
  59. data/app/assets/javascripts/comfy/vendor/redactor/i18n/fr.js +75 -0
  60. data/app/assets/javascripts/comfy/vendor/redactor/i18n/ge.js +75 -0
  61. data/app/assets/javascripts/comfy/vendor/redactor/i18n/gr.js +75 -0
  62. data/app/assets/javascripts/comfy/vendor/redactor/i18n/he.js +75 -0
  63. data/app/assets/javascripts/comfy/vendor/redactor/i18n/hr.js +78 -0
  64. data/app/assets/javascripts/comfy/vendor/redactor/i18n/hu.js +77 -0
  65. data/app/assets/javascripts/comfy/vendor/redactor/i18n/id.js +76 -0
  66. data/app/assets/javascripts/comfy/vendor/redactor/i18n/it.js +78 -0
  67. data/app/assets/javascripts/comfy/vendor/redactor/i18n/ja.js +76 -0
  68. data/app/assets/javascripts/comfy/vendor/redactor/i18n/kn.js +75 -0
  69. data/app/assets/javascripts/comfy/vendor/redactor/i18n/ko.js +76 -0
  70. data/app/assets/javascripts/comfy/vendor/redactor/i18n/lt.js +75 -0
  71. data/app/assets/javascripts/comfy/vendor/redactor/i18n/lv.js +75 -0
  72. data/app/assets/javascripts/comfy/vendor/redactor/i18n/mk.js +75 -0
  73. data/app/assets/javascripts/comfy/vendor/redactor/i18n/nb.js +77 -0
  74. data/app/assets/javascripts/comfy/vendor/redactor/i18n/nl.js +80 -0
  75. data/app/assets/javascripts/comfy/vendor/redactor/i18n/pl.js +78 -0
  76. data/app/assets/javascripts/comfy/vendor/redactor/i18n/pt-BR.js +75 -0
  77. data/app/assets/javascripts/comfy/vendor/redactor/i18n/pt.js +75 -0
  78. data/app/assets/javascripts/comfy/vendor/redactor/i18n/ro.js +76 -0
  79. data/app/assets/javascripts/comfy/vendor/redactor/i18n/ru.js +76 -0
  80. data/app/assets/javascripts/comfy/vendor/redactor/i18n/sk.js +76 -0
  81. data/app/assets/javascripts/comfy/vendor/redactor/i18n/sl.js +79 -0
  82. data/app/assets/javascripts/comfy/vendor/redactor/i18n/sq.js +79 -0
  83. data/app/assets/javascripts/comfy/vendor/redactor/i18n/sr-CIR.js +79 -0
  84. data/app/assets/javascripts/comfy/vendor/redactor/i18n/sr-LAT.js +79 -0
  85. data/app/assets/javascripts/comfy/vendor/redactor/i18n/sv.js +74 -0
  86. data/app/assets/javascripts/comfy/vendor/redactor/i18n/th.js +74 -0
  87. data/app/assets/javascripts/comfy/vendor/redactor/i18n/tr.js +75 -0
  88. data/app/assets/javascripts/comfy/vendor/redactor/i18n/ua.js +77 -0
  89. data/app/assets/javascripts/comfy/vendor/redactor/i18n/uk.js +77 -0
  90. data/app/assets/javascripts/comfy/vendor/redactor/i18n/vi.js +74 -0
  91. data/app/assets/javascripts/comfy/vendor/redactor/i18n/zh-CN.js +75 -0
  92. data/app/assets/javascripts/comfy/vendor/redactor/i18n/zh-TW.js +75 -0
  93. data/app/assets/javascripts/comfy/vendor/redactor/imagemanager.js +68 -0
  94. data/app/assets/javascripts/comfy/vendor/redactor/table.js +348 -0
  95. data/app/assets/javascripts/comfy/vendor/redactor/video.js +82 -0
  96. data/app/assets/javascripts/comfy/vendor/redactor.js +9563 -0
  97. data/app/assets/stylesheets/comfy/admin/cms/application.sass +11 -0
  98. data/app/assets/stylesheets/comfy/admin/cms/base.sass +256 -0
  99. data/app/assets/stylesheets/comfy/admin/cms/codemirror_overrides.sass +19 -0
  100. data/app/assets/stylesheets/comfy/admin/cms/custom.sass +1 -0
  101. data/app/assets/stylesheets/comfy/admin/cms/redactor_overrides.sass +22 -0
  102. data/app/assets/stylesheets/comfy/vendor/bootstrap/_alert.scss +51 -0
  103. data/app/assets/stylesheets/comfy/vendor/bootstrap/_badge.scss +54 -0
  104. data/app/assets/stylesheets/comfy/vendor/bootstrap/_breadcrumb.scss +41 -0
  105. data/app/assets/stylesheets/comfy/vendor/bootstrap/_button-group.scss +163 -0
  106. data/app/assets/stylesheets/comfy/vendor/bootstrap/_buttons.scss +137 -0
  107. data/app/assets/stylesheets/comfy/vendor/bootstrap/_card.scss +289 -0
  108. data/app/assets/stylesheets/comfy/vendor/bootstrap/_carousel.scss +197 -0
  109. data/app/assets/stylesheets/comfy/vendor/bootstrap/_close.scss +41 -0
  110. data/app/assets/stylesheets/comfy/vendor/bootstrap/_code.scss +48 -0
  111. data/app/assets/stylesheets/comfy/vendor/bootstrap/_custom-forms.scss +507 -0
  112. data/app/assets/stylesheets/comfy/vendor/bootstrap/_dropdown.scss +191 -0
  113. data/app/assets/stylesheets/comfy/vendor/bootstrap/_forms.scss +330 -0
  114. data/app/assets/stylesheets/comfy/vendor/bootstrap/_functions.scss +86 -0
  115. data/app/assets/stylesheets/comfy/vendor/bootstrap/_grid.scss +52 -0
  116. data/app/assets/stylesheets/comfy/vendor/bootstrap/_images.scss +42 -0
  117. data/app/assets/stylesheets/comfy/vendor/bootstrap/_input-group.scss +193 -0
  118. data/app/assets/stylesheets/comfy/vendor/bootstrap/_jumbotron.scss +17 -0
  119. data/app/assets/stylesheets/comfy/vendor/bootstrap/_list-group.scss +149 -0
  120. data/app/assets/stylesheets/comfy/vendor/bootstrap/_media.scss +8 -0
  121. data/app/assets/stylesheets/comfy/vendor/bootstrap/_mixins.scss +47 -0
  122. data/app/assets/stylesheets/comfy/vendor/bootstrap/_modal.scss +229 -0
  123. data/app/assets/stylesheets/comfy/vendor/bootstrap/_nav.scss +120 -0
  124. data/app/assets/stylesheets/comfy/vendor/bootstrap/_navbar.scss +294 -0
  125. data/app/assets/stylesheets/comfy/vendor/bootstrap/_pagination.scss +73 -0
  126. data/app/assets/stylesheets/comfy/vendor/bootstrap/_popover.scss +171 -0
  127. data/app/assets/stylesheets/comfy/vendor/bootstrap/_print.scss +141 -0
  128. data/app/assets/stylesheets/comfy/vendor/bootstrap/_progress.scss +43 -0
  129. data/app/assets/stylesheets/comfy/vendor/bootstrap/_reboot.scss +483 -0
  130. data/app/assets/stylesheets/comfy/vendor/bootstrap/_root.scss +19 -0
  131. data/app/assets/stylesheets/comfy/vendor/bootstrap/_spinners.scss +55 -0
  132. data/app/assets/stylesheets/comfy/vendor/bootstrap/_tables.scss +185 -0
  133. data/app/assets/stylesheets/comfy/vendor/bootstrap/_toasts.scss +44 -0
  134. data/app/assets/stylesheets/comfy/vendor/bootstrap/_tooltip.scss +115 -0
  135. data/app/assets/stylesheets/comfy/vendor/bootstrap/_transitions.scss +20 -0
  136. data/app/assets/stylesheets/comfy/vendor/bootstrap/_type.scss +125 -0
  137. data/app/assets/stylesheets/comfy/vendor/bootstrap/_utilities.scss +17 -0
  138. data/app/assets/stylesheets/comfy/vendor/bootstrap/_variables.scss +1123 -0
  139. data/app/assets/stylesheets/comfy/vendor/bootstrap/bootstrap-grid.scss +29 -0
  140. data/app/assets/stylesheets/comfy/vendor/bootstrap/bootstrap-reboot.scss +12 -0
  141. data/app/assets/stylesheets/comfy/vendor/bootstrap/bootstrap.scss +44 -0
  142. data/app/assets/stylesheets/comfy/vendor/bootstrap/mixins/_alert.scss +13 -0
  143. data/app/assets/stylesheets/comfy/vendor/bootstrap/mixins/_background-variant.scss +21 -0
  144. data/app/assets/stylesheets/comfy/vendor/bootstrap/mixins/_badge.scss +17 -0
  145. data/app/assets/stylesheets/comfy/vendor/bootstrap/mixins/_border-radius.scss +63 -0
  146. data/app/assets/stylesheets/comfy/vendor/bootstrap/mixins/_box-shadow.scss +20 -0
  147. data/app/assets/stylesheets/comfy/vendor/bootstrap/mixins/_breakpoints.scss +123 -0
  148. data/app/assets/stylesheets/comfy/vendor/bootstrap/mixins/_buttons.scss +107 -0
  149. data/app/assets/stylesheets/comfy/vendor/bootstrap/mixins/_caret.scss +62 -0
  150. data/app/assets/stylesheets/comfy/vendor/bootstrap/mixins/_clearfix.scss +7 -0
  151. data/app/assets/stylesheets/comfy/vendor/bootstrap/mixins/_deprecate.scss +10 -0
  152. data/app/assets/stylesheets/comfy/vendor/bootstrap/mixins/_float.scss +14 -0
  153. data/app/assets/stylesheets/comfy/vendor/bootstrap/mixins/_forms.scss +192 -0
  154. data/app/assets/stylesheets/comfy/vendor/bootstrap/mixins/_gradients.scss +45 -0
  155. data/app/assets/stylesheets/comfy/vendor/bootstrap/mixins/_grid-framework.scss +66 -0
  156. data/app/assets/stylesheets/comfy/vendor/bootstrap/mixins/_grid.scss +51 -0
  157. data/app/assets/stylesheets/comfy/vendor/bootstrap/mixins/_hover.scss +37 -0
  158. data/app/assets/stylesheets/comfy/vendor/bootstrap/mixins/_image.scss +36 -0
  159. data/app/assets/stylesheets/comfy/vendor/bootstrap/mixins/_list-group.scss +21 -0
  160. data/app/assets/stylesheets/comfy/vendor/bootstrap/mixins/_lists.scss +7 -0
  161. data/app/assets/stylesheets/comfy/vendor/bootstrap/mixins/_nav-divider.scss +10 -0
  162. data/app/assets/stylesheets/comfy/vendor/bootstrap/mixins/_pagination.scss +22 -0
  163. data/app/assets/stylesheets/comfy/vendor/bootstrap/mixins/_reset-text.scss +17 -0
  164. data/app/assets/stylesheets/comfy/vendor/bootstrap/mixins/_resize.scss +6 -0
  165. data/app/assets/stylesheets/comfy/vendor/bootstrap/mixins/_screen-reader.scss +33 -0
  166. data/app/assets/stylesheets/comfy/vendor/bootstrap/mixins/_size.scss +7 -0
  167. data/app/assets/stylesheets/comfy/vendor/bootstrap/mixins/_table-row.scss +39 -0
  168. data/app/assets/stylesheets/comfy/vendor/bootstrap/mixins/_text-emphasis.scss +16 -0
  169. data/app/assets/stylesheets/comfy/vendor/bootstrap/mixins/_text-hide.scss +11 -0
  170. data/app/assets/stylesheets/comfy/vendor/bootstrap/mixins/_text-truncate.scss +8 -0
  171. data/app/assets/stylesheets/comfy/vendor/bootstrap/mixins/_transition.scss +16 -0
  172. data/app/assets/stylesheets/comfy/vendor/bootstrap/mixins/_visibility.scss +8 -0
  173. data/app/assets/stylesheets/comfy/vendor/bootstrap/utilities/_align.scss +8 -0
  174. data/app/assets/stylesheets/comfy/vendor/bootstrap/utilities/_background.scss +19 -0
  175. data/app/assets/stylesheets/comfy/vendor/bootstrap/utilities/_borders.scss +75 -0
  176. data/app/assets/stylesheets/comfy/vendor/bootstrap/utilities/_clearfix.scss +3 -0
  177. data/app/assets/stylesheets/comfy/vendor/bootstrap/utilities/_display.scss +26 -0
  178. data/app/assets/stylesheets/comfy/vendor/bootstrap/utilities/_embed.scss +39 -0
  179. data/app/assets/stylesheets/comfy/vendor/bootstrap/utilities/_flex.scss +51 -0
  180. data/app/assets/stylesheets/comfy/vendor/bootstrap/utilities/_float.scss +11 -0
  181. data/app/assets/stylesheets/comfy/vendor/bootstrap/utilities/_overflow.scss +5 -0
  182. data/app/assets/stylesheets/comfy/vendor/bootstrap/utilities/_position.scss +32 -0
  183. data/app/assets/stylesheets/comfy/vendor/bootstrap/utilities/_screenreaders.scss +11 -0
  184. data/app/assets/stylesheets/comfy/vendor/bootstrap/utilities/_shadows.scss +6 -0
  185. data/app/assets/stylesheets/comfy/vendor/bootstrap/utilities/_sizing.scss +20 -0
  186. data/app/assets/stylesheets/comfy/vendor/bootstrap/utilities/_spacing.scss +73 -0
  187. data/app/assets/stylesheets/comfy/vendor/bootstrap/utilities/_stretched-link.scss +19 -0
  188. data/app/assets/stylesheets/comfy/vendor/bootstrap/utilities/_text.scss +72 -0
  189. data/app/assets/stylesheets/comfy/vendor/bootstrap/utilities/_visibility.scss +13 -0
  190. data/app/assets/stylesheets/comfy/vendor/bootstrap/vendor/_rfs.scss +204 -0
  191. data/app/assets/stylesheets/comfy/vendor/fontawesome.css +343 -0
  192. data/app/assets/stylesheets/comfy/vendor/redactor.css +948 -0
  193. data/app/controllers/application_controller.rb +5 -0
  194. data/app/controllers/comfy/admin/base_controller.rb +20 -0
  195. data/app/controllers/comfy/admin/cms/base_controller.rb +54 -0
  196. data/app/controllers/comfy/admin/cms/categories_controller.rb +38 -0
  197. data/app/controllers/comfy/admin/cms/files_controller.rb +140 -0
  198. data/app/controllers/comfy/admin/cms/layouts_controller.rb +69 -0
  199. data/app/controllers/comfy/admin/cms/pages_controller.rb +193 -0
  200. data/app/controllers/comfy/admin/cms/revisions/base_controller.rb +48 -0
  201. data/app/controllers/comfy/admin/cms/revisions/layout_controller.rb +16 -0
  202. data/app/controllers/comfy/admin/cms/revisions/page_controller.rb +28 -0
  203. data/app/controllers/comfy/admin/cms/revisions/snippet_controller.rb +16 -0
  204. data/app/controllers/comfy/admin/cms/revisions/translation_controller.rb +29 -0
  205. data/app/controllers/comfy/admin/cms/sites_controller.rb +68 -0
  206. data/app/controllers/comfy/admin/cms/snippets_controller.rb +66 -0
  207. data/app/controllers/comfy/admin/cms/translations_controller.rb +117 -0
  208. data/app/controllers/comfy/cms/assets_controller.rb +38 -0
  209. data/app/controllers/comfy/cms/base_controller.rb +35 -0
  210. data/app/controllers/comfy/cms/content_controller.rb +90 -0
  211. data/app/controllers/concerns/comfy/paginate.rb +11 -0
  212. data/app/controllers/concerns/comfy/reorder_action.rb +19 -0
  213. data/app/helpers/comfy/admin/cms_helper.rb +50 -0
  214. data/app/helpers/comfy/cms_helper.rb +71 -0
  215. data/app/models/comfy/cms/categorization.rb +23 -0
  216. data/app/models/comfy/cms/category.rb +22 -0
  217. data/app/models/comfy/cms/file.rb +65 -0
  218. data/app/models/comfy/cms/fragment.rb +65 -0
  219. data/app/models/comfy/cms/layout.rb +119 -0
  220. data/app/models/comfy/cms/page.rb +182 -0
  221. data/app/models/comfy/cms/revision.rb +10 -0
  222. data/app/models/comfy/cms/site.rb +93 -0
  223. data/app/models/comfy/cms/snippet.rb +43 -0
  224. data/app/models/comfy/cms/translation.rb +47 -0
  225. data/app/models/concerns/comfy/cms/with_categories.rb +51 -0
  226. data/app/models/concerns/comfy/cms/with_fragments.rb +123 -0
  227. data/app/views/comfy/admin/cms/categories/_categories.html.haml +4 -0
  228. data/app/views/comfy/admin/cms/categories/_edit.html.haml +7 -0
  229. data/app/views/comfy/admin/cms/categories/_form.html.haml +5 -0
  230. data/app/views/comfy/admin/cms/categories/_index.html.haml +28 -0
  231. data/app/views/comfy/admin/cms/categories/_show.html.haml +15 -0
  232. data/app/views/comfy/admin/cms/categories/create.js.erb +7 -0
  233. data/app/views/comfy/admin/cms/categories/destroy.js.erb +8 -0
  234. data/app/views/comfy/admin/cms/categories/edit.js.erb +3 -0
  235. data/app/views/comfy/admin/cms/categories/update.js.erb +6 -0
  236. data/app/views/comfy/admin/cms/files/_file.html.haml +31 -0
  237. data/app/views/comfy/admin/cms/files/_form.html.haml +26 -0
  238. data/app/views/comfy/admin/cms/files/_modal.html.haml +3 -0
  239. data/app/views/comfy/admin/cms/files/destroy.js.erb +4 -0
  240. data/app/views/comfy/admin/cms/files/edit.html.haml +5 -0
  241. data/app/views/comfy/admin/cms/files/index.html.haml +31 -0
  242. data/app/views/comfy/admin/cms/files/new.html.haml +5 -0
  243. data/app/views/comfy/admin/cms/fragments/_form_fragment_attachments.html.haml +23 -0
  244. data/app/views/comfy/admin/cms/fragments/_form_fragments.html.haml +38 -0
  245. data/app/views/comfy/admin/cms/layouts/_form.html.haml +20 -0
  246. data/app/views/comfy/admin/cms/layouts/_index_branch.html.haml +26 -0
  247. data/app/views/comfy/admin/cms/layouts/edit.html.haml +9 -0
  248. data/app/views/comfy/admin/cms/layouts/index.html.haml +10 -0
  249. data/app/views/comfy/admin/cms/layouts/new.html.haml +5 -0
  250. data/app/views/comfy/admin/cms/pages/_form.html.haml +31 -0
  251. data/app/views/comfy/admin/cms/pages/_index_branch.html.haml +44 -0
  252. data/app/views/comfy/admin/cms/pages/edit.html.haml +14 -0
  253. data/app/views/comfy/admin/cms/pages/index.html.haml +13 -0
  254. data/app/views/comfy/admin/cms/pages/new.html.haml +5 -0
  255. data/app/views/comfy/admin/cms/pages/toggle_branch.js.erb +19 -0
  256. data/app/views/comfy/admin/cms/revisions/_sidebar.html.haml +2 -0
  257. data/app/views/comfy/admin/cms/revisions/show.html.haml +36 -0
  258. data/app/views/comfy/admin/cms/sites/_form.html.haml +15 -0
  259. data/app/views/comfy/admin/cms/sites/edit.html.haml +5 -0
  260. data/app/views/comfy/admin/cms/sites/index.html.haml +28 -0
  261. data/app/views/comfy/admin/cms/sites/new.html.haml +5 -0
  262. data/app/views/comfy/admin/cms/snippets/_form.html.haml +19 -0
  263. data/app/views/comfy/admin/cms/snippets/edit.html.haml +9 -0
  264. data/app/views/comfy/admin/cms/snippets/index.html.haml +33 -0
  265. data/app/views/comfy/admin/cms/snippets/new.html.haml +5 -0
  266. data/app/views/comfy/admin/cms/translations/_form.html.haml +20 -0
  267. data/app/views/comfy/admin/cms/translations/_sidebar.html.haml +19 -0
  268. data/app/views/comfy/admin/cms/translations/edit.html.haml +10 -0
  269. data/app/views/comfy/admin/cms/translations/new.html.haml +5 -0
  270. data/app/views/kaminari/comfy/_first_page.html.haml +3 -0
  271. data/app/views/kaminari/comfy/_gap.html.haml +2 -0
  272. data/app/views/kaminari/comfy/_last_page.html.haml +3 -0
  273. data/app/views/kaminari/comfy/_next_page.html.haml +2 -0
  274. data/app/views/kaminari/comfy/_page.html.haml +2 -0
  275. data/app/views/kaminari/comfy/_paginator.html.haml +11 -0
  276. data/app/views/kaminari/comfy/_prev_page.html.haml +2 -0
  277. data/app/views/layouts/comfy/admin/cms/_body.html.haml +14 -0
  278. data/app/views/layouts/comfy/admin/cms/_flash.html.haml +4 -0
  279. data/app/views/layouts/comfy/admin/cms/_footer_js.html.haml +6 -0
  280. data/app/views/layouts/comfy/admin/cms/_head.html.haml +19 -0
  281. data/app/views/layouts/comfy/admin/cms/_left.html.haml +46 -0
  282. data/app/views/layouts/comfy/admin/cms/_right.html.haml +5 -0
  283. data/app/views/layouts/comfy/admin/cms.html.haml +4 -0
  284. data/bin/bundle +3 -0
  285. data/bin/rails +4 -0
  286. data/bin/rake +4 -0
  287. data/bin/setup +36 -0
  288. data/bin/update +31 -0
  289. data/bin/yarn +11 -0
  290. data/comfy_middle_seat.gemspec +35 -0
  291. data/config/application.rb +62 -0
  292. data/config/boot.rb +5 -0
  293. data/config/cable.yml +10 -0
  294. data/config/cms_routes.rb +8 -0
  295. data/config/database.yml +40 -0
  296. data/config/environment.rb +7 -0
  297. data/config/environments/development.rb +81 -0
  298. data/config/environments/production.rb +93 -0
  299. data/config/environments/test.rb +74 -0
  300. data/config/initializers/comfortable_media_surfer.rb +122 -0
  301. data/config/initializers/content_security_policy.rb +27 -0
  302. data/config/initializers/filter_parameter_logging.rb +10 -0
  303. data/config/initializers/inflections.rb +18 -0
  304. data/config/initializers/json.rb +11 -0
  305. data/config/initializers/new_framework_defaults_7_0.rb +144 -0
  306. data/config/initializers/new_framework_defaults_7_1.rb +225 -0
  307. data/config/initializers/permissions_policy.rb +15 -0
  308. data/config/locales/ar.yml +268 -0
  309. data/config/locales/ca.yml +268 -0
  310. data/config/locales/cs.yml +268 -0
  311. data/config/locales/da.yml +268 -0
  312. data/config/locales/de.yml +268 -0
  313. data/config/locales/en.yml +268 -0
  314. data/config/locales/es.yml +268 -0
  315. data/config/locales/fi.yml +268 -0
  316. data/config/locales/fr.yml +268 -0
  317. data/config/locales/gr.yml +268 -0
  318. data/config/locales/hr.yml +268 -0
  319. data/config/locales/it.yml +268 -0
  320. data/config/locales/ja.yml +268 -0
  321. data/config/locales/nb.yml +268 -0
  322. data/config/locales/nl.yml +268 -0
  323. data/config/locales/pl.yml +268 -0
  324. data/config/locales/pt-BR.yml +268 -0
  325. data/config/locales/ru.yml +268 -0
  326. data/config/locales/sk.yml +268 -0
  327. data/config/locales/sv.yml +268 -0
  328. data/config/locales/tr.yml +268 -0
  329. data/config/locales/uk.yml +268 -0
  330. data/config/locales/zh-CN.yml +268 -0
  331. data/config/locales/zh-TW.yml +268 -0
  332. data/config/puma.rb +43 -0
  333. data/config/storage.yml +35 -0
  334. data/config.ru +6 -0
  335. data/db/cms_seeds/sample-site/files/_default.jpg.yml +5 -0
  336. data/db/cms_seeds/sample-site/files/default.jpg +0 -0
  337. data/db/cms_seeds/sample-site/layouts/default/content.html +16 -0
  338. data/db/cms_seeds/sample-site/layouts/default/nested/content.html +14 -0
  339. data/db/cms_seeds/sample-site/pages/index/child_a/content.html +7 -0
  340. data/db/cms_seeds/sample-site/pages/index/child_b/child_a/content.html +6 -0
  341. data/db/cms_seeds/sample-site/pages/index/child_b/content.html +6 -0
  342. data/db/cms_seeds/sample-site/pages/index/cms logo.png +0 -0
  343. data/db/cms_seeds/sample-site/pages/index/content.es.html +6 -0
  344. data/db/cms_seeds/sample-site/pages/index/content.fr.html +6 -0
  345. data/db/cms_seeds/sample-site/pages/index/content.html +25 -0
  346. data/db/cms_seeds/sample-site/pages/index/footer.png +0 -0
  347. data/db/cms_seeds/sample-site/pages/index/header.png +0 -0
  348. data/db/cms_seeds/sample-site/snippets/default.html +8 -0
  349. data/db/migrate/00_create_active_storage_tables.active_storage.rb +50 -0
  350. data/db/migrate/01_create_cms.rb +142 -0
  351. data/db/migrate/02_add_markdown_to_snippets.rb +5 -0
  352. data/gemfiles/7.2.gemfile +36 -0
  353. data/gemfiles/8.0.gemfile +36 -0
  354. data/gemfiles/8.1.gemfile +36 -0
  355. data/lib/comfortable_media_surfer/access_control/admin_authentication.rb +22 -0
  356. data/lib/comfortable_media_surfer/access_control/admin_authorization.rb +10 -0
  357. data/lib/comfortable_media_surfer/access_control/public_authentication.rb +10 -0
  358. data/lib/comfortable_media_surfer/access_control/public_authorization.rb +10 -0
  359. data/lib/comfortable_media_surfer/configuration.rb +138 -0
  360. data/lib/comfortable_media_surfer/content/block.rb +12 -0
  361. data/lib/comfortable_media_surfer/content/params_parser.rb +149 -0
  362. data/lib/comfortable_media_surfer/content/renderer.rb +146 -0
  363. data/lib/comfortable_media_surfer/content/tag.rb +48 -0
  364. data/lib/comfortable_media_surfer/content/tags/asset.rb +57 -0
  365. data/lib/comfortable_media_surfer/content/tags/audio.rb +39 -0
  366. data/lib/comfortable_media_surfer/content/tags/breadcrumbs.rb +43 -0
  367. data/lib/comfortable_media_surfer/content/tags/checkbox.rb +27 -0
  368. data/lib/comfortable_media_surfer/content/tags/children.rb +52 -0
  369. data/lib/comfortable_media_surfer/content/tags/date.rb +19 -0
  370. data/lib/comfortable_media_surfer/content/tags/datetime.rb +42 -0
  371. data/lib/comfortable_media_surfer/content/tags/file.rb +70 -0
  372. data/lib/comfortable_media_surfer/content/tags/file_link.rb +58 -0
  373. data/lib/comfortable_media_surfer/content/tags/files.rb +41 -0
  374. data/lib/comfortable_media_surfer/content/tags/fragment.rb +64 -0
  375. data/lib/comfortable_media_surfer/content/tags/helper.rb +53 -0
  376. data/lib/comfortable_media_surfer/content/tags/image.rb +56 -0
  377. data/lib/comfortable_media_surfer/content/tags/markdown.rb +22 -0
  378. data/lib/comfortable_media_surfer/content/tags/mixins/file_content.rb +42 -0
  379. data/lib/comfortable_media_surfer/content/tags/number.rb +18 -0
  380. data/lib/comfortable_media_surfer/content/tags/page_file_link.rb +86 -0
  381. data/lib/comfortable_media_surfer/content/tags/partial.rb +47 -0
  382. data/lib/comfortable_media_surfer/content/tags/siblings.rb +66 -0
  383. data/lib/comfortable_media_surfer/content/tags/snippet.rb +36 -0
  384. data/lib/comfortable_media_surfer/content/tags/template.rb +42 -0
  385. data/lib/comfortable_media_surfer/content/tags/text.rb +18 -0
  386. data/lib/comfortable_media_surfer/content/tags/textarea.rb +19 -0
  387. data/lib/comfortable_media_surfer/content/tags/wysiwyg.rb +19 -0
  388. data/lib/comfortable_media_surfer/content/tags.rb +29 -0
  389. data/lib/comfortable_media_surfer/content.rb +11 -0
  390. data/lib/comfortable_media_surfer/engine.rb +24 -0
  391. data/lib/comfortable_media_surfer/error.rb +24 -0
  392. data/lib/comfortable_media_surfer/extensions/acts_as_tree.rb +114 -0
  393. data/lib/comfortable_media_surfer/extensions/has_revisions.rb +68 -0
  394. data/lib/comfortable_media_surfer/extensions.rb +8 -0
  395. data/lib/comfortable_media_surfer/form_builder.rb +43 -0
  396. data/lib/comfortable_media_surfer/render_methods.rb +106 -0
  397. data/lib/comfortable_media_surfer/routing.rb +87 -0
  398. data/lib/comfortable_media_surfer/seeds/file/exporter.rb +37 -0
  399. data/lib/comfortable_media_surfer/seeds/file/importer.rb +55 -0
  400. data/lib/comfortable_media_surfer/seeds/layout/exporter.rb +38 -0
  401. data/lib/comfortable_media_surfer/seeds/layout/importer.rb +60 -0
  402. data/lib/comfortable_media_surfer/seeds/page/exporter.rb +88 -0
  403. data/lib/comfortable_media_surfer/seeds/page/importer.rb +245 -0
  404. data/lib/comfortable_media_surfer/seeds/snippet/exporter.rb +32 -0
  405. data/lib/comfortable_media_surfer/seeds/snippet/importer.rb +47 -0
  406. data/lib/comfortable_media_surfer/seeds.rb +110 -0
  407. data/lib/comfortable_media_surfer/version.rb +3 -0
  408. data/lib/comfortable_media_surfer/view_hooks.rb +33 -0
  409. data/lib/comfortable_media_surfer.rb +56 -0
  410. data/lib/comfy_middle_seat/version.rb +5 -0
  411. data/lib/comfy_middle_seat.rb +3 -0
  412. data/lib/generators/comfy/cms/README +21 -0
  413. data/lib/generators/comfy/cms/assets_generator.rb +16 -0
  414. data/lib/generators/comfy/cms/cms_generator.rb +69 -0
  415. data/lib/generators/comfy/cms/controllers_generator.rb +15 -0
  416. data/lib/generators/comfy/cms/models_generator.rb +15 -0
  417. data/lib/generators/comfy/cms/views_generator.rb +15 -0
  418. data/lib/generators/comfy/scaffold/scaffold_generator.rb +79 -0
  419. data/lib/generators/comfy/scaffold/templates/controller.rb.tt +62 -0
  420. data/lib/generators/comfy/scaffold/templates/migration.rb.tt +11 -0
  421. data/lib/generators/comfy/scaffold/templates/model.rb.tt +21 -0
  422. data/lib/generators/comfy/scaffold/templates/tests/controller.rb.tt +115 -0
  423. data/lib/generators/comfy/scaffold/templates/tests/fixture.yml.tt +4 -0
  424. data/lib/generators/comfy/scaffold/templates/tests/model.rb.tt +26 -0
  425. data/lib/generators/comfy/scaffold/templates/views/_form.haml.tt +7 -0
  426. data/lib/generators/comfy/scaffold/templates/views/edit.haml.tt +5 -0
  427. data/lib/generators/comfy/scaffold/templates/views/index.haml.tt +21 -0
  428. data/lib/generators/comfy/scaffold/templates/views/new.haml.tt +5 -0
  429. data/lib/generators/comfy/scaffold/templates/views/show.haml.tt +4 -0
  430. data/lib/tasks/cms_assets.rake +19 -0
  431. data/lib/tasks/cms_seeds.rake +40 -0
  432. data/package-lock.json +989 -0
  433. data/package.json +23 -0
  434. data/public/favicon.ico +0 -0
  435. data/rakelib/create_release.rake +82 -0
  436. metadata +665 -0
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Comfy::Cms::Fragment < ActiveRecord::Base
4
+ self.table_name = 'comfy_cms_fragments'
5
+
6
+ has_many_attached :attachments
7
+
8
+ serialize :content, coder: Psych
9
+
10
+ attr_reader :files
11
+
12
+ # -- Callbacks ---------------------------------------------------------------
13
+ # active_storage attachment behavior changed in rails 6
14
+ before_save :remove_attachments, :add_attachments
15
+
16
+ # -- Relationships -----------------------------------------------------------
17
+ belongs_to :record, polymorphic: true, touch: true
18
+
19
+ # -- Validations -------------------------------------------------------------
20
+ validates :identifier,
21
+ presence: true,
22
+ uniqueness: { scope: :record }
23
+ validate :record_is_immutable, on: :update
24
+
25
+ # -- Instance Methods --------------------------------------------------------
26
+
27
+ # Temporary accessor for uploaded files. We can only attach to persisted
28
+ # records so we are deffering it to the after_save callback.
29
+ # Note: hijacking dirty tracking to force trigger callbacks later.
30
+ def files=(files)
31
+ @files = [files].flatten.compact
32
+ content_will_change! if @files.present?
33
+ end
34
+
35
+ def file_ids_destroy=(ids)
36
+ @file_ids_destroy = [ids].flatten.compact
37
+ content_will_change! if @file_ids_destroy.present?
38
+ end
39
+
40
+ protected
41
+
42
+ def record_is_immutable
43
+ return unless will_save_change_to_record_id? || will_save_change_to_record_type?
44
+
45
+ errors.add(:record, :invalid)
46
+ end
47
+
48
+ def remove_attachments
49
+ return unless @file_ids_destroy.present?
50
+
51
+ attachments.where(id: @file_ids_destroy).destroy_all
52
+ end
53
+
54
+ def add_attachments
55
+ return if @files.blank?
56
+
57
+ # If we're dealing with a single file
58
+ if tag == 'file'
59
+ @files = [@files.first]
60
+ attachments&.purge_later
61
+ end
62
+
63
+ attachments.attach(@files)
64
+ end
65
+ end
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Comfy::Cms::Layout < ActiveRecord::Base
4
+ self.table_name = 'comfy_cms_layouts'
5
+
6
+ require_relative '../../../../lib/comfortable_media_surfer/extensions/acts_as_tree'
7
+
8
+ cms_acts_as_tree
9
+ cms_has_revisions_for :content, :css, :js
10
+
11
+ # -- Relationships --------------------------------------------------------
12
+ belongs_to :site
13
+ has_many :pages, dependent: :nullify
14
+
15
+ # -- Callbacks ---------------------------------------------------------------
16
+ before_validation :assign_label
17
+ before_create :assign_position
18
+ after_save :clear_page_content_cache
19
+ after_destroy :clear_page_content_cache
20
+
21
+ # -- Validations -------------------------------------------------------------
22
+ validates :site_id,
23
+ presence: true
24
+ validates :label,
25
+ presence: true
26
+ validates :identifier,
27
+ presence: true,
28
+ uniqueness: { scope: :site_id },
29
+ format: { with: %r{\A\w[a-z0-9_-]*\z}i }
30
+ validate :parent_belongs_to_site
31
+
32
+ # -- Class Methods -----------------------------------------------------------
33
+ # Tree-like structure for layouts
34
+ def self.options_for_select(site, current_layout = nil)
35
+ options = []
36
+
37
+ options_for_layout = ->(layout, depth = 0) do
38
+ return if layout == current_layout
39
+
40
+ options << ["#{'. . ' * depth}#{layout.label}", layout.id]
41
+
42
+ layout.children.order(:position).each do |child_layout|
43
+ options_for_layout.call(child_layout, depth + 1)
44
+ end
45
+ end
46
+
47
+ site.layouts.roots.order(:position).each do |layout|
48
+ options_for_layout.call(layout)
49
+ end
50
+
51
+ options
52
+ end
53
+
54
+ # List of available application layouts
55
+ def self.app_layouts_for_select(view_paths)
56
+ view_paths.map(&:to_s).select { |path| path.start_with?(Rails.root.to_s) }.flat_map do |full_path|
57
+ Dir.glob("#{full_path}/layouts/**/*.html.*").collect do |filename|
58
+ filename.gsub!("#{full_path}/layouts/", '')
59
+ filename.split('/').last[0...1] == '_' ? nil : filename.split('.').first
60
+ end.compact.sort
61
+ end.compact.uniq.sort
62
+ end
63
+
64
+ # -- Instance Methods --------------------------------------------------------
65
+ # Tokenized layout content that also pulls in parent layout (if there's one)
66
+ # and merges on the {{cms:tag_type content}} tag (if parent layout has that).
67
+ # Returns a list of tokens that can be fed into the renderer.
68
+ def content_tokens
69
+ renderer = ComfortableMediaSurfer::Content::Renderer.new(nil)
70
+ tokens = renderer.tokenize(content)
71
+
72
+ if parent
73
+ fragment_tags = ComfortableMediaSurfer::Content::Tags::Fragment.subclasses.map do |c|
74
+ ComfortableMediaSurfer::Content::Renderer.tags.key(c)
75
+ end
76
+
77
+ parent_tokens = parent.content_tokens
78
+ replacement_position = parent_tokens.index do |n|
79
+ n.is_a?(Hash) &&
80
+ fragment_tags.member?(n[:tag_class]) &&
81
+ n[:tag_params].split(%r{\s}).first == 'content'
82
+ end
83
+
84
+ if replacement_position
85
+ parent_tokens[replacement_position] = tokens
86
+ tokens = parent_tokens.flatten
87
+ end
88
+ end
89
+
90
+ tokens
91
+ end
92
+
93
+ def cache_buster
94
+ updated_at.to_i
95
+ end
96
+
97
+ # Forcing page content reload
98
+ def clear_page_content_cache
99
+ Comfy::Cms::Page.where(id: pages.pluck(:id)).update_all(content_cache: nil)
100
+ children.each(&:clear_page_content_cache)
101
+ end
102
+
103
+ protected
104
+
105
+ def parent_belongs_to_site
106
+ errors.add(:parent_id, :invalid) if site && parent && site_id != parent.site_id
107
+ end
108
+
109
+ def assign_label
110
+ self.label = label.blank? ? identifier.try(:titleize) : label
111
+ end
112
+
113
+ def assign_position
114
+ return if position.to_i.positive?
115
+
116
+ max = site.layouts.where(parent_id: parent_id).maximum(:position)
117
+ self.position = max ? max + 1 : 0
118
+ end
119
+ end
@@ -0,0 +1,182 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Comfy::Cms::Page < ActiveRecord::Base
4
+ self.table_name = 'comfy_cms_pages'
5
+
6
+ include Comfy::Cms::WithFragments
7
+ include Comfy::Cms::WithCategories
8
+
9
+ cms_acts_as_tree counter_cache: :children_count, order: :position
10
+ cms_has_revisions_for :fragments_attributes
11
+
12
+ attr_accessor :content
13
+
14
+ # -- Relationships -----------------------------------------------------------
15
+ belongs_to :site
16
+ belongs_to :target_page,
17
+ class_name: 'Comfy::Cms::Page',
18
+ optional: true
19
+
20
+ has_many :translations,
21
+ dependent: :destroy
22
+
23
+ # -- Callbacks ---------------------------------------------------------------
24
+ before_validation :assigns_label,
25
+ :assign_parent,
26
+ :escape_slug,
27
+ :assign_full_path
28
+ before_create :assign_position
29
+ after_save :sync_child_full_paths!
30
+ after_find :unescape_slug_and_path
31
+
32
+ # -- Validations -------------------------------------------------------------
33
+ validates :label,
34
+ presence: true
35
+ validates :slug,
36
+ presence: true,
37
+ uniqueness: { scope: :parent_id },
38
+ unless: ->(p) {
39
+ p.site && (!p.site.pages.exists? || p.site.pages.root == self)
40
+ }
41
+ validate :validate_target_page
42
+ validate :validate_format_of_unescaped_slug
43
+ validate :associations_belong_to_site
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..].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 associations_belong_to_site
111
+ return unless site
112
+
113
+ errors.add(:parent_id, :invalid) if parent && site_id != parent.site_id
114
+ errors.add(:layout_id, :invalid) if layout && site_id != layout.site_id
115
+ errors.add(:target_page_id, :invalid) if target_page && site_id != target_page.site_id
116
+ end
117
+
118
+ def assigns_label
119
+ self.label = label.blank? ? slug.try(:titleize) : label
120
+ end
121
+
122
+ def assign_parent
123
+ return unless site
124
+
125
+ self.parent ||= site.pages.root unless self == site.pages.root || site.pages.none?
126
+ end
127
+
128
+ def assign_full_path
129
+ self.full_path =
130
+ if parent
131
+ [CGI.escape(parent.full_path).gsub('%2F', '/'), slug].join('/').squeeze('/')
132
+ else
133
+ '/'
134
+ end
135
+ end
136
+
137
+ def assign_position
138
+ return unless parent
139
+ return if position.to_i.positive?
140
+
141
+ max = parent.children.maximum(:position)
142
+ self.position = max ? max + 1 : 0
143
+ end
144
+
145
+ def validate_target_page
146
+ return unless target_page
147
+
148
+ p = self
149
+ while p.target_page
150
+ if (p = p.target_page) == self
151
+ return errors.add(:target_page_id, 'Invalid Redirect')
152
+ end
153
+ end
154
+ end
155
+
156
+ def validate_format_of_unescaped_slug
157
+ return unless slug.present?
158
+
159
+ unescaped_slug = CGI.unescape(slug)
160
+ errors.add(:slug, :invalid) unless unescaped_slug =~ %r{^\p{Alnum}[.\p{Alnum}\p{Mark}_-]*$}i
161
+ end
162
+
163
+ # Forcing re-saves for child pages so they can update full_paths
164
+ def sync_child_full_paths!
165
+ return unless full_path_previously_changed?
166
+
167
+ children.each do |p|
168
+ p.update_attribute(:full_path, p.send(:assign_full_path))
169
+ end
170
+ end
171
+
172
+ # Escape slug unless it's nonexistent (root)
173
+ def escape_slug
174
+ self.slug = CGI.escape(slug) unless slug.nil?
175
+ end
176
+
177
+ # Unescape the slug and full path back into their original forms unless they're nonexistent
178
+ def unescape_slug_and_path
179
+ self.slug = CGI.unescape(slug) unless slug.nil?
180
+ self.full_path = CGI.unescape(full_path) unless full_path.nil?
181
+ end
182
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Comfy::Cms::Revision < ActiveRecord::Base
4
+ self.table_name = 'comfy_cms_revisions'
5
+
6
+ serialize :data, coder: Psych
7
+
8
+ # -- Relationships --------------------------------------------------------
9
+ belongs_to :record, polymorphic: true
10
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Comfy::Cms::Site < ActiveRecord::Base
4
+ self.table_name = 'comfy_cms_sites'
5
+
6
+ # -- Relationships -----------------------------------------------------------
7
+ with_options dependent: :destroy do |site|
8
+ site.has_many :layouts
9
+ site.has_many :pages
10
+ site.has_many :snippets
11
+ site.has_many :files
12
+ site.has_many :categories
13
+ end
14
+
15
+ # -- Callbacks ---------------------------------------------------------------
16
+ before_validation :assign_identifier,
17
+ :assign_hostname,
18
+ :assign_label,
19
+ :clean_path
20
+
21
+ # -- Validations -------------------------------------------------------------
22
+ validates :identifier,
23
+ presence: true,
24
+ uniqueness: true,
25
+ format: { with: %r{\A\w[a-z0-9_-]*\z}i }
26
+ validates :label,
27
+ presence: true
28
+ validates :hostname,
29
+ presence: true,
30
+ uniqueness: { scope: :path },
31
+ format: { with: %r{\A[\w.-]+(?::\d+)?\z} }
32
+
33
+ # -- Class Methods -----------------------------------------------------------
34
+ # returning the Comfy::Cms::Site instance based on host and path
35
+ def self.find_site(host, path = nil)
36
+ return Comfy::Cms::Site.first if Comfy::Cms::Site.one?
37
+
38
+ cms_site = nil
39
+
40
+ public_cms_path = ComfortableMediaSurfer.configuration.public_cms_path
41
+ if path && public_cms_path != '/'
42
+ path = path.sub(%r{\A#{public_cms_path}}, '')
43
+ end
44
+
45
+ Comfy::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 = ComfortableMediaSurfer.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 = ComfortableMediaSurfer.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
+ path.squeeze!('/')
90
+ path.gsub!(%r{/$}, '')
91
+ self.path = nil if path.blank?
92
+ end
93
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Comfy::Cms::Snippet < ActiveRecord::Base
4
+ self.table_name = 'comfy_cms_snippets'
5
+
6
+ include Comfy::Cms::WithCategories
7
+
8
+ cms_has_revisions_for :content
9
+
10
+ # -- Relationships -----------------------------------------------------------
11
+ belongs_to :site
12
+
13
+ # -- Callbacks ---------------------------------------------------------------
14
+ before_validation :assign_label
15
+ before_create :assign_position
16
+ after_save :clear_page_content_cache
17
+ after_destroy :clear_page_content_cache
18
+
19
+ # -- Validations -------------------------------------------------------------
20
+ validates :label,
21
+ presence: true
22
+ validates :identifier,
23
+ presence: true,
24
+ uniqueness: { scope: :site_id },
25
+ format: { with: %r{\A\w[a-z0-9_-]*\z}i }
26
+
27
+ protected
28
+
29
+ def assign_label
30
+ self.label = label.blank? ? identifier.try(:titleize) : label
31
+ end
32
+
33
+ # When snippet is changed or removed we need to blow away all page caches as
34
+ # we don't know where it was used.
35
+ def clear_page_content_cache
36
+ Comfy::Cms::Page.where(id: site.pages.pluck(:id)).update_all(content_cache: nil)
37
+ end
38
+
39
+ def assign_position
40
+ max = site.snippets.maximum(:position)
41
+ self.position = max ? max + 1 : 0
42
+ end
43
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Comfy::Cms::Translation < ActiveRecord::Base
4
+ self.table_name = 'comfy_cms_translations'
5
+
6
+ include Comfy::Cms::WithFragments
7
+
8
+ cms_has_revisions_for :fragments_attributes
9
+
10
+ delegate :site, to: :page
11
+
12
+ # -- Relationships -----------------------------------------------------------
13
+ belongs_to :page
14
+
15
+ # -- Callbacks ---------------------------------------------------------------
16
+ before_validation :assign_layout
17
+
18
+ # -- Scopes ------------------------------------------------------------------
19
+ scope :published, -> { where(is_published: true) }
20
+
21
+ # -- Validations -------------------------------------------------------------
22
+ validates :label,
23
+ presence: true
24
+
25
+ validates :locale,
26
+ presence: true,
27
+ uniqueness: { scope: :page_id }
28
+
29
+ validate :validate_locale
30
+ validate :layout_belongs_to_site
31
+
32
+ private
33
+
34
+ def layout_belongs_to_site
35
+ errors.add(:layout_id, :invalid) if page && layout && page.site_id != layout.site_id
36
+ end
37
+
38
+ def validate_locale
39
+ return unless page
40
+
41
+ errors.add(:locale) if locale == page.site.locale
42
+ end
43
+
44
+ def assign_layout
45
+ self.layout ||= page.layout if page.present?
46
+ end
47
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Comfy::Cms::WithCategories
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ has_many :categorizations,
8
+ as: :categorized,
9
+ class_name: 'Comfy::Cms::Categorization',
10
+ dependent: :destroy
11
+ has_many :categories,
12
+ through: :categorizations,
13
+ class_name: 'Comfy::Cms::Category'
14
+
15
+ attr_writer :category_ids
16
+
17
+ after_save :sync_categories
18
+
19
+ scope :for_category, ->(*categories) {
20
+ if (categories = [categories].flatten.compact).present?
21
+ distinct
22
+ .joins(categorizations: :category)
23
+ .where('comfy_cms_categories.label' => categories)
24
+ end
25
+ }
26
+ end
27
+
28
+ def category_ids
29
+ @category_ids ||= categories.pluck(:id)
30
+ end
31
+
32
+ def sync_categories
33
+ return unless category_ids.is_a?(Array)
34
+
35
+ scope = Comfy::Cms::Category.where(site_id: site_id).of_type(self.class.to_s)
36
+ existing_ids = scope.pluck(:id)
37
+
38
+ ids_to_add = category_ids.map(&:to_i)
39
+
40
+ # adding categorizations
41
+ ids_to_add.each do |id|
42
+ if (category = scope.find_by_id(id))
43
+ category.categorizations.create(categorized: self)
44
+ end
45
+ end
46
+
47
+ # removing categorizations
48
+ ids_to_remove = existing_ids - ids_to_add
49
+ categorizations.where(category_id: ids_to_remove).destroy_all
50
+ end
51
+ end