pageflow 17.1.0 → 17.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (302) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +319 -345
  3. data/README.md +3 -4
  4. data/admins/pageflow/accounts.rb +6 -6
  5. data/admins/pageflow/entry.rb +10 -8
  6. data/admins/pageflow/membership.rb +11 -11
  7. data/admins/pageflow/translations.rb +5 -5
  8. data/app/assets/images/pageflow/admin/icons/comment.svg +1 -0
  9. data/app/assets/javascripts/pageflow/dist/ui.js +1350 -630
  10. data/app/assets/javascripts/pageflow/editor/vendor.js +0 -1
  11. data/app/assets/javascripts/pageflow/ui.js +0 -1
  12. data/app/assets/stylesheets/pageflow/admin/entry_comments_indicator.scss +45 -0
  13. data/app/assets/stylesheets/pageflow/admin/permalink_input.scss +8 -1
  14. data/app/assets/stylesheets/pageflow/admin.scss +1 -0
  15. data/app/assets/stylesheets/pageflow/animations/spin.scss +9 -0
  16. data/app/assets/stylesheets/pageflow/animations.scss +1 -0
  17. data/app/assets/stylesheets/pageflow/editor/background_positioning.scss +21 -5
  18. data/app/assets/stylesheets/pageflow/editor/base.scss +9 -1
  19. data/app/assets/stylesheets/pageflow/editor/drop_down_button.scss +13 -1
  20. data/app/assets/stylesheets/pageflow/editor/edit_configuration_view.scss +5 -0
  21. data/app/assets/stylesheets/pageflow/editor/file_folders.scss +127 -0
  22. data/app/assets/stylesheets/pageflow/editor/file_meta_data.scss +0 -1
  23. data/app/assets/stylesheets/pageflow/editor/file_meta_data_overlay.scss +45 -0
  24. data/app/assets/stylesheets/pageflow/editor/file_preview.scss +138 -0
  25. data/app/assets/stylesheets/pageflow/editor/file_references.scss +68 -0
  26. data/app/assets/stylesheets/pageflow/editor/file_settings_dialog.scss +1 -1
  27. data/app/assets/stylesheets/pageflow/editor/file_stage_icons.scss +17 -0
  28. data/app/assets/stylesheets/pageflow/editor/file_stages.scss +25 -32
  29. data/app/assets/stylesheets/pageflow/editor/file_thumbnails.scss +31 -48
  30. data/app/assets/stylesheets/pageflow/editor/file_type_pills.scss +73 -0
  31. data/app/assets/stylesheets/pageflow/editor/files.scss +230 -50
  32. data/app/assets/stylesheets/pageflow/editor/filtered_files.scss +142 -14
  33. data/app/assets/stylesheets/pageflow/editor/folder_breadcrumb.scss +40 -0
  34. data/app/assets/stylesheets/pageflow/editor/info_box.scss +12 -0
  35. data/app/assets/stylesheets/pageflow/editor/inputs/edit_defaults_button.scss +15 -0
  36. data/app/assets/stylesheets/pageflow/editor/inputs.scss +1 -0
  37. data/app/assets/stylesheets/pageflow/editor/list.scss +88 -38
  38. data/app/assets/stylesheets/pageflow/editor/manage_files.scss +9 -0
  39. data/app/assets/stylesheets/pageflow/editor/menu.scss +14 -0
  40. data/app/assets/stylesheets/pageflow/editor/move_to_folder_dialog.scss +83 -0
  41. data/app/assets/stylesheets/pageflow/mixins/pageflow.scss +9 -5
  42. data/app/assets/stylesheets/pageflow/ui/color_picker.scss +230 -0
  43. data/app/assets/stylesheets/pageflow/ui/forms.scss +34 -4
  44. data/app/assets/stylesheets/pageflow/ui/functions.scss +12 -2
  45. data/app/assets/stylesheets/pageflow/ui/input/color_input.scss +9 -16
  46. data/app/assets/stylesheets/pageflow/ui/input/text_area_input.scss +15 -0
  47. data/app/assets/stylesheets/pageflow/ui/overrides.scss +8 -0
  48. data/app/assets/stylesheets/pageflow/ui/properties.scss +29 -0
  49. data/app/assets/stylesheets/pageflow/ui/tabs_view.scss +8 -3
  50. data/app/assets/stylesheets/pageflow/ui.scss +4 -2
  51. data/app/controllers/pageflow/editor/entries_controller.rb +1 -0
  52. data/app/controllers/pageflow/editor/file_folders_controller.rb +80 -0
  53. data/app/controllers/pageflow/editor/file_import_controller.rb +1 -0
  54. data/app/controllers/pageflow/editor/files_controller.rb +15 -6
  55. data/app/controllers/pageflow/editor/oembeds_controller.rb +21 -0
  56. data/app/controllers/pageflow/entries_controller.rb +17 -13
  57. data/app/controllers/pageflow/review/comment_thread_reads_controller.rb +30 -0
  58. data/app/controllers/pageflow/review/comment_threads_controller.rb +60 -0
  59. data/app/controllers/pageflow/review/comments_controller.rb +53 -0
  60. data/app/controllers/pageflow/revisions_controller.rb +4 -1
  61. data/app/helpers/pageflow/admin/entries_helper.rb +40 -0
  62. data/app/helpers/pageflow/admin/widgets_helper.rb +2 -2
  63. data/app/helpers/pageflow/common_entry_seed_helper.rb +5 -3
  64. data/app/helpers/pageflow/editor/config_helper.rb +3 -1
  65. data/app/helpers/pageflow/entries_helper.rb +24 -42
  66. data/app/helpers/pageflow/entry_json_seed_helper.rb +4 -4
  67. data/app/helpers/pageflow/public_i18n_helper.rb +9 -8
  68. data/app/helpers/pageflow/structured_data_helper.rb +4 -1
  69. data/app/helpers/pageflow/themes_helper.rb +1 -3
  70. data/app/helpers/pageflow/widgets_helper.rb +2 -1
  71. data/app/inputs/pageflow_permalink_input.rb +6 -2
  72. data/app/models/concerns/pageflow/auto_generated_perma_id.rb +11 -4
  73. data/app/models/concerns/pageflow/reusable_file.rb +1 -1
  74. data/app/models/concerns/pageflow/serialization_blacklist.rb +2 -4
  75. data/app/models/concerns/pageflow/translated_attributes.rb +101 -0
  76. data/app/models/concerns/pageflow/uploadable_file.rb +1 -1
  77. data/app/models/pageflow/comment.rb +30 -0
  78. data/app/models/pageflow/comment_thread.rb +65 -0
  79. data/app/models/pageflow/comment_thread_read.rb +22 -0
  80. data/app/models/pageflow/customized_theme.rb +3 -2
  81. data/app/models/pageflow/draft_entry.rb +20 -8
  82. data/app/models/pageflow/entry.rb +2 -0
  83. data/app/models/pageflow/entry_at_revision.rb +9 -1
  84. data/app/models/pageflow/entry_comment_summary.rb +114 -0
  85. data/app/models/pageflow/file_folder.rb +74 -0
  86. data/app/models/pageflow/file_reuse.rb +3 -2
  87. data/app/models/pageflow/file_usage.rb +8 -0
  88. data/app/models/pageflow/image_file.rb +24 -20
  89. data/app/models/pageflow/image_file_url_templates.rb +3 -3
  90. data/app/models/pageflow/legal_info.rb +38 -0
  91. data/app/models/pageflow/nested_revision_component_copy.rb +1 -1
  92. data/app/models/pageflow/permalink.rb +1 -0
  93. data/app/models/pageflow/revision.rb +6 -2
  94. data/app/models/pageflow/site.rb +10 -0
  95. data/app/models/pageflow/used_file.rb +8 -2
  96. data/app/models/pageflow/widget.rb +2 -2
  97. data/app/views/pageflow/audio_files/_audio_file.json.jbuilder +0 -1
  98. data/app/views/pageflow/editor/config/_seeds.json.jbuilder +4 -2
  99. data/app/views/pageflow/editor/entries/_entry.json.jbuilder +2 -1
  100. data/app/views/pageflow/editor/entries/seed.json.erb +1 -0
  101. data/app/views/pageflow/editor/file_folders/_file_folder.json.jbuilder +5 -0
  102. data/app/views/pageflow/editor/file_folders/create.json.jbuilder +1 -0
  103. data/app/views/pageflow/editor/file_folders/index.json.jbuilder +3 -0
  104. data/app/views/pageflow/editor/files/_file.json.jbuilder +2 -1
  105. data/app/views/pageflow/editor/image_files/_image_file.json.jbuilder +3 -0
  106. data/app/views/pageflow/editor/video_files/_video_file.json.jbuilder +3 -0
  107. data/app/views/pageflow/files/_file.json.jbuilder +2 -0
  108. data/app/views/pageflow/image_files/_image_file.json.jbuilder +0 -1
  109. data/app/views/pageflow/review/comment_threads/_comment_thread.json.jbuilder +20 -0
  110. data/app/views/pageflow/review/comment_threads/create.json.jbuilder +2 -0
  111. data/app/views/pageflow/review/comment_threads/index.json.jbuilder +13 -0
  112. data/app/views/pageflow/review/comments/_comment.json.jbuilder +13 -0
  113. data/app/views/pageflow/review/comments/create.json.jbuilder +1 -0
  114. data/app/views/pageflow/structured_data/_entry.json.jbuilder +2 -3
  115. data/app/views/pageflow/video_files/_video_file.json.jbuilder +0 -1
  116. data/config/locales/de.yml +152 -14
  117. data/config/locales/en.yml +150 -14
  118. data/config/routes.rb +14 -0
  119. data/db/migrate/20140418225525_setup_schema.rb +2 -2
  120. data/db/migrate/20251015000000_add_structured_data_type_name_to_revisions.rb +5 -0
  121. data/db/migrate/20251029142849_convert_legacy_social_embeds_to_unified_social_embed.rb +38 -0
  122. data/db/migrate/20251119083347_fix_main_storyline_positions.rb +16 -0
  123. data/db/migrate/20251119083348_ensure_scrolled_entries_have_excursion_storyline.rb +15 -0
  124. data/db/migrate/20260303000000_add_layout_version_to_revisions.rb +6 -0
  125. data/db/migrate/20260323000000_create_comment_threads_and_comments.rb +30 -0
  126. data/db/migrate/20260414000000_add_subject_range_to_comment_threads.rb +5 -0
  127. data/db/migrate/20260415000000_add_section_perma_id_to_comment_threads.rb +5 -0
  128. data/db/migrate/20260727000000_add_quote_to_comments.rb +5 -0
  129. data/db/migrate/20260728000000_create_file_folders.rb +17 -0
  130. data/db/migrate/20260805000000_add_attribute_translations_to_sites.rb +5 -0
  131. data/db/migrate/20260806000000_add_edited_at_to_comments.rb +5 -0
  132. data/db/migrate/20260810000000_create_pageflow_scrolled_lottie_files.rb +24 -0
  133. data/db/migrate/20260817000000_create_comment_thread_reads.rb +16 -0
  134. data/db/migrate/20260819000000_add_unread_comments_since_at_to_users.rb +18 -0
  135. data/entry_types/paged/app/assets/javascripts/pageflow_paged/dist/editor.js +8665 -3890
  136. data/entry_types/paged/app/assets/javascripts/pageflow_paged/dist/frontend.js +979 -3571
  137. data/entry_types/paged/app/assets/javascripts/pageflow_paged/dist/react-client.js +20199 -28120
  138. data/entry_types/paged/app/assets/javascripts/pageflow_paged/dist/react-server.js +18652 -26584
  139. data/entry_types/scrolled/app/assets/stylesheets/pageflow_scrolled/ui.scss +2 -0
  140. data/entry_types/scrolled/app/controllers/pageflow_scrolled/editor/content_elements_controller.rb +33 -3
  141. data/entry_types/scrolled/app/controllers/pageflow_scrolled/editor/sections_controller.rb +2 -3
  142. data/entry_types/scrolled/app/controllers/pageflow_scrolled/entries_controller.rb +21 -1
  143. data/entry_types/scrolled/app/helpers/pageflow_scrolled/cache_helper.rb +3 -3
  144. data/entry_types/scrolled/app/helpers/pageflow_scrolled/editor/entry_json_seed_helper.rb +7 -5
  145. data/entry_types/scrolled/app/helpers/pageflow_scrolled/editor/seed_html_helper.rb +5 -2
  146. data/entry_types/scrolled/app/helpers/pageflow_scrolled/entry_json_seed_helper.rb +13 -5
  147. data/entry_types/scrolled/app/helpers/pageflow_scrolled/i18n_helper.rb +28 -13
  148. data/entry_types/scrolled/app/helpers/pageflow_scrolled/packs_helper.rb +26 -11
  149. data/entry_types/scrolled/app/helpers/pageflow_scrolled/react_server_side_rendering_helper.rb +2 -2
  150. data/entry_types/scrolled/app/helpers/pageflow_scrolled/sprockets_helper.rb +16 -0
  151. data/entry_types/scrolled/app/helpers/pageflow_scrolled/themes_helper.rb +174 -3
  152. data/entry_types/scrolled/app/models/pageflow_scrolled/content_element.rb +49 -7
  153. data/entry_types/scrolled/app/models/pageflow_scrolled/lottie_file.rb +6 -0
  154. data/entry_types/scrolled/app/models/pageflow_scrolled/lottie_file_url_templates.rb +16 -0
  155. data/entry_types/scrolled/app/models/pageflow_scrolled/storyline.rb +1 -0
  156. data/entry_types/scrolled/app/views/pageflow_scrolled/entries/show.html.erb +13 -12
  157. data/entry_types/scrolled/app/views/pageflow_scrolled/entry_json_seed/_entry.json.jbuilder +30 -10
  158. data/entry_types/scrolled/app/views/pageflow_scrolled/entry_json_seed/_theme.json.jbuilder +9 -0
  159. data/entry_types/scrolled/app/views/pageflow_scrolled/favicons/_entry.html.erb +1 -1
  160. data/entry_types/scrolled/config/configuration_schemas/pageflow-scrolled.json +414 -0
  161. data/entry_types/scrolled/config/locales/de.yml +512 -65
  162. data/entry_types/scrolled/config/locales/en.yml +518 -52
  163. data/entry_types/scrolled/lib/pageflow_scrolled/additional_seed_data.rb +2 -2
  164. data/entry_types/scrolled/lib/pageflow_scrolled/configuration.rb +51 -6
  165. data/entry_types/scrolled/lib/pageflow_scrolled/configuration_schemas.rb +91 -0
  166. data/entry_types/scrolled/lib/pageflow_scrolled/engine.rb +2 -1
  167. data/entry_types/scrolled/lib/pageflow_scrolled/entry_file_reference_locations.rb +71 -0
  168. data/entry_types/scrolled/lib/pageflow_scrolled/entry_structured_data_types/faq_page.rb +58 -0
  169. data/entry_types/scrolled/lib/pageflow_scrolled/file_reference_locations.rb +69 -0
  170. data/entry_types/scrolled/lib/pageflow_scrolled/plugin.rb +205 -22
  171. data/entry_types/scrolled/lib/pageflow_scrolled/react_widget_type.rb +2 -4
  172. data/entry_types/scrolled/lib/pageflow_scrolled/seeds.rb +56 -36
  173. data/entry_types/scrolled/lib/pageflow_scrolled/theme_options_default_scale.rb +35 -0
  174. data/entry_types/scrolled/lib/pageflow_scrolled.rb +13 -0
  175. data/entry_types/scrolled/lib/tasks/pageflow_scrolled/storybook.rake +19 -1
  176. data/entry_types/scrolled/package/config/configurationSchemas.js +70 -0
  177. data/entry_types/scrolled/package/config/rollup/configurationSchemas.js +19 -0
  178. data/entry_types/scrolled/package/config/webpack.js +72 -0
  179. data/entry_types/scrolled/package/contentElements/hotspots-frontend.css +1 -1
  180. data/entry_types/scrolled/package/contentElements/hotspots-frontend.js +1181 -915
  181. data/entry_types/scrolled/package/contentElements/inlineBeforeAfter-frontend.css +1 -0
  182. data/entry_types/scrolled/package/contentElements/inlineBeforeAfter-frontend.js +219 -0
  183. data/entry_types/scrolled/package/contentElements/lottieAnimation-frontend.css +1 -0
  184. data/entry_types/scrolled/package/contentElements/lottieAnimation-frontend.js +244 -0
  185. data/entry_types/scrolled/package/contentElements/socialEmbed-frontend.css +1 -0
  186. data/entry_types/scrolled/package/contentElements/socialEmbed-frontend.js +823 -0
  187. data/entry_types/scrolled/package/contentElements/tikTokEmbed-frontend.js +71 -21
  188. data/entry_types/scrolled/package/contentElements/twitterEmbed-frontend.css +1 -0
  189. data/entry_types/scrolled/package/contentElements/twitterEmbed-frontend.js +277 -0
  190. data/entry_types/scrolled/package/contentElements/videoEmbed-frontend.css +1 -0
  191. data/entry_types/scrolled/package/contentElements/videoEmbed-frontend.js +244 -0
  192. data/entry_types/scrolled/package/contentElements-editor.js +1915 -1097
  193. data/entry_types/scrolled/package/contentElements-frontend.css +1 -1
  194. data/entry_types/scrolled/package/contentElements-frontend.js +3093 -1679
  195. data/entry_types/scrolled/package/editor.css +1 -0
  196. data/entry_types/scrolled/package/editor.js +8667 -4077
  197. data/entry_types/scrolled/package/entryState.js +1600 -0
  198. data/entry_types/scrolled/package/frontend/FloatingPortalRootProvider-20c600de.js +1979 -0
  199. data/entry_types/scrolled/package/frontend/FloatingPortalRootProvider-58189220.js +2538 -0
  200. data/entry_types/scrolled/package/frontend/{PhonePlatformContext-9f76033e.js → PhonePlatformContext-035a99fa.js} +7 -12
  201. data/entry_types/scrolled/package/frontend/PhonePlatformContext-9da43797.js +33 -0
  202. data/entry_types/scrolled/package/frontend/Placeholder.module-bf0966b4.js +5267 -0
  203. data/entry_types/scrolled/package/frontend/Placeholder.module-d22a9335.js +4180 -0
  204. data/entry_types/scrolled/package/frontend/ThemeIcon-ab834849.js +754 -0
  205. data/entry_types/scrolled/package/frontend/ThemeIcon-f6cb3a8f.js +773 -0
  206. data/entry_types/scrolled/package/frontend/{ToggleFullscreenCornerButton-788e9cdb.js → ToggleFullscreenCornerButton-30fd1124.js} +6 -6
  207. data/entry_types/scrolled/package/frontend/ToggleFullscreenCornerButton-61c25224.js +72 -0
  208. data/entry_types/scrolled/package/frontend/Viewer-1074fd59.js +147 -0
  209. data/entry_types/scrolled/package/frontend/Viewer-1482ca34.js +480 -0
  210. data/entry_types/scrolled/package/frontend/Viewer-70504231.js +210 -0
  211. data/entry_types/scrolled/package/frontend/{Viewer-0345ce57.js → Viewer-cc8d4072.js} +84 -92
  212. data/entry_types/scrolled/package/frontend/Wavesurfer-656e3c36.js +302 -0
  213. data/entry_types/scrolled/package/frontend/Wavesurfer-b753b838.js +385 -0
  214. data/entry_types/scrolled/package/frontend/commenting.css +1 -0
  215. data/entry_types/scrolled/package/frontend/extensions-167ae143.js +1455 -0
  216. data/entry_types/scrolled/package/frontend/extensions-2318f92b.js +5918 -0
  217. data/entry_types/scrolled/package/frontend/extensions-a461fd13.js +4890 -0
  218. data/entry_types/scrolled/package/frontend/extensions-c98269fd.js +1982 -0
  219. data/entry_types/scrolled/package/frontend/i18n-493cd2a6.js +93 -0
  220. data/entry_types/scrolled/package/frontend/i18n-e06cac14.js +100 -0
  221. data/entry_types/scrolled/package/frontend/{index-eb670c2f.js → index-2e5546f7.js} +36 -44
  222. data/entry_types/scrolled/package/frontend/index-de2b418d.js +130 -0
  223. data/entry_types/scrolled/package/frontend/index.css +1 -1
  224. data/entry_types/scrolled/package/frontend/index.js +1870 -875
  225. data/entry_types/scrolled/package/frontend/inlineEditing.css +1 -0
  226. data/entry_types/scrolled/package/frontend/useContentElementEditorState-a084912e.js +25 -0
  227. data/entry_types/scrolled/package/frontend/useDelayedBoolean-a387d85b.js +24 -0
  228. data/entry_types/scrolled/package/frontend/{usePhonePlatform-c2ba875d.js → usePhonePlatform-4ecb96d4.js} +1 -1
  229. data/entry_types/scrolled/package/frontend/usePhonePlatform-ce9ff08d.js +8 -0
  230. data/entry_types/scrolled/package/frontend-server.js +20 -209
  231. data/entry_types/scrolled/package/package.json +5 -3
  232. data/entry_types/scrolled/package/review.css +1 -0
  233. data/entry_types/scrolled/package/review.js +3306 -0
  234. data/entry_types/scrolled/package/testHelpers.js +836 -359
  235. data/entry_types/scrolled/package/widgets/consentBar.css +1 -1
  236. data/entry_types/scrolled/package/widgets/consentBar.js +269 -290
  237. data/entry_types/scrolled/package/widgets/defaultNavigation.css +2 -2
  238. data/entry_types/scrolled/package/widgets/defaultNavigation.js +911 -352
  239. data/entry_types/scrolled/package/widgets/excursionSheet.css +1 -1
  240. data/entry_types/scrolled/package/widgets/excursionSheet.js +87 -124
  241. data/entry_types/scrolled/package/widgets/iconInlineFileRights.css +1 -1
  242. data/entry_types/scrolled/package/widgets/iconInlineFileRights.js +62 -29
  243. data/entry_types/scrolled/package/widgets/iconScrollIndicator.js +32 -13
  244. data/entry_types/scrolled/package/widgets/mainStorylineSheet.css +1 -1
  245. data/entry_types/scrolled/package/widgets/mainStorylineSheet.js +56 -100
  246. data/entry_types/scrolled/package/widgets/textInlineFileRights.css +1 -1
  247. data/entry_types/scrolled/package/widgets/textInlineFileRights.js +33 -28
  248. data/entry_types/scrolled/package/widgets-server.js +1 -0
  249. data/entry_types/scrolled/spec/factories/lottie_files.rb +29 -0
  250. data/entry_types/scrolled/spec/fixtures/animation.lottie +0 -0
  251. data/entry_types/scrolled/spec/fixtures/font.woff +0 -0
  252. data/entry_types/scrolled/spec/fixtures/font.woff2 +0 -0
  253. data/lib/generators/pageflow/resque/resque_generator.rb +0 -2
  254. data/lib/pageflow/configuration.rb +49 -7
  255. data/lib/pageflow/deprecation.rb +19 -0
  256. data/lib/pageflow/engine.rb +1 -1
  257. data/lib/pageflow/entries_controller_env_helper.rb +32 -2
  258. data/lib/pageflow/entry_export_import/entry_serialization.rb +4 -4
  259. data/lib/pageflow/entry_export_import/page_type_versions.rb +2 -2
  260. data/lib/pageflow/entry_export_import/revision_serialization/import.rb +10 -0
  261. data/lib/pageflow/entry_export_import/revision_serialization.rb +3 -0
  262. data/lib/pageflow/entry_structured_data_types.rb +43 -0
  263. data/lib/pageflow/entry_type_configuration.rb +1 -0
  264. data/lib/pageflow/file_type.rb +2 -2
  265. data/lib/pageflow/file_types.rb +1 -1
  266. data/lib/pageflow/oembed_providers.rb +42 -0
  267. data/lib/pageflow/primary_domain_entry_redirect.rb +6 -0
  268. data/lib/pageflow/rails_version.rb +13 -2
  269. data/lib/pageflow/themes.rb +41 -0
  270. data/lib/pageflow/user_mixin.rb +12 -0
  271. data/lib/pageflow/version.rb +1 -1
  272. data/lib/pageflow.rb +1 -0
  273. data/package/config/eslint-rules/documented-in-toc.js +125 -0
  274. data/package/config/jest/index.js +0 -1
  275. data/package/config/webpack.js +0 -1
  276. data/package/config/webpack5.js +0 -1
  277. data/package/editor.js +4378 -1897
  278. data/package/frontend.js +647 -886
  279. data/package/package.json +2 -1
  280. data/package/review.js +384 -0
  281. data/package/testHelpers.js +248 -188
  282. data/package/ui.js +1346 -630
  283. data/package/vendor/stubs/wysihtml5.js +2 -0
  284. data/spec/factories/comment_thread_reads.rb +10 -0
  285. data/spec/factories/comment_threads.rb +10 -0
  286. data/spec/factories/comments.rb +9 -0
  287. data/spec/factories/file_folders.rb +8 -0
  288. data/spec/factories/published_entries.rb +2 -0
  289. metadata +188 -67
  290. data/app/assets/stylesheets/pageflow/editor/select_button.scss +0 -51
  291. data/entry_types/scrolled/app/helpers/pageflow_scrolled/generated_media_queries_helper.rb +0 -55
  292. data/entry_types/scrolled/package/frontend/FloatingPortalRootProvider-51914be7.js +0 -5439
  293. data/entry_types/scrolled/package/frontend/ThemeIcon-81f2f066.js +0 -1932
  294. data/entry_types/scrolled/package/frontend/Viewer-cdc549cc.js +0 -147
  295. data/entry_types/scrolled/package/frontend/Wavesurfer-1cdc3925.js +0 -313
  296. data/entry_types/scrolled/package/frontend/components-96660ffd.js +0 -3803
  297. data/entry_types/scrolled/package/frontend/inherits-539844a6.js +0 -99
  298. data/entry_types/scrolled/package/frontend/useContentElementEditorState-77fe6c79.js +0 -51
  299. data/entry_types/scrolled/package/widgets/excursionDialog.css +0 -1
  300. data/entry_types/scrolled/package/widgets/excursionDialog.js +0 -109
  301. data/lib/generators/pageflow/resque/templates/resque_enqueue_after_commit_patch.rb +0 -25
  302. data/package/vendor/jquery.minicolors.js +0 -1108
@@ -0,0 +1,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 };