pageflow 17.0.5 → 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 (636) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +342 -41
  3. data/README.md +3 -5
  4. data/admins/pageflow/accounts.rb +22 -20
  5. data/admins/pageflow/entry.rb +76 -26
  6. data/admins/pageflow/entry_templates.rb +10 -9
  7. data/admins/pageflow/folder.rb +1 -1
  8. data/admins/pageflow/membership.rb +15 -15
  9. data/admins/pageflow/revisions.rb +5 -5
  10. data/admins/pageflow/site_root_entry.rb +64 -0
  11. data/admins/pageflow/sites.rb +12 -3
  12. data/admins/pageflow/translations.rb +75 -0
  13. data/admins/pageflow/user.rb +21 -35
  14. data/app/assets/images/pageflow/admin/icons/comment.svg +1 -0
  15. data/app/assets/javascripts/pageflow/admin/entries.js +30 -5
  16. data/app/assets/javascripts/pageflow/dist/ui.js +4228 -900
  17. data/app/assets/javascripts/pageflow/editor/vendor.js +0 -1
  18. data/app/assets/javascripts/pageflow/ui.js +0 -1
  19. data/app/assets/stylesheets/pageflow/admin/entries/index_table.scss +2 -1
  20. data/app/assets/stylesheets/pageflow/admin/entry_comments_indicator.scss +45 -0
  21. data/app/assets/stylesheets/pageflow/admin/permalink_input.scss +8 -0
  22. data/app/assets/stylesheets/pageflow/admin.scss +1 -0
  23. data/app/assets/stylesheets/pageflow/animations/spin.scss +9 -0
  24. data/app/assets/stylesheets/pageflow/animations.scss +1 -0
  25. data/app/assets/stylesheets/pageflow/editor/background_positioning.scss +27 -5
  26. data/app/assets/stylesheets/pageflow/editor/base.scss +11 -1
  27. data/app/assets/stylesheets/pageflow/editor/drop_down_button.scss +20 -2
  28. data/app/assets/stylesheets/pageflow/editor/edit_configuration_view.scss +5 -0
  29. data/app/assets/stylesheets/pageflow/editor/file_folders.scss +127 -0
  30. data/app/assets/stylesheets/pageflow/editor/file_meta_data.scss +5 -2
  31. data/app/assets/stylesheets/pageflow/editor/file_meta_data_overlay.scss +45 -0
  32. data/app/assets/stylesheets/pageflow/editor/file_preview.scss +138 -0
  33. data/app/assets/stylesheets/pageflow/editor/file_references.scss +68 -0
  34. data/app/assets/stylesheets/pageflow/editor/file_settings_dialog.scss +1 -1
  35. data/app/assets/stylesheets/pageflow/editor/file_stage_icons.scss +17 -0
  36. data/app/assets/stylesheets/pageflow/editor/file_stages.scss +25 -32
  37. data/app/assets/stylesheets/pageflow/editor/file_thumbnails.scss +31 -48
  38. data/app/assets/stylesheets/pageflow/editor/file_type_pills.scss +73 -0
  39. data/app/assets/stylesheets/pageflow/editor/files.scss +242 -50
  40. data/app/assets/stylesheets/pageflow/editor/filtered_files.scss +161 -9
  41. data/app/assets/stylesheets/pageflow/editor/folder_breadcrumb.scss +40 -0
  42. data/app/assets/stylesheets/pageflow/editor/info_box.scss +23 -3
  43. data/app/assets/stylesheets/pageflow/editor/inputs/edit_defaults_button.scss +15 -0
  44. data/app/assets/stylesheets/pageflow/editor/inputs.scss +1 -0
  45. data/app/assets/stylesheets/pageflow/editor/list.scss +89 -37
  46. data/app/assets/stylesheets/pageflow/editor/list_search_field.scss +52 -0
  47. data/app/assets/stylesheets/pageflow/editor/manage_files.scss +9 -0
  48. data/app/assets/stylesheets/pageflow/editor/menu.scss +14 -0
  49. data/app/assets/stylesheets/pageflow/editor/move_to_folder_dialog.scss +83 -0
  50. data/app/assets/stylesheets/pageflow/editor/outline.scss +0 -13
  51. data/app/assets/stylesheets/pageflow/editor/sortable.scss +12 -0
  52. data/app/assets/stylesheets/pageflow/mixins/buttons.scss +4 -4
  53. data/app/assets/stylesheets/pageflow/mixins/pageflow.scss +9 -5
  54. data/app/assets/stylesheets/pageflow/ui/color_picker.scss +230 -0
  55. data/app/assets/stylesheets/pageflow/ui/forms.scss +38 -4
  56. data/app/assets/stylesheets/pageflow/ui/functions.scss +12 -2
  57. data/app/assets/stylesheets/pageflow/ui/input/color_input.scss +9 -8
  58. data/app/assets/stylesheets/pageflow/ui/input/file_name_input.scss +37 -0
  59. data/app/assets/stylesheets/pageflow/ui/input/text_area_input.scss +15 -0
  60. data/app/assets/stylesheets/pageflow/ui/overrides.scss +8 -0
  61. data/app/assets/stylesheets/pageflow/ui/properties.scss +34 -0
  62. data/app/assets/stylesheets/pageflow/ui/tabs_view.scss +8 -3
  63. data/app/assets/stylesheets/pageflow/ui.scss +4 -1
  64. data/app/controllers/concerns/pageflow/controller_delegation.rb +1 -1
  65. data/app/controllers/concerns/pageflow/edit_locking.rb +17 -10
  66. data/app/controllers/concerns/pageflow/entry_password_protection.rb +1 -0
  67. data/app/controllers/concerns/pageflow/public_https_mode.rb +2 -1
  68. data/app/controllers/concerns/pageflow/quota_verification.rb +5 -4
  69. data/app/controllers/pageflow/admin/initial_passwords_controller.rb +1 -0
  70. data/app/controllers/pageflow/application_controller.rb +19 -19
  71. data/app/controllers/pageflow/chapters_controller.rb +3 -2
  72. data/app/controllers/pageflow/edit_locks_controller.rb +3 -2
  73. data/app/controllers/pageflow/editor/encoding_confirmations_controller.rb +1 -0
  74. data/app/controllers/pageflow/editor/entries_controller.rb +3 -1
  75. data/app/controllers/pageflow/editor/entry_publications_controller.rb +1 -0
  76. data/app/controllers/pageflow/editor/file_folders_controller.rb +80 -0
  77. data/app/controllers/pageflow/editor/file_import_controller.rb +4 -3
  78. data/app/controllers/pageflow/editor/files_controller.rb +20 -10
  79. data/app/controllers/pageflow/editor/oembeds_controller.rb +21 -0
  80. data/app/controllers/pageflow/editor/widgets_controller.rb +1 -0
  81. data/app/controllers/pageflow/entries_controller.rb +85 -21
  82. data/app/controllers/pageflow/feeds_controller.rb +1 -1
  83. data/app/controllers/pageflow/files_controller.rb +1 -0
  84. data/app/controllers/pageflow/pages_controller.rb +4 -2
  85. data/app/controllers/pageflow/review/comment_thread_reads_controller.rb +30 -0
  86. data/app/controllers/pageflow/review/comment_threads_controller.rb +60 -0
  87. data/app/controllers/pageflow/review/comments_controller.rb +53 -0
  88. data/app/controllers/pageflow/revisions_controller.rb +5 -1
  89. data/app/controllers/pageflow/sitemaps_controller.rb +1 -1
  90. data/app/controllers/pageflow/storylines_controller.rb +1 -0
  91. data/app/helpers/pageflow/admin/cutoff_modes_helper.rb +12 -0
  92. data/app/helpers/pageflow/admin/entries_helper.rb +41 -1
  93. data/app/helpers/pageflow/admin/entry_translations_helper.rb +18 -0
  94. data/app/helpers/pageflow/admin/features_helper.rb +1 -1
  95. data/app/helpers/pageflow/admin/form_helper.rb +3 -3
  96. data/app/helpers/pageflow/admin/locales_helper.rb +10 -2
  97. data/app/helpers/pageflow/admin/memberships_helper.rb +32 -32
  98. data/app/helpers/pageflow/admin/revisions_helper.rb +1 -1
  99. data/app/helpers/pageflow/admin/sites_helper.rb +22 -0
  100. data/app/helpers/pageflow/admin/users_helper.rb +7 -5
  101. data/app/helpers/pageflow/admin/widgets_helper.rb +5 -5
  102. data/app/helpers/pageflow/asset_urls_helper.rb +1 -1
  103. data/app/helpers/pageflow/audio_files_helper.rb +5 -2
  104. data/app/helpers/pageflow/background_image_helper.rb +7 -6
  105. data/app/helpers/pageflow/common_entry_seed_helper.rb +7 -7
  106. data/app/helpers/pageflow/editor/config_helper.rb +3 -1
  107. data/app/helpers/pageflow/editor/files_helper.rb +1 -1
  108. data/app/helpers/pageflow/embed_code_helper.rb +3 -3
  109. data/app/helpers/pageflow/entries_helper.rb +52 -46
  110. data/app/helpers/pageflow/entry_json_seed_helper.rb +7 -6
  111. data/app/helpers/pageflow/feeds_helper.rb +2 -2
  112. data/app/helpers/pageflow/file_background_images_helper.rb +2 -2
  113. data/app/helpers/pageflow/file_thumbnails_helper.rb +4 -3
  114. data/app/helpers/pageflow/files_helper.rb +5 -4
  115. data/app/helpers/pageflow/folders_helper.rb +8 -5
  116. data/app/helpers/pageflow/help_entries_helper.rb +2 -2
  117. data/app/helpers/pageflow/hreflang_links_helper.rb +37 -0
  118. data/app/helpers/pageflow/info_box_helper.rb +7 -11
  119. data/app/helpers/pageflow/media_query_helper.rb +3 -3
  120. data/app/helpers/pageflow/navigation_bar_helper.rb +1 -1
  121. data/app/helpers/pageflow/overview_helper.rb +1 -1
  122. data/app/helpers/pageflow/page_types_helper.rb +5 -5
  123. data/app/helpers/pageflow/pages_helper.rb +29 -15
  124. data/app/helpers/pageflow/public_i18n_helper.rb +10 -9
  125. data/app/helpers/pageflow/quota_helper.rb +4 -4
  126. data/app/helpers/pageflow/render_json_helper.rb +6 -3
  127. data/app/helpers/pageflow/revision_file_helper.rb +2 -1
  128. data/app/helpers/pageflow/sites_helper.rb +3 -3
  129. data/app/helpers/pageflow/social_share_helper.rb +24 -10
  130. data/app/helpers/pageflow/social_share_links_helper.rb +12 -9
  131. data/app/helpers/pageflow/structured_data_helper.rb +5 -2
  132. data/app/helpers/pageflow/stub_page_configuration.rb +7 -3
  133. data/app/helpers/pageflow/text_direction_helper.rb +1 -1
  134. data/app/helpers/pageflow/themes_helper.rb +2 -4
  135. data/app/helpers/pageflow/video_files_helper.rb +29 -13
  136. data/app/helpers/pageflow/widgets_helper.rb +8 -6
  137. data/app/inputs/pageflow_permalink_input.rb +8 -3
  138. data/app/jobs/pageflow/application_job.rb +1 -0
  139. data/app/jobs/pageflow/entry_export_import/upload_and_publish_file_job.rb +1 -1
  140. data/app/jobs/pageflow/poll_meta_data_from_zencoder_job.rb +1 -0
  141. data/app/jobs/pageflow/poll_zencoder_job.rb +3 -2
  142. data/app/jobs/pageflow/process_image_or_text_track_job.rb +1 -0
  143. data/app/jobs/pageflow/prune_auto_snapshots_job.rb +1 -0
  144. data/app/jobs/pageflow/request_meta_data_from_zencoder_job.rb +1 -0
  145. data/app/jobs/pageflow/submit_file_to_zencoder_job.rb +1 -0
  146. data/app/mailers/pageflow/user_mailer.rb +1 -0
  147. data/app/models/concerns/pageflow/auto_generated_perma_id.rb +20 -1
  148. data/app/models/concerns/pageflow/entry_publication_states.rb +7 -3
  149. data/app/models/concerns/pageflow/feature_target.rb +8 -4
  150. data/app/models/concerns/pageflow/output_source.rb +3 -3
  151. data/app/models/concerns/pageflow/permalinkable.rb +2 -1
  152. data/app/models/concerns/pageflow/reusable_file.rb +24 -24
  153. data/app/models/concerns/pageflow/serialization_blacklist.rb +3 -5
  154. data/app/models/concerns/pageflow/suspendable.rb +5 -3
  155. data/app/models/concerns/pageflow/theme_referencer.rb +1 -1
  156. data/app/models/concerns/pageflow/translatable.rb +62 -0
  157. data/app/models/concerns/pageflow/translated_attributes.rb +101 -0
  158. data/app/models/concerns/pageflow/uploadable_file.rb +6 -6
  159. data/app/models/pageflow/account.rb +6 -5
  160. data/app/models/pageflow/account_member_query.rb +1 -1
  161. data/app/models/pageflow/account_role_query.rb +1 -1
  162. data/app/models/pageflow/application_query.rb +1 -1
  163. data/app/models/pageflow/application_record.rb +1 -1
  164. data/app/models/pageflow/audio_file.rb +4 -4
  165. data/app/models/pageflow/audio_file_url_templates.rb +1 -1
  166. data/app/models/pageflow/authentication_token.rb +2 -2
  167. data/app/models/pageflow/chapter.rb +6 -1
  168. data/app/models/pageflow/chapter_scaffold.rb +2 -4
  169. data/app/models/pageflow/cname_site_request_scope.rb +1 -1
  170. data/app/models/pageflow/comment.rb +30 -0
  171. data/app/models/pageflow/comment_thread.rb +65 -0
  172. data/app/models/pageflow/comment_thread_read.rb +22 -0
  173. data/app/models/pageflow/customized_theme.rb +9 -8
  174. data/app/models/pageflow/draft_entry.rb +49 -9
  175. data/app/models/pageflow/edit_lock.rb +19 -21
  176. data/app/models/pageflow/encoding_confirmation.rb +2 -2
  177. data/app/models/pageflow/entries_feed.rb +2 -2
  178. data/app/models/pageflow/entry.rb +46 -30
  179. data/app/models/pageflow/entry_at_revision.rb +14 -2
  180. data/app/models/pageflow/entry_comment_summary.rb +114 -0
  181. data/app/models/pageflow/entry_duplicate.rb +8 -5
  182. data/app/models/pageflow/entry_publication.rb +2 -2
  183. data/app/models/pageflow/entry_role_query.rb +4 -4
  184. data/app/models/pageflow/entry_template.rb +4 -4
  185. data/app/models/pageflow/entry_title_or_account_name_query.rb +2 -2
  186. data/app/models/pageflow/entry_translation_group.rb +42 -0
  187. data/app/models/pageflow/file_folder.rb +74 -0
  188. data/app/models/pageflow/file_reuse.rb +5 -4
  189. data/app/models/pageflow/file_usage.rb +18 -3
  190. data/app/models/pageflow/folder.rb +2 -2
  191. data/app/models/pageflow/home_button.rb +7 -7
  192. data/app/models/pageflow/image_file.rb +44 -25
  193. data/app/models/pageflow/image_file_url_templates.rb +4 -4
  194. data/app/models/pageflow/invitation_form.rb +2 -1
  195. data/app/models/pageflow/legal_info.rb +38 -0
  196. data/app/models/pageflow/managed_user_query.rb +1 -1
  197. data/app/models/pageflow/membership.rb +5 -5
  198. data/app/models/pageflow/nested_revision_component_copy.rb +263 -0
  199. data/app/models/pageflow/null_user.rb +1 -1
  200. data/app/models/pageflow/overview_button.rb +1 -1
  201. data/app/models/pageflow/page.rb +7 -3
  202. data/app/models/pageflow/permalink.rb +24 -2
  203. data/app/models/pageflow/permalink_directory.rb +7 -0
  204. data/app/models/pageflow/permalink_redirect.rb +7 -0
  205. data/app/models/pageflow/positioned_file.rb +5 -5
  206. data/app/models/pageflow/potential_entry_translations.rb +55 -0
  207. data/app/models/pageflow/potential_memberships.rb +5 -4
  208. data/app/models/pageflow/published_entry.rb +62 -11
  209. data/app/models/pageflow/revision.rb +29 -16
  210. data/app/models/pageflow/roles.rb +14 -18
  211. data/app/models/pageflow/site.rb +46 -3
  212. data/app/models/pageflow/site_root_entry_form.rb +27 -0
  213. data/app/models/pageflow/sitemaps.rb +10 -1
  214. data/app/models/pageflow/storyline.rb +5 -2
  215. data/app/models/pageflow/storyline_scaffold.rb +1 -1
  216. data/app/models/pageflow/text_track_file.rb +1 -1
  217. data/app/models/pageflow/text_track_file_url_templates.rb +1 -1
  218. data/app/models/pageflow/theme_customization_file.rb +3 -2
  219. data/app/models/pageflow/thumbnail_file_resolver.rb +1 -1
  220. data/app/models/pageflow/url_template.rb +1 -1
  221. data/app/models/pageflow/used_file.rb +13 -3
  222. data/app/models/pageflow/user_name_query.rb +2 -2
  223. data/app/models/pageflow/video_file.rb +22 -15
  224. data/app/models/pageflow/video_file_url_templates.rb +6 -4
  225. data/app/models/pageflow/widget.rb +5 -4
  226. data/app/models/pageflow/with_file_usage_extension.rb +1 -1
  227. data/app/models/pageflow/zencoder_attachment.rb +8 -8
  228. data/app/policies/pageflow/account_policy.rb +3 -1
  229. data/app/policies/pageflow/admin/admin_only_tab_policy.rb +1 -0
  230. data/app/policies/pageflow/admin/entry_tab_policy.rb +1 -0
  231. data/app/policies/pageflow/application_policy.rb +2 -0
  232. data/app/policies/pageflow/entry_policy.rb +7 -1
  233. data/app/policies/pageflow/entry_template_policy.rb +2 -1
  234. data/app/policies/pageflow/file_policy.rb +4 -7
  235. data/app/policies/pageflow/folder_policy.rb +18 -8
  236. data/app/policies/pageflow/membership_policy.rb +6 -4
  237. data/app/policies/pageflow/site_policy.rb +10 -3
  238. data/app/policies/pageflow/user_policy.rb +6 -4
  239. data/app/state_machines/pageflow/image_and_text_track_processing_state_machine.rb +3 -2
  240. data/app/state_machines/pageflow/media_encoding_state_machine.rb +5 -4
  241. data/app/views/admin/accounts/_entry_template_details.html.arb +2 -2
  242. data/app/views/admin/entries/_attributes_table.html.arb +6 -5
  243. data/app/views/admin/entries/_form.html.erb +2 -1
  244. data/app/views/admin/entries/_permalink_inputs.html.erb +9 -2
  245. data/app/views/admin/entry_templates/_form.html.erb +1 -3
  246. data/app/views/admin/site_root_entry/choose.html.erb +19 -0
  247. data/app/views/admin/sites/_fields.html.erb +14 -1
  248. data/app/views/admin/translations/_form.html.erb +31 -0
  249. data/app/views/admin/users/_attributes_table.html.arb +13 -0
  250. data/app/views/components/pageflow/admin/add_membership_button.rb +13 -12
  251. data/app/views/components/pageflow/admin/custom_scopes_renderer.rb +1 -0
  252. data/app/views/components/pageflow/admin/embed_code_field.rb +1 -0
  253. data/app/views/components/pageflow/admin/embedded_index_table.rb +9 -10
  254. data/app/views/components/pageflow/admin/entries_tab.rb +1 -0
  255. data/app/views/components/pageflow/admin/entry_publication_state_indicator.rb +6 -5
  256. data/app/views/components/pageflow/admin/entry_templates_tab.rb +2 -1
  257. data/app/views/components/pageflow/admin/entry_translations_tab.rb +102 -0
  258. data/app/views/components/pageflow/admin/entry_user_badge_list.rb +1 -0
  259. data/app/views/components/pageflow/admin/extensible_attributes_table.rb +9 -8
  260. data/app/views/components/pageflow/admin/features_tab.rb +1 -0
  261. data/app/views/components/pageflow/admin/grouped_folder_list.rb +1 -0
  262. data/app/views/components/pageflow/admin/icon_link_to.rb +1 -0
  263. data/app/views/components/pageflow/admin/members_tab.rb +4 -3
  264. data/app/views/components/pageflow/admin/membership_role_with_tooltip.rb +3 -2
  265. data/app/views/components/pageflow/admin/revisions_tab.rb +14 -10
  266. data/app/views/components/pageflow/admin/sites_tab.rb +1 -0
  267. data/app/views/components/pageflow/admin/tabs_view.rb +1 -0
  268. data/app/views/components/pageflow/admin/timestamp.rb +6 -5
  269. data/app/views/components/pageflow/admin/user_account_badge_list.rb +2 -3
  270. data/app/views/components/pageflow/admin/user_accounts_tab.rb +1 -0
  271. data/app/views/components/pageflow/admin/user_entries_tab.rb +1 -0
  272. data/app/views/components/pageflow/admin/users_tab.rb +1 -0
  273. data/app/views/pageflow/audio_files/_audio_file.json.jbuilder +0 -1
  274. data/app/views/pageflow/editor/config/_seeds.json.jbuilder +5 -3
  275. data/app/views/pageflow/editor/entries/_entry.json.jbuilder +2 -1
  276. data/app/views/pageflow/editor/entries/index.json.jbuilder +1 -1
  277. data/app/views/pageflow/editor/entries/seed.json.erb +1 -0
  278. data/app/views/pageflow/editor/entries/show.json.jbuilder +1 -1
  279. data/app/views/pageflow/editor/entry_publications/check.json.jbuilder +2 -2
  280. data/app/views/pageflow/editor/file_folders/_file_folder.json.jbuilder +5 -0
  281. data/app/views/pageflow/editor/file_folders/create.json.jbuilder +1 -0
  282. data/app/views/pageflow/editor/file_folders/index.json.jbuilder +3 -0
  283. data/app/views/pageflow/editor/files/_file.json.jbuilder +7 -14
  284. data/app/views/pageflow/editor/files/create.json.jbuilder +1 -1
  285. data/app/views/pageflow/editor/files/index.json.jbuilder +1 -1
  286. data/app/views/pageflow/editor/image_files/_image_file.json.jbuilder +3 -0
  287. data/app/views/pageflow/editor/sites/_site.json.jbuilder +1 -0
  288. data/app/views/pageflow/editor/video_files/_video_file.json.jbuilder +4 -3
  289. data/app/views/pageflow/entries/share_menu/_bluesky_link.html.erb +8 -0
  290. data/app/views/pageflow/entries/share_menu/_threads_link.html.erb +8 -0
  291. data/app/views/pageflow/files/_file.json.jbuilder +5 -3
  292. data/app/views/pageflow/image_files/_image_file.json.jbuilder +0 -1
  293. data/app/views/pageflow/review/comment_threads/_comment_thread.json.jbuilder +20 -0
  294. data/app/views/pageflow/review/comment_threads/create.json.jbuilder +2 -0
  295. data/app/views/pageflow/review/comment_threads/index.json.jbuilder +13 -0
  296. data/app/views/pageflow/review/comments/_comment.json.jbuilder +13 -0
  297. data/app/views/pageflow/review/comments/create.json.jbuilder +1 -0
  298. data/app/views/pageflow/sitemaps/index.xml.builder +9 -1
  299. data/app/views/pageflow/social_share/_entry_meta_tags.html.erb +1 -1
  300. data/app/views/pageflow/social_share/_page_meta_tags.html.erb +1 -1
  301. data/app/views/pageflow/structured_data/_entry.json.jbuilder +2 -3
  302. data/app/views/pageflow/video_files/_video_file.json.jbuilder +0 -1
  303. data/config/initializers/active_admin_patches.rb +3 -6
  304. data/config/initializers/admin_resource_tabs.rb +3 -0
  305. data/config/initializers/paperclip.rb +1 -0
  306. data/config/initializers/symmetric_encryption.rb +1 -1
  307. data/config/initializers/zencoder.rb +24 -13
  308. data/config/locales/de.yml +256 -12
  309. data/config/locales/en.yml +253 -15
  310. data/config/routes.rb +29 -13
  311. data/config/spring.rb +2 -1
  312. data/db/migrate/20140418225525_setup_schema.rb +2 -2
  313. data/db/migrate/20190306161431_copy_file_attributes_of_failed_uploads.rb +2 -2
  314. data/db/migrate/20190820152900_drop_accounts_themes.rb +2 -1
  315. data/db/migrate/20191202145757_create_pageflow_scrolled_sections.rb +11 -11
  316. data/db/migrate/20191202150657_create_pageflow_scrolled_chapters.rb +1 -1
  317. data/db/migrate/20191202154723_create_pageflow_scrolled_content_elements.rb +9 -9
  318. data/db/migrate/20191219143450_add_position_to_content_elements.rb +2 -1
  319. data/db/migrate/20200117133200_change_revision_appearance_option_default_and_null.rb +2 -1
  320. data/db/migrate/20200122115400_create_pageflow_entry_templates.rb +25 -25
  321. data/db/migrate/20200206134400_convert_legacy_scrolled_content_element_types.rb +3 -3
  322. data/db/migrate/20221215120856_associate_entry_templates_with_sites.rb +2 -2
  323. data/db/migrate/20230120092923_create_other_files.rb +2 -1
  324. data/db/migrate/20230419083307_create_pageflow_entry_translation_group.rb +9 -0
  325. data/db/migrate/20240612110434_add_cutoff_mode_name_to_sites.rb +5 -0
  326. data/db/migrate/20240918084059_create_pageflow_permalink_redirects.rb +14 -0
  327. data/db/migrate/20250508172234_ensure_scrolled_entries_have_main_storyline.rb +14 -0
  328. data/db/migrate/20250617090048_add_custom404_entry_to_sites.rb +5 -0
  329. data/db/migrate/20250617100000_add_perma_id_counter_to_entries.rb +28 -0
  330. data/db/migrate/20250722174123_add_perma_id_to_chapters.rb +10 -0
  331. data/db/migrate/20250725080603_add_perma_id_indexes_to_revision_components.rb +13 -0
  332. data/db/migrate/20250726000000_add_display_name_to_file_usages.rb +5 -0
  333. data/db/migrate/20251015000000_add_structured_data_type_name_to_revisions.rb +5 -0
  334. data/db/migrate/20251029142849_convert_legacy_social_embeds_to_unified_social_embed.rb +38 -0
  335. data/db/migrate/20251119083347_fix_main_storyline_positions.rb +16 -0
  336. data/db/migrate/20251119083348_ensure_scrolled_entries_have_excursion_storyline.rb +15 -0
  337. data/db/migrate/20260303000000_add_layout_version_to_revisions.rb +6 -0
  338. data/db/migrate/20260323000000_create_comment_threads_and_comments.rb +30 -0
  339. data/db/migrate/20260414000000_add_subject_range_to_comment_threads.rb +5 -0
  340. data/db/migrate/20260415000000_add_section_perma_id_to_comment_threads.rb +5 -0
  341. data/db/migrate/20260727000000_add_quote_to_comments.rb +5 -0
  342. data/db/migrate/20260728000000_create_file_folders.rb +17 -0
  343. data/db/migrate/20260805000000_add_attribute_translations_to_sites.rb +5 -0
  344. data/db/migrate/20260806000000_add_edited_at_to_comments.rb +5 -0
  345. data/db/migrate/20260810000000_create_pageflow_scrolled_lottie_files.rb +24 -0
  346. data/db/migrate/20260817000000_create_comment_thread_reads.rb +16 -0
  347. data/db/migrate/20260819000000_add_unread_comments_since_at_to_users.rb +18 -0
  348. data/entry_types/paged/app/assets/javascripts/pageflow_paged/dist/editor.js +12599 -5398
  349. data/entry_types/paged/app/assets/javascripts/pageflow_paged/dist/frontend.js +1275 -4560
  350. data/entry_types/paged/app/assets/javascripts/pageflow_paged/dist/react-client.js +20204 -22
  351. data/entry_types/paged/app/assets/javascripts/pageflow_paged/dist/react-server.js +18657 -19
  352. data/entry_types/paged/app/controllers/pageflow_paged/entries_controller.rb +1 -0
  353. data/entry_types/paged/app/helpers/pageflow_paged/page_background_asset_helper.rb +1 -1
  354. data/entry_types/paged/app/helpers/pageflow_paged/third_party_embed_consent_helper.rb +7 -7
  355. data/entry_types/paged/app/views/pageflow_paged/editor/entries/_seed.json.jbuilder +1 -0
  356. data/entry_types/paged/app/views/pageflow_paged/entries/show.html.erb +1 -0
  357. data/entry_types/paged/lib/pageflow_paged/plugin.rb +4 -0
  358. data/entry_types/paged/lib/pageflow_paged/react/page_type.rb +1 -1
  359. data/entry_types/paged/lib/pageflow_paged/react/widget_type.rb +3 -3
  360. data/entry_types/paged/lib/pageflow_paged.rb +1 -1
  361. data/entry_types/scrolled/app/assets/stylesheets/pageflow_scrolled/ui.scss +2 -0
  362. data/entry_types/scrolled/app/controllers/pageflow_scrolled/editor/chapters_controller.rb +5 -3
  363. data/entry_types/scrolled/app/controllers/pageflow_scrolled/editor/content_elements_controller.rb +35 -5
  364. data/entry_types/scrolled/app/controllers/pageflow_scrolled/editor/sections_controller.rb +4 -5
  365. data/entry_types/scrolled/app/controllers/pageflow_scrolled/entries_controller.rb +23 -2
  366. data/entry_types/scrolled/app/helpers/pageflow_scrolled/cache_helper.rb +3 -3
  367. data/entry_types/scrolled/app/helpers/pageflow_scrolled/editor/entry_json_seed_helper.rb +8 -5
  368. data/entry_types/scrolled/app/helpers/pageflow_scrolled/editor/seed_html_helper.rb +7 -3
  369. data/entry_types/scrolled/app/helpers/pageflow_scrolled/entry_json_seed_helper.rb +72 -10
  370. data/entry_types/scrolled/app/helpers/pageflow_scrolled/i18n_helper.rb +28 -13
  371. data/entry_types/scrolled/app/helpers/pageflow_scrolled/packs_helper.rb +41 -12
  372. data/entry_types/scrolled/app/helpers/pageflow_scrolled/react_server_side_rendering_helper.rb +23 -25
  373. data/entry_types/scrolled/app/helpers/pageflow_scrolled/sprockets_helper.rb +16 -0
  374. data/entry_types/scrolled/app/helpers/pageflow_scrolled/themes_helper.rb +189 -3
  375. data/entry_types/scrolled/app/models/pageflow_scrolled/chapter.rb +15 -14
  376. data/entry_types/scrolled/app/models/pageflow_scrolled/content_element.rb +61 -7
  377. data/entry_types/scrolled/app/models/pageflow_scrolled/lottie_file.rb +6 -0
  378. data/entry_types/scrolled/app/models/pageflow_scrolled/lottie_file_url_templates.rb +16 -0
  379. data/entry_types/scrolled/app/models/pageflow_scrolled/section.rb +12 -1
  380. data/entry_types/scrolled/app/models/pageflow_scrolled/storyline.rb +20 -1
  381. data/entry_types/scrolled/app/views/pageflow_scrolled/editor/entries/_head.html.erb +1 -0
  382. data/entry_types/scrolled/app/views/pageflow_scrolled/editor/sections/_section_with_content_elements.json.jbuilder +2 -2
  383. data/entry_types/scrolled/app/views/pageflow_scrolled/entries/show.html.erb +15 -12
  384. data/entry_types/scrolled/app/views/pageflow_scrolled/entry_json_seed/_entry.json.jbuilder +45 -17
  385. data/entry_types/scrolled/app/views/pageflow_scrolled/entry_json_seed/_entry_translations.json.jbuilder +14 -0
  386. data/entry_types/scrolled/app/views/pageflow_scrolled/entry_json_seed/_theme.json.jbuilder +34 -5
  387. data/entry_types/scrolled/app/views/pageflow_scrolled/favicons/_entry.html.erb +1 -1
  388. data/entry_types/scrolled/app/views/pageflow_scrolled/storylines/_storyline.json.jbuilder +7 -0
  389. data/entry_types/scrolled/config/configuration_schemas/pageflow-scrolled.json +414 -0
  390. data/entry_types/scrolled/config/locales/de.yml +1094 -78
  391. data/entry_types/scrolled/config/locales/en.yml +1083 -78
  392. data/entry_types/scrolled/config/routes.rb +18 -8
  393. data/entry_types/scrolled/lib/generators/pageflow_scrolled/install/install_generator.rb +11 -11
  394. data/entry_types/scrolled/lib/generators/pageflow_scrolled/install/templates/theme/logoDarkVariantDesktop.svg +56 -0
  395. data/entry_types/scrolled/lib/generators/pageflow_scrolled/install/templates/theme/logoDarkVariantMobile.svg +22 -0
  396. data/entry_types/scrolled/lib/generators/pageflow_scrolled/install/templates/themes_plugin.rb.tt +26 -11
  397. data/entry_types/scrolled/lib/pageflow_scrolled/additional_packs.rb +27 -8
  398. data/entry_types/scrolled/lib/pageflow_scrolled/additional_seed_data.rb +2 -2
  399. data/entry_types/scrolled/lib/pageflow_scrolled/additional_theme_assets.rb +27 -0
  400. data/entry_types/scrolled/lib/pageflow_scrolled/configuration.rb +67 -10
  401. data/entry_types/scrolled/lib/pageflow_scrolled/configuration_schemas.rb +91 -0
  402. data/entry_types/scrolled/lib/pageflow_scrolled/content_element_consent_vendors.rb +1 -1
  403. data/entry_types/scrolled/lib/pageflow_scrolled/engine.rb +2 -1
  404. data/entry_types/scrolled/lib/pageflow_scrolled/entry_file_reference_locations.rb +71 -0
  405. data/entry_types/scrolled/lib/pageflow_scrolled/entry_structured_data_types/faq_page.rb +58 -0
  406. data/entry_types/scrolled/lib/pageflow_scrolled/file_reference_locations.rb +69 -0
  407. data/entry_types/scrolled/lib/pageflow_scrolled/plugin.rb +222 -19
  408. data/entry_types/scrolled/lib/pageflow_scrolled/react_widget_type.rb +2 -4
  409. data/entry_types/scrolled/lib/pageflow_scrolled/seeds.rb +76 -54
  410. data/entry_types/scrolled/lib/pageflow_scrolled/theme_options_default_scale.rb +35 -0
  411. data/entry_types/scrolled/lib/pageflow_scrolled.rb +14 -1
  412. data/entry_types/scrolled/lib/tasks/pageflow_scrolled/storybook.rake +24 -5
  413. data/entry_types/scrolled/package/config/configurationSchemas.js +70 -0
  414. data/entry_types/scrolled/package/config/rollup/configurationSchemas.js +19 -0
  415. data/entry_types/scrolled/package/config/webpack.js +94 -0
  416. data/entry_types/scrolled/package/contentElements/hotspots-frontend.css +1 -0
  417. data/entry_types/scrolled/package/contentElements/hotspots-frontend.js +1795 -0
  418. data/entry_types/scrolled/package/contentElements/inlineBeforeAfter-frontend.css +1 -0
  419. data/entry_types/scrolled/package/contentElements/inlineBeforeAfter-frontend.js +219 -0
  420. data/entry_types/scrolled/package/contentElements/lottieAnimation-frontend.css +1 -0
  421. data/entry_types/scrolled/package/contentElements/lottieAnimation-frontend.js +244 -0
  422. data/entry_types/scrolled/package/contentElements/socialEmbed-frontend.css +1 -0
  423. data/entry_types/scrolled/package/contentElements/socialEmbed-frontend.js +823 -0
  424. data/entry_types/scrolled/package/contentElements/tikTokEmbed-frontend.css +1 -0
  425. data/entry_types/scrolled/package/contentElements/tikTokEmbed-frontend.js +109 -0
  426. data/entry_types/scrolled/package/contentElements/twitterEmbed-frontend.css +1 -0
  427. data/entry_types/scrolled/package/contentElements/twitterEmbed-frontend.js +277 -0
  428. data/entry_types/scrolled/package/contentElements/videoEmbed-frontend.css +1 -0
  429. data/entry_types/scrolled/package/contentElements/videoEmbed-frontend.js +244 -0
  430. data/entry_types/scrolled/package/contentElements-editor.js +3155 -583
  431. data/entry_types/scrolled/package/contentElements-frontend.css +1 -1
  432. data/entry_types/scrolled/package/contentElements-frontend.js +3375 -1585
  433. data/entry_types/scrolled/package/editor.css +1 -0
  434. data/entry_types/scrolled/package/editor.js +8452 -2867
  435. data/entry_types/scrolled/package/entryState.js +1600 -0
  436. data/entry_types/scrolled/package/frontend/FloatingPortalRootProvider-20c600de.js +1979 -0
  437. data/entry_types/scrolled/package/frontend/FloatingPortalRootProvider-58189220.js +2538 -0
  438. data/entry_types/scrolled/package/frontend/{PhonePlatformContext-4ec6b2de.js → PhonePlatformContext-035a99fa.js} +9 -15
  439. data/entry_types/scrolled/package/frontend/PhonePlatformContext-9da43797.js +33 -0
  440. data/entry_types/scrolled/package/frontend/Placeholder.module-bf0966b4.js +5267 -0
  441. data/entry_types/scrolled/package/frontend/Placeholder.module-d22a9335.js +4180 -0
  442. data/entry_types/scrolled/package/frontend/ThemeIcon-ab834849.js +754 -0
  443. data/entry_types/scrolled/package/frontend/ThemeIcon-f6cb3a8f.js +773 -0
  444. data/entry_types/scrolled/package/frontend/ToggleFullscreenCornerButton-30fd1124.js +39 -0
  445. data/entry_types/scrolled/package/frontend/ToggleFullscreenCornerButton-61c25224.js +72 -0
  446. data/entry_types/scrolled/package/frontend/Viewer-1074fd59.js +147 -0
  447. data/entry_types/scrolled/package/frontend/Viewer-1482ca34.js +480 -0
  448. data/entry_types/scrolled/package/frontend/Viewer-70504231.js +210 -0
  449. data/entry_types/scrolled/package/frontend/{Viewer-1bb5597c.js → Viewer-cc8d4072.js} +100 -135
  450. data/entry_types/scrolled/package/frontend/Wavesurfer-656e3c36.js +302 -0
  451. data/entry_types/scrolled/package/frontend/Wavesurfer-b753b838.js +385 -0
  452. data/entry_types/scrolled/package/frontend/commenting.css +1 -0
  453. data/entry_types/scrolled/package/frontend/extensions-167ae143.js +1455 -0
  454. data/entry_types/scrolled/package/frontend/extensions-2318f92b.js +5918 -0
  455. data/entry_types/scrolled/package/frontend/extensions-a461fd13.js +4890 -0
  456. data/entry_types/scrolled/package/frontend/extensions-c98269fd.js +1982 -0
  457. data/entry_types/scrolled/package/frontend/i18n-493cd2a6.js +93 -0
  458. data/entry_types/scrolled/package/frontend/i18n-e06cac14.js +100 -0
  459. data/entry_types/scrolled/package/frontend/{index-11f32f10.js → index-2e5546f7.js} +39 -54
  460. data/entry_types/scrolled/package/frontend/index-de2b418d.js +130 -0
  461. data/entry_types/scrolled/package/frontend/index.css +1 -1
  462. data/entry_types/scrolled/package/frontend/index.js +1976 -1367
  463. data/entry_types/scrolled/package/frontend/inlineEditing.css +1 -0
  464. data/entry_types/scrolled/package/frontend/useContentElementEditorState-a084912e.js +25 -0
  465. data/entry_types/scrolled/package/frontend/useDelayedBoolean-a387d85b.js +24 -0
  466. data/entry_types/scrolled/package/frontend/usePhonePlatform-4ecb96d4.js +8 -0
  467. data/entry_types/scrolled/package/frontend/usePhonePlatform-ce9ff08d.js +8 -0
  468. data/entry_types/scrolled/package/frontend-server.js +20 -210
  469. data/entry_types/scrolled/package/package.json +22 -12
  470. data/entry_types/scrolled/package/review.css +1 -0
  471. data/entry_types/scrolled/package/review.js +3306 -0
  472. data/entry_types/scrolled/package/testHelpers.js +952 -239
  473. data/entry_types/scrolled/package/values/widgets.module.css +18 -0
  474. data/entry_types/scrolled/package/widgets/consentBar.css +1 -1
  475. data/entry_types/scrolled/package/widgets/consentBar.js +275 -315
  476. data/entry_types/scrolled/package/widgets/defaultNavigation.css +2 -2
  477. data/entry_types/scrolled/package/widgets/defaultNavigation.js +961 -432
  478. data/entry_types/scrolled/package/widgets/excursionSheet.css +1 -0
  479. data/entry_types/scrolled/package/widgets/excursionSheet.js +225 -0
  480. data/entry_types/scrolled/package/widgets/iconInlineFileRights.css +1 -1
  481. data/entry_types/scrolled/package/widgets/iconInlineFileRights.js +62 -32
  482. data/entry_types/scrolled/package/widgets/iconScrollIndicator.css +1 -0
  483. data/entry_types/scrolled/package/widgets/iconScrollIndicator.js +67 -0
  484. data/entry_types/scrolled/package/widgets/mainStorylineSheet.css +1 -0
  485. data/entry_types/scrolled/package/widgets/mainStorylineSheet.js +100 -0
  486. data/entry_types/scrolled/package/widgets/textInlineFileRights.css +1 -1
  487. data/entry_types/scrolled/package/widgets/textInlineFileRights.js +48 -26
  488. data/entry_types/scrolled/package/widgets-server.js +2 -0
  489. data/entry_types/scrolled/spec/factories/chapters.rb +14 -1
  490. data/entry_types/scrolled/spec/factories/content_elements.rb +1 -1
  491. data/entry_types/scrolled/spec/factories/lottie_files.rb +29 -0
  492. data/entry_types/scrolled/spec/fixtures/animation.lottie +0 -0
  493. data/entry_types/scrolled/spec/fixtures/font.woff +0 -0
  494. data/entry_types/scrolled/spec/fixtures/font.woff2 +0 -0
  495. data/lib/generators/pageflow/active_admin_initializer/active_admin_initializer_generator.rb +5 -3
  496. data/lib/generators/pageflow/assets/assets_generator.rb +7 -5
  497. data/lib/generators/pageflow/cancan/cancan_generator.rb +2 -1
  498. data/lib/generators/pageflow/cancan/templates/ability.rb +1 -1
  499. data/lib/generators/pageflow/error_pages/error_pages_generator.rb +2 -1
  500. data/lib/generators/pageflow/initializer/initializer_generator.rb +3 -2
  501. data/lib/generators/pageflow/install/install_generator.rb +2 -2
  502. data/lib/generators/pageflow/procfile/procfile_generator.rb +2 -1
  503. data/lib/generators/pageflow/resque/resque_generator.rb +4 -4
  504. data/lib/generators/pageflow/resque/templates/resque.rake +1 -1
  505. data/lib/generators/pageflow/routes/routes_generator.rb +4 -2
  506. data/lib/generators/pageflow/seeds/seeds_generator.rb +3 -2
  507. data/lib/generators/pageflow/seeds/templates/seeds.rb +6 -6
  508. data/lib/generators/pageflow/theme/theme_generator.rb +2 -1
  509. data/lib/generators/pageflow/user/user_generator.rb +2 -1
  510. data/lib/pageflow/ability_mixin.rb +25 -15
  511. data/lib/pageflow/active_admin_patches/views/attributes_table.rb +1 -0
  512. data/lib/pageflow/active_admin_patches/views/pages/base.rb +3 -2
  513. data/lib/pageflow/active_admin_patches/views/table_for.rb +1 -0
  514. data/lib/pageflow/additional_headers.rb +27 -0
  515. data/lib/pageflow/admin/attributes_table_rows.rb +3 -3
  516. data/lib/pageflow/admin/tabs.rb +1 -1
  517. data/lib/pageflow/built_in_file_type.rb +1 -1
  518. data/lib/pageflow/built_in_page_types_plugin.rb +1 -1
  519. data/lib/pageflow/built_in_widget_type.rb +2 -2
  520. data/lib/pageflow/built_in_widget_types_plugin.rb +2 -1
  521. data/lib/pageflow/configuration.rb +183 -18
  522. data/lib/pageflow/cutoff_modes.rb +39 -0
  523. data/lib/pageflow/deprecation.rb +19 -0
  524. data/lib/pageflow/editor_controller.rb +6 -10
  525. data/lib/pageflow/engine.rb +5 -6
  526. data/lib/pageflow/entries_controller_env_helper.rb +32 -2
  527. data/lib/pageflow/entry_export_import/attachment_files.rb +1 -1
  528. data/lib/pageflow/entry_export_import/entry_serialization.rb +9 -8
  529. data/lib/pageflow/entry_export_import/file_mappings.rb +1 -0
  530. data/lib/pageflow/entry_export_import/page_type_versions.rb +2 -2
  531. data/lib/pageflow/entry_export_import/revision_serialization/import.rb +11 -1
  532. data/lib/pageflow/entry_export_import/revision_serialization.rb +3 -0
  533. data/lib/pageflow/entry_export_import.rb +3 -3
  534. data/lib/pageflow/entry_structured_data_types.rb +43 -0
  535. data/lib/pageflow/entry_type_configuration.rb +1 -0
  536. data/lib/pageflow/entry_types.rb +2 -2
  537. data/lib/pageflow/feature.rb +1 -1
  538. data/lib/pageflow/features.rb +4 -6
  539. data/lib/pageflow/file_importers.rb +4 -4
  540. data/lib/pageflow/file_type.rb +5 -5
  541. data/lib/pageflow/file_types.rb +4 -4
  542. data/lib/pageflow/global_config_api.rb +5 -6
  543. data/lib/pageflow/help_entries.rb +7 -7
  544. data/lib/pageflow/help_entry.rb +1 -1
  545. data/lib/pageflow/hooks.rb +1 -1
  546. data/lib/pageflow/nested_revision_component.rb +9 -27
  547. data/lib/pageflow/news_item_api.rb +1 -1
  548. data/lib/pageflow/oembed_providers.rb +42 -0
  549. data/lib/pageflow/page_type.rb +2 -3
  550. data/lib/pageflow/page_types.rb +3 -3
  551. data/lib/pageflow/paperclip_interpolations/support.rb +1 -1
  552. data/lib/pageflow/paperclip_processors/vtt.rb +1 -0
  553. data/lib/pageflow/paperclip_processors/webp.rb +2 -2
  554. data/lib/pageflow/partial_editor_fragment_renderer.rb +2 -2
  555. data/lib/pageflow/plugin.rb +1 -2
  556. data/lib/pageflow/primary_domain_entry_redirect.rb +6 -0
  557. data/lib/pageflow/quota.rb +9 -5
  558. data/lib/pageflow/quotas.rb +1 -1
  559. data/lib/pageflow/rails_version.rb +14 -2
  560. data/lib/pageflow/react.rb +1 -1
  561. data/lib/pageflow/revision_component.rb +8 -40
  562. data/lib/pageflow/revision_components.rb +16 -6
  563. data/lib/pageflow/seeds.rb +12 -16
  564. data/lib/pageflow/theme.rb +1 -1
  565. data/lib/pageflow/theme_customizations.rb +10 -10
  566. data/lib/pageflow/themes.rb +44 -3
  567. data/lib/pageflow/user_mixin.rb +18 -6
  568. data/lib/pageflow/version.rb +1 -1
  569. data/lib/pageflow/widget_type.rb +4 -4
  570. data/lib/pageflow/widget_types.rb +9 -9
  571. data/lib/pageflow/zencoder_api.rb +32 -42
  572. data/lib/pageflow/zencoder_audio_output_definition.rb +14 -13
  573. data/lib/pageflow/zencoder_meta_data_output_definition.rb +3 -2
  574. data/lib/pageflow/zencoder_output_definition.rb +16 -14
  575. data/lib/pageflow/zencoder_video_output_definition.rb +140 -88
  576. data/lib/pageflow.rb +8 -2
  577. data/lib/tasks/pageflow_tasks.rake +1 -1
  578. data/package/config/eslint-rules/documented-in-toc.js +125 -0
  579. data/package/config/jest/index.js +0 -1
  580. data/package/config/webpack.js +0 -1
  581. data/package/config/webpack5.js +0 -1
  582. data/package/editor.js +4880 -2313
  583. data/package/frontend.js +710 -1223
  584. data/package/package.json +3 -1
  585. data/package/review.js +384 -0
  586. data/package/testHelpers.js +257 -243
  587. data/package/ui.js +1487 -813
  588. data/package/vendor/stubs/wysihtml5.js +2 -0
  589. data/spec/factories/accounts.rb +37 -22
  590. data/spec/factories/audio_files.rb +2 -2
  591. data/spec/factories/authentication_tokens.rb +4 -4
  592. data/spec/factories/chapters.rb +4 -4
  593. data/spec/factories/comment_thread_reads.rb +10 -0
  594. data/spec/factories/comment_threads.rb +10 -0
  595. data/spec/factories/comments.rb +9 -0
  596. data/spec/factories/draft_entries.rb +14 -6
  597. data/spec/factories/edit_locks.rb +1 -1
  598. data/spec/factories/entries.rb +28 -19
  599. data/spec/factories/entry_translation_groups.rb +6 -0
  600. data/spec/factories/file_folders.rb +8 -0
  601. data/spec/factories/file_usages.rb +2 -1
  602. data/spec/factories/folders.rb +1 -1
  603. data/spec/factories/image_files.rb +2 -2
  604. data/spec/factories/pages.rb +3 -3
  605. data/spec/factories/published_entries.rb +28 -8
  606. data/spec/factories/revisions.rb +1 -1
  607. data/spec/factories/sites.rb +6 -0
  608. data/spec/factories/test_multi_attachment_files.rb +1 -1
  609. data/spec/factories/text_track_files.rb +1 -1
  610. data/spec/factories/uploadable_files.rb +1 -1
  611. data/spec/factories/users.rb +6 -6
  612. data/spec/factories/video_files.rb +2 -2
  613. data/spec/factories/widgets.rb +1 -1
  614. data/spec/fixtures/7x15_rotated.jpg +0 -0
  615. data/vendor/assets/javascripts/wysihtml-toolbar.js +19288 -0
  616. metadata +266 -92
  617. data/app/assets/stylesheets/pageflow/editor/select_button.scss +0 -51
  618. data/config/initializers/revision_components.rb +0 -5
  619. data/config/locales/twitter_to_x.de.yml +0 -6
  620. data/config/locales/twitter_to_x.en.yml +0 -6
  621. data/entry_types/scrolled/app/helpers/pageflow_scrolled/generated_media_queries_helper.rb +0 -55
  622. data/entry_types/scrolled/config/locales/twitter_to_x.de.yml +0 -12
  623. data/entry_types/scrolled/config/locales/twitter_to_x.en.yml +0 -12
  624. data/entry_types/scrolled/config/locales/vr_image_projection.de.yml +0 -24
  625. data/entry_types/scrolled/config/locales/vr_image_projection.en.yml +0 -24
  626. data/entry_types/scrolled/package/frontend/EditableInlineText.module-ebd22921.js +0 -5497
  627. data/entry_types/scrolled/package/frontend/ToggleFullscreenCornerButton-b79159cc.js +0 -107
  628. data/entry_types/scrolled/package/frontend/Viewer-1ecf3375.js +0 -154
  629. data/entry_types/scrolled/package/frontend/Wavesurfer-7d9cf1b7.js +0 -333
  630. data/entry_types/scrolled/package/frontend/arrowRight-62998af9.js +0 -77
  631. data/entry_types/scrolled/package/frontend/components-024a9893.js +0 -2853
  632. data/entry_types/scrolled/package/frontend/createSuper-d0f30da3.js +0 -115
  633. data/entry_types/scrolled/package/frontend/i18n-ce13a8bf.js +0 -1129
  634. data/entry_types/scrolled/package/frontend/useContentElementEditorState-4f4c3cf6.js +0 -52
  635. data/lib/generators/pageflow/resque/templates/resque_enqueue_after_commit_patch.rb +0 -25
  636. data/package/vendor/jquery.minicolors.js +0 -1108
@@ -0,0 +1,3306 @@
1
+ import React, { useMemo, createContext, useReducer, useContext, useEffect, useCallback, useState, useRef, forwardRef } from 'react';
2
+ import { useContentElement, useSectionPermaIdOfSubject, useEntryStructureWithContentElements } from 'pageflow-scrolled/entryState';
3
+ import classNames from 'classnames';
4
+ import { useI18n, useFloatingPortalRoot, useLocale } from 'pageflow-scrolled/frontend';
5
+ import { useFloating, offset, flip, shift, autoUpdate, useClick, useDismiss, useRole, useListNavigation, useTypeahead, useInteractions, FloatingPortal, FloatingFocusManager } from '@floating-ui/react';
6
+ import { Text, Node as Node$1, Range, Point } from 'slate';
7
+
8
+ // Kept out of the editor and frontend registries to keep Slate out of the
9
+ // always-on frontend bundle.
10
+ class ContentElementTypeRegistry {
11
+ constructor() {
12
+ this.types = {};
13
+ }
14
+ register(typeName, options) {
15
+ this.types[typeName] = options;
16
+ }
17
+ findCompareRanges(typeName) {
18
+ var _this$types$typeName;
19
+ return (_this$types$typeName = this.types[typeName]) === null || _this$types$typeName === void 0 ? void 0 : _this$types$typeName.compareRanges;
20
+ }
21
+ findExtractQuote(typeName) {
22
+ var _this$types$typeName2;
23
+ return (_this$types$typeName2 = this.types[typeName]) === null || _this$types$typeName2 === void 0 ? void 0 : _this$types$typeName2.extractQuote;
24
+ }
25
+ }
26
+
27
+ const review = {
28
+ contentElementTypes: new ContentElementTypeRegistry()
29
+ };
30
+
31
+ function postCreateCommentThreadMessage({
32
+ subjectType,
33
+ subjectId,
34
+ subjectRange,
35
+ sectionPermaId,
36
+ body,
37
+ quote
38
+ }) {
39
+ window.top.postMessage({
40
+ type: 'CREATE_COMMENT_THREAD',
41
+ payload: {
42
+ subjectType,
43
+ subjectId,
44
+ subjectRange,
45
+ sectionPermaId,
46
+ body,
47
+ quote
48
+ }
49
+ }, window.location.origin);
50
+ }
51
+ function postCreateCommentMessage({
52
+ threadId,
53
+ body,
54
+ quote
55
+ }) {
56
+ window.top.postMessage({
57
+ type: 'CREATE_COMMENT',
58
+ payload: {
59
+ threadId,
60
+ body,
61
+ quote
62
+ }
63
+ }, window.location.origin);
64
+ }
65
+ function postUpdateCommentMessage({
66
+ threadId,
67
+ commentId,
68
+ body
69
+ }) {
70
+ window.top.postMessage({
71
+ type: 'UPDATE_COMMENT',
72
+ payload: {
73
+ threadId,
74
+ commentId,
75
+ body
76
+ }
77
+ }, window.location.origin);
78
+ }
79
+ function postSetCommentDraftMessage(draft) {
80
+ window.top.postMessage({
81
+ type: 'SET_COMMENT_DRAFT',
82
+ payload: draft
83
+ }, window.location.origin);
84
+ }
85
+ function postMarkThreadsReadMessage(permaIds) {
86
+ window.top.postMessage({
87
+ type: 'MARK_THREADS_READ',
88
+ payload: {
89
+ permaIds
90
+ }
91
+ }, window.location.origin);
92
+ }
93
+ function postUpdateThreadMessage({
94
+ threadId,
95
+ resolved
96
+ }) {
97
+ window.top.postMessage({
98
+ type: 'UPDATE_THREAD',
99
+ payload: {
100
+ threadId,
101
+ resolved
102
+ }
103
+ }, window.location.origin);
104
+ }
105
+ function postReviewStateResetMessage(targetWindow, state) {
106
+ targetWindow.postMessage({
107
+ type: 'REVIEW_STATE_RESET',
108
+ payload: state
109
+ }, window.location.origin);
110
+ }
111
+ function postReviewStateThreadChangeMessage(targetWindow, thread) {
112
+ targetWindow.postMessage({
113
+ type: 'REVIEW_STATE_THREAD_CHANGE',
114
+ payload: thread
115
+ }, window.location.origin);
116
+ }
117
+ function postReviewStateDraftsChangeMessage(targetWindow, drafts) {
118
+ targetWindow.postMessage({
119
+ type: 'REVIEW_STATE_DRAFTS_CHANGE',
120
+ payload: drafts
121
+ }, window.location.origin);
122
+ }
123
+ function postReviewStateReadsChangeMessage(targetWindow, reads) {
124
+ targetWindow.postMessage({
125
+ type: 'REVIEW_STATE_READS_CHANGE',
126
+ payload: reads
127
+ }, window.location.origin);
128
+ }
129
+
130
+ // Matches Pageflow::Comment::QUOTE_LIMIT.
131
+ const QUOTE_LIMIT = 4000;
132
+
133
+ /**
134
+ * @private
135
+ */
136
+ function useSubjectQuote({
137
+ subjectType,
138
+ subjectId,
139
+ subjectRange
140
+ }) {
141
+ const contentElement = useContentElement({
142
+ permaId: subjectId
143
+ });
144
+ const isContentElement = subjectType === 'ContentElement';
145
+ return useMemo(() => {
146
+ if (!isContentElement || !contentElement) return null;
147
+ const extractQuote = review.contentElementTypes.findExtractQuote(contentElement.type);
148
+ const quote = extractQuote ? extractQuote(contentElement.props, subjectRange) : null;
149
+ return quote ? quote.slice(0, QUOTE_LIMIT) : null;
150
+ }, [isContentElement, subjectRange, contentElement]);
151
+ }
152
+
153
+ var _jsxFileName = "/home/tfischbach/code/pageflow/entry_types/scrolled/package/src/review/ReviewStateProvider.js";
154
+ const ReviewStateContext = createContext(null);
155
+ const CommentDraftsContext = createContext(null);
156
+ const CommentThreadReadsContext = createContext(null);
157
+ const emptyCommentThreadReads = {};
158
+ function ReviewStateProvider({
159
+ initialState,
160
+ initialDrafts,
161
+ setDraft,
162
+ children
163
+ }) {
164
+ const [state, dispatch] = useReducer(reducer, {
165
+ initialState,
166
+ initialDrafts
167
+ }, initState);
168
+ useStateMessages(dispatch);
169
+ const value = useMemo(() => ({
170
+ currentUser: state.currentUser,
171
+ commentThreads: Object.values(state.threads)
172
+ }), [state.currentUser, state.threads]);
173
+ const draftsValue = useDraftsValue({
174
+ drafts: state.drafts,
175
+ setDraft,
176
+ dispatch
177
+ });
178
+ const readsValue = useReadsValue(state.commentThreadReads);
179
+ return /*#__PURE__*/React.createElement(ReviewStateContext.Provider, {
180
+ value: value,
181
+ __self: this,
182
+ __source: {
183
+ fileName: _jsxFileName,
184
+ lineNumber: 40,
185
+ columnNumber: 5
186
+ }
187
+ }, /*#__PURE__*/React.createElement(CommentDraftsContext.Provider, {
188
+ value: draftsValue,
189
+ __self: this,
190
+ __source: {
191
+ fileName: _jsxFileName,
192
+ lineNumber: 41,
193
+ columnNumber: 7
194
+ }
195
+ }, /*#__PURE__*/React.createElement(CommentThreadReadsContext.Provider, {
196
+ value: readsValue,
197
+ __self: this,
198
+ __source: {
199
+ fileName: _jsxFileName,
200
+ lineNumber: 42,
201
+ columnNumber: 9
202
+ }
203
+ }, children)));
204
+ }
205
+ function useCommentDraft({
206
+ threadId,
207
+ subjectType,
208
+ subjectId
209
+ }) {
210
+ const {
211
+ drafts,
212
+ setDraft
213
+ } = useContext(CommentDraftsContext);
214
+ return [drafts[draftKey({
215
+ threadId,
216
+ subjectType,
217
+ subjectId
218
+ })], useCallback(body => setDraft(threadId ? {
219
+ threadId,
220
+ body
221
+ } : {
222
+ subjectType,
223
+ subjectId,
224
+ body
225
+ }), [setDraft, threadId, subjectType, subjectId])];
226
+ }
227
+ function useCreateCommentThread({
228
+ subjectType,
229
+ subjectId,
230
+ subjectRange
231
+ }) {
232
+ const {
233
+ createThread
234
+ } = useContext(CommentDraftsContext);
235
+ const sectionPermaId = useSectionPermaIdOfSubject({
236
+ subjectType,
237
+ subjectId
238
+ });
239
+ const quote = useSubjectQuote({
240
+ subjectType,
241
+ subjectId,
242
+ subjectRange
243
+ });
244
+ return useCallback(body => createThread({
245
+ subjectType,
246
+ subjectId,
247
+ subjectRange,
248
+ sectionPermaId,
249
+ body,
250
+ quote
251
+ }), [createThread, subjectType, subjectId, subjectRange, sectionPermaId, quote]);
252
+ }
253
+ function useCreateComment({
254
+ threadId,
255
+ subjectType,
256
+ subjectId,
257
+ subjectRange
258
+ }) {
259
+ const {
260
+ createComment
261
+ } = useContext(CommentDraftsContext);
262
+ const quote = useSubjectQuote({
263
+ subjectType,
264
+ subjectId,
265
+ subjectRange
266
+ });
267
+ return useCallback(body => createComment({
268
+ threadId,
269
+ body,
270
+ quote
271
+ }), [createComment, threadId, quote]);
272
+ }
273
+ function useUpdateComment({
274
+ threadId,
275
+ commentId
276
+ }) {
277
+ return useCallback(body => postUpdateCommentMessage({
278
+ threadId,
279
+ commentId,
280
+ body
281
+ }), [threadId, commentId]);
282
+ }
283
+ function useCurrentUser() {
284
+ const context = useContext(ReviewStateContext);
285
+ return context ? context.currentUser : null;
286
+ }
287
+ function useCommentThreadReads() {
288
+ const context = useContext(CommentThreadReadsContext);
289
+ return context ? context.commentThreadReads : emptyCommentThreadReads;
290
+ }
291
+ function useMarkThreadRead() {
292
+ const context = useContext(CommentThreadReadsContext);
293
+ return context === null || context === void 0 ? void 0 : context.markThreadRead;
294
+ }
295
+ function useCommentThread(threadId) {
296
+ const context = useContext(ReviewStateContext);
297
+ return context === null || context === void 0 ? void 0 : context.commentThreads.find(t => t.id === threadId);
298
+ }
299
+ function useCommentThreads({
300
+ subjectType,
301
+ subjectId,
302
+ subjectRange,
303
+ resolution = 'all',
304
+ revealedThreadId
305
+ } = {}) {
306
+ const context = useContext(ReviewStateContext);
307
+ const commentThreads = context ? context.commentThreads : [];
308
+ const hasSubject = subjectType !== undefined;
309
+ return useMemo(() => {
310
+ const rangeKey = subjectRange ? JSON.stringify(subjectRange) : undefined;
311
+ return commentThreads.filter(thread => (!hasSubject || thread.subjectType === subjectType && thread.subjectId === subjectId && (!rangeKey || JSON.stringify(thread.subjectRange) === rangeKey)) && (matchesResolution(thread, resolution) || thread.id === revealedThreadId));
312
+ }, [commentThreads, hasSubject, subjectType, subjectId, subjectRange, resolution, revealedThreadId]);
313
+ }
314
+ function matchesResolution(thread, resolution) {
315
+ return resolution === 'all' || resolution === 'unresolved' && !thread.resolvedAt || resolution === 'resolved' && !!thread.resolvedAt;
316
+ }
317
+ function useStateMessages(dispatch) {
318
+ useEffect(() => {
319
+ function handleMessage(event) {
320
+ if (window.location.href.indexOf(event.origin) !== 0) return;
321
+ const {
322
+ type,
323
+ payload
324
+ } = event.data;
325
+ if (type === 'REVIEW_STATE_RESET') {
326
+ dispatch({
327
+ type: 'RESET',
328
+ payload
329
+ });
330
+ } else if (type === 'REVIEW_STATE_THREAD_CHANGE') {
331
+ dispatch({
332
+ type: 'UPSERT_THREAD',
333
+ payload
334
+ });
335
+ } else if (type === 'REVIEW_STATE_DRAFTS_CHANGE') {
336
+ dispatch({
337
+ type: 'SET_DRAFTS',
338
+ payload
339
+ });
340
+ } else if (type === 'REVIEW_STATE_READS_CHANGE') {
341
+ dispatch({
342
+ type: 'SET_READS',
343
+ payload
344
+ });
345
+ }
346
+ }
347
+ window.addEventListener('message', handleMessage);
348
+ return () => window.removeEventListener('message', handleMessage);
349
+ }, [dispatch]);
350
+ }
351
+ function useDraftsValue({
352
+ drafts,
353
+ setDraft,
354
+ dispatch
355
+ }) {
356
+ const createThread = useCallback(payload => {
357
+ const {
358
+ subjectType,
359
+ subjectId,
360
+ body
361
+ } = payload;
362
+ dispatch({
363
+ type: 'SET_DRAFT',
364
+ payload: {
365
+ subjectType,
366
+ subjectId,
367
+ body,
368
+ pending: true
369
+ }
370
+ });
371
+ postCreateCommentThreadMessage(payload);
372
+ }, [dispatch]);
373
+ const createComment = useCallback(payload => {
374
+ const {
375
+ threadId,
376
+ body
377
+ } = payload;
378
+ dispatch({
379
+ type: 'SET_DRAFT',
380
+ payload: {
381
+ threadId,
382
+ body,
383
+ pending: true
384
+ }
385
+ });
386
+ postCreateCommentMessage(payload);
387
+ }, [dispatch]);
388
+ return useMemo(() => ({
389
+ drafts,
390
+ setDraft: setDraft || postSetCommentDraftMessage,
391
+ createThread,
392
+ createComment
393
+ }), [drafts, setDraft, createThread, createComment]);
394
+ }
395
+ function useReadsValue(commentThreadReads) {
396
+ const markThreadRead = useCallback(permaId => postMarkThreadsReadMessage([permaId]), []);
397
+ return useMemo(() => ({
398
+ commentThreadReads,
399
+ markThreadRead
400
+ }), [commentThreadReads, markThreadRead]);
401
+ }
402
+ function initState({
403
+ initialState,
404
+ initialDrafts
405
+ }) {
406
+ const empty = {
407
+ currentUser: null,
408
+ threads: {},
409
+ drafts: initialDrafts || {},
410
+ commentThreadReads: {}
411
+ };
412
+ if (initialState) {
413
+ return reducer(empty, {
414
+ type: 'RESET',
415
+ payload: initialState
416
+ });
417
+ }
418
+ return empty;
419
+ }
420
+ function reducer(state, action) {
421
+ switch (action.type) {
422
+ case 'RESET':
423
+ {
424
+ const threads = {};
425
+ action.payload.commentThreads.forEach(thread => {
426
+ threads[thread.id] = thread;
427
+ });
428
+ return {
429
+ ...state,
430
+ currentUser: action.payload.currentUser,
431
+ threads,
432
+ commentThreadReads: action.payload.commentThreadReads || {}
433
+ };
434
+ }
435
+ case 'SET_READS':
436
+ return {
437
+ ...state,
438
+ commentThreadReads: action.payload
439
+ };
440
+ case 'SET_DRAFTS':
441
+ return {
442
+ ...state,
443
+ drafts: action.payload
444
+ };
445
+ case 'SET_DRAFT':
446
+ return {
447
+ ...state,
448
+ drafts: {
449
+ ...state.drafts,
450
+ [draftKey(action.payload)]: action.payload
451
+ }
452
+ };
453
+ case 'UPSERT_THREAD':
454
+ return {
455
+ ...state,
456
+ threads: {
457
+ ...state.threads,
458
+ [action.payload.id]: action.payload
459
+ }
460
+ };
461
+ default:
462
+ return state;
463
+ }
464
+ }
465
+ function draftKey({
466
+ threadId,
467
+ subjectType,
468
+ subjectId
469
+ }) {
470
+ return threadId ? `Thread:${threadId}` : `${subjectType}:${subjectId}`;
471
+ }
472
+
473
+ function sortByRange(threads, compareRanges) {
474
+ if (!compareRanges) return threads;
475
+ return [...threads].sort((a, b) => compareRanges(a.subjectRange, b.subjectRange));
476
+ }
477
+
478
+ var _jsxFileName$1 = "/home/tfischbach/code/pageflow/entry_types/scrolled/package/src/review/useLocatedCommentThreads.js";
479
+ const EMPTY = {
480
+ chapters: [],
481
+ threads: [],
482
+ bySubject: new Map()
483
+ };
484
+ const LocatedCommentThreadsContext = createContext(EMPTY);
485
+ function LocatedCommentThreadsProvider({
486
+ children
487
+ }) {
488
+ const located = useComputeLocatedCommentThreads();
489
+ return /*#__PURE__*/React.createElement(LocatedCommentThreadsContext.Provider, {
490
+ value: located,
491
+ __self: this,
492
+ __source: {
493
+ fileName: _jsxFileName$1,
494
+ lineNumber: 17,
495
+ columnNumber: 5
496
+ }
497
+ }, children);
498
+ }
499
+
500
+ /**
501
+ * @private
502
+ */
503
+ function useLocatedCommentThreads() {
504
+ return useContext(LocatedCommentThreadsContext);
505
+ }
506
+ function useComputeLocatedCommentThreads() {
507
+ const structure = useEntryStructureWithContentElements();
508
+ const allThreads = useCommentThreads();
509
+ return useMemo(() => locateThreads(structure, allThreads), [structure, allThreads]);
510
+ }
511
+ function locateThreads(structure, allThreads) {
512
+ const threadsBySection = groupBySection(allThreads);
513
+ const chapters = placeThreadsOnSubjects(structure, groupBySubject(allThreads), threadsBySection);
514
+ prependOrphans(chapters, threadsBySection);
515
+ chapters.forEach(chapter => {
516
+ chapter.threadCount = countThreads(chapter.sections);
517
+ });
518
+ return {
519
+ chapters,
520
+ threads: flatten(chapters),
521
+ bySubject: buildSubjectIndex(chapters)
522
+ };
523
+ }
524
+ function placeThreadsOnSubjects(structure, threadsBySubject, threadsBySection) {
525
+ function take(subjectType, subjectId, compareRanges) {
526
+ const subjectThreads = sortByRange(threadsBySubject[subjectKey(subjectType, subjectId)] || [], compareRanges);
527
+ subjectThreads.forEach(thread => {
528
+ var _threadsBySection$get;
529
+ return (_threadsBySection$get = threadsBySection.get(thread.sectionPermaId)) === null || _threadsBySection$get === void 0 ? void 0 : _threadsBySection$get.delete(thread);
530
+ });
531
+ return subjectThreads;
532
+ }
533
+ return [...structure.main, ...structure.excursions].map(chapter => ({
534
+ ...chapter,
535
+ sections: chapter.sections.map(section => ({
536
+ ...section,
537
+ threads: take('Section', section.permaId),
538
+ contentElements: section.contentElements.map(contentElement => ({
539
+ ...contentElement,
540
+ threads: take('ContentElement', contentElement.permaId, review.contentElementTypes.findCompareRanges(contentElement.type))
541
+ }))
542
+ }))
543
+ }));
544
+ }
545
+ function prependOrphans(chapters, threadsBySection) {
546
+ chapters.forEach(chapter => chapter.sections.forEach(section => {
547
+ section.threads = [...drainSection(threadsBySection, section.permaId), ...section.threads];
548
+ }));
549
+ prependHomelessOrphans(chapters, threadsBySection);
550
+ }
551
+ function prependHomelessOrphans(chapters, threadsBySection) {
552
+ var _chapters$;
553
+ const firstSection = (_chapters$ = chapters[0]) === null || _chapters$ === void 0 ? void 0 : _chapters$.sections[0];
554
+ const homelessOrphans = [...threadsBySection.values()].flatMap(set => [...set]).map(markOrphan);
555
+ if (firstSection && homelessOrphans.length > 0) {
556
+ firstSection.threads = [...homelessOrphans, ...firstSection.threads];
557
+ }
558
+ }
559
+ function drainSection(threadsBySection, sectionPermaId) {
560
+ const orphans = threadsBySection.get(sectionPermaId);
561
+ threadsBySection.delete(sectionPermaId);
562
+ return orphans ? [...orphans].map(markOrphan) : [];
563
+ }
564
+ function markOrphan(thread) {
565
+ return {
566
+ ...thread,
567
+ orphaned: true
568
+ };
569
+ }
570
+ function flatten(chapters) {
571
+ const threads = [];
572
+ chapters.forEach(chapter => chapter.sections.forEach(section => {
573
+ threads.push(...section.threads);
574
+ section.contentElements.forEach(contentElement => threads.push(...contentElement.threads));
575
+ }));
576
+ return threads;
577
+ }
578
+ function buildSubjectIndex(chapters) {
579
+ const bySubject = new Map();
580
+ chapters.forEach(chapter => chapter.sections.forEach(section => {
581
+ bySubject.set(subjectKey('Section', section.permaId), section.threads);
582
+ section.contentElements.forEach(contentElement => bySubject.set(subjectKey('ContentElement', contentElement.permaId), contentElement.threads));
583
+ }));
584
+ return bySubject;
585
+ }
586
+ function countThreads(sections) {
587
+ return sections.reduce((count, section) => count + section.threads.length + section.contentElements.reduce((sum, element) => sum + element.threads.length, 0), 0);
588
+ }
589
+ function groupBySubject(threads) {
590
+ const result = {};
591
+ threads.forEach(thread => {
592
+ const key = subjectKey(thread.subjectType, thread.subjectId);
593
+ (result[key] || (result[key] = [])).push(thread);
594
+ });
595
+ return result;
596
+ }
597
+ function groupBySection(threads) {
598
+ const result = new Map();
599
+ threads.forEach(thread => {
600
+ if (!result.has(thread.sectionPermaId)) {
601
+ result.set(thread.sectionPermaId, new Set());
602
+ }
603
+ result.get(thread.sectionPermaId).add(thread);
604
+ });
605
+ return result;
606
+ }
607
+ function subjectKey(subjectType, subjectId) {
608
+ return `${subjectType}:${subjectId}`;
609
+ }
610
+
611
+ const NONE = [];
612
+
613
+ /**
614
+ * @private
615
+ */
616
+ function useLocatedCommentThreadsForSubject({
617
+ subjectType,
618
+ subjectId,
619
+ subjectRange,
620
+ resolution = 'all',
621
+ revealedThreadId
622
+ }) {
623
+ const {
624
+ bySubject
625
+ } = useLocatedCommentThreads();
626
+ return useMemo(() => {
627
+ const threads = bySubject.get(`${subjectType}:${subjectId}`) || NONE;
628
+ const rangeKey = subjectRange ? JSON.stringify(subjectRange) : undefined;
629
+ return threads.filter(thread => (matchesResolution(thread, resolution) || thread.id === revealedThreadId) && (!rangeKey || JSON.stringify(thread.subjectRange) === rangeKey));
630
+ }, [bySubject, subjectType, subjectId, subjectRange, resolution, revealedThreadId]);
631
+ }
632
+
633
+ var _jsxFileName$2 = "/home/tfischbach/code/pageflow/entry_types/scrolled/package/src/review/CommentDisplayFilterProvider.js";
634
+ const noop = () => {};
635
+ const CommentDisplayFilterContext = createContext({
636
+ resolution: 'unresolved',
637
+ alwaysShowComments: true,
638
+ setResolution: noop
639
+ });
640
+ function CommentDisplayFilterProvider({
641
+ resolution = 'unresolved',
642
+ alwaysShowComments = true,
643
+ setResolution = noop,
644
+ children
645
+ }) {
646
+ const value = useMemo(() => ({
647
+ resolution,
648
+ alwaysShowComments,
649
+ setResolution
650
+ }), [resolution, alwaysShowComments, setResolution]);
651
+ return /*#__PURE__*/React.createElement(CommentDisplayFilterContext.Provider, {
652
+ value: value,
653
+ __self: this,
654
+ __source: {
655
+ fileName: _jsxFileName$2,
656
+ lineNumber: 20,
657
+ columnNumber: 5
658
+ }
659
+ }, children);
660
+ }
661
+ function useCommentDisplayFilter() {
662
+ return useContext(CommentDisplayFilterContext);
663
+ }
664
+ function useStoredCommentDisplayFilter(storageKey) {
665
+ const [resolution, setResolution] = useState(() => readResolution(storageKey));
666
+ const store = useCallback(resolution => {
667
+ setResolution(resolution);
668
+ storeResolution(storageKey, resolution);
669
+ }, [storageKey]);
670
+ return useMemo(() => ({
671
+ resolution,
672
+ setResolution: store
673
+ }), [resolution, store]);
674
+ }
675
+ function readResolution(storageKey) {
676
+ var _getLocalStorage;
677
+ return ((_getLocalStorage = getLocalStorage()) === null || _getLocalStorage === void 0 ? void 0 : _getLocalStorage[storageKey]) === 'all' ? 'all' : 'unresolved';
678
+ }
679
+ function storeResolution(storageKey, resolution) {
680
+ const storage = getLocalStorage();
681
+ if (storage) {
682
+ storage[storageKey] = resolution;
683
+ }
684
+ }
685
+ function getLocalStorage() {
686
+ try {
687
+ return typeof window === 'undefined' ? null : window.localStorage;
688
+ } catch (e) {
689
+ // Safari throws SecurityError when accessing window.localStorage
690
+ // if cookies/website data are disabled.
691
+ return null;
692
+ }
693
+ }
694
+
695
+ const ReviewMessageHandler = {
696
+ create({
697
+ session,
698
+ targetWindow
699
+ }) {
700
+ function handleMessage(event) {
701
+ if (window.location.href.indexOf(event.origin) !== 0) return;
702
+ if (event.source !== targetWindow) return;
703
+ const {
704
+ type,
705
+ payload
706
+ } = event.data;
707
+ if (type === 'CREATE_COMMENT_THREAD') {
708
+ session.createThread(payload);
709
+ } else if (type === 'CREATE_COMMENT') {
710
+ session.createComment(payload);
711
+ } else if (type === 'UPDATE_THREAD') {
712
+ session.updateThread(payload);
713
+ } else if (type === 'UPDATE_COMMENT') {
714
+ session.updateComment(payload);
715
+ } else if (type === 'SET_COMMENT_DRAFT') {
716
+ session.setDraft(payload);
717
+ } else if (type === 'MARK_THREADS_READ') {
718
+ session.markThreadsRead(payload.permaIds);
719
+ }
720
+ }
721
+ function handleReset(state) {
722
+ postReviewStateResetMessage(targetWindow, state);
723
+ }
724
+ function handleThreadChange(thread) {
725
+ postReviewStateThreadChangeMessage(targetWindow, thread);
726
+ }
727
+ function handleDraftsChange(drafts) {
728
+ postReviewStateDraftsChangeMessage(targetWindow, drafts);
729
+ }
730
+ function handleReadsChange(reads) {
731
+ postReviewStateReadsChangeMessage(targetWindow, reads);
732
+ }
733
+ window.addEventListener('message', handleMessage);
734
+ session.on('reset', handleReset);
735
+ session.on('change:thread', handleThreadChange);
736
+ session.on('change:drafts', handleDraftsChange);
737
+ session.on('change:reads', handleReadsChange);
738
+ return {
739
+ dispose() {
740
+ window.removeEventListener('message', handleMessage);
741
+ session.off('reset', handleReset);
742
+ session.off('change:thread', handleThreadChange);
743
+ session.off('change:drafts', handleDraftsChange);
744
+ session.off('change:reads', handleReadsChange);
745
+ }
746
+ };
747
+ }
748
+ };
749
+
750
+ var _jsxFileName$3 = "/home/tfischbach/code/pageflow/entry_types/scrolled/package/src/review/commentThreadReadsSnapshot.js";
751
+ const CommentThreadReadsSnapshotContext = createContext(null);
752
+ function CommentThreadReadsSnapshot({
753
+ enabled = true,
754
+ resetOn,
755
+ children
756
+ }) {
757
+ const outerSnapshot = useContext(CommentThreadReadsSnapshotContext);
758
+ const liveReads = useCommentThreadReads();
759
+ const currentUser = useCurrentUser();
760
+ const snapshot = useRef(null);
761
+ const lastResetOn = useRef(resetOn);
762
+ if (resetOn !== lastResetOn.current) {
763
+ lastResetOn.current = resetOn;
764
+ snapshot.current = null;
765
+ }
766
+ if (!enabled) {
767
+ snapshot.current = null;
768
+ } else if (!snapshot.current && currentUser) {
769
+ snapshot.current = liveReads;
770
+ }
771
+ return /*#__PURE__*/React.createElement(CommentThreadReadsSnapshotContext.Provider, {
772
+ value: outerSnapshot || snapshot.current,
773
+ __self: this,
774
+ __source: {
775
+ fileName: _jsxFileName$3,
776
+ lineNumber: 29,
777
+ columnNumber: 5
778
+ }
779
+ }, children);
780
+ }
781
+ function useDisplayedCommentThreadReads() {
782
+ const snapshot = useContext(CommentThreadReadsSnapshotContext);
783
+ const liveReads = useCommentThreadReads();
784
+ return snapshot || liveReads;
785
+ }
786
+
787
+ function useUnreadActivityCount(threads) {
788
+ const currentUser = useCurrentUser();
789
+ const commentThreadReads = useDisplayedCommentThreadReads();
790
+ return useMemo(() => threads.reduce((count, thread) => count + unreadActivity(thread, {
791
+ currentUser,
792
+ readAt: commentThreadReads[thread.permaId]
793
+ }).length, 0), [threads, currentUser, commentThreadReads]);
794
+ }
795
+ function useUnreadActivity(thread) {
796
+ const currentUser = useCurrentUser();
797
+ const commentThreadReads = useDisplayedCommentThreadReads();
798
+ return useMemo(() => unreadActivity(thread, {
799
+ currentUser,
800
+ readAt: commentThreadReads[thread.permaId]
801
+ }), [thread, currentUser, commentThreadReads]);
802
+ }
803
+ function useLiveUnreadActivity(thread) {
804
+ const currentUser = useCurrentUser();
805
+ const commentThreadReads = useCommentThreadReads();
806
+ return useMemo(() => unreadActivity(thread, {
807
+ currentUser,
808
+ readAt: commentThreadReads[thread.permaId]
809
+ }), [thread, currentUser, commentThreadReads]);
810
+ }
811
+ function threadActivity(thread) {
812
+ const activity = thread.comments.map(comment => ({
813
+ ...comment,
814
+ at: comment.createdAt
815
+ }));
816
+ if (thread.resolvedAt) {
817
+ activity.push({
818
+ resolution: true,
819
+ at: thread.resolvedAt,
820
+ createdAt: thread.resolvedAt,
821
+ creatorId: thread.resolvedById
822
+ });
823
+ }
824
+ return activity;
825
+ }
826
+ function unreadActivity(thread, {
827
+ currentUser,
828
+ readAt
829
+ }) {
830
+ return threadActivity(thread).filter(event => isUnread(event, {
831
+ currentUser,
832
+ readAt
833
+ }));
834
+ }
835
+
836
+ // Kept in sync with Pageflow::EntryCommentSummary, which applies the same
837
+ // rule server side.
838
+ function isUnread({
839
+ creatorId,
840
+ createdAt
841
+ }, {
842
+ currentUser,
843
+ readAt
844
+ }) {
845
+ if (!currentUser || creatorId === currentUser.id) return false;
846
+ const seenUpTo = latestTime([readAt, currentUser.unreadCommentsSinceAt]);
847
+ return seenUpTo === null || new Date(createdAt).getTime() > seenUpTo;
848
+ }
849
+ function latestTime(timestamps) {
850
+ const times = timestamps.filter(Boolean).map(timestamp => new Date(timestamp).getTime());
851
+ return times.length ? Math.max(...times) : null;
852
+ }
853
+
854
+ function watchUnreadComments({
855
+ entry,
856
+ session
857
+ }) {
858
+ function update() {
859
+ entry.set('hasUnreadComments', hasUnreadComments(session.state));
860
+ }
861
+ session.on('reset', update);
862
+ session.on('change:thread', update);
863
+ session.on('change:reads', update);
864
+ update();
865
+ }
866
+ function hasUnreadComments(state) {
867
+ if (!state) {
868
+ return false;
869
+ }
870
+ const {
871
+ currentUser,
872
+ commentThreads,
873
+ commentThreadReads = {}
874
+ } = state;
875
+ return commentThreads.some(thread => unreadActivity(thread, {
876
+ currentUser,
877
+ readAt: commentThreadReads[thread.permaId]
878
+ }).length > 0);
879
+ }
880
+
881
+ function _extends() {
882
+ _extends = Object.assign ? Object.assign.bind() : function (target) {
883
+ for (var i = 1; i < arguments.length; i++) {
884
+ var source = arguments[i];
885
+ for (var key in source) {
886
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
887
+ target[key] = source[key];
888
+ }
889
+ }
890
+ }
891
+ return target;
892
+ };
893
+ return _extends.apply(this, arguments);
894
+ }
895
+ var CommentIcon = (({
896
+ styles = {},
897
+ ...props
898
+ }) => /*#__PURE__*/React.createElement("svg", _extends({
899
+ xmlns: "http://www.w3.org/2000/svg",
900
+ viewBox: "0 0 24 24",
901
+ fill: "none",
902
+ stroke: "currentColor",
903
+ strokeWidth: "1.75",
904
+ strokeLinecap: "round",
905
+ strokeLinejoin: "round",
906
+ width: "16",
907
+ height: "16"
908
+ }, props), /*#__PURE__*/React.createElement("path", {
909
+ d: "M22 17a2 2 0 01-2 2H6.828a2 2 0 00-1.414.586l-2.202 2.202A.71.71 0 012 21.286V5a2 2 0 012-2h16a2 2 0 012 2zM7 11h10M7 15h6M7 7h8"
910
+ })));
911
+
912
+ var styles = {"badge":"Badge-module_badge__3p6yC","active":"Badge-module_active__j183x","resolved":"Badge-module_resolved__3lpoW","dot":"Badge-module_dot__7JmxD","iconOnly":"Badge-module_iconOnly__3DRC8","icon":"Badge-module_icon__3DcSk","unread":"Badge-module_unread__2Gbxx"};
913
+
914
+ var _jsxFileName$4 = "/home/tfischbach/code/pageflow/entry_types/scrolled/package/src/review/Badge.js";
915
+ const Badge = forwardRef(function Badge({
916
+ counter,
917
+ hasThreads = counter > 0,
918
+ mode,
919
+ resolved,
920
+ unreadCount = 0,
921
+ onClick
922
+ }, ref) {
923
+ const {
924
+ t
925
+ } = useI18n({
926
+ locale: 'ui'
927
+ });
928
+ const unread = unreadCount > 0;
929
+ const variant = resolveVariant(mode, hasThreads, unread);
930
+ if (!variant) {
931
+ return null;
932
+ }
933
+ return /*#__PURE__*/React.createElement("button", {
934
+ ref: ref,
935
+ role: "status",
936
+ "aria-label": unread ? t('pageflow_scrolled.review.unread_count', {
937
+ count: unreadCount
938
+ }) : undefined,
939
+ className: classNames(styles.badge, styles[variant], {
940
+ [styles.resolved]: resolved,
941
+ [styles.unread]: unread
942
+ }),
943
+ onClick: onClick,
944
+ __self: this,
945
+ __source: {
946
+ fileName: _jsxFileName$4,
947
+ lineNumber: 21,
948
+ columnNumber: 5
949
+ }
950
+ }, variant !== 'dot' && /*#__PURE__*/React.createElement(CommentIcon, {
951
+ className: styles.icon,
952
+ __self: this,
953
+ __source: {
954
+ fileName: _jsxFileName$4,
955
+ lineNumber: 31,
956
+ columnNumber: 29
957
+ }
958
+ }), (variant === 'active' || variant === 'expanded') && counter > 1 ? counter : null);
959
+ });
960
+ function resolveVariant(mode, hasThreads, unread) {
961
+ switch (mode) {
962
+ case 'active':
963
+ return 'active';
964
+ case 'icon':
965
+ return hasThreads ? 'expanded' : 'iconOnly';
966
+ case 'none':
967
+ return null;
968
+ case 'dot':
969
+ // A dot would leave the unread dot sitting on a dot.
970
+ return hasThreads ? unread ? 'expanded' : 'dot' : null;
971
+ default:
972
+ return hasThreads ? 'expanded' : null;
973
+ }
974
+ }
975
+
976
+ var _jsxFileName$5 = "/home/tfischbach/code/pageflow/entry_types/scrolled/package/src/review/ThreadsBadge.js";
977
+ function ThreadsBadge({
978
+ subjectType,
979
+ subjectId,
980
+ subjectRange,
981
+ onClick,
982
+ mode,
983
+ resolution = 'unresolved',
984
+ revealedThreadId
985
+ }) {
986
+ const threads = useLocatedCommentThreadsForSubject({
987
+ subjectType,
988
+ subjectId,
989
+ subjectRange,
990
+ resolution,
991
+ revealedThreadId
992
+ });
993
+ const unresolvedThreads = useLocatedCommentThreadsForSubject({
994
+ subjectType,
995
+ subjectId,
996
+ subjectRange,
997
+ resolution: 'unresolved'
998
+ });
999
+ const counted = threads.filter(thread => thread.id !== revealedThreadId);
1000
+ const unreadCount = useUnreadActivityCount(threads);
1001
+ const handleClick = useCallback(() => {
1002
+ if (onClick) onClick(threads);
1003
+ }, [onClick, threads]);
1004
+ const resolved = threads.length > 0 && unresolvedThreads.length === 0;
1005
+ return /*#__PURE__*/React.createElement(Badge, {
1006
+ counter: counted.length,
1007
+ hasThreads: threads.length > 0,
1008
+ mode: mode,
1009
+ resolved: resolved,
1010
+ unreadCount: unreadCount,
1011
+ onClick: handleClick,
1012
+ __self: this,
1013
+ __source: {
1014
+ fileName: _jsxFileName$5,
1015
+ lineNumber: 24,
1016
+ columnNumber: 10
1017
+ }
1018
+ });
1019
+ }
1020
+
1021
+ var styles$1 = {"avatar":"Avatar-module_avatar__3yj0L","avatarStack":"Avatar-module_avatarStack__1g6pW","stackedAvatar":"Avatar-module_stackedAvatar__mthMW"};
1022
+
1023
+ var _jsxFileName$6 = "/home/tfischbach/code/pageflow/entry_types/scrolled/package/src/review/Avatar.js";
1024
+ function Avatar({
1025
+ name,
1026
+ className
1027
+ }) {
1028
+ const initial = (name || '?')[0].toUpperCase();
1029
+ const hue = nameToHue(name);
1030
+ return /*#__PURE__*/React.createElement("span", {
1031
+ className: `${styles$1.avatar} ${className || ''}`,
1032
+ style: {
1033
+ backgroundColor: `hsl(${hue}, 45%, 40%)`
1034
+ },
1035
+ __self: this,
1036
+ __source: {
1037
+ fileName: _jsxFileName$6,
1038
+ lineNumber: 10,
1039
+ columnNumber: 5
1040
+ }
1041
+ }, initial);
1042
+ }
1043
+ function AvatarStack({
1044
+ names
1045
+ }) {
1046
+ const unique = [...new Set(names.filter(Boolean))];
1047
+ return /*#__PURE__*/React.createElement("span", {
1048
+ className: styles$1.avatarStack,
1049
+ __self: this,
1050
+ __source: {
1051
+ fileName: _jsxFileName$6,
1052
+ lineNumber: 21,
1053
+ columnNumber: 5
1054
+ }
1055
+ }, unique.map((name, i) => /*#__PURE__*/React.createElement(Avatar, {
1056
+ key: i,
1057
+ name: name,
1058
+ className: styles$1.stackedAvatar,
1059
+ __self: this,
1060
+ __source: {
1061
+ fileName: _jsxFileName$6,
1062
+ lineNumber: 23,
1063
+ columnNumber: 9
1064
+ }
1065
+ })));
1066
+ }
1067
+ function nameToHue(name) {
1068
+ let hash = 0;
1069
+ for (let i = 0; i < (name || '').length; i++) {
1070
+ hash = name.charCodeAt(i) + ((hash << 5) - hash);
1071
+ }
1072
+ return Math.abs(hash) % 360;
1073
+ }
1074
+
1075
+ function _extends$1() {
1076
+ _extends$1 = Object.assign ? Object.assign.bind() : function (target) {
1077
+ for (var i = 1; i < arguments.length; i++) {
1078
+ var source = arguments[i];
1079
+ for (var key in source) {
1080
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
1081
+ target[key] = source[key];
1082
+ }
1083
+ }
1084
+ }
1085
+ return target;
1086
+ };
1087
+ return _extends$1.apply(this, arguments);
1088
+ }
1089
+ var EllipsisIcon = (({
1090
+ styles = {},
1091
+ ...props
1092
+ }) => /*#__PURE__*/React.createElement("svg", _extends$1({
1093
+ xmlns: "http://www.w3.org/2000/svg",
1094
+ viewBox: "0 0 24 24",
1095
+ fill: "none",
1096
+ stroke: "currentColor",
1097
+ strokeWidth: "2",
1098
+ strokeLinecap: "round",
1099
+ strokeLinejoin: "round",
1100
+ width: "16",
1101
+ height: "16"
1102
+ }, props), /*#__PURE__*/React.createElement("circle", {
1103
+ cx: "12",
1104
+ cy: "5",
1105
+ r: "1"
1106
+ }), /*#__PURE__*/React.createElement("circle", {
1107
+ cx: "12",
1108
+ cy: "12",
1109
+ r: "1"
1110
+ }), /*#__PURE__*/React.createElement("circle", {
1111
+ cx: "12",
1112
+ cy: "19",
1113
+ r: "1"
1114
+ })));
1115
+
1116
+ var styles$2 = {"menu":"CommentMenu-module_menu__24kxI","button":"CommentMenu-module_button__10Le9","list":"CommentMenu-module_list__3B1nV","item":"CommentMenu-module_item__2exBD"};
1117
+
1118
+ var _jsxFileName$7 = "/home/tfischbach/code/pageflow/entry_types/scrolled/package/src/review/CommentMenu.js";
1119
+ function CommentMenu({
1120
+ label,
1121
+ items
1122
+ }) {
1123
+ const portalRoot = useFloatingPortalRoot();
1124
+ const [open, setOpen] = useState(false);
1125
+ const [activeIndex, setActiveIndex] = useState(null);
1126
+ const elementsRef = useRef([]);
1127
+ const labelsRef = useRef([]);
1128
+ labelsRef.current = items.map(item => item.label);
1129
+ const {
1130
+ refs,
1131
+ floatingStyles,
1132
+ context
1133
+ } = useFloating({
1134
+ open,
1135
+ onOpenChange: setOpen,
1136
+ placement: 'bottom-end',
1137
+ middleware: [offset(4), flip({
1138
+ padding: 8
1139
+ }), shift({
1140
+ padding: 8
1141
+ })],
1142
+ whileElementsMounted: autoUpdate
1143
+ });
1144
+ const click = useClick(context);
1145
+ const dismiss = useDismiss(context, {
1146
+ escapeKey: false
1147
+ });
1148
+ const role = useRole(context, {
1149
+ role: 'menu'
1150
+ });
1151
+ const listNavigation = useListNavigation(context, {
1152
+ listRef: elementsRef,
1153
+ activeIndex,
1154
+ onNavigate: setActiveIndex,
1155
+ loop: true
1156
+ });
1157
+ const typeahead = useTypeahead(context, {
1158
+ listRef: labelsRef,
1159
+ activeIndex,
1160
+ onMatch: setActiveIndex,
1161
+ enabled: open
1162
+ });
1163
+ const {
1164
+ getReferenceProps,
1165
+ getFloatingProps,
1166
+ getItemProps
1167
+ } = useInteractions([click, dismiss, role, listNavigation, typeahead]);
1168
+
1169
+ // React 16 dispatches all events from the document, so enclosing popovers
1170
+ // listening there for Escape would close along with the menu.
1171
+ useEffect(() => {
1172
+ if (!open) return;
1173
+ function closeOnEscape(event) {
1174
+ if (event.key === 'Escape') {
1175
+ event.stopPropagation();
1176
+ setOpen(false);
1177
+ }
1178
+ }
1179
+ document.addEventListener('keydown', closeOnEscape, true);
1180
+ return () => document.removeEventListener('keydown', closeOnEscape, true);
1181
+ }, [open]);
1182
+ function stopPropagation(event) {
1183
+ event.stopPropagation();
1184
+ }
1185
+ return /*#__PURE__*/React.createElement("span", {
1186
+ className: styles$2.menu,
1187
+ onClick: stopPropagation,
1188
+ __self: this,
1189
+ __source: {
1190
+ fileName: _jsxFileName$7,
1191
+ lineNumber: 74,
1192
+ columnNumber: 5
1193
+ }
1194
+ }, /*#__PURE__*/React.createElement("button", Object.assign({
1195
+ ref: refs.setReference,
1196
+ type: "button",
1197
+ className: styles$2.button,
1198
+ "aria-label": label
1199
+ }, getReferenceProps(), {
1200
+ __self: this,
1201
+ __source: {
1202
+ fileName: _jsxFileName$7,
1203
+ lineNumber: 75,
1204
+ columnNumber: 7
1205
+ }
1206
+ }), /*#__PURE__*/React.createElement(EllipsisIcon, {
1207
+ __self: this,
1208
+ __source: {
1209
+ fileName: _jsxFileName$7,
1210
+ lineNumber: 80,
1211
+ columnNumber: 9
1212
+ }
1213
+ })), open && /*#__PURE__*/React.createElement(FloatingPortal, {
1214
+ root: portalRoot,
1215
+ __self: this,
1216
+ __source: {
1217
+ fileName: _jsxFileName$7,
1218
+ lineNumber: 84,
1219
+ columnNumber: 9
1220
+ }
1221
+ }, /*#__PURE__*/React.createElement(FloatingFocusManager, {
1222
+ context: context,
1223
+ modal: false,
1224
+ __self: this,
1225
+ __source: {
1226
+ fileName: _jsxFileName$7,
1227
+ lineNumber: 85,
1228
+ columnNumber: 11
1229
+ }
1230
+ }, /*#__PURE__*/React.createElement("div", Object.assign({
1231
+ ref: refs.setFloating,
1232
+ "data-comment-menu": true,
1233
+ "data-floating-raised": true,
1234
+ className: styles$2.list,
1235
+ style: floatingStyles,
1236
+ onClick: stopPropagation
1237
+ }, getFloatingProps(), {
1238
+ __self: this,
1239
+ __source: {
1240
+ fileName: _jsxFileName$7,
1241
+ lineNumber: 86,
1242
+ columnNumber: 13
1243
+ }
1244
+ }), items.map(({
1245
+ icon: Icon,
1246
+ label,
1247
+ onSelect
1248
+ }, index) => /*#__PURE__*/React.createElement("button", Object.assign({
1249
+ key: label,
1250
+ ref: node => elementsRef.current[index] = node,
1251
+ type: "button",
1252
+ role: "menuitem",
1253
+ className: styles$2.item,
1254
+ tabIndex: activeIndex === index ? 0 : -1
1255
+ }, getItemProps({
1256
+ onClick() {
1257
+ setOpen(false);
1258
+ if (onSelect) onSelect();
1259
+ }
1260
+ }), {
1261
+ __self: this,
1262
+ __source: {
1263
+ fileName: _jsxFileName$7,
1264
+ lineNumber: 94,
1265
+ columnNumber: 17
1266
+ }
1267
+ }), /*#__PURE__*/React.createElement(Icon, {
1268
+ __self: this,
1269
+ __source: {
1270
+ fileName: _jsxFileName$7,
1271
+ lineNumber: 107,
1272
+ columnNumber: 19
1273
+ }
1274
+ }), label))))));
1275
+ }
1276
+
1277
+ function autoGrow(el) {
1278
+ el.style.height = 'auto';
1279
+ if (el.scrollHeight > 0) {
1280
+ el.style.height = el.scrollHeight + 'px';
1281
+ }
1282
+ }
1283
+ function autoResize(el) {
1284
+ if (el) {
1285
+ autoGrow(el);
1286
+ }
1287
+ }
1288
+
1289
+ function formatDate(isoString, locale, options) {
1290
+ const date = new Date(isoString);
1291
+ const fromCurrentYear = date.getFullYear() === new Date().getFullYear();
1292
+ return date.toLocaleString(locale, {
1293
+ month: 'short',
1294
+ day: 'numeric',
1295
+ ...(!fromCurrentYear && {
1296
+ year: 'numeric'
1297
+ }),
1298
+ ...options
1299
+ });
1300
+ }
1301
+ function formatDateTime(isoString, locale) {
1302
+ return formatDate(isoString, locale, {
1303
+ hour: 'numeric',
1304
+ minute: '2-digit'
1305
+ });
1306
+ }
1307
+
1308
+ function isSubmitShortcut(event) {
1309
+ return (event.metaKey || event.ctrlKey) && event.key === 'Enter';
1310
+ }
1311
+
1312
+ function _extends$2() {
1313
+ _extends$2 = Object.assign ? Object.assign.bind() : function (target) {
1314
+ for (var i = 1; i < arguments.length; i++) {
1315
+ var source = arguments[i];
1316
+ for (var key in source) {
1317
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
1318
+ target[key] = source[key];
1319
+ }
1320
+ }
1321
+ }
1322
+ return target;
1323
+ };
1324
+ return _extends$2.apply(this, arguments);
1325
+ }
1326
+ var EditIcon = (({
1327
+ styles = {},
1328
+ ...props
1329
+ }) => /*#__PURE__*/React.createElement("svg", _extends$2({
1330
+ xmlns: "http://www.w3.org/2000/svg",
1331
+ viewBox: "0 0 24 24",
1332
+ fill: "none",
1333
+ stroke: "currentColor",
1334
+ strokeWidth: "2",
1335
+ strokeLinecap: "round",
1336
+ strokeLinejoin: "round",
1337
+ width: "16",
1338
+ height: "16"
1339
+ }, props), /*#__PURE__*/React.createElement("path", {
1340
+ d: "M21.174 6.812a1 1 0 00-3.986-3.987L3.842 16.174a2 2 0 00-.5.83l-1.321 4.352a.5.5 0 00.623.622l4.353-1.32a2 2 0 00.83-.497zM15 5l4 4"
1341
+ })));
1342
+
1343
+ var styles$3 = {"header":"Comment-module_header__2oN4m","headerText":"Comment-module_headerText__1ZuhQ","author":"Comment-module_author__2LNS1","headerMenu":"Comment-module_headerMenu__25LDn","timestamp":"Comment-module_timestamp__2zNl2","quote":"Comment-module_quote__1G-yv","body":"Comment-module_body__3KfZL","editedHint":"Comment-module_editedHint__buRnz","editForm":"Comment-module_editForm__qbsHA","editInput":"Comment-module_editInput__2M2h8","editActions":"Comment-module_editActions__39B25","saveButton":"Comment-module_saveButton__GW-z-","cancelButton":"Comment-module_cancelButton__3vfW0"};
1344
+
1345
+ var _jsxFileName$8 = "/home/tfischbach/code/pageflow/entry_types/scrolled/package/src/review/Comment.js";
1346
+ function Comment({
1347
+ comment,
1348
+ threadId,
1349
+ showQuote,
1350
+ editing,
1351
+ onEdit,
1352
+ onEditEnd
1353
+ }) {
1354
+ const {
1355
+ t
1356
+ } = useI18n({
1357
+ locale: 'ui'
1358
+ });
1359
+ const locale = useLocale({
1360
+ locale: 'ui'
1361
+ });
1362
+ const currentUser = useCurrentUser();
1363
+ const editable = !!onEdit && comment.creatorId === (currentUser === null || currentUser === void 0 ? void 0 : currentUser.id);
1364
+ return /*#__PURE__*/React.createElement("div", {
1365
+ __self: this,
1366
+ __source: {
1367
+ fileName: _jsxFileName$8,
1368
+ lineNumber: 22,
1369
+ columnNumber: 5
1370
+ }
1371
+ }, /*#__PURE__*/React.createElement("div", {
1372
+ className: styles$3.header,
1373
+ __self: this,
1374
+ __source: {
1375
+ fileName: _jsxFileName$8,
1376
+ lineNumber: 23,
1377
+ columnNumber: 7
1378
+ }
1379
+ }, /*#__PURE__*/React.createElement(Avatar, {
1380
+ name: comment.creatorName,
1381
+ __self: this,
1382
+ __source: {
1383
+ fileName: _jsxFileName$8,
1384
+ lineNumber: 24,
1385
+ columnNumber: 9
1386
+ }
1387
+ }), /*#__PURE__*/React.createElement("div", {
1388
+ className: styles$3.headerText,
1389
+ __self: this,
1390
+ __source: {
1391
+ fileName: _jsxFileName$8,
1392
+ lineNumber: 25,
1393
+ columnNumber: 9
1394
+ }
1395
+ }, /*#__PURE__*/React.createElement("span", {
1396
+ className: styles$3.author,
1397
+ __self: this,
1398
+ __source: {
1399
+ fileName: _jsxFileName$8,
1400
+ lineNumber: 26,
1401
+ columnNumber: 11
1402
+ }
1403
+ }, comment.creatorName), comment.createdAt && /*#__PURE__*/React.createElement("time", {
1404
+ className: styles$3.timestamp,
1405
+ dateTime: comment.createdAt,
1406
+ __self: this,
1407
+ __source: {
1408
+ fileName: _jsxFileName$8,
1409
+ lineNumber: 28,
1410
+ columnNumber: 13
1411
+ }
1412
+ }, formatDate(comment.createdAt, locale))), editable && /*#__PURE__*/React.createElement("span", {
1413
+ className: styles$3.headerMenu,
1414
+ __self: this,
1415
+ __source: {
1416
+ fileName: _jsxFileName$8,
1417
+ lineNumber: 33,
1418
+ columnNumber: 11
1419
+ }
1420
+ }, /*#__PURE__*/React.createElement(CommentMenu, {
1421
+ label: t('pageflow_scrolled.review.comment_actions'),
1422
+ items: [{
1423
+ icon: EditIcon,
1424
+ label: t('pageflow_scrolled.review.edit_comment'),
1425
+ onSelect: onEdit
1426
+ }],
1427
+ __self: this,
1428
+ __source: {
1429
+ fileName: _jsxFileName$8,
1430
+ lineNumber: 34,
1431
+ columnNumber: 13
1432
+ }
1433
+ }))), showQuote && /*#__PURE__*/React.createElement("blockquote", {
1434
+ className: styles$3.quote,
1435
+ __self: this,
1436
+ __source: {
1437
+ fileName: _jsxFileName$8,
1438
+ lineNumber: 41,
1439
+ columnNumber: 9
1440
+ }
1441
+ }, comment.quote), editing ? /*#__PURE__*/React.createElement(EditForm, {
1442
+ comment: comment,
1443
+ threadId: threadId,
1444
+ onDone: onEditEnd,
1445
+ __self: this,
1446
+ __source: {
1447
+ fileName: _jsxFileName$8,
1448
+ lineNumber: 43,
1449
+ columnNumber: 11
1450
+ }
1451
+ }) : /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("p", {
1452
+ className: styles$3.body,
1453
+ __self: this,
1454
+ __source: {
1455
+ fileName: _jsxFileName$8,
1456
+ lineNumber: 45,
1457
+ columnNumber: 13
1458
+ }
1459
+ }, comment.body), comment.editedAt && /*#__PURE__*/React.createElement("p", {
1460
+ className: styles$3.editedHint,
1461
+ __self: this,
1462
+ __source: {
1463
+ fileName: _jsxFileName$8,
1464
+ lineNumber: 47,
1465
+ columnNumber: 15
1466
+ }
1467
+ }, t('pageflow_scrolled.review.edited', {
1468
+ date: formatDateTime(comment.editedAt, locale)
1469
+ }))));
1470
+ }
1471
+ function EditForm({
1472
+ comment,
1473
+ threadId,
1474
+ onDone
1475
+ }) {
1476
+ const {
1477
+ t
1478
+ } = useI18n({
1479
+ locale: 'ui'
1480
+ });
1481
+ const [body, setBody] = useState(comment.body);
1482
+ const hasText = body.trim().length > 0;
1483
+ const updateComment = useUpdateComment({
1484
+ threadId,
1485
+ commentId: comment.id
1486
+ });
1487
+
1488
+ // Focusing without preventScroll yanks the page to the top before
1489
+ // floating-ui has positioned the portaled popover.
1490
+ const setInputRef = useCallback(node => {
1491
+ autoResize(node);
1492
+ node === null || node === void 0 ? void 0 : node.focus({
1493
+ preventScroll: true
1494
+ });
1495
+ }, []);
1496
+ function handleChange(e) {
1497
+ setBody(e.target.value);
1498
+ autoGrow(e.target);
1499
+ }
1500
+ function handleSubmit(event) {
1501
+ event.preventDefault();
1502
+ save();
1503
+ }
1504
+ function handleKeyDown(event) {
1505
+ if (isSubmitShortcut(event)) {
1506
+ event.preventDefault();
1507
+ save();
1508
+ }
1509
+ }
1510
+ function save() {
1511
+ if (!hasText) return;
1512
+ updateComment(body);
1513
+ onDone();
1514
+ }
1515
+ return /*#__PURE__*/React.createElement("form", {
1516
+ className: styles$3.editForm,
1517
+ onSubmit: handleSubmit,
1518
+ __self: this,
1519
+ __source: {
1520
+ fileName: _jsxFileName$8,
1521
+ lineNumber: 96,
1522
+ columnNumber: 5
1523
+ }
1524
+ }, /*#__PURE__*/React.createElement("textarea", {
1525
+ className: styles$3.editInput,
1526
+ ref: setInputRef,
1527
+ value: body,
1528
+ onChange: handleChange,
1529
+ onKeyDown: handleKeyDown,
1530
+ rows: 1,
1531
+ __self: this,
1532
+ __source: {
1533
+ fileName: _jsxFileName$8,
1534
+ lineNumber: 97,
1535
+ columnNumber: 7
1536
+ }
1537
+ }), /*#__PURE__*/React.createElement("div", {
1538
+ className: styles$3.editActions,
1539
+ __self: this,
1540
+ __source: {
1541
+ fileName: _jsxFileName$8,
1542
+ lineNumber: 103,
1543
+ columnNumber: 7
1544
+ }
1545
+ }, /*#__PURE__*/React.createElement("button", {
1546
+ className: styles$3.cancelButton,
1547
+ type: "button",
1548
+ onClick: onDone,
1549
+ __self: this,
1550
+ __source: {
1551
+ fileName: _jsxFileName$8,
1552
+ lineNumber: 104,
1553
+ columnNumber: 9
1554
+ }
1555
+ }, t('pageflow_scrolled.review.cancel')), /*#__PURE__*/React.createElement("button", {
1556
+ className: styles$3.saveButton,
1557
+ type: "submit",
1558
+ disabled: !hasText,
1559
+ __self: this,
1560
+ __source: {
1561
+ fileName: _jsxFileName$8,
1562
+ lineNumber: 107,
1563
+ columnNumber: 9
1564
+ }
1565
+ }, t('pageflow_scrolled.review.save'))));
1566
+ }
1567
+
1568
+ function useDraftedBody(draftOf) {
1569
+ const [draft, setDraft] = useCommentDraft(draftOf);
1570
+ const [body, setBody] = useState(() => (draft === null || draft === void 0 ? void 0 : draft.body) || '');
1571
+ const pending = !!(draft === null || draft === void 0 ? void 0 : draft.pending);
1572
+ const latest = useRef();
1573
+ latest.current = {
1574
+ body,
1575
+ pending,
1576
+ setDraft
1577
+ };
1578
+ const wasPending = useRef(false);
1579
+ useEffect(() => {
1580
+ if (pending) {
1581
+ wasPending.current = true;
1582
+ } else if (wasPending.current) {
1583
+ wasPending.current = false;
1584
+ if (!draft) setBody('');
1585
+ }
1586
+ }, [pending, draft]);
1587
+ useEffect(() => () => {
1588
+ const {
1589
+ body,
1590
+ pending,
1591
+ setDraft
1592
+ } = latest.current;
1593
+ if (pending) return;
1594
+ setDraft(body);
1595
+ }, []);
1596
+ return {
1597
+ body,
1598
+ setBody,
1599
+ submitting: pending
1600
+ };
1601
+ }
1602
+
1603
+ function _extends$3() {
1604
+ _extends$3 = Object.assign ? Object.assign.bind() : function (target) {
1605
+ for (var i = 1; i < arguments.length; i++) {
1606
+ var source = arguments[i];
1607
+ for (var key in source) {
1608
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
1609
+ target[key] = source[key];
1610
+ }
1611
+ }
1612
+ }
1613
+ return target;
1614
+ };
1615
+ return _extends$3.apply(this, arguments);
1616
+ }
1617
+ var SendIcon = (({
1618
+ styles = {},
1619
+ ...props
1620
+ }) => /*#__PURE__*/React.createElement("svg", _extends$3({
1621
+ xmlns: "http://www.w3.org/2000/svg",
1622
+ viewBox: "0 0 24 24",
1623
+ fill: "none",
1624
+ stroke: "currentColor",
1625
+ strokeWidth: "2",
1626
+ strokeLinecap: "round",
1627
+ strokeLinejoin: "round",
1628
+ width: "16",
1629
+ height: "16"
1630
+ }, props), /*#__PURE__*/React.createElement("path", {
1631
+ d: "M3.714 3.048a.498.498 0 00-.683.627l2.843 7.627a2 2 0 010 1.396l-2.842 7.627a.498.498 0 00.682.627l18-8.5a.5.5 0 000-.904zM6 12h16"
1632
+ })));
1633
+
1634
+ function _extends$4() {
1635
+ _extends$4 = Object.assign ? Object.assign.bind() : function (target) {
1636
+ for (var i = 1; i < arguments.length; i++) {
1637
+ var source = arguments[i];
1638
+ for (var key in source) {
1639
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
1640
+ target[key] = source[key];
1641
+ }
1642
+ }
1643
+ }
1644
+ return target;
1645
+ };
1646
+ return _extends$4.apply(this, arguments);
1647
+ }
1648
+ var SpinnerIcon = (({
1649
+ styles = {},
1650
+ ...props
1651
+ }) => /*#__PURE__*/React.createElement("svg", _extends$4({
1652
+ xmlns: "http://www.w3.org/2000/svg",
1653
+ fill: "none",
1654
+ viewBox: "0 0 24 24"
1655
+ }, props), /*#__PURE__*/React.createElement("circle", {
1656
+ cx: "12",
1657
+ cy: "12",
1658
+ r: "10",
1659
+ stroke: "currentColor",
1660
+ strokeWidth: "4",
1661
+ opacity: ".23"
1662
+ }), /*#__PURE__*/React.createElement("path", {
1663
+ fill: "currentColor",
1664
+ d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z",
1665
+ opacity: ".75"
1666
+ })));
1667
+
1668
+ var styles$4 = {"form":"ReplyForm-module_form__3OLBD","input":"ReplyForm-module_input__3g68H","actions":"ReplyForm-module_actions__3ZEiR","hint":"ReplyForm-module_hint__1N8JU","submitButton":"ReplyForm-module_submitButton__2nYwT","spinner":"ReplyForm-module_spinner__3uGA7","spin":"ReplyForm-module_spin__23uBX"};
1669
+
1670
+ var _jsxFileName$9 = "/home/tfischbach/code/pageflow/entry_types/scrolled/package/src/review/ReplyForm.js";
1671
+ function ReplyForm({
1672
+ threadId,
1673
+ subjectType,
1674
+ subjectId,
1675
+ subjectRange,
1676
+ onSubmit
1677
+ }) {
1678
+ const {
1679
+ t
1680
+ } = useI18n({
1681
+ locale: 'ui'
1682
+ });
1683
+ const {
1684
+ body,
1685
+ setBody,
1686
+ submitting
1687
+ } = useDraftedBody({
1688
+ threadId
1689
+ });
1690
+ const hasText = body.trim().length > 0;
1691
+ const createComment = useCreateComment({
1692
+ threadId,
1693
+ subjectType,
1694
+ subjectId,
1695
+ subjectRange
1696
+ });
1697
+ function handleChange(e) {
1698
+ setBody(e.target.value);
1699
+ autoGrow(e.target);
1700
+ }
1701
+ function handleSubmit(event) {
1702
+ event.preventDefault();
1703
+ createReply();
1704
+ }
1705
+ function handleKeyDown(event) {
1706
+ if (isSubmitShortcut(event)) {
1707
+ event.preventDefault();
1708
+ createReply();
1709
+ }
1710
+ }
1711
+ function createReply() {
1712
+ if (!hasText || submitting) return;
1713
+ createComment(body);
1714
+ if (onSubmit) onSubmit();
1715
+ }
1716
+ return /*#__PURE__*/React.createElement("form", {
1717
+ className: styles$4.form,
1718
+ onSubmit: handleSubmit,
1719
+ "aria-busy": submitting,
1720
+ __self: this,
1721
+ __source: {
1722
+ fileName: _jsxFileName$9,
1723
+ lineNumber: 49,
1724
+ columnNumber: 5
1725
+ }
1726
+ }, /*#__PURE__*/React.createElement("textarea", {
1727
+ className: styles$4.input,
1728
+ ref: autoResize,
1729
+ value: body,
1730
+ onChange: handleChange,
1731
+ onKeyDown: handleKeyDown,
1732
+ placeholder: t('pageflow_scrolled.review.reply_placeholder'),
1733
+ disabled: submitting,
1734
+ rows: 1,
1735
+ __self: this,
1736
+ __source: {
1737
+ fileName: _jsxFileName$9,
1738
+ lineNumber: 50,
1739
+ columnNumber: 7
1740
+ }
1741
+ }), hasText && /*#__PURE__*/React.createElement("div", {
1742
+ className: styles$4.actions,
1743
+ __self: this,
1744
+ __source: {
1745
+ fileName: _jsxFileName$9,
1746
+ lineNumber: 59,
1747
+ columnNumber: 9
1748
+ }
1749
+ }, !submitting && /*#__PURE__*/React.createElement("span", {
1750
+ className: styles$4.hint,
1751
+ __self: this,
1752
+ __source: {
1753
+ fileName: _jsxFileName$9,
1754
+ lineNumber: 61,
1755
+ columnNumber: 13
1756
+ }
1757
+ }, t('pageflow_scrolled.review.enter_for_new_line')), /*#__PURE__*/React.createElement("button", {
1758
+ className: styles$4.submitButton,
1759
+ type: "submit",
1760
+ disabled: submitting,
1761
+ "aria-label": t('pageflow_scrolled.review.send'),
1762
+ __self: this,
1763
+ __source: {
1764
+ fileName: _jsxFileName$9,
1765
+ lineNumber: 64,
1766
+ columnNumber: 11
1767
+ }
1768
+ }, submitting ? /*#__PURE__*/React.createElement(SpinnerIcon, {
1769
+ className: styles$4.spinner,
1770
+ __self: this,
1771
+ __source: {
1772
+ fileName: _jsxFileName$9,
1773
+ lineNumber: 68,
1774
+ columnNumber: 27
1775
+ }
1776
+ }) : /*#__PURE__*/React.createElement(SendIcon, {
1777
+ __self: this,
1778
+ __source: {
1779
+ fileName: _jsxFileName$9,
1780
+ lineNumber: 68,
1781
+ columnNumber: 72
1782
+ }
1783
+ }), t('pageflow_scrolled.review.send'))));
1784
+ }
1785
+
1786
+ function commentsWithOutdatedQuote(comments, currentQuote) {
1787
+ const ids = new Set();
1788
+ comments.forEach((comment, index) => {
1789
+ const previousQuote = index > 0 ? comments[index - 1].quote : undefined;
1790
+ if (comment.quote && comment.quote !== currentQuote && comment.quote !== previousQuote) {
1791
+ ids.add(comment.id);
1792
+ }
1793
+ });
1794
+ return ids;
1795
+ }
1796
+
1797
+ const DWELL_TIME = 800;
1798
+
1799
+ // A thread taller than the viewport could never reach a visibility ratio.
1800
+ const ROOT_MARGIN = '-10% 0px -10% 0px';
1801
+ function useMarkThreadReadWhenSeen({
1802
+ thread,
1803
+ ref,
1804
+ enabled
1805
+ }) {
1806
+ const unread = useLiveUnreadActivity(thread);
1807
+ const markThreadRead = useMarkThreadRead();
1808
+ const {
1809
+ permaId
1810
+ } = thread;
1811
+ const hasUnread = unread.length > 0;
1812
+ useEffect(() => {
1813
+ const element = ref.current;
1814
+ if (!enabled || !hasUnread || !markThreadRead || !element) return;
1815
+ let timeout;
1816
+ const observer = new IntersectionObserver(entries => {
1817
+ clearTimeout(timeout);
1818
+ if (entries[entries.length - 1].isIntersecting) {
1819
+ timeout = setTimeout(() => markThreadRead(permaId), DWELL_TIME);
1820
+ }
1821
+ }, {
1822
+ rootMargin: ROOT_MARGIN
1823
+ });
1824
+ observer.observe(element);
1825
+ return () => {
1826
+ clearTimeout(timeout);
1827
+ observer.disconnect();
1828
+ };
1829
+ }, [enabled, hasUnread, markThreadRead, permaId, ref]);
1830
+ }
1831
+
1832
+ var _jsxFileName$a = "/home/tfischbach/code/pageflow/entry_types/scrolled/package/src/review/scrollHighlightedThreadIntoView.js";
1833
+ const ScrollHighlightedThreadIntoViewContext = createContext(false);
1834
+ function ScrollHighlightedThreadIntoViewProvider({
1835
+ children
1836
+ }) {
1837
+ return /*#__PURE__*/React.createElement(ScrollHighlightedThreadIntoViewContext.Provider, {
1838
+ value: true,
1839
+ __self: this,
1840
+ __source: {
1841
+ fileName: _jsxFileName$a,
1842
+ lineNumber: 7,
1843
+ columnNumber: 5
1844
+ }
1845
+ }, children);
1846
+ }
1847
+ function useScrollHighlightedThreadIntoView() {
1848
+ return useContext(ScrollHighlightedThreadIntoViewContext);
1849
+ }
1850
+
1851
+ function _extends$5() {
1852
+ _extends$5 = Object.assign ? Object.assign.bind() : function (target) {
1853
+ for (var i = 1; i < arguments.length; i++) {
1854
+ var source = arguments[i];
1855
+ for (var key in source) {
1856
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
1857
+ target[key] = source[key];
1858
+ }
1859
+ }
1860
+ }
1861
+ return target;
1862
+ };
1863
+ return _extends$5.apply(this, arguments);
1864
+ }
1865
+ var ChevronIcon = (({
1866
+ styles = {},
1867
+ ...props
1868
+ }) => /*#__PURE__*/React.createElement("svg", _extends$5({
1869
+ xmlns: "http://www.w3.org/2000/svg",
1870
+ viewBox: "0 0 24 24",
1871
+ fill: "none",
1872
+ stroke: "currentColor",
1873
+ strokeWidth: "2",
1874
+ strokeLinecap: "round",
1875
+ strokeLinejoin: "round",
1876
+ width: "16",
1877
+ height: "16"
1878
+ }, props), /*#__PURE__*/React.createElement("path", {
1879
+ d: "M6 9l6 6 6-6"
1880
+ })));
1881
+
1882
+ function _extends$6() {
1883
+ _extends$6 = Object.assign ? Object.assign.bind() : function (target) {
1884
+ for (var i = 1; i < arguments.length; i++) {
1885
+ var source = arguments[i];
1886
+ for (var key in source) {
1887
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
1888
+ target[key] = source[key];
1889
+ }
1890
+ }
1891
+ }
1892
+ return target;
1893
+ };
1894
+ return _extends$6.apply(this, arguments);
1895
+ }
1896
+ var ResolveIcon = (({
1897
+ styles = {},
1898
+ ...props
1899
+ }) => /*#__PURE__*/React.createElement("svg", _extends$6({
1900
+ xmlns: "http://www.w3.org/2000/svg",
1901
+ viewBox: "0 0 24 24",
1902
+ fill: "none",
1903
+ stroke: "currentColor",
1904
+ strokeWidth: "2",
1905
+ strokeLinecap: "round",
1906
+ strokeLinejoin: "round",
1907
+ width: "16",
1908
+ height: "16"
1909
+ }, props), /*#__PURE__*/React.createElement("circle", {
1910
+ cx: "12",
1911
+ cy: "12",
1912
+ r: "10"
1913
+ }), /*#__PURE__*/React.createElement("path", {
1914
+ d: "M9 12l2 2 4-4"
1915
+ })));
1916
+
1917
+ function _extends$7() {
1918
+ _extends$7 = Object.assign ? Object.assign.bind() : function (target) {
1919
+ for (var i = 1; i < arguments.length; i++) {
1920
+ var source = arguments[i];
1921
+ for (var key in source) {
1922
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
1923
+ target[key] = source[key];
1924
+ }
1925
+ }
1926
+ }
1927
+ return target;
1928
+ };
1929
+ return _extends$7.apply(this, arguments);
1930
+ }
1931
+ var UnresolveIcon = (({
1932
+ styles = {},
1933
+ ...props
1934
+ }) => /*#__PURE__*/React.createElement("svg", _extends$7({
1935
+ xmlns: "http://www.w3.org/2000/svg",
1936
+ viewBox: "0 0 24 24",
1937
+ fill: "none",
1938
+ stroke: "currentColor",
1939
+ strokeWidth: "2",
1940
+ strokeLinecap: "round",
1941
+ strokeLinejoin: "round",
1942
+ width: "16",
1943
+ height: "16"
1944
+ }, props), /*#__PURE__*/React.createElement("circle", {
1945
+ cx: "12",
1946
+ cy: "12",
1947
+ r: "10"
1948
+ }), /*#__PURE__*/React.createElement("path", {
1949
+ d: "M15 9l-6 6m0-6l6 6"
1950
+ })));
1951
+
1952
+ var styles$5 = {"thread":"Thread-module_thread__2dVNT","clickable":"Thread-module_clickable__363ed","highlighted":"Thread-module_highlighted__QPdWq","unreadTopic":"Thread-module_unreadTopic__lV9Gx","unreadDot":"Thread-module_unreadDot__2eXv2","replyCount":"Thread-module_replyCount__1BtL6","counts":"Thread-module_counts__xwlLv","count":"Thread-module_count__2M10h","unreadReplyCount":"Thread-module_unreadReplyCount__3edml","unreadRepliesDivider":"Thread-module_unreadRepliesDivider__3MluU","deletedHint":"Thread-module_deletedHint___87Af","replyChevron":"Thread-module_replyChevron__1D9Id","chevronExpanded":"Thread-module_chevronExpanded__3yPgT","repliesToggle":"Thread-module_repliesToggle__3AB0y","resolveRow":"Thread-module_resolveRow__J-YwZ","resolveButton":"Thread-module_resolveButton__3jHc5","resolveRowIcon":"Thread-module_resolveRowIcon__jadO-","resolveIcon":"Thread-module_resolveIcon__3BY4k Thread-module_resolveRowIcon__jadO-","unreadResolution":"Thread-module_unreadResolution__aWpoR","resolutionIcon":"Thread-module_resolutionIcon__1Ukvm Thread-module_resolveRowIcon__jadO-","resolution":"Thread-module_resolution__ky9CL","resolver":"Thread-module_resolver__1uT7v","resolutionMeta":"Thread-module_resolutionMeta__16YZ7","foldedReplies":"Thread-module_foldedReplies__bF4Xu","foldedRepliesButton":"Thread-module_foldedRepliesButton__rZA-u Thread-module_foldedReplies__bF4Xu"};
1953
+
1954
+ var _jsxFileName$b = "/home/tfischbach/code/pageflow/entry_types/scrolled/package/src/review/Thread.js";
1955
+ function Thread({
1956
+ thread,
1957
+ collapsed: collapsedProp,
1958
+ visibleReplyCount,
1959
+ onExpandReplies,
1960
+ onToggle,
1961
+ onReply,
1962
+ onResolve,
1963
+ onClick,
1964
+ highlighted,
1965
+ showUnreadMarker,
1966
+ markReadWhenHighlighted,
1967
+ interactive = true
1968
+ }) {
1969
+ const {
1970
+ t
1971
+ } = useI18n({
1972
+ locale: 'ui'
1973
+ });
1974
+ const firstComment = thread.comments[0];
1975
+ const replies = thread.comments.slice(1);
1976
+ const [replyDraft] = useCommentDraft({
1977
+ threadId: thread.id
1978
+ });
1979
+ const collapsed = collapsedProp && !replyDraft;
1980
+ const repliesCollapsed = collapsed && replies.length > 0;
1981
+ const {
1982
+ shownReplies,
1983
+ foldedReplyCount
1984
+ } = foldReplies(replies, {
1985
+ visibleReplyCount,
1986
+ collapsed: repliesCollapsed
1987
+ });
1988
+ const hiddenReplies = repliesCollapsed ? replies : replies.slice(0, foldedReplyCount);
1989
+ const {
1990
+ unread,
1991
+ unreadTopic,
1992
+ unreadReplyCount,
1993
+ unreadResolution,
1994
+ firstUnreadReplyId,
1995
+ hidesUnread
1996
+ } = useUnreadMarkers({
1997
+ thread,
1998
+ firstComment,
1999
+ replies,
2000
+ hiddenReplies
2001
+ });
2002
+ const hidesUnreadReplies = repliesCollapsed && unreadReplyCount > 0;
2003
+ const [editingCommentId, setEditingCommentId] = useState(null);
2004
+ const editing = editingCommentId !== null;
2005
+ function editProps(comment) {
2006
+ return {
2007
+ threadId: thread.id,
2008
+ editing: editingCommentId === comment.id,
2009
+ onEdit: interactive ? () => setEditingCommentId(comment.id) : undefined,
2010
+ onEditEnd: () => setEditingCommentId(null)
2011
+ };
2012
+ }
2013
+ const currentQuote = useSubjectQuote(thread);
2014
+ const outdatedQuotes = useMemo(() => commentsWithOutdatedQuote(thread.comments, currentQuote), [thread.comments, currentQuote]);
2015
+ const ref = useRef();
2016
+ const scrollHighlightedIntoView = useScrollHighlightedThreadIntoView();
2017
+ useMarkThreadReadWhenSeen({
2018
+ thread,
2019
+ ref,
2020
+ enabled: !hidesUnread && (highlighted || !markReadWhenHighlighted)
2021
+ });
2022
+ useEffect(() => {
2023
+ if (scrollHighlightedIntoView && highlighted && ref.current) {
2024
+ ref.current.scrollIntoView({
2025
+ block: 'nearest',
2026
+ behavior: 'smooth'
2027
+ });
2028
+ }
2029
+ }, [scrollHighlightedIntoView, highlighted]);
2030
+ return /*#__PURE__*/React.createElement("div", {
2031
+ ref: ref,
2032
+ className: classNames(styles$5.thread, {
2033
+ [styles$5.highlighted]: highlighted,
2034
+ [styles$5.unreadTopic]: showUnreadMarker && unreadTopic,
2035
+ [styles$5.clickable]: onClick
2036
+ }),
2037
+ onClick: onClick,
2038
+ "aria-current": highlighted ? 'true' : undefined,
2039
+ __self: this,
2040
+ __source: {
2041
+ fileName: _jsxFileName$b,
2042
+ lineNumber: 79,
2043
+ columnNumber: 5
2044
+ }
2045
+ }, showUnreadMarker && unread.length > 0 && /*#__PURE__*/React.createElement("span", {
2046
+ role: "img",
2047
+ className: styles$5.unreadDot,
2048
+ "aria-label": t('pageflow_scrolled.review.unread_count', {
2049
+ count: unread.length
2050
+ }),
2051
+ __self: this,
2052
+ __source: {
2053
+ fileName: _jsxFileName$b,
2054
+ lineNumber: 88,
2055
+ columnNumber: 9
2056
+ }
2057
+ }), thread.orphaned && /*#__PURE__*/React.createElement("p", {
2058
+ className: styles$5.deletedHint,
2059
+ __self: this,
2060
+ __source: {
2061
+ fileName: _jsxFileName$b,
2062
+ lineNumber: 94,
2063
+ columnNumber: 9
2064
+ }
2065
+ }, t('pageflow_scrolled.review.refers_to_deleted_element')), firstComment && /*#__PURE__*/React.createElement(Comment, Object.assign({
2066
+ comment: firstComment,
2067
+ showQuote: outdatedQuotes.has(firstComment.id)
2068
+ }, editProps(firstComment), {
2069
+ __self: this,
2070
+ __source: {
2071
+ fileName: _jsxFileName$b,
2072
+ lineNumber: 99,
2073
+ columnNumber: 9
2074
+ }
2075
+ })), replies.length > 0 && !foldedReplyCount && /*#__PURE__*/React.createElement("button", {
2076
+ className: styles$5.repliesToggle,
2077
+ onClick: onToggle,
2078
+ "aria-expanded": !collapsed,
2079
+ __self: this,
2080
+ __source: {
2081
+ fileName: _jsxFileName$b,
2082
+ lineNumber: 104,
2083
+ columnNumber: 9
2084
+ }
2085
+ }, /*#__PURE__*/React.createElement("span", {
2086
+ className: styles$5.replyCount,
2087
+ __self: this,
2088
+ __source: {
2089
+ fileName: _jsxFileName$b,
2090
+ lineNumber: 107,
2091
+ columnNumber: 11
2092
+ }
2093
+ }, /*#__PURE__*/React.createElement("span", {
2094
+ className: styles$5.counts,
2095
+ __self: this,
2096
+ __source: {
2097
+ fileName: _jsxFileName$b,
2098
+ lineNumber: 108,
2099
+ columnNumber: 13
2100
+ }
2101
+ }, /*#__PURE__*/React.createElement("span", {
2102
+ className: styles$5.count,
2103
+ __self: this,
2104
+ __source: {
2105
+ fileName: _jsxFileName$b,
2106
+ lineNumber: 109,
2107
+ columnNumber: 15
2108
+ }
2109
+ }, t('pageflow_scrolled.review.reply_count', {
2110
+ count: replies.length
2111
+ })), hidesUnreadReplies && /*#__PURE__*/React.createElement("span", {
2112
+ className: styles$5.unreadReplyCount,
2113
+ __self: this,
2114
+ __source: {
2115
+ fileName: _jsxFileName$b,
2116
+ lineNumber: 113,
2117
+ columnNumber: 17
2118
+ }
2119
+ }, t('pageflow_scrolled.review.unread_reply_count', {
2120
+ count: unreadReplyCount
2121
+ }))), /*#__PURE__*/React.createElement(ChevronIcon, {
2122
+ className: classNames(styles$5.replyChevron, {
2123
+ [styles$5.chevronExpanded]: !collapsed
2124
+ }),
2125
+ __self: this,
2126
+ __source: {
2127
+ fileName: _jsxFileName$b,
2128
+ lineNumber: 117,
2129
+ columnNumber: 13
2130
+ }
2131
+ })), repliesCollapsed && /*#__PURE__*/React.createElement(AvatarStack, {
2132
+ names: replies.map(c => c.creatorName),
2133
+ __self: this,
2134
+ __source: {
2135
+ fileName: _jsxFileName$b,
2136
+ lineNumber: 120,
2137
+ columnNumber: 32
2138
+ }
2139
+ })), !collapsed && foldedReplyCount > 0 && /*#__PURE__*/React.createElement(FoldedReplies, {
2140
+ count: foldedReplyCount,
2141
+ onExpand: onExpandReplies,
2142
+ __self: this,
2143
+ __source: {
2144
+ fileName: _jsxFileName$b,
2145
+ lineNumber: 124,
2146
+ columnNumber: 9
2147
+ }
2148
+ }), !collapsed && shownReplies.map(comment => /*#__PURE__*/React.createElement(React.Fragment, {
2149
+ key: comment.id,
2150
+ __self: this,
2151
+ __source: {
2152
+ fileName: _jsxFileName$b,
2153
+ lineNumber: 127,
2154
+ columnNumber: 9
2155
+ }
2156
+ }, comment.id === firstUnreadReplyId && /*#__PURE__*/React.createElement("div", {
2157
+ className: styles$5.unreadRepliesDivider,
2158
+ __self: this,
2159
+ __source: {
2160
+ fileName: _jsxFileName$b,
2161
+ lineNumber: 129,
2162
+ columnNumber: 13
2163
+ }
2164
+ }, t('pageflow_scrolled.review.unread_replies')), /*#__PURE__*/React.createElement(Comment, Object.assign({
2165
+ comment: comment,
2166
+ showQuote: outdatedQuotes.has(comment.id)
2167
+ }, editProps(comment), {
2168
+ __self: this,
2169
+ __source: {
2170
+ fileName: _jsxFileName$b,
2171
+ lineNumber: 132,
2172
+ columnNumber: 11
2173
+ }
2174
+ })))), interactive && !thread.resolvedAt && !repliesCollapsed && !foldedReplyCount && !editing && /*#__PURE__*/React.createElement(ReplyForm, {
2175
+ threadId: thread.id,
2176
+ subjectType: thread.subjectType,
2177
+ subjectId: thread.subjectId,
2178
+ subjectRange: thread.subjectRange,
2179
+ onSubmit: onReply,
2180
+ __self: this,
2181
+ __source: {
2182
+ fileName: _jsxFileName$b,
2183
+ lineNumber: 139,
2184
+ columnNumber: 9
2185
+ }
2186
+ }), (thread.resolvedAt || interactive && onResolve && !repliesCollapsed) && /*#__PURE__*/React.createElement("div", {
2187
+ className: classNames(styles$5.resolveRow, {
2188
+ [styles$5.unreadResolution]: unreadResolution
2189
+ }),
2190
+ __self: this,
2191
+ __source: {
2192
+ fileName: _jsxFileName$b,
2193
+ lineNumber: 146,
2194
+ columnNumber: 9
2195
+ }
2196
+ }, thread.resolvedAt ? /*#__PURE__*/React.createElement(Resolution, {
2197
+ thread: thread,
2198
+ onUnresolve: interactive ? onResolve : undefined,
2199
+ __self: this,
2200
+ __source: {
2201
+ fileName: _jsxFileName$b,
2202
+ lineNumber: 149,
2203
+ columnNumber: 12
2204
+ }
2205
+ }) : /*#__PURE__*/React.createElement("button", {
2206
+ className: styles$5.resolveButton,
2207
+ onClick: onResolve,
2208
+ __self: this,
2209
+ __source: {
2210
+ fileName: _jsxFileName$b,
2211
+ lineNumber: 151,
2212
+ columnNumber: 12
2213
+ }
2214
+ }, /*#__PURE__*/React.createElement(ResolveIcon, {
2215
+ className: styles$5.resolveIcon,
2216
+ __self: this,
2217
+ __source: {
2218
+ fileName: _jsxFileName$b,
2219
+ lineNumber: 152,
2220
+ columnNumber: 14
2221
+ }
2222
+ }), t('pageflow_scrolled.review.resolve'))));
2223
+ }
2224
+ function foldReplies(replies, {
2225
+ visibleReplyCount,
2226
+ collapsed
2227
+ }) {
2228
+ const foldedReplyCount = collapsed || visibleReplyCount === undefined ? 0 : Math.max(replies.length - visibleReplyCount, 0);
2229
+ return {
2230
+ foldedReplyCount,
2231
+ shownReplies: foldedReplyCount > 0 ? replies.slice(foldedReplyCount) : replies
2232
+ };
2233
+ }
2234
+ function useUnreadMarkers({
2235
+ thread,
2236
+ firstComment,
2237
+ replies,
2238
+ hiddenReplies
2239
+ }) {
2240
+ const unread = useUnreadActivity(thread);
2241
+ const unreadIds = useMemo(() => new Set(unread.map(event => event.id)), [unread]);
2242
+ const firstUnreadReplyId = useMemo(() => {
2243
+ var _replies$find;
2244
+ if (!unread.length || unread[0].id === (firstComment === null || firstComment === void 0 ? void 0 : firstComment.id)) {
2245
+ return null;
2246
+ }
2247
+ return (_replies$find = replies.find(reply => unreadIds.has(reply.id))) === null || _replies$find === void 0 ? void 0 : _replies$find.id;
2248
+ }, [unread, unreadIds, replies, firstComment]);
2249
+ return {
2250
+ unread,
2251
+ firstUnreadReplyId,
2252
+ unreadTopic: !!firstComment && unreadIds.has(firstComment.id),
2253
+ unreadReplyCount: replies.filter(reply => unreadIds.has(reply.id)).length,
2254
+ unreadResolution: unread.some(event => event.resolution),
2255
+ hidesUnread: hiddenReplies.some(reply => unreadIds.has(reply.id))
2256
+ };
2257
+ }
2258
+ function Resolution({
2259
+ thread,
2260
+ onUnresolve
2261
+ }) {
2262
+ const {
2263
+ t
2264
+ } = useI18n({
2265
+ locale: 'ui'
2266
+ });
2267
+ const locale = useLocale({
2268
+ locale: 'ui'
2269
+ });
2270
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(ResolveIcon, {
2271
+ className: styles$5.resolutionIcon,
2272
+ __self: this,
2273
+ __source: {
2274
+ fileName: _jsxFileName$b,
2275
+ lineNumber: 203,
2276
+ columnNumber: 7
2277
+ }
2278
+ }), /*#__PURE__*/React.createElement("div", {
2279
+ className: styles$5.resolution,
2280
+ __self: this,
2281
+ __source: {
2282
+ fileName: _jsxFileName$b,
2283
+ lineNumber: 204,
2284
+ columnNumber: 7
2285
+ }
2286
+ }, /*#__PURE__*/React.createElement("span", {
2287
+ __self: this,
2288
+ __source: {
2289
+ fileName: _jsxFileName$b,
2290
+ lineNumber: 205,
2291
+ columnNumber: 9
2292
+ }
2293
+ }, t(thread.resolverName ? 'pageflow_scrolled.review.resolution_by' : 'pageflow_scrolled.review.resolution')), /*#__PURE__*/React.createElement("span", {
2294
+ className: styles$5.resolutionMeta,
2295
+ __self: this,
2296
+ __source: {
2297
+ fileName: _jsxFileName$b,
2298
+ lineNumber: 210,
2299
+ columnNumber: 9
2300
+ }
2301
+ }, thread.resolverName && /*#__PURE__*/React.createElement("span", {
2302
+ className: styles$5.resolver,
2303
+ __self: this,
2304
+ __source: {
2305
+ fileName: _jsxFileName$b,
2306
+ lineNumber: 212,
2307
+ columnNumber: 13
2308
+ }
2309
+ }, thread.resolverName), /*#__PURE__*/React.createElement("time", {
2310
+ dateTime: thread.resolvedAt,
2311
+ __self: this,
2312
+ __source: {
2313
+ fileName: _jsxFileName$b,
2314
+ lineNumber: 213,
2315
+ columnNumber: 11
2316
+ }
2317
+ }, formatDate(thread.resolvedAt, locale)))), onUnresolve && /*#__PURE__*/React.createElement(CommentMenu, {
2318
+ label: t('pageflow_scrolled.review.thread_actions'),
2319
+ items: [{
2320
+ icon: UnresolveIcon,
2321
+ label: t('pageflow_scrolled.review.unresolve'),
2322
+ onSelect: onUnresolve
2323
+ }],
2324
+ __self: this,
2325
+ __source: {
2326
+ fileName: _jsxFileName$b,
2327
+ lineNumber: 220,
2328
+ columnNumber: 9
2329
+ }
2330
+ }));
2331
+ }
2332
+ function FoldedReplies({
2333
+ count,
2334
+ onExpand
2335
+ }) {
2336
+ const {
2337
+ t
2338
+ } = useI18n({
2339
+ locale: 'ui'
2340
+ });
2341
+ const label = t('pageflow_scrolled.review.earlier_reply_count', {
2342
+ count
2343
+ });
2344
+ if (!onExpand) {
2345
+ return /*#__PURE__*/React.createElement("div", {
2346
+ className: styles$5.foldedReplies,
2347
+ __self: this,
2348
+ __source: {
2349
+ fileName: _jsxFileName$b,
2350
+ lineNumber: 233,
2351
+ columnNumber: 12
2352
+ }
2353
+ }, label);
2354
+ }
2355
+ return /*#__PURE__*/React.createElement("button", {
2356
+ className: styles$5.foldedRepliesButton,
2357
+ onClick: onExpand,
2358
+ __self: this,
2359
+ __source: {
2360
+ fileName: _jsxFileName$b,
2361
+ lineNumber: 237,
2362
+ columnNumber: 5
2363
+ }
2364
+ }, label);
2365
+ }
2366
+
2367
+ var styles$6 = {"form":"NewThreadForm-module_form__dNsMB","input":"NewThreadForm-module_input__2HX-m","actions":"NewThreadForm-module_actions__OVGqx","hint":"NewThreadForm-module_hint__1_RFf","submitButton":"NewThreadForm-module_submitButton__3_ZpM","spinner":"NewThreadForm-module_spinner__1XkBe","spin":"NewThreadForm-module_spin__3P_Js"};
2368
+
2369
+ var _jsxFileName$c = "/home/tfischbach/code/pageflow/entry_types/scrolled/package/src/review/NewThreadForm.js";
2370
+ function NewThreadForm({
2371
+ subjectType,
2372
+ subjectId,
2373
+ subjectRange,
2374
+ onSubmit
2375
+ }) {
2376
+ const {
2377
+ t
2378
+ } = useI18n({
2379
+ locale: 'ui'
2380
+ });
2381
+ const {
2382
+ body,
2383
+ setBody,
2384
+ submitting
2385
+ } = useDraftedBody({
2386
+ subjectType,
2387
+ subjectId
2388
+ });
2389
+ const hasText = body.trim().length > 0;
2390
+ const createCommentThread = useCreateCommentThread({
2391
+ subjectType,
2392
+ subjectId,
2393
+ subjectRange
2394
+ });
2395
+
2396
+ // Focusing without preventScroll yanks the page to the top before
2397
+ // floating-ui has positioned the portaled popover.
2398
+ const setInputRef = useCallback(node => {
2399
+ autoResize(node);
2400
+ node === null || node === void 0 ? void 0 : node.focus({
2401
+ preventScroll: true
2402
+ });
2403
+ }, []);
2404
+ function handleChange(e) {
2405
+ setBody(e.target.value);
2406
+ autoGrow(e.target);
2407
+ }
2408
+ function handleSubmit(event) {
2409
+ event.preventDefault();
2410
+ createThread();
2411
+ }
2412
+ function handleKeyDown(event) {
2413
+ if (isSubmitShortcut(event)) {
2414
+ event.preventDefault();
2415
+ createThread();
2416
+ }
2417
+ }
2418
+ function createThread() {
2419
+ if (!hasText || submitting) return;
2420
+ createCommentThread(body);
2421
+ if (onSubmit) onSubmit();
2422
+ }
2423
+ return /*#__PURE__*/React.createElement("form", {
2424
+ className: styles$6.form,
2425
+ onSubmit: handleSubmit,
2426
+ "aria-busy": submitting,
2427
+ __self: this,
2428
+ __source: {
2429
+ fileName: _jsxFileName$c,
2430
+ lineNumber: 56,
2431
+ columnNumber: 5
2432
+ }
2433
+ }, /*#__PURE__*/React.createElement("textarea", {
2434
+ className: styles$6.input,
2435
+ ref: setInputRef,
2436
+ value: body,
2437
+ onChange: handleChange,
2438
+ onKeyDown: handleKeyDown,
2439
+ placeholder: t('pageflow_scrolled.review.add_comment_placeholder'),
2440
+ disabled: submitting,
2441
+ rows: 3,
2442
+ __self: this,
2443
+ __source: {
2444
+ fileName: _jsxFileName$c,
2445
+ lineNumber: 57,
2446
+ columnNumber: 7
2447
+ }
2448
+ }), /*#__PURE__*/React.createElement("div", {
2449
+ className: styles$6.actions,
2450
+ __self: this,
2451
+ __source: {
2452
+ fileName: _jsxFileName$c,
2453
+ lineNumber: 65,
2454
+ columnNumber: 7
2455
+ }
2456
+ }, hasText && !submitting && /*#__PURE__*/React.createElement("span", {
2457
+ className: styles$6.hint,
2458
+ __self: this,
2459
+ __source: {
2460
+ fileName: _jsxFileName$c,
2461
+ lineNumber: 67,
2462
+ columnNumber: 11
2463
+ }
2464
+ }, t('pageflow_scrolled.review.enter_for_new_line')), /*#__PURE__*/React.createElement("button", {
2465
+ className: styles$6.submitButton,
2466
+ type: "submit",
2467
+ disabled: submitting,
2468
+ __self: this,
2469
+ __source: {
2470
+ fileName: _jsxFileName$c,
2471
+ lineNumber: 70,
2472
+ columnNumber: 9
2473
+ }
2474
+ }, submitting ? /*#__PURE__*/React.createElement(SpinnerIcon, {
2475
+ className: styles$6.spinner,
2476
+ __self: this,
2477
+ __source: {
2478
+ fileName: _jsxFileName$c,
2479
+ lineNumber: 73,
2480
+ columnNumber: 25
2481
+ }
2482
+ }) : /*#__PURE__*/React.createElement(SendIcon, {
2483
+ __self: this,
2484
+ __source: {
2485
+ fileName: _jsxFileName$c,
2486
+ lineNumber: 73,
2487
+ columnNumber: 70
2488
+ }
2489
+ }), t('pageflow_scrolled.review.send'))));
2490
+ }
2491
+
2492
+ function _extends$8() {
2493
+ _extends$8 = Object.assign ? Object.assign.bind() : function (target) {
2494
+ for (var i = 1; i < arguments.length; i++) {
2495
+ var source = arguments[i];
2496
+ for (var key in source) {
2497
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
2498
+ target[key] = source[key];
2499
+ }
2500
+ }
2501
+ }
2502
+ return target;
2503
+ };
2504
+ return _extends$8.apply(this, arguments);
2505
+ }
2506
+ var NewTopicIcon = (({
2507
+ styles = {},
2508
+ ...props
2509
+ }) => /*#__PURE__*/React.createElement("svg", _extends$8({
2510
+ xmlns: "http://www.w3.org/2000/svg",
2511
+ viewBox: "0 0 24 24",
2512
+ fill: "none",
2513
+ stroke: "currentColor",
2514
+ strokeWidth: "2",
2515
+ strokeLinecap: "round",
2516
+ strokeLinejoin: "round",
2517
+ width: "16",
2518
+ height: "16"
2519
+ }, props), /*#__PURE__*/React.createElement("path", {
2520
+ d: "M5 12h14m-7-7v14"
2521
+ })));
2522
+
2523
+ var styles$7 = {"container":"ThreadList-module_container__2tqnD","newTopicButton":"ThreadList-module_newTopicButton__hRHCb","reversed":"ThreadList-module_reversed__1ujqo","resolvedSection":"ThreadList-module_resolvedSection__2mZhS","resolvedPill":"ThreadList-module_resolvedPill__2i3zE","chevron":"ThreadList-module_chevron__3xHNN","chevronExpanded":"ThreadList-module_chevronExpanded__2WNCc","blankSlate":"ThreadList-module_blankSlate__hyyTF"};
2524
+
2525
+ var _jsxFileName$d = "/home/tfischbach/code/pageflow/entry_types/scrolled/package/src/review/ThreadList.js";
2526
+ function ThreadList({
2527
+ subjectType,
2528
+ subjectId,
2529
+ subjectRange,
2530
+ filter,
2531
+ resolution,
2532
+ highlightedThreadId,
2533
+ onThreadClick,
2534
+ restrictInteractionsToHighlighted,
2535
+ showNewForm: showNewFormProp,
2536
+ hideNewTopicButton,
2537
+ reversed,
2538
+ expandResolved,
2539
+ startCollapsed,
2540
+ markReadWhenHighlighted
2541
+ }) {
2542
+ const {
2543
+ t
2544
+ } = useI18n({
2545
+ locale: 'ui'
2546
+ });
2547
+ const pickedThreadId = Array.isArray(highlightedThreadId) ? null : highlightedThreadId;
2548
+ const {
2549
+ activeThreads,
2550
+ resolvedThreads
2551
+ } = useThreadsByResolution({
2552
+ subjectType,
2553
+ subjectId,
2554
+ subjectRange,
2555
+ filter,
2556
+ resolution,
2557
+ pickedThreadId
2558
+ });
2559
+ const isHighlighted = thread => Array.isArray(highlightedThreadId) ? highlightedThreadId.includes(thread.id) : thread.id === highlightedThreadId;
2560
+ const noThreads = activeThreads.length === 0 && resolvedThreads.length === 0;
2561
+ const [draft] = useCommentDraft({
2562
+ subjectType,
2563
+ subjectId
2564
+ });
2565
+ const [expandedThreadId, setExpandedThreadId] = useState(() => {
2566
+ var _ref;
2567
+ return startCollapsed ? undefined : (_ref = soleThread(activeThreads) || soleThread(resolvedThreads)) === null || _ref === void 0 ? void 0 : _ref.id;
2568
+ });
2569
+ const [resolvedToggled, setResolvedToggled] = useState(null);
2570
+ const revealsResolved = !!expandResolved || resolution === 'all' || resolvedThreads.some(thread => thread.id === pickedThreadId);
2571
+ const [formToggled, setFormToggled] = useState(showNewFormProp !== undefined ? showNewFormProp : revealsResolved ? noThreads : activeThreads.length === 0);
2572
+ const showResolved = resolvedToggled !== null ? resolvedToggled : revealsResolved;
2573
+ const showNewForm = showNewFormProp !== false && (!!draft || formToggled);
2574
+ function toggleThread(threadId) {
2575
+ setExpandedThreadId(expandedThreadId === threadId ? null : threadId);
2576
+ }
2577
+ return /*#__PURE__*/React.createElement(CommentThreadReadsSnapshot, {
2578
+ resetOn: expandedThreadId,
2579
+ __self: this,
2580
+ __source: {
2581
+ fileName: _jsxFileName$d,
2582
+ lineNumber: 56,
2583
+ columnNumber: 5
2584
+ }
2585
+ }, /*#__PURE__*/React.createElement("div", {
2586
+ className: styles$7.container,
2587
+ __self: this,
2588
+ __source: {
2589
+ fileName: _jsxFileName$d,
2590
+ lineNumber: 57,
2591
+ columnNumber: 7
2592
+ }
2593
+ }, !showNewForm && !hideNewTopicButton && /*#__PURE__*/React.createElement("button", {
2594
+ className: classNames(styles$7.newTopicButton, {
2595
+ [styles$7.reversed]: reversed
2596
+ }),
2597
+ onClick: () => setFormToggled(true),
2598
+ "aria-label": t('pageflow_scrolled.review.new_topic'),
2599
+ __self: this,
2600
+ __source: {
2601
+ fileName: _jsxFileName$d,
2602
+ lineNumber: 59,
2603
+ columnNumber: 11
2604
+ }
2605
+ }, /*#__PURE__*/React.createElement(NewTopicIcon, {
2606
+ __self: this,
2607
+ __source: {
2608
+ fileName: _jsxFileName$d,
2609
+ lineNumber: 63,
2610
+ columnNumber: 13
2611
+ }
2612
+ }), t('pageflow_scrolled.review.new_topic')), showNewForm && /*#__PURE__*/React.createElement(NewThreadForm, {
2613
+ subjectType: subjectType,
2614
+ subjectId: subjectId,
2615
+ subjectRange: subjectRange,
2616
+ onSubmit: () => setFormToggled(false),
2617
+ __self: this,
2618
+ __source: {
2619
+ fileName: _jsxFileName$d,
2620
+ lineNumber: 68,
2621
+ columnNumber: 11
2622
+ }
2623
+ }), noThreads && !showNewForm && /*#__PURE__*/React.createElement("p", {
2624
+ className: styles$7.blankSlate,
2625
+ __self: this,
2626
+ __source: {
2627
+ fileName: _jsxFileName$d,
2628
+ lineNumber: 74,
2629
+ columnNumber: 11
2630
+ }
2631
+ }, t('pageflow_scrolled.review.no_threads_yet')), activeThreads.map(thread => /*#__PURE__*/React.createElement(Thread, {
2632
+ key: thread.id,
2633
+ thread: thread,
2634
+ collapsed: expandedThreadId !== thread.id,
2635
+ showUnreadMarker: activeThreads.length > 1,
2636
+ onToggle: () => toggleThread(thread.id),
2637
+ onReply: () => setExpandedThreadId(thread.id),
2638
+ markReadWhenHighlighted: markReadWhenHighlighted,
2639
+ onResolve: () => postUpdateThreadMessage({
2640
+ threadId: thread.id,
2641
+ resolved: true
2642
+ }),
2643
+ onClick: onThreadClick && (() => onThreadClick(thread)),
2644
+ highlighted: isHighlighted(thread),
2645
+ interactive: !restrictInteractionsToHighlighted || isHighlighted(thread),
2646
+ __self: this,
2647
+ __source: {
2648
+ fileName: _jsxFileName$d,
2649
+ lineNumber: 79,
2650
+ columnNumber: 11
2651
+ }
2652
+ })), resolvedThreads.length > 0 && /*#__PURE__*/React.createElement("div", {
2653
+ className: styles$7.resolvedSection,
2654
+ __self: this,
2655
+ __source: {
2656
+ fileName: _jsxFileName$d,
2657
+ lineNumber: 93,
2658
+ columnNumber: 11
2659
+ }
2660
+ }, /*#__PURE__*/React.createElement("button", {
2661
+ className: styles$7.resolvedPill,
2662
+ onClick: () => setResolvedToggled(!showResolved),
2663
+ __self: this,
2664
+ __source: {
2665
+ fileName: _jsxFileName$d,
2666
+ lineNumber: 94,
2667
+ columnNumber: 13
2668
+ }
2669
+ }, t('pageflow_scrolled.review.resolved_count', {
2670
+ count: resolvedThreads.length
2671
+ }), /*#__PURE__*/React.createElement(ChevronIcon, {
2672
+ className: classNames(styles$7.chevron, {
2673
+ [styles$7.chevronExpanded]: showResolved
2674
+ }),
2675
+ __self: this,
2676
+ __source: {
2677
+ fileName: _jsxFileName$d,
2678
+ lineNumber: 97,
2679
+ columnNumber: 15
2680
+ }
2681
+ })), showResolved && resolvedThreads.map(thread => /*#__PURE__*/React.createElement(Thread, {
2682
+ key: thread.id,
2683
+ thread: thread,
2684
+ collapsed: expandedThreadId !== thread.id,
2685
+ showUnreadMarker: resolvedThreads.length > 1,
2686
+ onToggle: () => toggleThread(thread.id),
2687
+ onReply: () => setExpandedThreadId(thread.id),
2688
+ markReadWhenHighlighted: markReadWhenHighlighted,
2689
+ onResolve: () => postUpdateThreadMessage({
2690
+ threadId: thread.id,
2691
+ resolved: false
2692
+ }),
2693
+ onClick: onThreadClick && (() => onThreadClick(thread)),
2694
+ highlighted: isHighlighted(thread),
2695
+ interactive: !restrictInteractionsToHighlighted || isHighlighted(thread),
2696
+ __self: this,
2697
+ __source: {
2698
+ fileName: _jsxFileName$d,
2699
+ lineNumber: 102,
2700
+ columnNumber: 15
2701
+ }
2702
+ })))));
2703
+ }
2704
+ function useThreadsByResolution({
2705
+ subjectType,
2706
+ subjectId,
2707
+ subjectRange,
2708
+ filter,
2709
+ resolution,
2710
+ pickedThreadId
2711
+ }) {
2712
+ const allActiveThreads = useLocatedCommentThreadsForSubject({
2713
+ subjectType,
2714
+ subjectId,
2715
+ subjectRange,
2716
+ resolution: 'unresolved'
2717
+ });
2718
+ const allResolvedThreads = useLocatedCommentThreadsForSubject({
2719
+ subjectType,
2720
+ subjectId,
2721
+ subjectRange,
2722
+ resolution: 'resolved'
2723
+ });
2724
+ const activeThreads = useMemo(() => filter ? allActiveThreads.filter(filter) : allActiveThreads, [allActiveThreads, filter]);
2725
+ const resolvedThreads = useMemo(() => {
2726
+ const threads = filter ? allResolvedThreads.filter(filter) : allResolvedThreads;
2727
+ return resolution === 'unresolved' ? threads.filter(thread => thread.id === pickedThreadId) : threads;
2728
+ }, [allResolvedThreads, filter, resolution, pickedThreadId]);
2729
+ return {
2730
+ activeThreads,
2731
+ resolvedThreads
2732
+ };
2733
+ }
2734
+ function soleThread(threads) {
2735
+ return threads.length === 1 ? threads[0] : undefined;
2736
+ }
2737
+
2738
+ function useActivityEntries() {
2739
+ const {
2740
+ threads
2741
+ } = useLocatedCommentThreads();
2742
+ const currentUser = useCurrentUser();
2743
+ const commentThreadReads = useDisplayedCommentThreadReads();
2744
+ const entries = useMemo(() => activityEntries({
2745
+ threads,
2746
+ currentUser,
2747
+ commentThreadReads
2748
+ }), [threads, currentUser, commentThreadReads]);
2749
+ return useHeldOrder(entries);
2750
+ }
2751
+ function useHeldOrder(entries) {
2752
+ const takenAt = useRef(new Map());
2753
+ return useMemo(() => {
2754
+ entries.forEach(entry => {
2755
+ if (!takenAt.current.has(entry.threadId)) {
2756
+ takenAt.current.set(entry.threadId, entry.at);
2757
+ }
2758
+ });
2759
+ return entries.map(entry => ({
2760
+ ...entry,
2761
+ at: takenAt.current.get(entry.threadId)
2762
+ })).sort(compareEntries);
2763
+ }, [entries]);
2764
+ }
2765
+ function useUnreadThreadCount() {
2766
+ const {
2767
+ threads
2768
+ } = useLocatedCommentThreads();
2769
+ const currentUser = useCurrentUser();
2770
+ const commentThreadReads = useCommentThreadReads();
2771
+ return useMemo(() => activityEntries({
2772
+ threads,
2773
+ currentUser,
2774
+ commentThreadReads
2775
+ }).filter(entry => entry.unreadCount > 0).length, [threads, currentUser, commentThreadReads]);
2776
+ }
2777
+ function activityEntries({
2778
+ threads,
2779
+ currentUser,
2780
+ commentThreadReads
2781
+ }) {
2782
+ return threads.map(thread => threadEntry(thread, {
2783
+ currentUser,
2784
+ readAt: commentThreadReads[thread.permaId]
2785
+ })).filter(Boolean).sort(compareEntries);
2786
+ }
2787
+ function threadEntry(thread, {
2788
+ currentUser,
2789
+ readAt
2790
+ }) {
2791
+ const events = threadActivity(thread);
2792
+ if (!events.length) return null;
2793
+ const latest = events.reduce((result, event) => new Date(event.at) >= new Date(result.at) ? event : result);
2794
+ const unreadEvents = events.filter(event => isUnread(event, {
2795
+ currentUser,
2796
+ readAt
2797
+ }));
2798
+ return {
2799
+ key: `thread-${thread.id}`,
2800
+ thread,
2801
+ threadId: thread.id,
2802
+ threadPermaId: thread.permaId,
2803
+ at: latest.at,
2804
+ unreadCount: unreadEvents.length,
2805
+ unreadCommentIds: unreadEvents.filter(event => event.id).map(event => event.id),
2806
+ resolved: !!thread.resolvedAt
2807
+ };
2808
+ }
2809
+ function compareEntries(a, b) {
2810
+ return new Date(b.at) - new Date(a.at) || a.threadId - b.threadId;
2811
+ }
2812
+
2813
+ var styles$8 = {"list":"ActivityList-module_list__C2frv","showMore":"ActivityList-module_showMore__2KhKz","blankSlate":"ActivityList-module_blankSlate__TIhSd","entry":"ActivityList-module_entry__7ZmzZ","summary":"ActivityList-module_summary__3EBBY","dayHeading":"ActivityList-module_dayHeading__ItHH0"};
2814
+
2815
+ var _jsxFileName$e = "/home/tfischbach/code/pageflow/entry_types/scrolled/package/src/review/ActivityList.js";
2816
+ function ActivityList({
2817
+ onEntryClick,
2818
+ highlightedThreadId,
2819
+ pageSize = 30
2820
+ }) {
2821
+ return /*#__PURE__*/React.createElement(CommentThreadReadsSnapshot, {
2822
+ __self: this,
2823
+ __source: {
2824
+ fileName: _jsxFileName$e,
2825
+ lineNumber: 14,
2826
+ columnNumber: 5
2827
+ }
2828
+ }, /*#__PURE__*/React.createElement(Entries, {
2829
+ onEntryClick: onEntryClick,
2830
+ highlightedThreadId: highlightedThreadId,
2831
+ pageSize: pageSize,
2832
+ __self: this,
2833
+ __source: {
2834
+ fileName: _jsxFileName$e,
2835
+ lineNumber: 15,
2836
+ columnNumber: 7
2837
+ }
2838
+ }));
2839
+ }
2840
+
2841
+ // The entries have to be read inside the snapshot for the freeze to apply.
2842
+ function Entries({
2843
+ onEntryClick,
2844
+ highlightedThreadId,
2845
+ pageSize
2846
+ }) {
2847
+ const {
2848
+ t
2849
+ } = useI18n({
2850
+ locale: 'ui'
2851
+ });
2852
+ const entries = useActivityEntries();
2853
+ const [pages, setPages] = useState(1);
2854
+ if (!entries.length) {
2855
+ return /*#__PURE__*/React.createElement("p", {
2856
+ className: styles$8.blankSlate,
2857
+ __self: this,
2858
+ __source: {
2859
+ fileName: _jsxFileName$e,
2860
+ lineNumber: 30,
2861
+ columnNumber: 7
2862
+ }
2863
+ }, t('pageflow_scrolled.review.activity.no_activity_yet'));
2864
+ }
2865
+ const shown = entries.slice(0, pages * pageSize);
2866
+ return /*#__PURE__*/React.createElement("div", {
2867
+ className: styles$8.list,
2868
+ __self: this,
2869
+ __source: {
2870
+ fileName: _jsxFileName$e,
2871
+ lineNumber: 39,
2872
+ columnNumber: 5
2873
+ }
2874
+ }, dayGroups(shown).map(group => /*#__PURE__*/React.createElement(React.Fragment, {
2875
+ key: group.day,
2876
+ __self: this,
2877
+ __source: {
2878
+ fileName: _jsxFileName$e,
2879
+ lineNumber: 41,
2880
+ columnNumber: 9
2881
+ }
2882
+ }, /*#__PURE__*/React.createElement(DayHeading, {
2883
+ day: group.day,
2884
+ at: group.at,
2885
+ __self: this,
2886
+ __source: {
2887
+ fileName: _jsxFileName$e,
2888
+ lineNumber: 42,
2889
+ columnNumber: 11
2890
+ }
2891
+ }), group.entries.map(entry => /*#__PURE__*/React.createElement(Entry, {
2892
+ key: entry.key,
2893
+ entry: entry,
2894
+ day: group.day,
2895
+ highlighted: entry.threadId === highlightedThreadId,
2896
+ onClick: onEntryClick && (() => onEntryClick(entry)),
2897
+ __self: this,
2898
+ __source: {
2899
+ fileName: _jsxFileName$e,
2900
+ lineNumber: 44,
2901
+ columnNumber: 13
2902
+ }
2903
+ })))), shown.length < entries.length && /*#__PURE__*/React.createElement("button", {
2904
+ className: styles$8.showMore,
2905
+ onClick: () => setPages(pages + 1),
2906
+ __self: this,
2907
+ __source: {
2908
+ fileName: _jsxFileName$e,
2909
+ lineNumber: 54,
2910
+ columnNumber: 9
2911
+ }
2912
+ }, t('pageflow_scrolled.review.activity.show_more')));
2913
+ }
2914
+ function Entry({
2915
+ entry,
2916
+ day,
2917
+ highlighted,
2918
+ onClick
2919
+ }) {
2920
+ const {
2921
+ t
2922
+ } = useI18n({
2923
+ locale: 'ui'
2924
+ });
2925
+ const [expanded, setExpanded] = useState(false);
2926
+ const [collapsed, setCollapsed] = useState(false);
2927
+ return /*#__PURE__*/React.createElement("div", {
2928
+ className: styles$8.entry,
2929
+ __self: this,
2930
+ __source: {
2931
+ fileName: _jsxFileName$e,
2932
+ lineNumber: 67,
2933
+ columnNumber: 5
2934
+ }
2935
+ }, /*#__PURE__*/React.createElement("p", {
2936
+ className: styles$8.summary,
2937
+ __self: this,
2938
+ __source: {
2939
+ fileName: _jsxFileName$e,
2940
+ lineNumber: 68,
2941
+ columnNumber: 7
2942
+ }
2943
+ }, summary(t, entry, day)), /*#__PURE__*/React.createElement(Thread, {
2944
+ thread: entry.thread,
2945
+ visibleReplyCount: expanded ? undefined : visibleReplyCount(entry, day),
2946
+ onExpandReplies: () => setExpanded(true),
2947
+ collapsed: collapsed,
2948
+ onToggle: () => setCollapsed(!collapsed),
2949
+ showUnreadMarker: true,
2950
+ markReadWhenHighlighted: true,
2951
+ onClick: onClick,
2952
+ highlighted: highlighted,
2953
+ onResolve: () => postUpdateThreadMessage({
2954
+ threadId: entry.threadId,
2955
+ resolved: !entry.resolved
2956
+ }),
2957
+ __self: this,
2958
+ __source: {
2959
+ fileName: _jsxFileName$e,
2960
+ lineNumber: 69,
2961
+ columnNumber: 7
2962
+ }
2963
+ }));
2964
+ }
2965
+ function summary(t, {
2966
+ thread
2967
+ }, day) {
2968
+ const replies = thread.comments.slice(1);
2969
+ const parts = [onDay(thread.comments[0], day) && t('pageflow_scrolled.review.activity.summary.topic'), replyCountPart(t, replies.filter(reply => onDay(reply, day)).length), thread.resolvedAt && dayOf(thread.resolvedAt) === day && t('pageflow_scrolled.review.activity.summary.resolution')].filter(Boolean);
2970
+ return joinParts(t, parts);
2971
+ }
2972
+ function replyCountPart(t, count) {
2973
+ return count > 0 && t('pageflow_scrolled.review.activity.summary.reply_count', {
2974
+ count
2975
+ });
2976
+ }
2977
+ function onDay(comment, day) {
2978
+ return dayOf(comment.createdAt) === day;
2979
+ }
2980
+ function joinParts(t, parts) {
2981
+ if (parts.length < 2) {
2982
+ return parts[0];
2983
+ }
2984
+ return [parts.slice(0, -1).join(', '), parts[parts.length - 1]].join(t('pageflow_scrolled.review.activity.summary.and'));
2985
+ }
2986
+ function visibleReplyCount({
2987
+ thread,
2988
+ unreadCommentIds
2989
+ }, day) {
2990
+ const replies = thread.comments.slice(1);
2991
+ const starts = [replies.findIndex(reply => unreadCommentIds.includes(reply.id)), replies.findIndex(reply => dayOf(reply.createdAt) === day)].filter(index => index >= 0);
2992
+ return starts.length ? replies.length - Math.min(...starts) : Math.min(replies.length, 1);
2993
+ }
2994
+ function DayHeading({
2995
+ day,
2996
+ at
2997
+ }) {
2998
+ const {
2999
+ t
3000
+ } = useI18n({
3001
+ locale: 'ui'
3002
+ });
3003
+ const locale = useLocale({
3004
+ locale: 'ui'
3005
+ });
3006
+ return /*#__PURE__*/React.createElement("h3", {
3007
+ className: styles$8.dayHeading,
3008
+ __self: this,
3009
+ __source: {
3010
+ fileName: _jsxFileName$e,
3011
+ lineNumber: 135,
3012
+ columnNumber: 5
3013
+ }
3014
+ }, /*#__PURE__*/React.createElement("time", {
3015
+ dateTime: day,
3016
+ __self: this,
3017
+ __source: {
3018
+ fileName: _jsxFileName$e,
3019
+ lineNumber: 136,
3020
+ columnNumber: 7
3021
+ }
3022
+ }, dayLabel(t, {
3023
+ day,
3024
+ at
3025
+ }, locale)));
3026
+ }
3027
+ function dayGroups(entries) {
3028
+ const groups = [];
3029
+ entries.forEach(entry => {
3030
+ const day = dayOf(entry.at);
3031
+ const current = groups[groups.length - 1];
3032
+ if (current && current.day === day) {
3033
+ current.entries.push(entry);
3034
+ } else {
3035
+ groups.push({
3036
+ day,
3037
+ at: entry.at,
3038
+ entries: [entry]
3039
+ });
3040
+ }
3041
+ });
3042
+ return groups;
3043
+ }
3044
+ function dayOf(at) {
3045
+ const date = new Date(at);
3046
+ const month = String(date.getMonth() + 1).padStart(2, '0');
3047
+ const day = String(date.getDate()).padStart(2, '0');
3048
+ return `${date.getFullYear()}-${month}-${day}`;
3049
+ }
3050
+
3051
+ // Formatted from a timestamp within the day: a bare date parses as UTC
3052
+ // midnight, which reads as the day before west of it.
3053
+ function dayLabel(t, {
3054
+ day,
3055
+ at
3056
+ }, locale) {
3057
+ const days = daysSince(day);
3058
+ if (days === 0) {
3059
+ return t('pageflow_scrolled.review.activity.today');
3060
+ }
3061
+ if (days === 1) {
3062
+ return t('pageflow_scrolled.review.activity.yesterday');
3063
+ }
3064
+ return formatDate(at, locale);
3065
+ }
3066
+ function daysSince(day) {
3067
+ const [year, month, date] = day.split('-').map(Number);
3068
+ const then = new Date(year, month - 1, date);
3069
+ const now = new Date();
3070
+ return Math.round((new Date(now.getFullYear(), now.getMonth(), now.getDate()) - then) / 86400000);
3071
+ }
3072
+
3073
+ const emptyDecorations = [];
3074
+ function useCommentHighlights(threads, subjectRange) {
3075
+ return useMemo(() => buildHighlights(threads, subjectRange), [threads, subjectRange]);
3076
+ }
3077
+ function decorateCommentHighlights(editor, highlights) {
3078
+ return function decorate([node, path]) {
3079
+ if (!Text.isText(node)) return emptyDecorations;
3080
+ const nodeRange = {
3081
+ anchor: {
3082
+ path,
3083
+ offset: 0
3084
+ },
3085
+ focus: {
3086
+ path,
3087
+ offset: node.text.length
3088
+ }
3089
+ };
3090
+ const decorations = [];
3091
+ for (const highlight of highlights) {
3092
+ if (!isValidRange(editor, highlight.range)) continue;
3093
+ const intersection = Range.intersection(highlight.range, nodeRange);
3094
+ if (intersection) {
3095
+ var _highlight$thread;
3096
+ const isFirst = Point.equals(Range.start(intersection), Range.start(highlight.range));
3097
+ const isLast = Point.equals(Range.end(intersection), Range.end(highlight.range));
3098
+ decorations.push({
3099
+ ...intersection,
3100
+ commentHighlight: true,
3101
+ subjectRange: highlight.range,
3102
+ rangeKey: highlight.key,
3103
+ resolved: !!((_highlight$thread = highlight.thread) === null || _highlight$thread === void 0 ? void 0 : _highlight$thread.resolvedAt),
3104
+ ...(isFirst && {
3105
+ firstInRange: true
3106
+ }),
3107
+ ...(isLast && {
3108
+ lastInRange: true
3109
+ })
3110
+ });
3111
+ }
3112
+ }
3113
+ return decorations;
3114
+ };
3115
+ }
3116
+ function buildHighlights(threads, subjectRange) {
3117
+ const highlights = threads.map(t => ({
3118
+ key: String(t.id),
3119
+ range: t.subjectRange,
3120
+ thread: t
3121
+ }));
3122
+ if (subjectRange && !threads.some(t => t.subjectRange === subjectRange)) {
3123
+ highlights.push({
3124
+ key: 'selection',
3125
+ range: subjectRange
3126
+ });
3127
+ }
3128
+ return highlights;
3129
+ }
3130
+ function isValidRange(editor, range) {
3131
+ return range && Node$1.has(editor, range.anchor.path) && Node$1.has(editor, range.focus.path);
3132
+ }
3133
+
3134
+ var commentHighlights_module = {"highlight":"commentHighlights-module_highlight__2X0JV","selected":"commentHighlights-module_selected__2AM1C","resolved":"commentHighlights-module_resolved__2Eu8w"};
3135
+
3136
+ var _jsxFileName$f = "/home/tfischbach/code/pageflow/entry_types/scrolled/package/src/review/rangeAnchors.js";
3137
+ function useRangeAnchors() {
3138
+ const containerRef = useRef(null);
3139
+ const elements = useRef(new Map());
3140
+ const [version, setVersion] = useState(0);
3141
+
3142
+ // Slate splits a decorated range wherever another decoration overlaps
3143
+ // it, so several elements can carry the same range key.
3144
+ const registerAnchor = useCallback((rangeKey, el, mounted = true) => {
3145
+ if (!el) return;
3146
+ const registered = elements.current.get(rangeKey) || [];
3147
+ const remaining = mounted ? [...registered, el] : registered.filter(other => other !== el);
3148
+ if (remaining.length) {
3149
+ elements.current.set(rangeKey, remaining);
3150
+ } else {
3151
+ elements.current.delete(rangeKey);
3152
+ }
3153
+ setVersion(v => v + 1);
3154
+ }, []);
3155
+ const anchors = useMemo(() => ({
3156
+ containerRef,
3157
+ _elements: elements,
3158
+ _version: version
3159
+ }), [version]);
3160
+ return {
3161
+ anchors,
3162
+ registerAnchor
3163
+ };
3164
+ }
3165
+ function RangeAnchor({
3166
+ rangeKey,
3167
+ onRegister,
3168
+ children
3169
+ }) {
3170
+ const ref = useRef(null);
3171
+ useEffect(() => {
3172
+ // The cleanup runs once React has detached the ref.
3173
+ const el = ref.current;
3174
+ onRegister(rangeKey, el);
3175
+ return () => onRegister(rangeKey, el, false);
3176
+ }, [rangeKey, onRegister]);
3177
+ return /*#__PURE__*/React.createElement("span", {
3178
+ ref: ref,
3179
+ __self: this,
3180
+ __source: {
3181
+ fileName: _jsxFileName$f,
3182
+ lineNumber: 52,
3183
+ columnNumber: 10
3184
+ }
3185
+ }, children);
3186
+ }
3187
+ function useAnchoredFloating(rangeKey, anchors, {
3188
+ placement = 'right-start',
3189
+ flipOnOverflow = false,
3190
+ mainAxisOffset = 32,
3191
+ fitWidth
3192
+ } = {}) {
3193
+ var _middlewareData$align;
3194
+ const hasAnchor = anchors._elements.current.has(rangeKey);
3195
+ const {
3196
+ refs,
3197
+ floatingStyles,
3198
+ placement: resolvedPlacement,
3199
+ isPositioned,
3200
+ middlewareData
3201
+ } = useFloating({
3202
+ placement,
3203
+ middleware: [alignToContainerEdge(anchors.containerRef, {
3204
+ mainAxisOffset,
3205
+ fitWidth
3206
+ }), ...(flipOnOverflow ? [flip({
3207
+ crossAxis: false,
3208
+ padding: 8
3209
+ })] : []), clampXToViewport({
3210
+ viewportPadding: 0
3211
+ })],
3212
+ whileElementsMounted: (reference, floating, update) => autoUpdate(reference, floating, update, {
3213
+ elementResize: false
3214
+ })
3215
+ });
3216
+ useEffect(() => {
3217
+ const registered = anchors._elements.current.get(rangeKey);
3218
+ if (registered) {
3219
+ refs.setReference(firstInDocument(registered));
3220
+ }
3221
+ }, [refs, anchors, rangeKey, anchors._version]);
3222
+ const fits = (_middlewareData$align = middlewareData.alignToContainerEdge) === null || _middlewareData$align === void 0 ? void 0 : _middlewareData$align.fits;
3223
+ return {
3224
+ refs,
3225
+ floatingStyles,
3226
+ placement: resolvedPlacement,
3227
+ isPositioned,
3228
+ hasAnchor,
3229
+ fits
3230
+ };
3231
+ }
3232
+ function firstInDocument(elements) {
3233
+ return elements.reduce((first, el) => first.compareDocumentPosition(el) & Node.DOCUMENT_POSITION_PRECEDING ? el : first);
3234
+ }
3235
+ function alignToContainerEdge(containerRef, {
3236
+ mainAxisOffset = 0,
3237
+ viewportPadding = 0,
3238
+ fitWidth
3239
+ } = {}) {
3240
+ return {
3241
+ name: 'alignToContainerEdge',
3242
+ fn(state) {
3243
+ const containerEl = containerRef.current;
3244
+ if (!containerEl) return {};
3245
+ const {
3246
+ rects,
3247
+ placement
3248
+ } = state;
3249
+ const containerRect = containerEl.getBoundingClientRect();
3250
+ const toLocalX = viewportToLocalX(state);
3251
+ let x;
3252
+ if (placement.startsWith('right')) {
3253
+ x = toLocalX(containerRect.right + mainAxisOffset);
3254
+ } else if (placement.startsWith('left')) {
3255
+ x = toLocalX(containerRect.left - mainAxisOffset) - rects.floating.width;
3256
+ } else {
3257
+ return {};
3258
+ }
3259
+ const data = {};
3260
+ if (fitWidth !== undefined) {
3261
+ const viewportWidth = document.documentElement.clientWidth;
3262
+ const fitsRight = containerRect.right + mainAxisOffset + fitWidth + viewportPadding <= viewportWidth;
3263
+ const fitsLeft = containerRect.left - mainAxisOffset - fitWidth - viewportPadding >= 0;
3264
+ data.fits = fitsRight || fitsLeft;
3265
+ }
3266
+ return {
3267
+ x,
3268
+ data
3269
+ };
3270
+ }
3271
+ };
3272
+ }
3273
+ function clampXToViewport({
3274
+ viewportPadding = 8
3275
+ } = {}) {
3276
+ return {
3277
+ name: 'clampXToViewport',
3278
+ fn(state) {
3279
+ const {
3280
+ x,
3281
+ rects
3282
+ } = state;
3283
+ const toLocalX = viewportToLocalX(state);
3284
+ const viewportWidth = document.documentElement.clientWidth;
3285
+ const minX = toLocalX(viewportPadding);
3286
+ const maxX = toLocalX(viewportWidth - viewportPadding) - rects.floating.width;
3287
+ return {
3288
+ x: Math.max(minX, Math.min(x, maxX))
3289
+ };
3290
+ }
3291
+ };
3292
+ }
3293
+
3294
+ // Floating UI applies `x`/`y` in the offsetParent's unscaled space, which
3295
+ // getBoundingClientRect's viewport coordinates only match while the
3296
+ // offsetParent is unscaled.
3297
+ function viewportToLocalX({
3298
+ rects,
3299
+ elements
3300
+ }) {
3301
+ const referenceRect = elements.reference.getBoundingClientRect();
3302
+ const scale = rects.reference.width ? referenceRect.width / rects.reference.width : 1;
3303
+ return viewportX => rects.reference.x + (viewportX - referenceRect.left) / scale;
3304
+ }
3305
+
3306
+ export { ActivityList, Badge, CommentDisplayFilterProvider, CommentThreadReadsSnapshot, LocatedCommentThreadsProvider, NewThreadForm, RangeAnchor, ReviewMessageHandler, ReviewStateProvider, ScrollHighlightedThreadIntoViewProvider, Thread, ThreadList, ThreadsBadge, activityEntries, alignToContainerEdge, commentHighlights_module as commentHighlightStyles, decorateCommentHighlights, matchesResolution, postCreateCommentThreadMessage, postUpdateThreadMessage, review, useActivityEntries, useAnchoredFloating, useCommentDisplayFilter, useCommentHighlights, useCommentThread, useCommentThreads, useLocatedCommentThreads, useLocatedCommentThreadsForSubject, useRangeAnchors, useStoredCommentDisplayFilter, useUnreadActivityCount, useUnreadThreadCount, watchUnreadComments };