pageflow 17.1.0 → 17.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 (302) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +319 -345
  3. data/README.md +3 -4
  4. data/admins/pageflow/accounts.rb +6 -6
  5. data/admins/pageflow/entry.rb +10 -8
  6. data/admins/pageflow/membership.rb +11 -11
  7. data/admins/pageflow/translations.rb +5 -5
  8. data/app/assets/images/pageflow/admin/icons/comment.svg +1 -0
  9. data/app/assets/javascripts/pageflow/dist/ui.js +1350 -630
  10. data/app/assets/javascripts/pageflow/editor/vendor.js +0 -1
  11. data/app/assets/javascripts/pageflow/ui.js +0 -1
  12. data/app/assets/stylesheets/pageflow/admin/entry_comments_indicator.scss +45 -0
  13. data/app/assets/stylesheets/pageflow/admin/permalink_input.scss +8 -1
  14. data/app/assets/stylesheets/pageflow/admin.scss +1 -0
  15. data/app/assets/stylesheets/pageflow/animations/spin.scss +9 -0
  16. data/app/assets/stylesheets/pageflow/animations.scss +1 -0
  17. data/app/assets/stylesheets/pageflow/editor/background_positioning.scss +21 -5
  18. data/app/assets/stylesheets/pageflow/editor/base.scss +9 -1
  19. data/app/assets/stylesheets/pageflow/editor/drop_down_button.scss +13 -1
  20. data/app/assets/stylesheets/pageflow/editor/edit_configuration_view.scss +5 -0
  21. data/app/assets/stylesheets/pageflow/editor/file_folders.scss +127 -0
  22. data/app/assets/stylesheets/pageflow/editor/file_meta_data.scss +0 -1
  23. data/app/assets/stylesheets/pageflow/editor/file_meta_data_overlay.scss +45 -0
  24. data/app/assets/stylesheets/pageflow/editor/file_preview.scss +138 -0
  25. data/app/assets/stylesheets/pageflow/editor/file_references.scss +68 -0
  26. data/app/assets/stylesheets/pageflow/editor/file_settings_dialog.scss +1 -1
  27. data/app/assets/stylesheets/pageflow/editor/file_stage_icons.scss +17 -0
  28. data/app/assets/stylesheets/pageflow/editor/file_stages.scss +25 -32
  29. data/app/assets/stylesheets/pageflow/editor/file_thumbnails.scss +31 -48
  30. data/app/assets/stylesheets/pageflow/editor/file_type_pills.scss +73 -0
  31. data/app/assets/stylesheets/pageflow/editor/files.scss +230 -50
  32. data/app/assets/stylesheets/pageflow/editor/filtered_files.scss +142 -14
  33. data/app/assets/stylesheets/pageflow/editor/folder_breadcrumb.scss +40 -0
  34. data/app/assets/stylesheets/pageflow/editor/info_box.scss +12 -0
  35. data/app/assets/stylesheets/pageflow/editor/inputs/edit_defaults_button.scss +15 -0
  36. data/app/assets/stylesheets/pageflow/editor/inputs.scss +1 -0
  37. data/app/assets/stylesheets/pageflow/editor/list.scss +88 -38
  38. data/app/assets/stylesheets/pageflow/editor/manage_files.scss +9 -0
  39. data/app/assets/stylesheets/pageflow/editor/menu.scss +14 -0
  40. data/app/assets/stylesheets/pageflow/editor/move_to_folder_dialog.scss +83 -0
  41. data/app/assets/stylesheets/pageflow/mixins/pageflow.scss +9 -5
  42. data/app/assets/stylesheets/pageflow/ui/color_picker.scss +230 -0
  43. data/app/assets/stylesheets/pageflow/ui/forms.scss +34 -4
  44. data/app/assets/stylesheets/pageflow/ui/functions.scss +12 -2
  45. data/app/assets/stylesheets/pageflow/ui/input/color_input.scss +9 -16
  46. data/app/assets/stylesheets/pageflow/ui/input/text_area_input.scss +15 -0
  47. data/app/assets/stylesheets/pageflow/ui/overrides.scss +8 -0
  48. data/app/assets/stylesheets/pageflow/ui/properties.scss +29 -0
  49. data/app/assets/stylesheets/pageflow/ui/tabs_view.scss +8 -3
  50. data/app/assets/stylesheets/pageflow/ui.scss +4 -2
  51. data/app/controllers/pageflow/editor/entries_controller.rb +1 -0
  52. data/app/controllers/pageflow/editor/file_folders_controller.rb +80 -0
  53. data/app/controllers/pageflow/editor/file_import_controller.rb +1 -0
  54. data/app/controllers/pageflow/editor/files_controller.rb +15 -6
  55. data/app/controllers/pageflow/editor/oembeds_controller.rb +21 -0
  56. data/app/controllers/pageflow/entries_controller.rb +17 -13
  57. data/app/controllers/pageflow/review/comment_thread_reads_controller.rb +30 -0
  58. data/app/controllers/pageflow/review/comment_threads_controller.rb +60 -0
  59. data/app/controllers/pageflow/review/comments_controller.rb +53 -0
  60. data/app/controllers/pageflow/revisions_controller.rb +4 -1
  61. data/app/helpers/pageflow/admin/entries_helper.rb +40 -0
  62. data/app/helpers/pageflow/admin/widgets_helper.rb +2 -2
  63. data/app/helpers/pageflow/common_entry_seed_helper.rb +5 -3
  64. data/app/helpers/pageflow/editor/config_helper.rb +3 -1
  65. data/app/helpers/pageflow/entries_helper.rb +24 -42
  66. data/app/helpers/pageflow/entry_json_seed_helper.rb +4 -4
  67. data/app/helpers/pageflow/public_i18n_helper.rb +9 -8
  68. data/app/helpers/pageflow/structured_data_helper.rb +4 -1
  69. data/app/helpers/pageflow/themes_helper.rb +1 -3
  70. data/app/helpers/pageflow/widgets_helper.rb +2 -1
  71. data/app/inputs/pageflow_permalink_input.rb +6 -2
  72. data/app/models/concerns/pageflow/auto_generated_perma_id.rb +11 -4
  73. data/app/models/concerns/pageflow/reusable_file.rb +1 -1
  74. data/app/models/concerns/pageflow/serialization_blacklist.rb +2 -4
  75. data/app/models/concerns/pageflow/translated_attributes.rb +101 -0
  76. data/app/models/concerns/pageflow/uploadable_file.rb +1 -1
  77. data/app/models/pageflow/comment.rb +30 -0
  78. data/app/models/pageflow/comment_thread.rb +65 -0
  79. data/app/models/pageflow/comment_thread_read.rb +22 -0
  80. data/app/models/pageflow/customized_theme.rb +3 -2
  81. data/app/models/pageflow/draft_entry.rb +20 -8
  82. data/app/models/pageflow/entry.rb +2 -0
  83. data/app/models/pageflow/entry_at_revision.rb +9 -1
  84. data/app/models/pageflow/entry_comment_summary.rb +114 -0
  85. data/app/models/pageflow/file_folder.rb +74 -0
  86. data/app/models/pageflow/file_reuse.rb +3 -2
  87. data/app/models/pageflow/file_usage.rb +8 -0
  88. data/app/models/pageflow/image_file.rb +24 -20
  89. data/app/models/pageflow/image_file_url_templates.rb +3 -3
  90. data/app/models/pageflow/legal_info.rb +38 -0
  91. data/app/models/pageflow/nested_revision_component_copy.rb +1 -1
  92. data/app/models/pageflow/permalink.rb +1 -0
  93. data/app/models/pageflow/revision.rb +6 -2
  94. data/app/models/pageflow/site.rb +10 -0
  95. data/app/models/pageflow/used_file.rb +8 -2
  96. data/app/models/pageflow/widget.rb +2 -2
  97. data/app/views/pageflow/audio_files/_audio_file.json.jbuilder +0 -1
  98. data/app/views/pageflow/editor/config/_seeds.json.jbuilder +4 -2
  99. data/app/views/pageflow/editor/entries/_entry.json.jbuilder +2 -1
  100. data/app/views/pageflow/editor/entries/seed.json.erb +1 -0
  101. data/app/views/pageflow/editor/file_folders/_file_folder.json.jbuilder +5 -0
  102. data/app/views/pageflow/editor/file_folders/create.json.jbuilder +1 -0
  103. data/app/views/pageflow/editor/file_folders/index.json.jbuilder +3 -0
  104. data/app/views/pageflow/editor/files/_file.json.jbuilder +2 -1
  105. data/app/views/pageflow/editor/image_files/_image_file.json.jbuilder +3 -0
  106. data/app/views/pageflow/editor/video_files/_video_file.json.jbuilder +3 -0
  107. data/app/views/pageflow/files/_file.json.jbuilder +2 -0
  108. data/app/views/pageflow/image_files/_image_file.json.jbuilder +0 -1
  109. data/app/views/pageflow/review/comment_threads/_comment_thread.json.jbuilder +20 -0
  110. data/app/views/pageflow/review/comment_threads/create.json.jbuilder +2 -0
  111. data/app/views/pageflow/review/comment_threads/index.json.jbuilder +13 -0
  112. data/app/views/pageflow/review/comments/_comment.json.jbuilder +13 -0
  113. data/app/views/pageflow/review/comments/create.json.jbuilder +1 -0
  114. data/app/views/pageflow/structured_data/_entry.json.jbuilder +2 -3
  115. data/app/views/pageflow/video_files/_video_file.json.jbuilder +0 -1
  116. data/config/locales/de.yml +152 -14
  117. data/config/locales/en.yml +150 -14
  118. data/config/routes.rb +14 -0
  119. data/db/migrate/20140418225525_setup_schema.rb +2 -2
  120. data/db/migrate/20251015000000_add_structured_data_type_name_to_revisions.rb +5 -0
  121. data/db/migrate/20251029142849_convert_legacy_social_embeds_to_unified_social_embed.rb +38 -0
  122. data/db/migrate/20251119083347_fix_main_storyline_positions.rb +16 -0
  123. data/db/migrate/20251119083348_ensure_scrolled_entries_have_excursion_storyline.rb +15 -0
  124. data/db/migrate/20260303000000_add_layout_version_to_revisions.rb +6 -0
  125. data/db/migrate/20260323000000_create_comment_threads_and_comments.rb +30 -0
  126. data/db/migrate/20260414000000_add_subject_range_to_comment_threads.rb +5 -0
  127. data/db/migrate/20260415000000_add_section_perma_id_to_comment_threads.rb +5 -0
  128. data/db/migrate/20260727000000_add_quote_to_comments.rb +5 -0
  129. data/db/migrate/20260728000000_create_file_folders.rb +17 -0
  130. data/db/migrate/20260805000000_add_attribute_translations_to_sites.rb +5 -0
  131. data/db/migrate/20260806000000_add_edited_at_to_comments.rb +5 -0
  132. data/db/migrate/20260810000000_create_pageflow_scrolled_lottie_files.rb +24 -0
  133. data/db/migrate/20260817000000_create_comment_thread_reads.rb +16 -0
  134. data/db/migrate/20260819000000_add_unread_comments_since_at_to_users.rb +18 -0
  135. data/entry_types/paged/app/assets/javascripts/pageflow_paged/dist/editor.js +8665 -3890
  136. data/entry_types/paged/app/assets/javascripts/pageflow_paged/dist/frontend.js +979 -3571
  137. data/entry_types/paged/app/assets/javascripts/pageflow_paged/dist/react-client.js +20199 -28120
  138. data/entry_types/paged/app/assets/javascripts/pageflow_paged/dist/react-server.js +18652 -26584
  139. data/entry_types/scrolled/app/assets/stylesheets/pageflow_scrolled/ui.scss +2 -0
  140. data/entry_types/scrolled/app/controllers/pageflow_scrolled/editor/content_elements_controller.rb +33 -3
  141. data/entry_types/scrolled/app/controllers/pageflow_scrolled/editor/sections_controller.rb +2 -3
  142. data/entry_types/scrolled/app/controllers/pageflow_scrolled/entries_controller.rb +21 -1
  143. data/entry_types/scrolled/app/helpers/pageflow_scrolled/cache_helper.rb +3 -3
  144. data/entry_types/scrolled/app/helpers/pageflow_scrolled/editor/entry_json_seed_helper.rb +7 -5
  145. data/entry_types/scrolled/app/helpers/pageflow_scrolled/editor/seed_html_helper.rb +5 -2
  146. data/entry_types/scrolled/app/helpers/pageflow_scrolled/entry_json_seed_helper.rb +13 -5
  147. data/entry_types/scrolled/app/helpers/pageflow_scrolled/i18n_helper.rb +28 -13
  148. data/entry_types/scrolled/app/helpers/pageflow_scrolled/packs_helper.rb +26 -11
  149. data/entry_types/scrolled/app/helpers/pageflow_scrolled/react_server_side_rendering_helper.rb +2 -2
  150. data/entry_types/scrolled/app/helpers/pageflow_scrolled/sprockets_helper.rb +16 -0
  151. data/entry_types/scrolled/app/helpers/pageflow_scrolled/themes_helper.rb +174 -3
  152. data/entry_types/scrolled/app/models/pageflow_scrolled/content_element.rb +49 -7
  153. data/entry_types/scrolled/app/models/pageflow_scrolled/lottie_file.rb +6 -0
  154. data/entry_types/scrolled/app/models/pageflow_scrolled/lottie_file_url_templates.rb +16 -0
  155. data/entry_types/scrolled/app/models/pageflow_scrolled/storyline.rb +1 -0
  156. data/entry_types/scrolled/app/views/pageflow_scrolled/entries/show.html.erb +13 -12
  157. data/entry_types/scrolled/app/views/pageflow_scrolled/entry_json_seed/_entry.json.jbuilder +30 -10
  158. data/entry_types/scrolled/app/views/pageflow_scrolled/entry_json_seed/_theme.json.jbuilder +9 -0
  159. data/entry_types/scrolled/app/views/pageflow_scrolled/favicons/_entry.html.erb +1 -1
  160. data/entry_types/scrolled/config/configuration_schemas/pageflow-scrolled.json +414 -0
  161. data/entry_types/scrolled/config/locales/de.yml +512 -65
  162. data/entry_types/scrolled/config/locales/en.yml +518 -52
  163. data/entry_types/scrolled/lib/pageflow_scrolled/additional_seed_data.rb +2 -2
  164. data/entry_types/scrolled/lib/pageflow_scrolled/configuration.rb +51 -6
  165. data/entry_types/scrolled/lib/pageflow_scrolled/configuration_schemas.rb +91 -0
  166. data/entry_types/scrolled/lib/pageflow_scrolled/engine.rb +2 -1
  167. data/entry_types/scrolled/lib/pageflow_scrolled/entry_file_reference_locations.rb +71 -0
  168. data/entry_types/scrolled/lib/pageflow_scrolled/entry_structured_data_types/faq_page.rb +58 -0
  169. data/entry_types/scrolled/lib/pageflow_scrolled/file_reference_locations.rb +69 -0
  170. data/entry_types/scrolled/lib/pageflow_scrolled/plugin.rb +205 -22
  171. data/entry_types/scrolled/lib/pageflow_scrolled/react_widget_type.rb +2 -4
  172. data/entry_types/scrolled/lib/pageflow_scrolled/seeds.rb +56 -36
  173. data/entry_types/scrolled/lib/pageflow_scrolled/theme_options_default_scale.rb +35 -0
  174. data/entry_types/scrolled/lib/pageflow_scrolled.rb +13 -0
  175. data/entry_types/scrolled/lib/tasks/pageflow_scrolled/storybook.rake +19 -1
  176. data/entry_types/scrolled/package/config/configurationSchemas.js +70 -0
  177. data/entry_types/scrolled/package/config/rollup/configurationSchemas.js +19 -0
  178. data/entry_types/scrolled/package/config/webpack.js +72 -0
  179. data/entry_types/scrolled/package/contentElements/hotspots-frontend.css +1 -1
  180. data/entry_types/scrolled/package/contentElements/hotspots-frontend.js +1181 -915
  181. data/entry_types/scrolled/package/contentElements/inlineBeforeAfter-frontend.css +1 -0
  182. data/entry_types/scrolled/package/contentElements/inlineBeforeAfter-frontend.js +219 -0
  183. data/entry_types/scrolled/package/contentElements/lottieAnimation-frontend.css +1 -0
  184. data/entry_types/scrolled/package/contentElements/lottieAnimation-frontend.js +244 -0
  185. data/entry_types/scrolled/package/contentElements/socialEmbed-frontend.css +1 -0
  186. data/entry_types/scrolled/package/contentElements/socialEmbed-frontend.js +823 -0
  187. data/entry_types/scrolled/package/contentElements/tikTokEmbed-frontend.js +71 -21
  188. data/entry_types/scrolled/package/contentElements/twitterEmbed-frontend.css +1 -0
  189. data/entry_types/scrolled/package/contentElements/twitterEmbed-frontend.js +277 -0
  190. data/entry_types/scrolled/package/contentElements/videoEmbed-frontend.css +1 -0
  191. data/entry_types/scrolled/package/contentElements/videoEmbed-frontend.js +244 -0
  192. data/entry_types/scrolled/package/contentElements-editor.js +1915 -1097
  193. data/entry_types/scrolled/package/contentElements-frontend.css +1 -1
  194. data/entry_types/scrolled/package/contentElements-frontend.js +3093 -1679
  195. data/entry_types/scrolled/package/editor.css +1 -0
  196. data/entry_types/scrolled/package/editor.js +8667 -4077
  197. data/entry_types/scrolled/package/entryState.js +1600 -0
  198. data/entry_types/scrolled/package/frontend/FloatingPortalRootProvider-20c600de.js +1979 -0
  199. data/entry_types/scrolled/package/frontend/FloatingPortalRootProvider-58189220.js +2538 -0
  200. data/entry_types/scrolled/package/frontend/{PhonePlatformContext-9f76033e.js → PhonePlatformContext-035a99fa.js} +7 -12
  201. data/entry_types/scrolled/package/frontend/PhonePlatformContext-9da43797.js +33 -0
  202. data/entry_types/scrolled/package/frontend/Placeholder.module-bf0966b4.js +5267 -0
  203. data/entry_types/scrolled/package/frontend/Placeholder.module-d22a9335.js +4180 -0
  204. data/entry_types/scrolled/package/frontend/ThemeIcon-ab834849.js +754 -0
  205. data/entry_types/scrolled/package/frontend/ThemeIcon-f6cb3a8f.js +773 -0
  206. data/entry_types/scrolled/package/frontend/{ToggleFullscreenCornerButton-788e9cdb.js → ToggleFullscreenCornerButton-30fd1124.js} +6 -6
  207. data/entry_types/scrolled/package/frontend/ToggleFullscreenCornerButton-61c25224.js +72 -0
  208. data/entry_types/scrolled/package/frontend/Viewer-1074fd59.js +147 -0
  209. data/entry_types/scrolled/package/frontend/Viewer-1482ca34.js +480 -0
  210. data/entry_types/scrolled/package/frontend/Viewer-70504231.js +210 -0
  211. data/entry_types/scrolled/package/frontend/{Viewer-0345ce57.js → Viewer-cc8d4072.js} +84 -92
  212. data/entry_types/scrolled/package/frontend/Wavesurfer-656e3c36.js +302 -0
  213. data/entry_types/scrolled/package/frontend/Wavesurfer-b753b838.js +385 -0
  214. data/entry_types/scrolled/package/frontend/commenting.css +1 -0
  215. data/entry_types/scrolled/package/frontend/extensions-167ae143.js +1455 -0
  216. data/entry_types/scrolled/package/frontend/extensions-2318f92b.js +5918 -0
  217. data/entry_types/scrolled/package/frontend/extensions-a461fd13.js +4890 -0
  218. data/entry_types/scrolled/package/frontend/extensions-c98269fd.js +1982 -0
  219. data/entry_types/scrolled/package/frontend/i18n-493cd2a6.js +93 -0
  220. data/entry_types/scrolled/package/frontend/i18n-e06cac14.js +100 -0
  221. data/entry_types/scrolled/package/frontend/{index-eb670c2f.js → index-2e5546f7.js} +36 -44
  222. data/entry_types/scrolled/package/frontend/index-de2b418d.js +130 -0
  223. data/entry_types/scrolled/package/frontend/index.css +1 -1
  224. data/entry_types/scrolled/package/frontend/index.js +1870 -875
  225. data/entry_types/scrolled/package/frontend/inlineEditing.css +1 -0
  226. data/entry_types/scrolled/package/frontend/useContentElementEditorState-a084912e.js +25 -0
  227. data/entry_types/scrolled/package/frontend/useDelayedBoolean-a387d85b.js +24 -0
  228. data/entry_types/scrolled/package/frontend/{usePhonePlatform-c2ba875d.js → usePhonePlatform-4ecb96d4.js} +1 -1
  229. data/entry_types/scrolled/package/frontend/usePhonePlatform-ce9ff08d.js +8 -0
  230. data/entry_types/scrolled/package/frontend-server.js +20 -209
  231. data/entry_types/scrolled/package/package.json +5 -3
  232. data/entry_types/scrolled/package/review.css +1 -0
  233. data/entry_types/scrolled/package/review.js +3306 -0
  234. data/entry_types/scrolled/package/testHelpers.js +836 -359
  235. data/entry_types/scrolled/package/widgets/consentBar.css +1 -1
  236. data/entry_types/scrolled/package/widgets/consentBar.js +269 -290
  237. data/entry_types/scrolled/package/widgets/defaultNavigation.css +2 -2
  238. data/entry_types/scrolled/package/widgets/defaultNavigation.js +911 -352
  239. data/entry_types/scrolled/package/widgets/excursionSheet.css +1 -1
  240. data/entry_types/scrolled/package/widgets/excursionSheet.js +87 -124
  241. data/entry_types/scrolled/package/widgets/iconInlineFileRights.css +1 -1
  242. data/entry_types/scrolled/package/widgets/iconInlineFileRights.js +62 -29
  243. data/entry_types/scrolled/package/widgets/iconScrollIndicator.js +32 -13
  244. data/entry_types/scrolled/package/widgets/mainStorylineSheet.css +1 -1
  245. data/entry_types/scrolled/package/widgets/mainStorylineSheet.js +56 -100
  246. data/entry_types/scrolled/package/widgets/textInlineFileRights.css +1 -1
  247. data/entry_types/scrolled/package/widgets/textInlineFileRights.js +33 -28
  248. data/entry_types/scrolled/package/widgets-server.js +1 -0
  249. data/entry_types/scrolled/spec/factories/lottie_files.rb +29 -0
  250. data/entry_types/scrolled/spec/fixtures/animation.lottie +0 -0
  251. data/entry_types/scrolled/spec/fixtures/font.woff +0 -0
  252. data/entry_types/scrolled/spec/fixtures/font.woff2 +0 -0
  253. data/lib/generators/pageflow/resque/resque_generator.rb +0 -2
  254. data/lib/pageflow/configuration.rb +49 -7
  255. data/lib/pageflow/deprecation.rb +19 -0
  256. data/lib/pageflow/engine.rb +1 -1
  257. data/lib/pageflow/entries_controller_env_helper.rb +32 -2
  258. data/lib/pageflow/entry_export_import/entry_serialization.rb +4 -4
  259. data/lib/pageflow/entry_export_import/page_type_versions.rb +2 -2
  260. data/lib/pageflow/entry_export_import/revision_serialization/import.rb +10 -0
  261. data/lib/pageflow/entry_export_import/revision_serialization.rb +3 -0
  262. data/lib/pageflow/entry_structured_data_types.rb +43 -0
  263. data/lib/pageflow/entry_type_configuration.rb +1 -0
  264. data/lib/pageflow/file_type.rb +2 -2
  265. data/lib/pageflow/file_types.rb +1 -1
  266. data/lib/pageflow/oembed_providers.rb +42 -0
  267. data/lib/pageflow/primary_domain_entry_redirect.rb +6 -0
  268. data/lib/pageflow/rails_version.rb +13 -2
  269. data/lib/pageflow/themes.rb +41 -0
  270. data/lib/pageflow/user_mixin.rb +12 -0
  271. data/lib/pageflow/version.rb +1 -1
  272. data/lib/pageflow.rb +1 -0
  273. data/package/config/eslint-rules/documented-in-toc.js +125 -0
  274. data/package/config/jest/index.js +0 -1
  275. data/package/config/webpack.js +0 -1
  276. data/package/config/webpack5.js +0 -1
  277. data/package/editor.js +4378 -1897
  278. data/package/frontend.js +647 -886
  279. data/package/package.json +2 -1
  280. data/package/review.js +384 -0
  281. data/package/testHelpers.js +248 -188
  282. data/package/ui.js +1346 -630
  283. data/package/vendor/stubs/wysihtml5.js +2 -0
  284. data/spec/factories/comment_thread_reads.rb +10 -0
  285. data/spec/factories/comment_threads.rb +10 -0
  286. data/spec/factories/comments.rb +9 -0
  287. data/spec/factories/file_folders.rb +8 -0
  288. data/spec/factories/published_entries.rb +2 -0
  289. metadata +188 -67
  290. data/app/assets/stylesheets/pageflow/editor/select_button.scss +0 -51
  291. data/entry_types/scrolled/app/helpers/pageflow_scrolled/generated_media_queries_helper.rb +0 -55
  292. data/entry_types/scrolled/package/frontend/FloatingPortalRootProvider-51914be7.js +0 -5439
  293. data/entry_types/scrolled/package/frontend/ThemeIcon-81f2f066.js +0 -1932
  294. data/entry_types/scrolled/package/frontend/Viewer-cdc549cc.js +0 -147
  295. data/entry_types/scrolled/package/frontend/Wavesurfer-1cdc3925.js +0 -313
  296. data/entry_types/scrolled/package/frontend/components-96660ffd.js +0 -3803
  297. data/entry_types/scrolled/package/frontend/inherits-539844a6.js +0 -99
  298. data/entry_types/scrolled/package/frontend/useContentElementEditorState-77fe6c79.js +0 -51
  299. data/entry_types/scrolled/package/widgets/excursionDialog.css +0 -1
  300. data/entry_types/scrolled/package/widgets/excursionDialog.js +0 -109
  301. data/lib/generators/pageflow/resque/templates/resque_enqueue_after_commit_patch.rb +0 -25
  302. data/package/vendor/jquery.minicolors.js +0 -1108
@@ -0,0 +1,30 @@
1
+ module Pageflow
2
+ # @api private
3
+ class Comment < ApplicationRecord
4
+ include NestedRevisionComponent
5
+
6
+ # Quotes are recorded by the client, so cut rather than reject: an
7
+ # unexpectedly long selection must not keep a comment from being saved.
8
+ # Kept in sync with the limit quote extraction applies in
9
+ # entry_types/scrolled/package/src/review/subjectQuote.js, so that a
10
+ # recorded quote stays comparable to the text as it reads later on.
11
+ QUOTE_LIMIT = 4_000
12
+
13
+ belongs_to :comment_thread
14
+ belongs_to :creator, class_name: 'User'
15
+
16
+ validates :body, presence: true
17
+
18
+ before_validation :truncate_quote
19
+
20
+ def entry_for_auto_generated_perma_id
21
+ comment_thread.revision.entry
22
+ end
23
+
24
+ private
25
+
26
+ def truncate_quote
27
+ self.quote = quote[0, QUOTE_LIMIT] if quote
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,65 @@
1
+ module Pageflow
2
+ # @api private
3
+ class CommentThread < ApplicationRecord
4
+ include RevisionComponent
5
+
6
+ serialize :subject_range, coder: JSON
7
+
8
+ belongs_to :creator, class_name: 'User'
9
+ belongs_to :resolver, class_name: 'User', foreign_key: :resolved_by_id, optional: true
10
+ # Ordered because clients read position as meaning: the first comment
11
+ # is the topic, the rest are replies.
12
+ has_many :comments, -> { order(:id) }, dependent: :destroy
13
+
14
+ nested_revision_components :comments
15
+
16
+ validates :subject_type, :subject_id, presence: true
17
+
18
+ def resolve(user)
19
+ update!(resolved_at: Time.current, resolver: user) unless resolved_at
20
+ end
21
+
22
+ def unresolve
23
+ update!(resolved_at: nil, resolver: nil)
24
+ end
25
+
26
+ def self.migrate_to_subject(revision:, subject_type:, subject_id:, thread_ids:)
27
+ return if thread_ids.blank?
28
+
29
+ where(revision_id: revision.id,
30
+ subject_type:,
31
+ id: thread_ids).update_all(subject_id:, updated_at: Time.current)
32
+ end
33
+
34
+ def self.update_subject_ranges_for(revision:, subject_type:, subject_id:, ranges:)
35
+ return if ranges.blank?
36
+
37
+ where(revision_id: revision.id,
38
+ subject_type:, subject_id:,
39
+ id: ranges.keys).update_all(
40
+ sanitize_sql_array(
41
+ ["subject_range = #{subject_range_case_sql(ranges)}, updated_at = ?",
42
+ Time.current]
43
+ )
44
+ )
45
+ end
46
+
47
+ def self.update_section_perma_id_for(revision:, subject_type:, subject_id:, section_perma_id:)
48
+ return if subject_id.blank?
49
+
50
+ where(revision_id: revision.id,
51
+ subject_type:,
52
+ subject_id:)
53
+ .where.not(section_perma_id:)
54
+ .update_all(section_perma_id:, updated_at: Time.current)
55
+ end
56
+
57
+ def self.subject_range_case_sql(ranges)
58
+ when_clauses = ranges.map do |id, range|
59
+ sanitize_sql_array(['WHEN ? THEN ?', id.to_i, range.to_json])
60
+ end
61
+ "CASE id #{when_clauses.join(' ')} END"
62
+ end
63
+ private_class_method :subject_range_case_sql
64
+ end
65
+ end
@@ -0,0 +1,22 @@
1
+ module Pageflow
2
+ # Records when a user last read a comment thread. Keyed by perma id
3
+ # so read state survives comment threads being copied to a new
4
+ # revision.
5
+ #
6
+ # @api private
7
+ class CommentThreadRead < ApplicationRecord
8
+ belongs_to :entry
9
+ belongs_to :user
10
+
11
+ def self.read_at_by_perma_id(entry:, user:)
12
+ where(entry:, user:).pluck(:comment_thread_perma_id, :read_at).to_h
13
+ end
14
+
15
+ def self.mark(entry:, user:, comment_thread_perma_ids:, read_at: Time.current)
16
+ comment_thread_perma_ids.each do |perma_id|
17
+ find_or_initialize_by(entry:, user:, comment_thread_perma_id: perma_id)
18
+ .update!(read_at:)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -1,9 +1,9 @@
1
1
  module Pageflow
2
2
  # @api private
3
3
  class CustomizedTheme < SimpleDelegator
4
- def initialize(theme, overrides, files)
4
+ def initialize(theme, transformed_options, overrides, files)
5
5
  super(theme)
6
- @options = __getobj__.options.deep_merge(overrides || {})
6
+ @options = transformed_options.deep_merge(overrides || {})
7
7
  @files = files
8
8
  end
9
9
 
@@ -24,6 +24,7 @@ module Pageflow
24
24
  config = Pageflow.config_for(entry)
25
25
 
26
26
  new(theme,
27
+ config.themes.apply_default_options(theme.options, entry:),
27
28
  config.transform_theme_customization_overrides.call(
28
29
  theme_customization.overrides,
29
30
  entry:,
@@ -31,15 +31,18 @@ module Pageflow
31
31
  def create_file!(file_type, attributes)
32
32
  check_foreign_key_custom_attributes(file_type.custom_attributes, attributes)
33
33
 
34
- file = file_type.model.create!(
35
- attributes
36
- .except(:configuration, :display_name)
37
- .merge(file_name: generate_file_name(attributes[:display_name]))
38
- ) { |f| f.entry = entry }
34
+ file_attributes = attributes.except(:configuration, :display_name, :folder_perma_id)
35
+
36
+ if file_name_attribute?(file_type)
37
+ file_attributes[:file_name] = generate_file_name(attributes[:display_name])
38
+ end
39
+
40
+ file = file_type.model.create!(file_attributes) { |f| f.entry = entry }
39
41
 
40
42
  usage = revision.file_usages.create!(file:,
41
43
  configuration: attributes[:configuration],
42
- display_name: attributes[:display_name])
44
+ display_name: attributes[:display_name],
45
+ folder_perma_id: attributes[:folder_perma_id])
43
46
  UsedFile.new(file, usage)
44
47
  end
45
48
 
@@ -59,10 +62,15 @@ module Pageflow
59
62
  file.destroy if file.usages.reload.empty?
60
63
  end
61
64
 
62
- def use_file(file)
65
+ def create_folder!(attributes)
66
+ draft.file_folders.create!(attributes)
67
+ end
68
+
69
+ def use_file(file, folder_perma_id: nil)
63
70
  draft.file_usages.create!(file: file.to_model,
64
71
  configuration: file.configuration,
65
- display_name: file.display_name)
72
+ display_name: file.display_name,
73
+ folder_perma_id:)
66
74
  end
67
75
 
68
76
  def save!
@@ -115,6 +123,10 @@ module Pageflow
115
123
  end
116
124
  end
117
125
 
126
+ def file_name_attribute?(file_type)
127
+ file_type.model.new.respond_to?(:file_name=)
128
+ end
129
+
118
130
  def generate_file_name(file_name)
119
131
  return nil if file_name.blank?
120
132
 
@@ -33,6 +33,8 @@ module Pageflow
33
33
 
34
34
  has_many :imports, class_name: 'Pageflow::FileImport', dependent: :destroy
35
35
 
36
+ has_many :comment_thread_reads, dependent: :destroy
37
+
36
38
  has_one :draft, -> { editable }, class_name: 'Revision', inverse_of: :entry
37
39
  has_one :published_revision, -> { published }, class_name: 'Revision', inverse_of: :entry
38
40
 
@@ -26,18 +26,22 @@ module Pageflow
26
26
  :type_name,
27
27
  to: :entry)
28
28
 
29
- delegate(:title, :summary, :credits,
29
+ delegate(:layout_version,
30
+ :title, :summary, :credits,
30
31
  :widgets,
31
32
  :storylines, :main_storyline_chapters, :chapters, :pages,
32
33
  :share_url, :share_image_id, :share_image_x, :share_image_y,
33
34
  :share_providers, :active_share_providers,
34
35
  :find_files, :find_file, :find_file_by_perma_id,
36
+ :file_folders,
35
37
  :image_files, :video_files, :audio_files,
36
38
  :locale,
37
39
  :author, :publisher, :keywords,
38
40
  :published_at,
39
41
  :noindex?,
42
+ :comment_threads,
40
43
  :configuration,
44
+ :structured_data_type_name,
41
45
  to: :revision)
42
46
 
43
47
  def resolve_widgets(options = {})
@@ -48,6 +52,10 @@ module Pageflow
48
52
  @theme ||= CustomizedTheme.find(entry: self, theme: revision.theme)
49
53
  end
50
54
 
55
+ def legal_info
56
+ @legal_info ||= site.legal_info(locale:)
57
+ end
58
+
51
59
  def home_button
52
60
  HomeButton.new(revision, site)
53
61
  end
@@ -0,0 +1,114 @@
1
+ module Pageflow
2
+ # Counts of comment topics and of activity the user has not seen, for
3
+ # displaying an indicator next to an entry in lists of entries.
4
+ #
5
+ # Built for a whole page of entries at once: rendering a list must not
6
+ # query per row.
7
+ #
8
+ # @api private
9
+ class EntryCommentSummary
10
+ attr_reader :topic_count, :unread_topic_count, :unread_reply_count, :unread_resolution_count
11
+
12
+ def self.for_entries(entries, user:)
13
+ entries = entries.to_a
14
+ return {} if entries.empty?
15
+
16
+ threads = threads_by_entry_id(entries)
17
+ read_at = read_at_by_entry_id(entries, user)
18
+
19
+ entries.to_h do |entry|
20
+ [entry.id, build(threads.fetch(entry.id, []),
21
+ read_at: read_at.fetch(entry.id, {}),
22
+ user:)]
23
+ end
24
+ end
25
+
26
+ def initialize(topic_count:, unread_topic_count:, unread_reply_count:,
27
+ unread_resolution_count: 0)
28
+ @topic_count = topic_count
29
+ @unread_topic_count = unread_topic_count
30
+ @unread_reply_count = unread_reply_count
31
+ @unread_resolution_count = unread_resolution_count
32
+ end
33
+
34
+ # Unread activity shows even where no topic is left open: the last
35
+ # one being resolved is exactly what the user should not miss.
36
+ def any?
37
+ topic_count.positive? || unread?
38
+ end
39
+
40
+ def unread?
41
+ unread_topic_count.positive? ||
42
+ unread_reply_count.positive? ||
43
+ unread_resolution_count.positive?
44
+ end
45
+
46
+ # Comment threads live on the draft revision, so entries are reached
47
+ # through their editable revision rather than directly. Resolved ones
48
+ # come along: somebody resolving a thread is activity of its own.
49
+ def self.threads_by_entry_id(entries)
50
+ entry_id_by_revision_id =
51
+ Revision.editable.where(entry_id: entries.map(&:id)).pluck(:id, :entry_id).to_h
52
+
53
+ CommentThread
54
+ .where(revision_id: entry_id_by_revision_id.keys)
55
+ .includes(:comments)
56
+ .group_by { |thread| entry_id_by_revision_id[thread.revision_id] }
57
+ end
58
+ private_class_method :threads_by_entry_id
59
+
60
+ def self.read_at_by_entry_id(entries, user)
61
+ CommentThreadRead
62
+ .where(user:, entry_id: entries.map(&:id))
63
+ .pluck(:entry_id, :comment_thread_perma_id, :read_at)
64
+ .group_by(&:first)
65
+ .transform_values do |rows|
66
+ rows.to_h { |(_entry_id, perma_id, read_at)| [perma_id, read_at] }
67
+ end
68
+ end
69
+ private_class_method :read_at_by_entry_id
70
+
71
+ def self.build(threads, read_at:, user:)
72
+ unread = threads.map { |thread| unread_activity(thread, read_at:, user:) }
73
+
74
+ new(topic_count: threads.count { |thread| thread.resolved_at.nil? },
75
+ unread_topic_count: unread.count { |kinds| kinds.include?(:topic) },
76
+ unread_reply_count: unread.sum { |kinds| kinds.count(:reply) },
77
+ unread_resolution_count: unread.count { |kinds| kinds.include?(:resolution) })
78
+ end
79
+ private_class_method :build
80
+
81
+ # What the user has not seen in a thread, as one symbol per event.
82
+ # The resolution goes by the thread's read mark like the comments do,
83
+ # having none of its own.
84
+ def self.unread_activity(thread, read_at:, user:)
85
+ seen_up_to = [read_at[thread.perma_id], user.unread_comments_since_at].compact.max
86
+ first, *replies = thread.comments.sort_by(&:id)
87
+
88
+ unread_replies = replies.count do |reply|
89
+ unread?(reply.creator_id, reply.created_at, seen_up_to, user)
90
+ end
91
+
92
+ kinds = Array.new(unread_replies, :reply)
93
+ kinds << :topic if first && unread?(first.creator_id, first.created_at, seen_up_to, user)
94
+ kinds << :resolution if unread_resolution?(thread, seen_up_to, user)
95
+ kinds
96
+ end
97
+ private_class_method :unread_activity
98
+
99
+ def self.unread_resolution?(thread, seen_up_to, user)
100
+ thread.resolved_at &&
101
+ unread?(thread.resolved_by_id, thread.resolved_at, seen_up_to, user)
102
+ end
103
+ private_class_method :unread_resolution?
104
+
105
+ # Mirrors the unread rule of the review interface: the user's own
106
+ # activity never counts, and neither does anything from before their
107
+ # baseline. Kept in sync with isUnread in
108
+ # entry_types/scrolled/package/src/review/unreadActivity.js.
109
+ def self.unread?(creator_id, created_at, seen_up_to, user)
110
+ creator_id != user.id && (seen_up_to.nil? || created_at > seen_up_to)
111
+ end
112
+ private_class_method :unread?
113
+ end
114
+ end
@@ -0,0 +1,74 @@
1
+ module Pageflow
2
+ # Groups the file usages of a revision. Folders refer to their parent
3
+ # folder by perma id since row ids would not survive copying a
4
+ # revision.
5
+ #
6
+ # @api private
7
+ class FileFolder < ApplicationRecord
8
+ include RevisionComponent
9
+
10
+ validates :name, presence: true
11
+ validate :parent_belongs_to_revision
12
+ validate :parent_is_not_nested_inside_folder
13
+
14
+ def parent
15
+ return if parent_folder_perma_id.blank?
16
+
17
+ folders_of_revision.find_by(perma_id: parent_folder_perma_id)
18
+ end
19
+
20
+ # Broken data with a folder nested inside one of its own descendants
21
+ # would make walking up the tree loop forever.
22
+ def ancestors
23
+ result = []
24
+ current = parent
25
+
26
+ while current && result.exclude?(current)
27
+ result.unshift(current)
28
+ current = current.parent
29
+ end
30
+
31
+ result
32
+ end
33
+
34
+ def children
35
+ folders_of_revision.where(parent_folder_perma_id: perma_id)
36
+ end
37
+
38
+ def file_usages
39
+ revision.file_usages.where(folder_perma_id: perma_id)
40
+ end
41
+
42
+ def empty?
43
+ children.none? && file_usages.none?
44
+ end
45
+
46
+ private
47
+
48
+ # A folder from another revision would leave this folder unreachable
49
+ # in any folder tree.
50
+ def parent_belongs_to_revision
51
+ return if parent_folder_perma_id.blank? || revision.blank?
52
+ return if folders_of_revision.exists?(perma_id: parent_folder_perma_id)
53
+
54
+ errors.add(:parent_folder_perma_id, 'unknown folder')
55
+ end
56
+
57
+ # Nesting a folder inside itself or inside one of its own subfolders
58
+ # would detach the whole subtree from the folder tree.
59
+ def parent_is_not_nested_inside_folder
60
+ return if parent_folder_perma_id.blank? || revision.blank?
61
+
62
+ parent_folder = parent
63
+
64
+ return if parent_folder.blank?
65
+ return if [parent_folder, *parent_folder.ancestors].exclude?(self)
66
+
67
+ errors.add(:parent_folder_perma_id, 'nested inside folder')
68
+ end
69
+
70
+ def folders_of_revision
71
+ FileFolder.all_for_revision(revision)
72
+ end
73
+ end
74
+ end
@@ -2,15 +2,16 @@ module Pageflow
2
2
  class FileReuse # rubocop:todo Style/Documentation
3
3
  attr_accessor :source_entry, :destination_entry, :file_type, :file
4
4
 
5
- def initialize(destination_entry, source_entry, file_type, file_id)
5
+ def initialize(destination_entry, source_entry, file_type, file_id, folder_perma_id: nil)
6
6
  @source_entry = source_entry
7
7
  @destination_entry = destination_entry
8
8
  @file_type = file_type
9
9
  @file = source_entry.find_file(file_type.model, file_id)
10
+ @folder_perma_id = folder_perma_id
10
11
  end
11
12
 
12
13
  def save! # rubocop:todo Metrics/AbcSize
13
- destination_entry.use_file(file)
14
+ destination_entry.use_file(file, folder_perma_id: @folder_perma_id)
14
15
 
15
16
  file_type.nested_file_types.each do |nested_file_type|
16
17
  source_entry.find_files(nested_file_type.model).each do |nested_file|
@@ -8,6 +8,7 @@ module Pageflow
8
8
  belongs_to :file, polymorphic: true
9
9
 
10
10
  validate :display_name_extension_matches
11
+ validate :folder_belongs_to_revision
11
12
 
12
13
  private
13
14
 
@@ -17,5 +18,12 @@ module Pageflow
17
18
 
18
19
  errors.add(:display_name, 'extension mismatch')
19
20
  end
21
+
22
+ def folder_belongs_to_revision
23
+ return if folder_perma_id.blank? || revision.blank?
24
+ return if FileFolder.all_for_revision(revision).exists?(perma_id: folder_perma_id)
25
+
26
+ errors.add(:folder_perma_id, 'unknown folder')
27
+ end
20
28
  end
21
29
  end
@@ -40,26 +40,30 @@ module Pageflow
40
40
 
41
41
  Pageflow
42
42
  .config.thumbnail_styles.transform_values { |options| options.merge(style_defaults) }
43
- .merge(
44
- print: {geometry: '300x300>',
45
- **style_defaults,
46
- convert_options: '-quality 10 -interlace Plane'},
47
- medium: {geometry: '1024x1024>',
48
- **style_defaults,
49
- convert_options: '-quality 70 -interlace Plane'},
50
- large: {geometry: '1920x1920>',
51
- **style_defaults,
52
- convert_options: '-quality 70 -interlace Plane'},
53
- ultra: {geometry: '3840x3840>',
54
- **style_defaults,
55
- convert_options: '-quality 90 -interlace Plane'},
56
- panorama_medium: {geometry: ImageFile.scale_down_to_cover(1024, 1024),
57
- format: panorama_format,
58
- convert_options: '-quality 90 -interlace Plane'},
59
- panorama_large: {geometry: ImageFile.scale_down_to_cover(1920, 1080),
60
- format: panorama_format,
61
- convert_options: '-quality 90 -interlace Plane'}
62
- )
43
+ .merge(
44
+ print: {geometry: '300x300>',
45
+ **style_defaults,
46
+ convert_options: '-quality 10 -interlace Plane'},
47
+ medium: {geometry: '1024x1024>',
48
+ **style_defaults,
49
+ convert_options: '-quality 70 -interlace Plane'},
50
+ large: {geometry: '1920x1920>',
51
+ **style_defaults,
52
+ convert_options: '-quality 70 -interlace Plane'},
53
+ ultra: {geometry: '3840x3840>',
54
+ **style_defaults,
55
+ convert_options: '-quality 90 -interlace Plane'},
56
+ panorama_medium: {
57
+ geometry: ImageFile.scale_down_to_cover(1024, 1024),
58
+ format: panorama_format,
59
+ convert_options: '-quality 90 -interlace Plane'
60
+ },
61
+ panorama_large: {
62
+ geometry: ImageFile.scale_down_to_cover(1920, 1080),
63
+ format: panorama_format,
64
+ convert_options: '-quality 90 -interlace Plane'
65
+ }
66
+ )
63
67
  .merge(social_image_styles)
64
68
  end
65
69
 
@@ -1,10 +1,10 @@
1
1
  module Pageflow
2
2
  class ImageFileUrlTemplates # rubocop:todo Style/Documentation
3
3
  def call
4
- styles.each_with_object({}) do |style, result|
5
- result[style] = replace_extension_with_placeholder(
4
+ styles.to_h do |style|
5
+ [style, replace_extension_with_placeholder(
6
6
  UrlTemplate.from_attachment(example_file.attachment, style)
7
- )
7
+ )]
8
8
  end
9
9
  end
10
10
 
@@ -0,0 +1,38 @@
1
+ module Pageflow
2
+ # Legal info of a site, resolved for a single locale. Each attribute
3
+ # falls back to the value stored in the site's column when there is
4
+ # no translation for the locale.
5
+ #
6
+ # @since 17.2
7
+ class LegalInfo
8
+ Link = Struct.new(:label, :url, keyword_init: true)
9
+
10
+ def initialize(site, locale)
11
+ @site = site
12
+ @locale = locale
13
+ end
14
+
15
+ def imprint
16
+ @imprint ||= link('imprint')
17
+ end
18
+
19
+ def copyright
20
+ @copyright ||= link('copyright')
21
+ end
22
+
23
+ def privacy_url
24
+ translated(:privacy_link_url)
25
+ end
26
+
27
+ private
28
+
29
+ def link(kind)
30
+ Link.new(label: translated("#{kind}_link_label"),
31
+ url: translated("#{kind}_link_url"))
32
+ end
33
+
34
+ def translated(attribute)
35
+ @site.translated(attribute, locale: @locale)
36
+ end
37
+ end
38
+ end
@@ -166,7 +166,7 @@ module Pageflow
166
166
  "#{[*chain, model].first.old_foreign_key} = #{@old_root_id}"
167
167
  end
168
168
 
169
- Model = Struct.new(:klass, :foreign_key, :level, keyword_init: true) do
169
+ Model = Struct.new(:klass, :foreign_key, :level) do
170
170
  def self.for(revision_components:)
171
171
  revision_components.map do |revision_component|
172
172
  Model.new(klass: revision_component, foreign_key: 'revision_id', level: 1)
@@ -53,6 +53,7 @@ module Pageflow
53
53
 
54
54
  def create_redirect
55
55
  directory.redirects.where(slug:).delete_all
56
+ PermalinkRedirect.where(slug: slug_was, directory_id: directory_id_was).delete_all
56
57
  entry.permalink_redirects.create!(slug: slug_was,
57
58
  directory_id: directory_id_was)
58
59
  end
@@ -27,7 +27,10 @@ module Pageflow
27
27
  has_many :chapters, -> { order(CHAPTER_ORDER) }, through: :storylines
28
28
  has_many :pages, -> { reorder(PAGE_ORDER) }, through: :storylines
29
29
 
30
+ has_many :comment_threads, dependent: :destroy
31
+
30
32
  has_many :file_usages, dependent: :destroy
33
+ has_many :file_folders, dependent: :destroy
31
34
 
32
35
  has_many :image_files, -> { extending WithFileUsageExtension },
33
36
  through: :file_usages, source: :file, source_type: 'Pageflow::ImageFile'
@@ -183,7 +186,8 @@ module Pageflow
183
186
  from: self,
184
187
  to: revision
185
188
  ).perform_for(
186
- revision_components: Pageflow.config.revision_components.to_a + [FileUsage]
189
+ revision_components: Pageflow.config.revision_components.to_a +
190
+ [FileFolder, FileUsage]
187
191
  )
188
192
 
189
193
  revision
@@ -212,7 +216,7 @@ module Pageflow
212
216
  'manual_start' => manual_start,
213
217
  'overview_button_enabled' => overview_button_enabled
214
218
  }.delete_if { |_k, v| v.nil? }
215
- .merge(read_attribute(:configuration) || {})
219
+ .merge(read_attribute(:configuration) || {})
216
220
  end
217
221
 
218
222
  def self.ransackable_attributes(_auth_object = nil)
@@ -1,5 +1,7 @@
1
1
  module Pageflow
2
2
  class Site < ApplicationRecord # rubocop:todo Style/Documentation
3
+ include TranslatedAttributes
4
+
3
5
  belongs_to :account
4
6
  belongs_to :custom_404_entry, class_name: 'Entry', optional: true
5
7
 
@@ -7,6 +9,10 @@ module Pageflow
7
9
  has_many :entries
8
10
  has_many :permalink_directories, -> { order('path ASC') }
9
11
 
12
+ translated_attributes :imprint_link_label, :imprint_link_url,
13
+ :copyright_link_label, :copyright_link_url,
14
+ :privacy_link_url
15
+
10
16
  scope :with_home_url, -> { where.not(home_url: '') }
11
17
  scope :for_request, ->(request) { Pageflow.config.site_request_scope.call(all, request) }
12
18
 
@@ -16,6 +22,10 @@ module Pageflow
16
22
 
17
23
  delegate :enabled_feature_names, to: :account
18
24
 
25
+ def legal_info(locale:)
26
+ LegalInfo.new(self, locale)
27
+ end
28
+
19
29
  def display_name
20
30
  name.presence || I18n.t('pageflow.admin.sites.default_name')
21
31
  end
@@ -10,9 +10,11 @@ module Pageflow
10
10
  @usage.configuration
11
11
  end
12
12
 
13
+ USAGE_ATTRIBUTES = [:configuration, :display_name, :folder_perma_id].freeze
14
+
13
15
  def update!(attributes)
14
- super(attributes.except(:configuration, :display_name))
15
- @usage.update!(attributes.slice(:configuration, :display_name))
16
+ super(attributes.except(*USAGE_ATTRIBUTES))
17
+ @usage.update!(attributes.slice(*USAGE_ATTRIBUTES))
16
18
  end
17
19
 
18
20
  def usage_id
@@ -27,6 +29,10 @@ module Pageflow
27
29
  @usage.display_name
28
30
  end
29
31
 
32
+ def folder_perma_id
33
+ @usage.folder_perma_id
34
+ end
35
+
30
36
  def cache_key
31
37
  [@file.cache_key, @usage.cache_key].join('-')
32
38
  end