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
data/package/package.json CHANGED
@@ -21,9 +21,10 @@
21
21
  },
22
22
  "scripts": {
23
23
  "test": "jest",
24
- "lint": "eslint"
24
+ "lint": "eslint --rulesdir config/eslint-rules"
25
25
  },
26
26
  "dependencies": {
27
+ "@floating-ui/dom": "^1.6.7",
27
28
  "backbone-events-standalone": "^0.2.7",
28
29
  "classlist.js": "^1.1.20150312",
29
30
  "core-js": "^3.4.1",
data/package/review.js ADDED
@@ -0,0 +1,384 @@
1
+ import BackboneEvents from 'backbone-events-standalone';
2
+
3
+ const FLUSH_READS_DELAY = 1000;
4
+ class ReviewSession {
5
+ constructor({
6
+ entryId,
7
+ request,
8
+ initialState = null
9
+ }) {
10
+ this._entryId = entryId;
11
+ this._request = request;
12
+ this._state = initialState;
13
+ this._drafts = {};
14
+ this._pendingReads = new Set();
15
+ this._flushReadsTimeout = null;
16
+ }
17
+ get state() {
18
+ return this._state;
19
+ }
20
+
21
+ // Unsent comment texts, not keyed by subject range: a draft written
22
+ // about one phrase of a text block is offered again when commenting on
23
+ // another phrase of the same block.
24
+ get drafts() {
25
+ return this._drafts;
26
+ }
27
+ setDraft({
28
+ body,
29
+ ...of
30
+ }) {
31
+ if (body.trim().length) {
32
+ this._writeDraft({
33
+ ...of,
34
+ body
35
+ });
36
+ } else {
37
+ this._deleteDraft(of);
38
+ }
39
+ }
40
+ async createThread({
41
+ subjectType,
42
+ subjectId,
43
+ subjectRange,
44
+ sectionPermaId,
45
+ body,
46
+ quote
47
+ }) {
48
+ // Written even when the form never stored a draft, so that the text
49
+ // outlives a failed attempt.
50
+ this._writeDraft({
51
+ subjectType,
52
+ subjectId,
53
+ body,
54
+ pending: true
55
+ });
56
+ const thread = await this._request({
57
+ url: `/review/entries/${this._entryId}/comment_threads`,
58
+ method: 'POST',
59
+ payload: {
60
+ comment_thread: {
61
+ subject_type: subjectType,
62
+ subject_id: subjectId,
63
+ ...(subjectRange && {
64
+ subject_range: subjectRange
65
+ }),
66
+ ...(sectionPermaId != null && {
67
+ section_perma_id: sectionPermaId
68
+ }),
69
+ comment: {
70
+ body,
71
+ ...(quote && {
72
+ quote
73
+ })
74
+ }
75
+ }
76
+ }
77
+ }).catch(error => {
78
+ this._writeDraft({
79
+ subjectType,
80
+ subjectId,
81
+ body,
82
+ pending: false
83
+ });
84
+ // Rethrown so that a failure still shows up as an unhandled
85
+ // rejection rather than only as a form that became editable again.
86
+ throw error;
87
+ });
88
+ this._upsertThread(thread);
89
+ this.trigger('change:thread', thread);
90
+ this.trigger('create:thread', thread);
91
+
92
+ // Dropped last so that the form closing does not briefly reveal a
93
+ // list without the thread.
94
+ this._deleteDraft({
95
+ subjectType,
96
+ subjectId
97
+ });
98
+ }
99
+ async updateThread({
100
+ threadId,
101
+ resolved
102
+ }) {
103
+ const thread = await this._request({
104
+ url: `/review/entries/${this._entryId}/comment_threads/${threadId}`,
105
+ method: 'PATCH',
106
+ payload: {
107
+ comment_thread: {
108
+ resolved
109
+ }
110
+ }
111
+ });
112
+ this._upsertThread(thread);
113
+ this.trigger('change:thread', thread);
114
+ }
115
+ async createComment({
116
+ threadId,
117
+ body,
118
+ quote
119
+ }) {
120
+ this._writeDraft({
121
+ threadId,
122
+ body,
123
+ pending: true
124
+ });
125
+ const comment = await this._request({
126
+ url: `/review/entries/${this._entryId}/comment_threads/${threadId}/comments`,
127
+ method: 'POST',
128
+ payload: {
129
+ comment: {
130
+ body,
131
+ ...(quote && {
132
+ quote
133
+ })
134
+ }
135
+ }
136
+ }).catch(error => {
137
+ this._writeDraft({
138
+ threadId,
139
+ body,
140
+ pending: false
141
+ });
142
+ throw error;
143
+ });
144
+ const thread = this._findThread(threadId);
145
+ if (thread) {
146
+ const updatedThread = {
147
+ ...thread,
148
+ comments: [...thread.comments, comment]
149
+ };
150
+ this._upsertThread(updatedThread);
151
+ this.trigger('change:thread', updatedThread);
152
+ }
153
+ this._deleteDraft({
154
+ threadId
155
+ });
156
+ }
157
+
158
+ // Edits are not drafted: unlike a new comment, the text they would
159
+ // restore is still in the thread, so a failed attempt only has to leave
160
+ // the form showing what the reviewer typed.
161
+ async updateComment({
162
+ threadId,
163
+ commentId,
164
+ body
165
+ }) {
166
+ const comment = await this._request({
167
+ url: `/review/entries/${this._entryId}/comment_threads/${threadId}/comments/${commentId}`,
168
+ method: 'PATCH',
169
+ payload: {
170
+ comment: {
171
+ body
172
+ }
173
+ }
174
+ });
175
+ const thread = this._findThread(threadId);
176
+ if (!thread) return;
177
+ const updatedThread = {
178
+ ...thread,
179
+ comments: thread.comments.map(c => c.id === commentId ? comment : c)
180
+ };
181
+ this._upsertThread(updatedThread);
182
+ this.trigger('change:thread', updatedThread);
183
+ }
184
+ diffSubjectRangeUpdates(ranges) {
185
+ const changed = {};
186
+ if (!this._state) return changed;
187
+ Object.entries(ranges).forEach(([id, range]) => {
188
+ const threadId = Number(id);
189
+ const thread = this._findThread(threadId);
190
+ if (!thread || sameRange(thread.subjectRange, range)) return;
191
+ changed[threadId] = range;
192
+ });
193
+ return changed;
194
+ }
195
+ applySubjectRangeUpdates(ranges) {
196
+ const updates = {};
197
+ Object.entries(ranges).forEach(([id, range]) => {
198
+ updates[id] = {
199
+ subjectRange: range
200
+ };
201
+ });
202
+ this.applyThreadUpdates(updates);
203
+ }
204
+ applyThreadUpdates(updates) {
205
+ if (!this._state) return;
206
+ Object.entries(updates).forEach(([id, changes]) => {
207
+ const threadId = Number(id);
208
+ const thread = this._findThread(threadId);
209
+ if (!thread) return;
210
+ const updated = {
211
+ ...thread,
212
+ ...changes
213
+ };
214
+ this._upsertThread(updated);
215
+ this.trigger('change:thread', updated);
216
+ });
217
+ }
218
+ findThreadsFor({
219
+ subjectType,
220
+ subjectId
221
+ }) {
222
+ if (!this._state) return [];
223
+ return this._state.commentThreads.filter(thread => thread.subjectType === subjectType && thread.subjectId === subjectId);
224
+ }
225
+ async fetch() {
226
+ const data = await this._request({
227
+ url: `/review/entries/${this._entryId}/comment_threads`,
228
+ method: 'GET'
229
+ });
230
+ this._state = {
231
+ currentUser: data.currentUser,
232
+ commentThreads: data.commentThreads,
233
+ commentThreadReads: data.commentThreadReads || {}
234
+ };
235
+ this.trigger('reset', this._state);
236
+ }
237
+
238
+ // Read marks are frequent and individually unimportant, so they are
239
+ // collected and sent as one request instead of one request per thread.
240
+ markThreadsRead(permaIds) {
241
+ if (!this._state || !permaIds.length) return;
242
+ const readAt = new Date().toISOString();
243
+ this._state = {
244
+ ...this._state,
245
+ commentThreadReads: {
246
+ ...this._state.commentThreadReads,
247
+ ...Object.fromEntries(permaIds.map(permaId => [permaId, readAt]))
248
+ }
249
+ };
250
+ permaIds.forEach(permaId => this._pendingReads.add(permaId));
251
+ this.trigger('change:reads', this._state.commentThreadReads);
252
+ this._scheduleFlushReads();
253
+ }
254
+ async flushReads() {
255
+ clearTimeout(this._flushReadsTimeout);
256
+ this._flushReadsTimeout = null;
257
+ if (!this._pendingReads.size) return;
258
+ const permaIds = [...this._pendingReads];
259
+ this._pendingReads.clear();
260
+ await this._request({
261
+ url: `/review/entries/${this._entryId}/comment_thread_reads`,
262
+ method: 'POST',
263
+ payload: {
264
+ comment_thread_perma_ids: permaIds
265
+ }
266
+ }).catch(() => {
267
+ // Local state already counts the threads as read. Keeping the
268
+ // perma ids pending lets the next flush try again instead of
269
+ // leaving them unread until the next page load.
270
+ permaIds.forEach(permaId => this._pendingReads.add(permaId));
271
+ });
272
+ }
273
+ _scheduleFlushReads() {
274
+ if (this._flushReadsTimeout) return;
275
+ this._flushReadsTimeout = setTimeout(() => this.flushReads(), FLUSH_READS_DELAY);
276
+ }
277
+ _writeDraft({
278
+ body,
279
+ pending = false,
280
+ ...of
281
+ }) {
282
+ this._drafts = {
283
+ ...this._drafts,
284
+ [draftKey(of)]: {
285
+ ...of,
286
+ body,
287
+ pending
288
+ }
289
+ };
290
+ this.trigger('change:drafts', this._drafts);
291
+ }
292
+ _deleteDraft(of) {
293
+ const key = draftKey(of);
294
+ if (!(key in this._drafts)) return;
295
+ this._drafts = {
296
+ ...this._drafts
297
+ };
298
+ delete this._drafts[key];
299
+ this.trigger('change:drafts', this._drafts);
300
+ }
301
+ _findThread(id) {
302
+ var _this$_state;
303
+ return (_this$_state = this._state) === null || _this$_state === void 0 ? void 0 : _this$_state.commentThreads.find(t => t.id === id);
304
+ }
305
+ _upsertThread(thread) {
306
+ if (!this._state) return;
307
+ const threads = this._state.commentThreads;
308
+ const index = threads.findIndex(t => t.id === thread.id);
309
+ if (index >= 0) {
310
+ this._state = {
311
+ ...this._state,
312
+ commentThreads: [...threads.slice(0, index), thread, ...threads.slice(index + 1)]
313
+ };
314
+ } else {
315
+ this._state = {
316
+ ...this._state,
317
+ commentThreads: [...threads, thread]
318
+ };
319
+ }
320
+ }
321
+ }
322
+ Object.assign(ReviewSession.prototype, BackboneEvents);
323
+
324
+ // Replies are drafted per thread, new threads per subject.
325
+ function draftKey({
326
+ threadId,
327
+ subjectType,
328
+ subjectId
329
+ }) {
330
+ return threadId ? `Thread:${threadId}` : `${subjectType}:${subjectId}`;
331
+ }
332
+ function sameRange(a, b) {
333
+ return JSON.stringify(a) === JSON.stringify(b);
334
+ }
335
+
336
+ async function request({
337
+ url,
338
+ method,
339
+ payload
340
+ }) {
341
+ const headers = {
342
+ 'Accept': 'application/json'
343
+ };
344
+ const options = {
345
+ method,
346
+ credentials: 'same-origin',
347
+ headers
348
+ };
349
+ if (payload) {
350
+ headers['Content-Type'] = 'application/json';
351
+ headers['X-CSRF-Token'] = getCSRFToken();
352
+ options.body = JSON.stringify(payload);
353
+ }
354
+ const response = await fetch(url, options);
355
+ if (!response.ok) {
356
+ throw new Error(`${response.status} ${response.statusText}`);
357
+ }
358
+ if (response.status === 204) {
359
+ return null;
360
+ }
361
+ return response.json();
362
+ }
363
+ function getCSRFToken() {
364
+ const meta = typeof document !== 'undefined' && document.querySelector('meta[name="csrf-token"]');
365
+ return meta ? meta.content : '';
366
+ }
367
+
368
+ function createReviewSession({
369
+ entryId,
370
+ initialState
371
+ }) {
372
+ const session = new ReviewSession({
373
+ entryId,
374
+ request,
375
+ initialState
376
+ });
377
+
378
+ // Read marks are sent with a delay, which a tab closing right after
379
+ // reading would otherwise cut short.
380
+ window.addEventListener('pagehide', () => session.flushReads());
381
+ return session;
382
+ }
383
+
384
+ export { ReviewSession, createReviewSession };