pageflow 17.0.5 → 17.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (636) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +342 -41
  3. data/README.md +3 -5
  4. data/admins/pageflow/accounts.rb +22 -20
  5. data/admins/pageflow/entry.rb +76 -26
  6. data/admins/pageflow/entry_templates.rb +10 -9
  7. data/admins/pageflow/folder.rb +1 -1
  8. data/admins/pageflow/membership.rb +15 -15
  9. data/admins/pageflow/revisions.rb +5 -5
  10. data/admins/pageflow/site_root_entry.rb +64 -0
  11. data/admins/pageflow/sites.rb +12 -3
  12. data/admins/pageflow/translations.rb +75 -0
  13. data/admins/pageflow/user.rb +21 -35
  14. data/app/assets/images/pageflow/admin/icons/comment.svg +1 -0
  15. data/app/assets/javascripts/pageflow/admin/entries.js +30 -5
  16. data/app/assets/javascripts/pageflow/dist/ui.js +4228 -900
  17. data/app/assets/javascripts/pageflow/editor/vendor.js +0 -1
  18. data/app/assets/javascripts/pageflow/ui.js +0 -1
  19. data/app/assets/stylesheets/pageflow/admin/entries/index_table.scss +2 -1
  20. data/app/assets/stylesheets/pageflow/admin/entry_comments_indicator.scss +45 -0
  21. data/app/assets/stylesheets/pageflow/admin/permalink_input.scss +8 -0
  22. data/app/assets/stylesheets/pageflow/admin.scss +1 -0
  23. data/app/assets/stylesheets/pageflow/animations/spin.scss +9 -0
  24. data/app/assets/stylesheets/pageflow/animations.scss +1 -0
  25. data/app/assets/stylesheets/pageflow/editor/background_positioning.scss +27 -5
  26. data/app/assets/stylesheets/pageflow/editor/base.scss +11 -1
  27. data/app/assets/stylesheets/pageflow/editor/drop_down_button.scss +20 -2
  28. data/app/assets/stylesheets/pageflow/editor/edit_configuration_view.scss +5 -0
  29. data/app/assets/stylesheets/pageflow/editor/file_folders.scss +127 -0
  30. data/app/assets/stylesheets/pageflow/editor/file_meta_data.scss +5 -2
  31. data/app/assets/stylesheets/pageflow/editor/file_meta_data_overlay.scss +45 -0
  32. data/app/assets/stylesheets/pageflow/editor/file_preview.scss +138 -0
  33. data/app/assets/stylesheets/pageflow/editor/file_references.scss +68 -0
  34. data/app/assets/stylesheets/pageflow/editor/file_settings_dialog.scss +1 -1
  35. data/app/assets/stylesheets/pageflow/editor/file_stage_icons.scss +17 -0
  36. data/app/assets/stylesheets/pageflow/editor/file_stages.scss +25 -32
  37. data/app/assets/stylesheets/pageflow/editor/file_thumbnails.scss +31 -48
  38. data/app/assets/stylesheets/pageflow/editor/file_type_pills.scss +73 -0
  39. data/app/assets/stylesheets/pageflow/editor/files.scss +242 -50
  40. data/app/assets/stylesheets/pageflow/editor/filtered_files.scss +161 -9
  41. data/app/assets/stylesheets/pageflow/editor/folder_breadcrumb.scss +40 -0
  42. data/app/assets/stylesheets/pageflow/editor/info_box.scss +23 -3
  43. data/app/assets/stylesheets/pageflow/editor/inputs/edit_defaults_button.scss +15 -0
  44. data/app/assets/stylesheets/pageflow/editor/inputs.scss +1 -0
  45. data/app/assets/stylesheets/pageflow/editor/list.scss +89 -37
  46. data/app/assets/stylesheets/pageflow/editor/list_search_field.scss +52 -0
  47. data/app/assets/stylesheets/pageflow/editor/manage_files.scss +9 -0
  48. data/app/assets/stylesheets/pageflow/editor/menu.scss +14 -0
  49. data/app/assets/stylesheets/pageflow/editor/move_to_folder_dialog.scss +83 -0
  50. data/app/assets/stylesheets/pageflow/editor/outline.scss +0 -13
  51. data/app/assets/stylesheets/pageflow/editor/sortable.scss +12 -0
  52. data/app/assets/stylesheets/pageflow/mixins/buttons.scss +4 -4
  53. data/app/assets/stylesheets/pageflow/mixins/pageflow.scss +9 -5
  54. data/app/assets/stylesheets/pageflow/ui/color_picker.scss +230 -0
  55. data/app/assets/stylesheets/pageflow/ui/forms.scss +38 -4
  56. data/app/assets/stylesheets/pageflow/ui/functions.scss +12 -2
  57. data/app/assets/stylesheets/pageflow/ui/input/color_input.scss +9 -8
  58. data/app/assets/stylesheets/pageflow/ui/input/file_name_input.scss +37 -0
  59. data/app/assets/stylesheets/pageflow/ui/input/text_area_input.scss +15 -0
  60. data/app/assets/stylesheets/pageflow/ui/overrides.scss +8 -0
  61. data/app/assets/stylesheets/pageflow/ui/properties.scss +34 -0
  62. data/app/assets/stylesheets/pageflow/ui/tabs_view.scss +8 -3
  63. data/app/assets/stylesheets/pageflow/ui.scss +4 -1
  64. data/app/controllers/concerns/pageflow/controller_delegation.rb +1 -1
  65. data/app/controllers/concerns/pageflow/edit_locking.rb +17 -10
  66. data/app/controllers/concerns/pageflow/entry_password_protection.rb +1 -0
  67. data/app/controllers/concerns/pageflow/public_https_mode.rb +2 -1
  68. data/app/controllers/concerns/pageflow/quota_verification.rb +5 -4
  69. data/app/controllers/pageflow/admin/initial_passwords_controller.rb +1 -0
  70. data/app/controllers/pageflow/application_controller.rb +19 -19
  71. data/app/controllers/pageflow/chapters_controller.rb +3 -2
  72. data/app/controllers/pageflow/edit_locks_controller.rb +3 -2
  73. data/app/controllers/pageflow/editor/encoding_confirmations_controller.rb +1 -0
  74. data/app/controllers/pageflow/editor/entries_controller.rb +3 -1
  75. data/app/controllers/pageflow/editor/entry_publications_controller.rb +1 -0
  76. data/app/controllers/pageflow/editor/file_folders_controller.rb +80 -0
  77. data/app/controllers/pageflow/editor/file_import_controller.rb +4 -3
  78. data/app/controllers/pageflow/editor/files_controller.rb +20 -10
  79. data/app/controllers/pageflow/editor/oembeds_controller.rb +21 -0
  80. data/app/controllers/pageflow/editor/widgets_controller.rb +1 -0
  81. data/app/controllers/pageflow/entries_controller.rb +85 -21
  82. data/app/controllers/pageflow/feeds_controller.rb +1 -1
  83. data/app/controllers/pageflow/files_controller.rb +1 -0
  84. data/app/controllers/pageflow/pages_controller.rb +4 -2
  85. data/app/controllers/pageflow/review/comment_thread_reads_controller.rb +30 -0
  86. data/app/controllers/pageflow/review/comment_threads_controller.rb +60 -0
  87. data/app/controllers/pageflow/review/comments_controller.rb +53 -0
  88. data/app/controllers/pageflow/revisions_controller.rb +5 -1
  89. data/app/controllers/pageflow/sitemaps_controller.rb +1 -1
  90. data/app/controllers/pageflow/storylines_controller.rb +1 -0
  91. data/app/helpers/pageflow/admin/cutoff_modes_helper.rb +12 -0
  92. data/app/helpers/pageflow/admin/entries_helper.rb +41 -1
  93. data/app/helpers/pageflow/admin/entry_translations_helper.rb +18 -0
  94. data/app/helpers/pageflow/admin/features_helper.rb +1 -1
  95. data/app/helpers/pageflow/admin/form_helper.rb +3 -3
  96. data/app/helpers/pageflow/admin/locales_helper.rb +10 -2
  97. data/app/helpers/pageflow/admin/memberships_helper.rb +32 -32
  98. data/app/helpers/pageflow/admin/revisions_helper.rb +1 -1
  99. data/app/helpers/pageflow/admin/sites_helper.rb +22 -0
  100. data/app/helpers/pageflow/admin/users_helper.rb +7 -5
  101. data/app/helpers/pageflow/admin/widgets_helper.rb +5 -5
  102. data/app/helpers/pageflow/asset_urls_helper.rb +1 -1
  103. data/app/helpers/pageflow/audio_files_helper.rb +5 -2
  104. data/app/helpers/pageflow/background_image_helper.rb +7 -6
  105. data/app/helpers/pageflow/common_entry_seed_helper.rb +7 -7
  106. data/app/helpers/pageflow/editor/config_helper.rb +3 -1
  107. data/app/helpers/pageflow/editor/files_helper.rb +1 -1
  108. data/app/helpers/pageflow/embed_code_helper.rb +3 -3
  109. data/app/helpers/pageflow/entries_helper.rb +52 -46
  110. data/app/helpers/pageflow/entry_json_seed_helper.rb +7 -6
  111. data/app/helpers/pageflow/feeds_helper.rb +2 -2
  112. data/app/helpers/pageflow/file_background_images_helper.rb +2 -2
  113. data/app/helpers/pageflow/file_thumbnails_helper.rb +4 -3
  114. data/app/helpers/pageflow/files_helper.rb +5 -4
  115. data/app/helpers/pageflow/folders_helper.rb +8 -5
  116. data/app/helpers/pageflow/help_entries_helper.rb +2 -2
  117. data/app/helpers/pageflow/hreflang_links_helper.rb +37 -0
  118. data/app/helpers/pageflow/info_box_helper.rb +7 -11
  119. data/app/helpers/pageflow/media_query_helper.rb +3 -3
  120. data/app/helpers/pageflow/navigation_bar_helper.rb +1 -1
  121. data/app/helpers/pageflow/overview_helper.rb +1 -1
  122. data/app/helpers/pageflow/page_types_helper.rb +5 -5
  123. data/app/helpers/pageflow/pages_helper.rb +29 -15
  124. data/app/helpers/pageflow/public_i18n_helper.rb +10 -9
  125. data/app/helpers/pageflow/quota_helper.rb +4 -4
  126. data/app/helpers/pageflow/render_json_helper.rb +6 -3
  127. data/app/helpers/pageflow/revision_file_helper.rb +2 -1
  128. data/app/helpers/pageflow/sites_helper.rb +3 -3
  129. data/app/helpers/pageflow/social_share_helper.rb +24 -10
  130. data/app/helpers/pageflow/social_share_links_helper.rb +12 -9
  131. data/app/helpers/pageflow/structured_data_helper.rb +5 -2
  132. data/app/helpers/pageflow/stub_page_configuration.rb +7 -3
  133. data/app/helpers/pageflow/text_direction_helper.rb +1 -1
  134. data/app/helpers/pageflow/themes_helper.rb +2 -4
  135. data/app/helpers/pageflow/video_files_helper.rb +29 -13
  136. data/app/helpers/pageflow/widgets_helper.rb +8 -6
  137. data/app/inputs/pageflow_permalink_input.rb +8 -3
  138. data/app/jobs/pageflow/application_job.rb +1 -0
  139. data/app/jobs/pageflow/entry_export_import/upload_and_publish_file_job.rb +1 -1
  140. data/app/jobs/pageflow/poll_meta_data_from_zencoder_job.rb +1 -0
  141. data/app/jobs/pageflow/poll_zencoder_job.rb +3 -2
  142. data/app/jobs/pageflow/process_image_or_text_track_job.rb +1 -0
  143. data/app/jobs/pageflow/prune_auto_snapshots_job.rb +1 -0
  144. data/app/jobs/pageflow/request_meta_data_from_zencoder_job.rb +1 -0
  145. data/app/jobs/pageflow/submit_file_to_zencoder_job.rb +1 -0
  146. data/app/mailers/pageflow/user_mailer.rb +1 -0
  147. data/app/models/concerns/pageflow/auto_generated_perma_id.rb +20 -1
  148. data/app/models/concerns/pageflow/entry_publication_states.rb +7 -3
  149. data/app/models/concerns/pageflow/feature_target.rb +8 -4
  150. data/app/models/concerns/pageflow/output_source.rb +3 -3
  151. data/app/models/concerns/pageflow/permalinkable.rb +2 -1
  152. data/app/models/concerns/pageflow/reusable_file.rb +24 -24
  153. data/app/models/concerns/pageflow/serialization_blacklist.rb +3 -5
  154. data/app/models/concerns/pageflow/suspendable.rb +5 -3
  155. data/app/models/concerns/pageflow/theme_referencer.rb +1 -1
  156. data/app/models/concerns/pageflow/translatable.rb +62 -0
  157. data/app/models/concerns/pageflow/translated_attributes.rb +101 -0
  158. data/app/models/concerns/pageflow/uploadable_file.rb +6 -6
  159. data/app/models/pageflow/account.rb +6 -5
  160. data/app/models/pageflow/account_member_query.rb +1 -1
  161. data/app/models/pageflow/account_role_query.rb +1 -1
  162. data/app/models/pageflow/application_query.rb +1 -1
  163. data/app/models/pageflow/application_record.rb +1 -1
  164. data/app/models/pageflow/audio_file.rb +4 -4
  165. data/app/models/pageflow/audio_file_url_templates.rb +1 -1
  166. data/app/models/pageflow/authentication_token.rb +2 -2
  167. data/app/models/pageflow/chapter.rb +6 -1
  168. data/app/models/pageflow/chapter_scaffold.rb +2 -4
  169. data/app/models/pageflow/cname_site_request_scope.rb +1 -1
  170. data/app/models/pageflow/comment.rb +30 -0
  171. data/app/models/pageflow/comment_thread.rb +65 -0
  172. data/app/models/pageflow/comment_thread_read.rb +22 -0
  173. data/app/models/pageflow/customized_theme.rb +9 -8
  174. data/app/models/pageflow/draft_entry.rb +49 -9
  175. data/app/models/pageflow/edit_lock.rb +19 -21
  176. data/app/models/pageflow/encoding_confirmation.rb +2 -2
  177. data/app/models/pageflow/entries_feed.rb +2 -2
  178. data/app/models/pageflow/entry.rb +46 -30
  179. data/app/models/pageflow/entry_at_revision.rb +14 -2
  180. data/app/models/pageflow/entry_comment_summary.rb +114 -0
  181. data/app/models/pageflow/entry_duplicate.rb +8 -5
  182. data/app/models/pageflow/entry_publication.rb +2 -2
  183. data/app/models/pageflow/entry_role_query.rb +4 -4
  184. data/app/models/pageflow/entry_template.rb +4 -4
  185. data/app/models/pageflow/entry_title_or_account_name_query.rb +2 -2
  186. data/app/models/pageflow/entry_translation_group.rb +42 -0
  187. data/app/models/pageflow/file_folder.rb +74 -0
  188. data/app/models/pageflow/file_reuse.rb +5 -4
  189. data/app/models/pageflow/file_usage.rb +18 -3
  190. data/app/models/pageflow/folder.rb +2 -2
  191. data/app/models/pageflow/home_button.rb +7 -7
  192. data/app/models/pageflow/image_file.rb +44 -25
  193. data/app/models/pageflow/image_file_url_templates.rb +4 -4
  194. data/app/models/pageflow/invitation_form.rb +2 -1
  195. data/app/models/pageflow/legal_info.rb +38 -0
  196. data/app/models/pageflow/managed_user_query.rb +1 -1
  197. data/app/models/pageflow/membership.rb +5 -5
  198. data/app/models/pageflow/nested_revision_component_copy.rb +263 -0
  199. data/app/models/pageflow/null_user.rb +1 -1
  200. data/app/models/pageflow/overview_button.rb +1 -1
  201. data/app/models/pageflow/page.rb +7 -3
  202. data/app/models/pageflow/permalink.rb +24 -2
  203. data/app/models/pageflow/permalink_directory.rb +7 -0
  204. data/app/models/pageflow/permalink_redirect.rb +7 -0
  205. data/app/models/pageflow/positioned_file.rb +5 -5
  206. data/app/models/pageflow/potential_entry_translations.rb +55 -0
  207. data/app/models/pageflow/potential_memberships.rb +5 -4
  208. data/app/models/pageflow/published_entry.rb +62 -11
  209. data/app/models/pageflow/revision.rb +29 -16
  210. data/app/models/pageflow/roles.rb +14 -18
  211. data/app/models/pageflow/site.rb +46 -3
  212. data/app/models/pageflow/site_root_entry_form.rb +27 -0
  213. data/app/models/pageflow/sitemaps.rb +10 -1
  214. data/app/models/pageflow/storyline.rb +5 -2
  215. data/app/models/pageflow/storyline_scaffold.rb +1 -1
  216. data/app/models/pageflow/text_track_file.rb +1 -1
  217. data/app/models/pageflow/text_track_file_url_templates.rb +1 -1
  218. data/app/models/pageflow/theme_customization_file.rb +3 -2
  219. data/app/models/pageflow/thumbnail_file_resolver.rb +1 -1
  220. data/app/models/pageflow/url_template.rb +1 -1
  221. data/app/models/pageflow/used_file.rb +13 -3
  222. data/app/models/pageflow/user_name_query.rb +2 -2
  223. data/app/models/pageflow/video_file.rb +22 -15
  224. data/app/models/pageflow/video_file_url_templates.rb +6 -4
  225. data/app/models/pageflow/widget.rb +5 -4
  226. data/app/models/pageflow/with_file_usage_extension.rb +1 -1
  227. data/app/models/pageflow/zencoder_attachment.rb +8 -8
  228. data/app/policies/pageflow/account_policy.rb +3 -1
  229. data/app/policies/pageflow/admin/admin_only_tab_policy.rb +1 -0
  230. data/app/policies/pageflow/admin/entry_tab_policy.rb +1 -0
  231. data/app/policies/pageflow/application_policy.rb +2 -0
  232. data/app/policies/pageflow/entry_policy.rb +7 -1
  233. data/app/policies/pageflow/entry_template_policy.rb +2 -1
  234. data/app/policies/pageflow/file_policy.rb +4 -7
  235. data/app/policies/pageflow/folder_policy.rb +18 -8
  236. data/app/policies/pageflow/membership_policy.rb +6 -4
  237. data/app/policies/pageflow/site_policy.rb +10 -3
  238. data/app/policies/pageflow/user_policy.rb +6 -4
  239. data/app/state_machines/pageflow/image_and_text_track_processing_state_machine.rb +3 -2
  240. data/app/state_machines/pageflow/media_encoding_state_machine.rb +5 -4
  241. data/app/views/admin/accounts/_entry_template_details.html.arb +2 -2
  242. data/app/views/admin/entries/_attributes_table.html.arb +6 -5
  243. data/app/views/admin/entries/_form.html.erb +2 -1
  244. data/app/views/admin/entries/_permalink_inputs.html.erb +9 -2
  245. data/app/views/admin/entry_templates/_form.html.erb +1 -3
  246. data/app/views/admin/site_root_entry/choose.html.erb +19 -0
  247. data/app/views/admin/sites/_fields.html.erb +14 -1
  248. data/app/views/admin/translations/_form.html.erb +31 -0
  249. data/app/views/admin/users/_attributes_table.html.arb +13 -0
  250. data/app/views/components/pageflow/admin/add_membership_button.rb +13 -12
  251. data/app/views/components/pageflow/admin/custom_scopes_renderer.rb +1 -0
  252. data/app/views/components/pageflow/admin/embed_code_field.rb +1 -0
  253. data/app/views/components/pageflow/admin/embedded_index_table.rb +9 -10
  254. data/app/views/components/pageflow/admin/entries_tab.rb +1 -0
  255. data/app/views/components/pageflow/admin/entry_publication_state_indicator.rb +6 -5
  256. data/app/views/components/pageflow/admin/entry_templates_tab.rb +2 -1
  257. data/app/views/components/pageflow/admin/entry_translations_tab.rb +102 -0
  258. data/app/views/components/pageflow/admin/entry_user_badge_list.rb +1 -0
  259. data/app/views/components/pageflow/admin/extensible_attributes_table.rb +9 -8
  260. data/app/views/components/pageflow/admin/features_tab.rb +1 -0
  261. data/app/views/components/pageflow/admin/grouped_folder_list.rb +1 -0
  262. data/app/views/components/pageflow/admin/icon_link_to.rb +1 -0
  263. data/app/views/components/pageflow/admin/members_tab.rb +4 -3
  264. data/app/views/components/pageflow/admin/membership_role_with_tooltip.rb +3 -2
  265. data/app/views/components/pageflow/admin/revisions_tab.rb +14 -10
  266. data/app/views/components/pageflow/admin/sites_tab.rb +1 -0
  267. data/app/views/components/pageflow/admin/tabs_view.rb +1 -0
  268. data/app/views/components/pageflow/admin/timestamp.rb +6 -5
  269. data/app/views/components/pageflow/admin/user_account_badge_list.rb +2 -3
  270. data/app/views/components/pageflow/admin/user_accounts_tab.rb +1 -0
  271. data/app/views/components/pageflow/admin/user_entries_tab.rb +1 -0
  272. data/app/views/components/pageflow/admin/users_tab.rb +1 -0
  273. data/app/views/pageflow/audio_files/_audio_file.json.jbuilder +0 -1
  274. data/app/views/pageflow/editor/config/_seeds.json.jbuilder +5 -3
  275. data/app/views/pageflow/editor/entries/_entry.json.jbuilder +2 -1
  276. data/app/views/pageflow/editor/entries/index.json.jbuilder +1 -1
  277. data/app/views/pageflow/editor/entries/seed.json.erb +1 -0
  278. data/app/views/pageflow/editor/entries/show.json.jbuilder +1 -1
  279. data/app/views/pageflow/editor/entry_publications/check.json.jbuilder +2 -2
  280. data/app/views/pageflow/editor/file_folders/_file_folder.json.jbuilder +5 -0
  281. data/app/views/pageflow/editor/file_folders/create.json.jbuilder +1 -0
  282. data/app/views/pageflow/editor/file_folders/index.json.jbuilder +3 -0
  283. data/app/views/pageflow/editor/files/_file.json.jbuilder +7 -14
  284. data/app/views/pageflow/editor/files/create.json.jbuilder +1 -1
  285. data/app/views/pageflow/editor/files/index.json.jbuilder +1 -1
  286. data/app/views/pageflow/editor/image_files/_image_file.json.jbuilder +3 -0
  287. data/app/views/pageflow/editor/sites/_site.json.jbuilder +1 -0
  288. data/app/views/pageflow/editor/video_files/_video_file.json.jbuilder +4 -3
  289. data/app/views/pageflow/entries/share_menu/_bluesky_link.html.erb +8 -0
  290. data/app/views/pageflow/entries/share_menu/_threads_link.html.erb +8 -0
  291. data/app/views/pageflow/files/_file.json.jbuilder +5 -3
  292. data/app/views/pageflow/image_files/_image_file.json.jbuilder +0 -1
  293. data/app/views/pageflow/review/comment_threads/_comment_thread.json.jbuilder +20 -0
  294. data/app/views/pageflow/review/comment_threads/create.json.jbuilder +2 -0
  295. data/app/views/pageflow/review/comment_threads/index.json.jbuilder +13 -0
  296. data/app/views/pageflow/review/comments/_comment.json.jbuilder +13 -0
  297. data/app/views/pageflow/review/comments/create.json.jbuilder +1 -0
  298. data/app/views/pageflow/sitemaps/index.xml.builder +9 -1
  299. data/app/views/pageflow/social_share/_entry_meta_tags.html.erb +1 -1
  300. data/app/views/pageflow/social_share/_page_meta_tags.html.erb +1 -1
  301. data/app/views/pageflow/structured_data/_entry.json.jbuilder +2 -3
  302. data/app/views/pageflow/video_files/_video_file.json.jbuilder +0 -1
  303. data/config/initializers/active_admin_patches.rb +3 -6
  304. data/config/initializers/admin_resource_tabs.rb +3 -0
  305. data/config/initializers/paperclip.rb +1 -0
  306. data/config/initializers/symmetric_encryption.rb +1 -1
  307. data/config/initializers/zencoder.rb +24 -13
  308. data/config/locales/de.yml +256 -12
  309. data/config/locales/en.yml +253 -15
  310. data/config/routes.rb +29 -13
  311. data/config/spring.rb +2 -1
  312. data/db/migrate/20140418225525_setup_schema.rb +2 -2
  313. data/db/migrate/20190306161431_copy_file_attributes_of_failed_uploads.rb +2 -2
  314. data/db/migrate/20190820152900_drop_accounts_themes.rb +2 -1
  315. data/db/migrate/20191202145757_create_pageflow_scrolled_sections.rb +11 -11
  316. data/db/migrate/20191202150657_create_pageflow_scrolled_chapters.rb +1 -1
  317. data/db/migrate/20191202154723_create_pageflow_scrolled_content_elements.rb +9 -9
  318. data/db/migrate/20191219143450_add_position_to_content_elements.rb +2 -1
  319. data/db/migrate/20200117133200_change_revision_appearance_option_default_and_null.rb +2 -1
  320. data/db/migrate/20200122115400_create_pageflow_entry_templates.rb +25 -25
  321. data/db/migrate/20200206134400_convert_legacy_scrolled_content_element_types.rb +3 -3
  322. data/db/migrate/20221215120856_associate_entry_templates_with_sites.rb +2 -2
  323. data/db/migrate/20230120092923_create_other_files.rb +2 -1
  324. data/db/migrate/20230419083307_create_pageflow_entry_translation_group.rb +9 -0
  325. data/db/migrate/20240612110434_add_cutoff_mode_name_to_sites.rb +5 -0
  326. data/db/migrate/20240918084059_create_pageflow_permalink_redirects.rb +14 -0
  327. data/db/migrate/20250508172234_ensure_scrolled_entries_have_main_storyline.rb +14 -0
  328. data/db/migrate/20250617090048_add_custom404_entry_to_sites.rb +5 -0
  329. data/db/migrate/20250617100000_add_perma_id_counter_to_entries.rb +28 -0
  330. data/db/migrate/20250722174123_add_perma_id_to_chapters.rb +10 -0
  331. data/db/migrate/20250725080603_add_perma_id_indexes_to_revision_components.rb +13 -0
  332. data/db/migrate/20250726000000_add_display_name_to_file_usages.rb +5 -0
  333. data/db/migrate/20251015000000_add_structured_data_type_name_to_revisions.rb +5 -0
  334. data/db/migrate/20251029142849_convert_legacy_social_embeds_to_unified_social_embed.rb +38 -0
  335. data/db/migrate/20251119083347_fix_main_storyline_positions.rb +16 -0
  336. data/db/migrate/20251119083348_ensure_scrolled_entries_have_excursion_storyline.rb +15 -0
  337. data/db/migrate/20260303000000_add_layout_version_to_revisions.rb +6 -0
  338. data/db/migrate/20260323000000_create_comment_threads_and_comments.rb +30 -0
  339. data/db/migrate/20260414000000_add_subject_range_to_comment_threads.rb +5 -0
  340. data/db/migrate/20260415000000_add_section_perma_id_to_comment_threads.rb +5 -0
  341. data/db/migrate/20260727000000_add_quote_to_comments.rb +5 -0
  342. data/db/migrate/20260728000000_create_file_folders.rb +17 -0
  343. data/db/migrate/20260805000000_add_attribute_translations_to_sites.rb +5 -0
  344. data/db/migrate/20260806000000_add_edited_at_to_comments.rb +5 -0
  345. data/db/migrate/20260810000000_create_pageflow_scrolled_lottie_files.rb +24 -0
  346. data/db/migrate/20260817000000_create_comment_thread_reads.rb +16 -0
  347. data/db/migrate/20260819000000_add_unread_comments_since_at_to_users.rb +18 -0
  348. data/entry_types/paged/app/assets/javascripts/pageflow_paged/dist/editor.js +12599 -5398
  349. data/entry_types/paged/app/assets/javascripts/pageflow_paged/dist/frontend.js +1275 -4560
  350. data/entry_types/paged/app/assets/javascripts/pageflow_paged/dist/react-client.js +20204 -22
  351. data/entry_types/paged/app/assets/javascripts/pageflow_paged/dist/react-server.js +18657 -19
  352. data/entry_types/paged/app/controllers/pageflow_paged/entries_controller.rb +1 -0
  353. data/entry_types/paged/app/helpers/pageflow_paged/page_background_asset_helper.rb +1 -1
  354. data/entry_types/paged/app/helpers/pageflow_paged/third_party_embed_consent_helper.rb +7 -7
  355. data/entry_types/paged/app/views/pageflow_paged/editor/entries/_seed.json.jbuilder +1 -0
  356. data/entry_types/paged/app/views/pageflow_paged/entries/show.html.erb +1 -0
  357. data/entry_types/paged/lib/pageflow_paged/plugin.rb +4 -0
  358. data/entry_types/paged/lib/pageflow_paged/react/page_type.rb +1 -1
  359. data/entry_types/paged/lib/pageflow_paged/react/widget_type.rb +3 -3
  360. data/entry_types/paged/lib/pageflow_paged.rb +1 -1
  361. data/entry_types/scrolled/app/assets/stylesheets/pageflow_scrolled/ui.scss +2 -0
  362. data/entry_types/scrolled/app/controllers/pageflow_scrolled/editor/chapters_controller.rb +5 -3
  363. data/entry_types/scrolled/app/controllers/pageflow_scrolled/editor/content_elements_controller.rb +35 -5
  364. data/entry_types/scrolled/app/controllers/pageflow_scrolled/editor/sections_controller.rb +4 -5
  365. data/entry_types/scrolled/app/controllers/pageflow_scrolled/entries_controller.rb +23 -2
  366. data/entry_types/scrolled/app/helpers/pageflow_scrolled/cache_helper.rb +3 -3
  367. data/entry_types/scrolled/app/helpers/pageflow_scrolled/editor/entry_json_seed_helper.rb +8 -5
  368. data/entry_types/scrolled/app/helpers/pageflow_scrolled/editor/seed_html_helper.rb +7 -3
  369. data/entry_types/scrolled/app/helpers/pageflow_scrolled/entry_json_seed_helper.rb +72 -10
  370. data/entry_types/scrolled/app/helpers/pageflow_scrolled/i18n_helper.rb +28 -13
  371. data/entry_types/scrolled/app/helpers/pageflow_scrolled/packs_helper.rb +41 -12
  372. data/entry_types/scrolled/app/helpers/pageflow_scrolled/react_server_side_rendering_helper.rb +23 -25
  373. data/entry_types/scrolled/app/helpers/pageflow_scrolled/sprockets_helper.rb +16 -0
  374. data/entry_types/scrolled/app/helpers/pageflow_scrolled/themes_helper.rb +189 -3
  375. data/entry_types/scrolled/app/models/pageflow_scrolled/chapter.rb +15 -14
  376. data/entry_types/scrolled/app/models/pageflow_scrolled/content_element.rb +61 -7
  377. data/entry_types/scrolled/app/models/pageflow_scrolled/lottie_file.rb +6 -0
  378. data/entry_types/scrolled/app/models/pageflow_scrolled/lottie_file_url_templates.rb +16 -0
  379. data/entry_types/scrolled/app/models/pageflow_scrolled/section.rb +12 -1
  380. data/entry_types/scrolled/app/models/pageflow_scrolled/storyline.rb +20 -1
  381. data/entry_types/scrolled/app/views/pageflow_scrolled/editor/entries/_head.html.erb +1 -0
  382. data/entry_types/scrolled/app/views/pageflow_scrolled/editor/sections/_section_with_content_elements.json.jbuilder +2 -2
  383. data/entry_types/scrolled/app/views/pageflow_scrolled/entries/show.html.erb +15 -12
  384. data/entry_types/scrolled/app/views/pageflow_scrolled/entry_json_seed/_entry.json.jbuilder +45 -17
  385. data/entry_types/scrolled/app/views/pageflow_scrolled/entry_json_seed/_entry_translations.json.jbuilder +14 -0
  386. data/entry_types/scrolled/app/views/pageflow_scrolled/entry_json_seed/_theme.json.jbuilder +34 -5
  387. data/entry_types/scrolled/app/views/pageflow_scrolled/favicons/_entry.html.erb +1 -1
  388. data/entry_types/scrolled/app/views/pageflow_scrolled/storylines/_storyline.json.jbuilder +7 -0
  389. data/entry_types/scrolled/config/configuration_schemas/pageflow-scrolled.json +414 -0
  390. data/entry_types/scrolled/config/locales/de.yml +1094 -78
  391. data/entry_types/scrolled/config/locales/en.yml +1083 -78
  392. data/entry_types/scrolled/config/routes.rb +18 -8
  393. data/entry_types/scrolled/lib/generators/pageflow_scrolled/install/install_generator.rb +11 -11
  394. data/entry_types/scrolled/lib/generators/pageflow_scrolled/install/templates/theme/logoDarkVariantDesktop.svg +56 -0
  395. data/entry_types/scrolled/lib/generators/pageflow_scrolled/install/templates/theme/logoDarkVariantMobile.svg +22 -0
  396. data/entry_types/scrolled/lib/generators/pageflow_scrolled/install/templates/themes_plugin.rb.tt +26 -11
  397. data/entry_types/scrolled/lib/pageflow_scrolled/additional_packs.rb +27 -8
  398. data/entry_types/scrolled/lib/pageflow_scrolled/additional_seed_data.rb +2 -2
  399. data/entry_types/scrolled/lib/pageflow_scrolled/additional_theme_assets.rb +27 -0
  400. data/entry_types/scrolled/lib/pageflow_scrolled/configuration.rb +67 -10
  401. data/entry_types/scrolled/lib/pageflow_scrolled/configuration_schemas.rb +91 -0
  402. data/entry_types/scrolled/lib/pageflow_scrolled/content_element_consent_vendors.rb +1 -1
  403. data/entry_types/scrolled/lib/pageflow_scrolled/engine.rb +2 -1
  404. data/entry_types/scrolled/lib/pageflow_scrolled/entry_file_reference_locations.rb +71 -0
  405. data/entry_types/scrolled/lib/pageflow_scrolled/entry_structured_data_types/faq_page.rb +58 -0
  406. data/entry_types/scrolled/lib/pageflow_scrolled/file_reference_locations.rb +69 -0
  407. data/entry_types/scrolled/lib/pageflow_scrolled/plugin.rb +222 -19
  408. data/entry_types/scrolled/lib/pageflow_scrolled/react_widget_type.rb +2 -4
  409. data/entry_types/scrolled/lib/pageflow_scrolled/seeds.rb +76 -54
  410. data/entry_types/scrolled/lib/pageflow_scrolled/theme_options_default_scale.rb +35 -0
  411. data/entry_types/scrolled/lib/pageflow_scrolled.rb +14 -1
  412. data/entry_types/scrolled/lib/tasks/pageflow_scrolled/storybook.rake +24 -5
  413. data/entry_types/scrolled/package/config/configurationSchemas.js +70 -0
  414. data/entry_types/scrolled/package/config/rollup/configurationSchemas.js +19 -0
  415. data/entry_types/scrolled/package/config/webpack.js +94 -0
  416. data/entry_types/scrolled/package/contentElements/hotspots-frontend.css +1 -0
  417. data/entry_types/scrolled/package/contentElements/hotspots-frontend.js +1795 -0
  418. data/entry_types/scrolled/package/contentElements/inlineBeforeAfter-frontend.css +1 -0
  419. data/entry_types/scrolled/package/contentElements/inlineBeforeAfter-frontend.js +219 -0
  420. data/entry_types/scrolled/package/contentElements/lottieAnimation-frontend.css +1 -0
  421. data/entry_types/scrolled/package/contentElements/lottieAnimation-frontend.js +244 -0
  422. data/entry_types/scrolled/package/contentElements/socialEmbed-frontend.css +1 -0
  423. data/entry_types/scrolled/package/contentElements/socialEmbed-frontend.js +823 -0
  424. data/entry_types/scrolled/package/contentElements/tikTokEmbed-frontend.css +1 -0
  425. data/entry_types/scrolled/package/contentElements/tikTokEmbed-frontend.js +109 -0
  426. data/entry_types/scrolled/package/contentElements/twitterEmbed-frontend.css +1 -0
  427. data/entry_types/scrolled/package/contentElements/twitterEmbed-frontend.js +277 -0
  428. data/entry_types/scrolled/package/contentElements/videoEmbed-frontend.css +1 -0
  429. data/entry_types/scrolled/package/contentElements/videoEmbed-frontend.js +244 -0
  430. data/entry_types/scrolled/package/contentElements-editor.js +3155 -583
  431. data/entry_types/scrolled/package/contentElements-frontend.css +1 -1
  432. data/entry_types/scrolled/package/contentElements-frontend.js +3375 -1585
  433. data/entry_types/scrolled/package/editor.css +1 -0
  434. data/entry_types/scrolled/package/editor.js +8452 -2867
  435. data/entry_types/scrolled/package/entryState.js +1600 -0
  436. data/entry_types/scrolled/package/frontend/FloatingPortalRootProvider-20c600de.js +1979 -0
  437. data/entry_types/scrolled/package/frontend/FloatingPortalRootProvider-58189220.js +2538 -0
  438. data/entry_types/scrolled/package/frontend/{PhonePlatformContext-4ec6b2de.js → PhonePlatformContext-035a99fa.js} +9 -15
  439. data/entry_types/scrolled/package/frontend/PhonePlatformContext-9da43797.js +33 -0
  440. data/entry_types/scrolled/package/frontend/Placeholder.module-bf0966b4.js +5267 -0
  441. data/entry_types/scrolled/package/frontend/Placeholder.module-d22a9335.js +4180 -0
  442. data/entry_types/scrolled/package/frontend/ThemeIcon-ab834849.js +754 -0
  443. data/entry_types/scrolled/package/frontend/ThemeIcon-f6cb3a8f.js +773 -0
  444. data/entry_types/scrolled/package/frontend/ToggleFullscreenCornerButton-30fd1124.js +39 -0
  445. data/entry_types/scrolled/package/frontend/ToggleFullscreenCornerButton-61c25224.js +72 -0
  446. data/entry_types/scrolled/package/frontend/Viewer-1074fd59.js +147 -0
  447. data/entry_types/scrolled/package/frontend/Viewer-1482ca34.js +480 -0
  448. data/entry_types/scrolled/package/frontend/Viewer-70504231.js +210 -0
  449. data/entry_types/scrolled/package/frontend/{Viewer-1bb5597c.js → Viewer-cc8d4072.js} +100 -135
  450. data/entry_types/scrolled/package/frontend/Wavesurfer-656e3c36.js +302 -0
  451. data/entry_types/scrolled/package/frontend/Wavesurfer-b753b838.js +385 -0
  452. data/entry_types/scrolled/package/frontend/commenting.css +1 -0
  453. data/entry_types/scrolled/package/frontend/extensions-167ae143.js +1455 -0
  454. data/entry_types/scrolled/package/frontend/extensions-2318f92b.js +5918 -0
  455. data/entry_types/scrolled/package/frontend/extensions-a461fd13.js +4890 -0
  456. data/entry_types/scrolled/package/frontend/extensions-c98269fd.js +1982 -0
  457. data/entry_types/scrolled/package/frontend/i18n-493cd2a6.js +93 -0
  458. data/entry_types/scrolled/package/frontend/i18n-e06cac14.js +100 -0
  459. data/entry_types/scrolled/package/frontend/{index-11f32f10.js → index-2e5546f7.js} +39 -54
  460. data/entry_types/scrolled/package/frontend/index-de2b418d.js +130 -0
  461. data/entry_types/scrolled/package/frontend/index.css +1 -1
  462. data/entry_types/scrolled/package/frontend/index.js +1976 -1367
  463. data/entry_types/scrolled/package/frontend/inlineEditing.css +1 -0
  464. data/entry_types/scrolled/package/frontend/useContentElementEditorState-a084912e.js +25 -0
  465. data/entry_types/scrolled/package/frontend/useDelayedBoolean-a387d85b.js +24 -0
  466. data/entry_types/scrolled/package/frontend/usePhonePlatform-4ecb96d4.js +8 -0
  467. data/entry_types/scrolled/package/frontend/usePhonePlatform-ce9ff08d.js +8 -0
  468. data/entry_types/scrolled/package/frontend-server.js +20 -210
  469. data/entry_types/scrolled/package/package.json +22 -12
  470. data/entry_types/scrolled/package/review.css +1 -0
  471. data/entry_types/scrolled/package/review.js +3306 -0
  472. data/entry_types/scrolled/package/testHelpers.js +952 -239
  473. data/entry_types/scrolled/package/values/widgets.module.css +18 -0
  474. data/entry_types/scrolled/package/widgets/consentBar.css +1 -1
  475. data/entry_types/scrolled/package/widgets/consentBar.js +275 -315
  476. data/entry_types/scrolled/package/widgets/defaultNavigation.css +2 -2
  477. data/entry_types/scrolled/package/widgets/defaultNavigation.js +961 -432
  478. data/entry_types/scrolled/package/widgets/excursionSheet.css +1 -0
  479. data/entry_types/scrolled/package/widgets/excursionSheet.js +225 -0
  480. data/entry_types/scrolled/package/widgets/iconInlineFileRights.css +1 -1
  481. data/entry_types/scrolled/package/widgets/iconInlineFileRights.js +62 -32
  482. data/entry_types/scrolled/package/widgets/iconScrollIndicator.css +1 -0
  483. data/entry_types/scrolled/package/widgets/iconScrollIndicator.js +67 -0
  484. data/entry_types/scrolled/package/widgets/mainStorylineSheet.css +1 -0
  485. data/entry_types/scrolled/package/widgets/mainStorylineSheet.js +100 -0
  486. data/entry_types/scrolled/package/widgets/textInlineFileRights.css +1 -1
  487. data/entry_types/scrolled/package/widgets/textInlineFileRights.js +48 -26
  488. data/entry_types/scrolled/package/widgets-server.js +2 -0
  489. data/entry_types/scrolled/spec/factories/chapters.rb +14 -1
  490. data/entry_types/scrolled/spec/factories/content_elements.rb +1 -1
  491. data/entry_types/scrolled/spec/factories/lottie_files.rb +29 -0
  492. data/entry_types/scrolled/spec/fixtures/animation.lottie +0 -0
  493. data/entry_types/scrolled/spec/fixtures/font.woff +0 -0
  494. data/entry_types/scrolled/spec/fixtures/font.woff2 +0 -0
  495. data/lib/generators/pageflow/active_admin_initializer/active_admin_initializer_generator.rb +5 -3
  496. data/lib/generators/pageflow/assets/assets_generator.rb +7 -5
  497. data/lib/generators/pageflow/cancan/cancan_generator.rb +2 -1
  498. data/lib/generators/pageflow/cancan/templates/ability.rb +1 -1
  499. data/lib/generators/pageflow/error_pages/error_pages_generator.rb +2 -1
  500. data/lib/generators/pageflow/initializer/initializer_generator.rb +3 -2
  501. data/lib/generators/pageflow/install/install_generator.rb +2 -2
  502. data/lib/generators/pageflow/procfile/procfile_generator.rb +2 -1
  503. data/lib/generators/pageflow/resque/resque_generator.rb +4 -4
  504. data/lib/generators/pageflow/resque/templates/resque.rake +1 -1
  505. data/lib/generators/pageflow/routes/routes_generator.rb +4 -2
  506. data/lib/generators/pageflow/seeds/seeds_generator.rb +3 -2
  507. data/lib/generators/pageflow/seeds/templates/seeds.rb +6 -6
  508. data/lib/generators/pageflow/theme/theme_generator.rb +2 -1
  509. data/lib/generators/pageflow/user/user_generator.rb +2 -1
  510. data/lib/pageflow/ability_mixin.rb +25 -15
  511. data/lib/pageflow/active_admin_patches/views/attributes_table.rb +1 -0
  512. data/lib/pageflow/active_admin_patches/views/pages/base.rb +3 -2
  513. data/lib/pageflow/active_admin_patches/views/table_for.rb +1 -0
  514. data/lib/pageflow/additional_headers.rb +27 -0
  515. data/lib/pageflow/admin/attributes_table_rows.rb +3 -3
  516. data/lib/pageflow/admin/tabs.rb +1 -1
  517. data/lib/pageflow/built_in_file_type.rb +1 -1
  518. data/lib/pageflow/built_in_page_types_plugin.rb +1 -1
  519. data/lib/pageflow/built_in_widget_type.rb +2 -2
  520. data/lib/pageflow/built_in_widget_types_plugin.rb +2 -1
  521. data/lib/pageflow/configuration.rb +183 -18
  522. data/lib/pageflow/cutoff_modes.rb +39 -0
  523. data/lib/pageflow/deprecation.rb +19 -0
  524. data/lib/pageflow/editor_controller.rb +6 -10
  525. data/lib/pageflow/engine.rb +5 -6
  526. data/lib/pageflow/entries_controller_env_helper.rb +32 -2
  527. data/lib/pageflow/entry_export_import/attachment_files.rb +1 -1
  528. data/lib/pageflow/entry_export_import/entry_serialization.rb +9 -8
  529. data/lib/pageflow/entry_export_import/file_mappings.rb +1 -0
  530. data/lib/pageflow/entry_export_import/page_type_versions.rb +2 -2
  531. data/lib/pageflow/entry_export_import/revision_serialization/import.rb +11 -1
  532. data/lib/pageflow/entry_export_import/revision_serialization.rb +3 -0
  533. data/lib/pageflow/entry_export_import.rb +3 -3
  534. data/lib/pageflow/entry_structured_data_types.rb +43 -0
  535. data/lib/pageflow/entry_type_configuration.rb +1 -0
  536. data/lib/pageflow/entry_types.rb +2 -2
  537. data/lib/pageflow/feature.rb +1 -1
  538. data/lib/pageflow/features.rb +4 -6
  539. data/lib/pageflow/file_importers.rb +4 -4
  540. data/lib/pageflow/file_type.rb +5 -5
  541. data/lib/pageflow/file_types.rb +4 -4
  542. data/lib/pageflow/global_config_api.rb +5 -6
  543. data/lib/pageflow/help_entries.rb +7 -7
  544. data/lib/pageflow/help_entry.rb +1 -1
  545. data/lib/pageflow/hooks.rb +1 -1
  546. data/lib/pageflow/nested_revision_component.rb +9 -27
  547. data/lib/pageflow/news_item_api.rb +1 -1
  548. data/lib/pageflow/oembed_providers.rb +42 -0
  549. data/lib/pageflow/page_type.rb +2 -3
  550. data/lib/pageflow/page_types.rb +3 -3
  551. data/lib/pageflow/paperclip_interpolations/support.rb +1 -1
  552. data/lib/pageflow/paperclip_processors/vtt.rb +1 -0
  553. data/lib/pageflow/paperclip_processors/webp.rb +2 -2
  554. data/lib/pageflow/partial_editor_fragment_renderer.rb +2 -2
  555. data/lib/pageflow/plugin.rb +1 -2
  556. data/lib/pageflow/primary_domain_entry_redirect.rb +6 -0
  557. data/lib/pageflow/quota.rb +9 -5
  558. data/lib/pageflow/quotas.rb +1 -1
  559. data/lib/pageflow/rails_version.rb +14 -2
  560. data/lib/pageflow/react.rb +1 -1
  561. data/lib/pageflow/revision_component.rb +8 -40
  562. data/lib/pageflow/revision_components.rb +16 -6
  563. data/lib/pageflow/seeds.rb +12 -16
  564. data/lib/pageflow/theme.rb +1 -1
  565. data/lib/pageflow/theme_customizations.rb +10 -10
  566. data/lib/pageflow/themes.rb +44 -3
  567. data/lib/pageflow/user_mixin.rb +18 -6
  568. data/lib/pageflow/version.rb +1 -1
  569. data/lib/pageflow/widget_type.rb +4 -4
  570. data/lib/pageflow/widget_types.rb +9 -9
  571. data/lib/pageflow/zencoder_api.rb +32 -42
  572. data/lib/pageflow/zencoder_audio_output_definition.rb +14 -13
  573. data/lib/pageflow/zencoder_meta_data_output_definition.rb +3 -2
  574. data/lib/pageflow/zencoder_output_definition.rb +16 -14
  575. data/lib/pageflow/zencoder_video_output_definition.rb +140 -88
  576. data/lib/pageflow.rb +8 -2
  577. data/lib/tasks/pageflow_tasks.rake +1 -1
  578. data/package/config/eslint-rules/documented-in-toc.js +125 -0
  579. data/package/config/jest/index.js +0 -1
  580. data/package/config/webpack.js +0 -1
  581. data/package/config/webpack5.js +0 -1
  582. data/package/editor.js +4880 -2313
  583. data/package/frontend.js +710 -1223
  584. data/package/package.json +3 -1
  585. data/package/review.js +384 -0
  586. data/package/testHelpers.js +257 -243
  587. data/package/ui.js +1487 -813
  588. data/package/vendor/stubs/wysihtml5.js +2 -0
  589. data/spec/factories/accounts.rb +37 -22
  590. data/spec/factories/audio_files.rb +2 -2
  591. data/spec/factories/authentication_tokens.rb +4 -4
  592. data/spec/factories/chapters.rb +4 -4
  593. data/spec/factories/comment_thread_reads.rb +10 -0
  594. data/spec/factories/comment_threads.rb +10 -0
  595. data/spec/factories/comments.rb +9 -0
  596. data/spec/factories/draft_entries.rb +14 -6
  597. data/spec/factories/edit_locks.rb +1 -1
  598. data/spec/factories/entries.rb +28 -19
  599. data/spec/factories/entry_translation_groups.rb +6 -0
  600. data/spec/factories/file_folders.rb +8 -0
  601. data/spec/factories/file_usages.rb +2 -1
  602. data/spec/factories/folders.rb +1 -1
  603. data/spec/factories/image_files.rb +2 -2
  604. data/spec/factories/pages.rb +3 -3
  605. data/spec/factories/published_entries.rb +28 -8
  606. data/spec/factories/revisions.rb +1 -1
  607. data/spec/factories/sites.rb +6 -0
  608. data/spec/factories/test_multi_attachment_files.rb +1 -1
  609. data/spec/factories/text_track_files.rb +1 -1
  610. data/spec/factories/uploadable_files.rb +1 -1
  611. data/spec/factories/users.rb +6 -6
  612. data/spec/factories/video_files.rb +2 -2
  613. data/spec/factories/widgets.rb +1 -1
  614. data/spec/fixtures/7x15_rotated.jpg +0 -0
  615. data/vendor/assets/javascripts/wysihtml-toolbar.js +19288 -0
  616. metadata +266 -92
  617. data/app/assets/stylesheets/pageflow/editor/select_button.scss +0 -51
  618. data/config/initializers/revision_components.rb +0 -5
  619. data/config/locales/twitter_to_x.de.yml +0 -6
  620. data/config/locales/twitter_to_x.en.yml +0 -6
  621. data/entry_types/scrolled/app/helpers/pageflow_scrolled/generated_media_queries_helper.rb +0 -55
  622. data/entry_types/scrolled/config/locales/twitter_to_x.de.yml +0 -12
  623. data/entry_types/scrolled/config/locales/twitter_to_x.en.yml +0 -12
  624. data/entry_types/scrolled/config/locales/vr_image_projection.de.yml +0 -24
  625. data/entry_types/scrolled/config/locales/vr_image_projection.en.yml +0 -24
  626. data/entry_types/scrolled/package/frontend/EditableInlineText.module-ebd22921.js +0 -5497
  627. data/entry_types/scrolled/package/frontend/ToggleFullscreenCornerButton-b79159cc.js +0 -107
  628. data/entry_types/scrolled/package/frontend/Viewer-1ecf3375.js +0 -154
  629. data/entry_types/scrolled/package/frontend/Wavesurfer-7d9cf1b7.js +0 -333
  630. data/entry_types/scrolled/package/frontend/arrowRight-62998af9.js +0 -77
  631. data/entry_types/scrolled/package/frontend/components-024a9893.js +0 -2853
  632. data/entry_types/scrolled/package/frontend/createSuper-d0f30da3.js +0 -115
  633. data/entry_types/scrolled/package/frontend/i18n-ce13a8bf.js +0 -1129
  634. data/entry_types/scrolled/package/frontend/useContentElementEditorState-4f4c3cf6.js +0 -52
  635. data/lib/generators/pageflow/resque/templates/resque_enqueue_after_commit_patch.rb +0 -25
  636. data/package/vendor/jquery.minicolors.js +0 -1108
@@ -0,0 +1,1979 @@
1
+ import React, { createContext, useState, useRef, useContext, useEffect, useMemo, useCallback } from 'react';
2
+ import classNames from 'classnames';
3
+ import { u as useDelayedBoolean } from './useDelayedBoolean-a387d85b.js';
4
+ import BackboneEvents from 'backbone-events-standalone';
5
+ import { useTheme, useEntryStructure, useEmbedOriginUrl, useChapter, useDownloadableFile } from 'pageflow-scrolled/entryState';
6
+ import Measure from 'react-measure';
7
+
8
+ const OnScreenObserverRootContext = createContext();
9
+ const OnScreenObserverRootProvider = OnScreenObserverRootContext.Provider;
10
+ function useOnScreen(ref, {
11
+ rootMargin,
12
+ onIntersecting,
13
+ onChange,
14
+ skipIframeFix
15
+ } = {}) {
16
+ var _useContext;
17
+ const [isIntersecting, setIntersecting] = useState(false);
18
+ const onIntersectingRef = useRef();
19
+ const onChangeRef = useRef();
20
+ const root = (_useContext = useContext(OnScreenObserverRootContext)) === null || _useContext === void 0 ? void 0 : _useContext.current;
21
+ useEffect(() => {
22
+ onIntersectingRef.current = onIntersecting;
23
+ }, [onIntersecting]);
24
+ useEffect(() => {
25
+ onChangeRef.current = onChange;
26
+ }, [onChange]);
27
+ useEffect(() => {
28
+ const current = ref.current;
29
+ const observer = createIntersectionObserver(entries => {
30
+ // Even when observing only a single element, multiple entries
31
+ // may have queued up. In Chrome this can be observed when
32
+ // moving an observed element in the DOM: The callback is
33
+ // invoked once with two entries for the same target, one
34
+ // claiming the element no longer intersects and one - with a
35
+ // later timestamp - saying that is does intersect. Assuming
36
+ // entries are ordered according to time, we only consider the
37
+ // last entry.
38
+ const entry = entries[entries.length - 1];
39
+ setIntersecting(entry.isIntersecting);
40
+ if (entry.isIntersecting && onIntersectingRef.current) {
41
+ onIntersectingRef.current();
42
+ }
43
+ if (onChangeRef.current) {
44
+ onChangeRef.current(entry.isIntersecting);
45
+ }
46
+ }, {
47
+ rootMargin,
48
+ root
49
+ }, skipIframeFix);
50
+ if (ref.current) {
51
+ observer.observe(current);
52
+ }
53
+ return () => {
54
+ observer.unobserve(current);
55
+ };
56
+ }, [ref, rootMargin, root, skipIframeFix]);
57
+ return isIntersecting;
58
+ }
59
+ function createIntersectionObserver(callback, options, skipIframeFix) {
60
+ if (skipIframeFix || options.root) {
61
+ return new IntersectionObserver(callback, options);
62
+ }
63
+
64
+ // Positive root margins are ignored in iframes [1] (i.e. in
65
+ // the Pageflow editor). To make it work, the iframe document
66
+ // needs to be passed as `root` [2].
67
+ // This leads to a `TypeError`, though, in browers that do not
68
+ // support this yet (e.g. Chrome 80). We catch the error and
69
+ // skip passing the `root` option.
70
+ //
71
+ // [1] https://github.com/w3c/IntersectionObserver/issues/283
72
+ // [2] https://github.com/w3c/IntersectionObserver/issues/372
73
+ try {
74
+ let optionsWithIframeFix = options;
75
+ if (options.rootMargin && window.parent !== window) {
76
+ optionsWithIframeFix = {
77
+ ...options,
78
+ root: window.document
79
+ };
80
+ }
81
+ return new IntersectionObserver(callback, optionsWithIframeFix);
82
+ } catch (e) {
83
+ // Normally we would check for TypeError here. Since the polyfill
84
+ // throws a generic error, we retry either way and trust that the
85
+ // error will happen again if it is not related to the `root`
86
+ // option.
87
+ return createIntersectionObserver(callback, options, true);
88
+ }
89
+ }
90
+
91
+ const StorylineActivityContext = createContext('active');
92
+ const MainStorylineCoverageContext = createContext({
93
+ mainStorylineCovered: false,
94
+ setMainStorylineCovered: () => {}
95
+ });
96
+ function MainStorylineActivity({
97
+ activeExcursion,
98
+ children
99
+ }) {
100
+ const {
101
+ mainStorylineCovered
102
+ } = useContext(MainStorylineCoverageContext);
103
+ const mode = activeExcursion ? mainStorylineCovered ? 'covered' : 'background' : 'active';
104
+ return /*#__PURE__*/React.createElement(StorylineActivityContext.Provider, {
105
+ value: mode
106
+ }, children);
107
+ }
108
+ function MainStorylineCoverageProvider({
109
+ children
110
+ }) {
111
+ const [mainStorylineCovered, setMainStorylineCovered] = useState(false);
112
+ const value = useMemo(() => ({
113
+ mainStorylineCovered,
114
+ setMainStorylineCovered
115
+ }), [mainStorylineCovered]);
116
+ return /*#__PURE__*/React.createElement(MainStorylineCoverageContext.Provider, {
117
+ value: value
118
+ }, children);
119
+ }
120
+ function useStorylineActivity() {
121
+ return useContext(StorylineActivityContext);
122
+ }
123
+ function useMainStorylineCoverage() {
124
+ return useContext(MainStorylineCoverageContext);
125
+ }
126
+
127
+ var styles = {"wrapper":"useScrollPositionLifecycle-module_wrapper__1a6Kr","isActiveProbe":"useScrollPositionLifecycle-module_isActiveProbe__3VKB5"};
128
+
129
+ const StaticPreviewContext = createContext(false);
130
+ function StaticPreview({
131
+ children
132
+ }) {
133
+ return /*#__PURE__*/React.createElement(StaticPreviewContext.Provider, {
134
+ value: true
135
+ }, children);
136
+ }
137
+
138
+ /**
139
+ * Use inside a content element component to determine whether the
140
+ * component is being rendered in a static preview, e.g. editor
141
+ * thumbnails.
142
+ *
143
+ * @example
144
+ * const isStaticPreview = useIsStaticPreview();
145
+ */
146
+ function useIsStaticPreview() {
147
+ return useContext(StaticPreviewContext);
148
+ }
149
+ function createScrollPositionLifecycleProvider(Context) {
150
+ return function ScrollPositionLifecycleProvider({
151
+ children,
152
+ onActivate,
153
+ entersWithFadeTransition
154
+ }) {
155
+ const ref = useRef();
156
+ const isActiveProbeRef = useRef();
157
+ const isStaticPreview = useContext(StaticPreviewContext);
158
+ const mode = useStorylineActivity();
159
+ const shouldLoad = useOnScreen(ref, {
160
+ rootMargin: '200% 0px 200% 0px'
161
+ });
162
+ const shouldPrepare = useOnScreen(ref, {
163
+ rootMargin: '25% 0px 25% 0px'
164
+ }) && !isStaticPreview;
165
+
166
+ // Sections that enter with fade transition only become visible
167
+ // once they reach the center of the viewport. We want to reflect
168
+ // that in `isVisible`/`onVisible` to prevent background videos
169
+ // from starting too soon. Since fade section might still exit
170
+ // with a scroll transition, we want to keep `isVisible` true
171
+ // until the section has completely left the viewport. We do not
172
+ // care about when exactly a background video pauses.
173
+ //
174
+ // Note that with fade transitions sections actually stay visible
175
+ // a bit longer while they are still fading out. This is handled
176
+ // by `isVisibleWithDelay` below.
177
+ const shouldBeVisible = useOnScreen(ref, {
178
+ rootMargin: entersWithFadeTransition ? '0px 0px -50% 0px' : undefined
179
+ }) && !isStaticPreview;
180
+ const shouldBeActive = useOnScreen(isActiveProbeRef, {
181
+ rootMargin: '-50% 0px -50% 0px',
182
+ onIntersecting: onActivate
183
+ }) && !isStaticPreview;
184
+
185
+ // useDelayedBoolean causes an extra render once the delay has
186
+ // elapsed. When entersWithFadeTransition is false,
187
+ // isVisibleWithDelay is never used, though. Since hooks can not
188
+ // be wrapped in conditionals, we ensure that the value passed to
189
+ // useDelayedBoolean is always false if entersWithFadeTransition
190
+ // is false. This prevents the extra render.
191
+ const isVisibleWithDelay = useDelayedBoolean(shouldBeVisible && entersWithFadeTransition, {
192
+ fromTrueToFalse: 1000
193
+ });
194
+ const isVisible = entersWithFadeTransition ? isVisibleWithDelay : shouldBeVisible;
195
+ // Elements in covered storylines are not visible.
196
+ // Elements in background storylines are visible (but not active).
197
+ // Elements in active storylines are both visible and active.
198
+ const isVisibleFinal = isVisible && mode !== 'covered';
199
+ const inForeground = mode === 'active';
200
+
201
+ // We want to make sure that `onActivate` is never called before
202
+ // `onVisible`, no matter in which order the intersection
203
+ // observers above fire.
204
+ const isActive = isVisibleFinal && shouldBeActive && inForeground;
205
+ const value = useMemo(() => ({
206
+ shouldLoad,
207
+ shouldPrepare,
208
+ isVisible: isVisibleFinal,
209
+ isActive,
210
+ inForeground
211
+ }), [shouldLoad, shouldPrepare, isVisibleFinal, isActive, inForeground]);
212
+ return /*#__PURE__*/React.createElement("div", {
213
+ ref: ref,
214
+ className: classNames(styles.wrapper)
215
+ }, /*#__PURE__*/React.createElement("div", {
216
+ ref: isActiveProbeRef,
217
+ className: styles.isActiveProbe
218
+ }), /*#__PURE__*/React.createElement(Context.Provider, {
219
+ value: value
220
+ }, children));
221
+ };
222
+ }
223
+ function createScrollPositionLifecycleHook(Context) {
224
+ return function useScrollPositionLifecycle({
225
+ onActivate,
226
+ onDeactivate,
227
+ onVisible,
228
+ onInvisible,
229
+ onEnterBackground,
230
+ onEnterForeground
231
+ } = {}) {
232
+ const result = useContext(Context);
233
+ const wasActive = useRef();
234
+ const wasVisible = useRef();
235
+ const wasForeground = useRef();
236
+ const {
237
+ isActive,
238
+ isVisible,
239
+ inForeground
240
+ } = result || {};
241
+ useEffect(() => {
242
+ if (!wasVisible.current && isVisible && onVisible) {
243
+ onVisible();
244
+ }
245
+ if (!wasActive.current && isActive && onActivate) {
246
+ onActivate();
247
+ } else if (wasActive.current && !isActive && onDeactivate) {
248
+ onDeactivate();
249
+ }
250
+ if (wasForeground.current && !inForeground && onEnterBackground) {
251
+ onEnterBackground();
252
+ } else if (!wasForeground.current && inForeground && onEnterForeground) {
253
+ onEnterForeground();
254
+ }
255
+ if (wasVisible.current && !isVisible && onInvisible) {
256
+ onInvisible();
257
+ }
258
+ wasActive.current = isActive;
259
+ wasVisible.current = isVisible;
260
+ wasForeground.current = inForeground;
261
+ });
262
+ return result;
263
+ };
264
+ }
265
+
266
+ function extensible(name, Component) {
267
+ return function ExtensibleComponent(props) {
268
+ const isStaticPreview = useIsStaticPreview();
269
+ const extensions = useExtensions();
270
+ if (isStaticPreview) {
271
+ return /*#__PURE__*/React.createElement(Component, props);
272
+ }
273
+ const Alternative = extensions.alternatives[name];
274
+ if (Alternative) {
275
+ return /*#__PURE__*/React.createElement(Alternative, props);
276
+ }
277
+ const Decorator = extensions.decorators[name];
278
+ if (Decorator) {
279
+ return /*#__PURE__*/React.createElement(Decorator, props, /*#__PURE__*/React.createElement(Component, props));
280
+ }
281
+ return /*#__PURE__*/React.createElement(Component, props);
282
+ };
283
+ }
284
+ function provideExtensions({
285
+ decorators: d,
286
+ alternatives: a
287
+ } = {}) {
288
+ decorators = d || {};
289
+ alternatives = a || {};
290
+ notifyListeners();
291
+ }
292
+ function ExtensionsProvider({
293
+ children
294
+ }) {
295
+ const [version, setVersion] = useState(0);
296
+ useEffect(() => subscribe(() => setVersion(v => v + 1)), []);
297
+ return /*#__PURE__*/React.createElement(ExtensionsContext.Provider, {
298
+ value: version
299
+ }, children);
300
+ }
301
+ let decorators = {};
302
+ let alternatives = {};
303
+ let listeners = [];
304
+ const ExtensionsContext = createContext(0);
305
+ function useExtensions() {
306
+ useContext(ExtensionsContext);
307
+ return {
308
+ decorators,
309
+ alternatives
310
+ };
311
+ }
312
+ function subscribe(listener) {
313
+ listeners = [...listeners, listener];
314
+ return () => {
315
+ listeners = listeners.filter(l => l !== listener);
316
+ };
317
+ }
318
+ function notifyListeners() {
319
+ listeners.forEach(l => l());
320
+ }
321
+
322
+ /**
323
+ * Register new types of content elements.
324
+ * @name frontend_contentElementTypes
325
+ */
326
+ class ContentElementTypeRegistry {
327
+ constructor() {
328
+ this.types = {};
329
+ }
330
+
331
+ /**
332
+ * Register a new type of content element.
333
+ *
334
+ * @param {string} typeName - Name of the content element type.
335
+ * @param {Object} options
336
+ * @param {React.Component} options.component
337
+ * @param {boolean} [options.supportsWrappingAroundFloats] -
338
+ * In sections with centered layout, content elements can be
339
+ * floated to the left or right. By default all content
340
+ * elements are cleared to position them below floating
341
+ * elements. If a content element renders mainly text that
342
+ * can wrap around floating elements, clearing can be
343
+ * disabled via this option.
344
+ * @memberof frontend_contentElementTypes
345
+ *
346
+ * @example
347
+ *
348
+ * // frontend.js
349
+ *
350
+ * import {frontend} from 'pageflow-scrolled/frontend';
351
+ * import {InlineImage} from './InlineImage';
352
+ *
353
+ * frontend.contentElementTypes.register('inlineImage', {
354
+ * component: InlineImage
355
+ * });
356
+ */
357
+ register(typeName, options) {
358
+ this.types[typeName] = options;
359
+ }
360
+ getComponent(typeName) {
361
+ return this.types[typeName] && this.types[typeName].component;
362
+ }
363
+ getOptions(typeName) {
364
+ return this.types[typeName];
365
+ }
366
+ consentVendors({
367
+ contentElements,
368
+ t
369
+ }) {
370
+ const vendorsByName = {};
371
+ contentElements.forEach(contentElement => {
372
+ const type = this.types[contentElement.typeName];
373
+ const consentVendors = typeof type.consentVendors === 'function' ? type.consentVendors({
374
+ configuration: contentElement.configuration,
375
+ t
376
+ }) : type.consentVendors || [];
377
+ consentVendors.forEach(vendor => {
378
+ vendorsByName[vendor.name] = vendor;
379
+ });
380
+ });
381
+ return Object.values(vendorsByName);
382
+ }
383
+ }
384
+
385
+ /**
386
+ * Register new types of widgets.
387
+ * @name frontend_widgetTypes
388
+ */
389
+ class WidgetTypeRegistry {
390
+ constructor() {
391
+ this.types = {};
392
+ }
393
+
394
+ /**
395
+ * Register a new type of widget.
396
+ *
397
+ * @param {string} typeName - Name of the content element type.
398
+ * @param {Object} options
399
+ * @param {React.Component} options.component
400
+ * @memberof frontend_widgetTypes
401
+ *
402
+ * @example
403
+ *
404
+ * // frontend.js
405
+ *
406
+ * import {frontend} from 'pageflow-scrolled/frontend';
407
+ * import {CustomNavigationBar} from './CustomNavigationBar';
408
+ *
409
+ * frontend.contentElementTypes.register('customNavigationBar', {
410
+ * component: CustomNavigationBar
411
+ * });
412
+ */
413
+ register(typeName, options) {
414
+ if (!options.component) {
415
+ throw new Error(`Missing required component option for widget type '${typeName}'.`);
416
+ }
417
+ this.types[typeName] = options;
418
+ }
419
+ getComponent(typeName) {
420
+ if (!this.types[typeName]) {
421
+ throw new Error(`Unknown widget type '${typeName}'. Consider calling frontent.widgetTypes.register.`);
422
+ }
423
+ return this.types[typeName].component;
424
+ }
425
+ getPresenceProvider(typeName) {
426
+ var _this$types$typeName;
427
+ return (_this$types$typeName = this.types[typeName]) === null || _this$types$typeName === void 0 ? void 0 : _this$types$typeName.presenceProvider;
428
+ }
429
+ }
430
+
431
+ const api = {
432
+ contentElementTypes: new ContentElementTypeRegistry(),
433
+ widgetTypes: new WidgetTypeRegistry(),
434
+ /**
435
+ * Custom error boundary component to wrap content elements.
436
+ * Allows integration with error monitoring tools. The component receives
437
+ * typeName (string), configuration (object), fallback (function returning
438
+ * default UI), and children (content element).
439
+ *
440
+ * @name frontend_contentElementErrorBoundary
441
+ * @type {React.Component}
442
+ */
443
+ contentElementErrorBoundary: undefined
444
+ };
445
+
446
+ const ContentElementAttributesContext = createContext({});
447
+ function ContentElementAttributesProvider({
448
+ id,
449
+ permaId,
450
+ width,
451
+ position,
452
+ inlineComments,
453
+ children
454
+ }) {
455
+ const attributes = useMemo(() => ({
456
+ contentElementId: id,
457
+ contentElementPermaId: permaId,
458
+ width,
459
+ position,
460
+ inlineComments
461
+ }), [id, permaId, width, position, inlineComments]);
462
+ return /*#__PURE__*/React.createElement(ContentElementAttributesContext.Provider, {
463
+ value: attributes
464
+ }, children);
465
+ }
466
+ function useContentElementAttributes() {
467
+ return useContext(ContentElementAttributesContext);
468
+ }
469
+
470
+ const ContentElementLifecycleContext = createContext();
471
+ const LifecycleProvider = createScrollPositionLifecycleProvider(ContentElementLifecycleContext);
472
+ const useLifecycle = createScrollPositionLifecycleHook(ContentElementLifecycleContext);
473
+ function ContentElementLifecycleProvider({
474
+ type,
475
+ children,
476
+ override
477
+ }) {
478
+ const {
479
+ lifecycle
480
+ } = api.contentElementTypes.getOptions(type);
481
+ if (override) {
482
+ return /*#__PURE__*/React.createElement(ContentElementLifecycleContext.Provider, {
483
+ value: override,
484
+ children: children
485
+ });
486
+ } else if (lifecycle) {
487
+ return /*#__PURE__*/React.createElement(LifecycleProvider, null, children);
488
+ } else {
489
+ return children;
490
+ }
491
+ }
492
+
493
+ /**
494
+ * Returns an object containing information about the scroll position
495
+ * related lifecylce of the content element. Requires the `lifecycle`
496
+ * option to be set to true in the `frontend.contentElements.register`
497
+ * call for the content element's type.
498
+ *
499
+ * * `shouldLoad` is true if the content element should start lazy
500
+ * load. Becomes true before `shouldPrepare`.
501
+ *
502
+ * * `shouldPrepare` is true if the content element is about to enter
503
+ * the viewport.
504
+ *
505
+ * * `isActive` is true if the content element is completely in the
506
+ * viewport.
507
+ *
508
+ * @param {Function} onActivate -
509
+ * Invoked when content element has entered the viewport.
510
+ *
511
+ * @param {Function} onDeactivate -
512
+ * Invoked when content element has left the viewport.
513
+ *
514
+ * @example
515
+ *
516
+ * const {isActive, shouldPrepare} = useContentElementLifecycle();
517
+ */
518
+ function useContentElementLifecycle(options) {
519
+ const result = useLifecycle(options);
520
+ if (!result) {
521
+ throw new Error('useContentElementLifecycle is only available in ' + 'content elements for which `lifecycle: true` has ' + 'been passed to frontend.contentElements.register');
522
+ }
523
+ return result;
524
+ }
525
+
526
+ // Offsets of the subject's top edge relative to the viewport's top
527
+ // edge at the milestones of a content element's view timeline.
528
+ //
529
+ // Elements that are pinned in the viewport for part of their scroll
530
+ // space are measured along a taller subject: They move with the
531
+ // subject's top edge until they are pinned and continue with the
532
+ // subject's bottom edge once they have been.
533
+ const milestones = {
534
+ firstVisible: ({
535
+ viewportHeight
536
+ }) => viewportHeight,
537
+ firstContained: ({
538
+ viewportHeight,
539
+ elementHeight
540
+ }) => viewportHeight - elementHeight,
541
+ reachesCenter: ({
542
+ viewportHeight
543
+ }) => viewportHeight / 2,
544
+ leavesCenter: ({
545
+ subjectHeight,
546
+ viewportHeight
547
+ }) => viewportHeight / 2 - subjectHeight,
548
+ lastContained: ({
549
+ subjectHeight,
550
+ elementHeight
551
+ }) => elementHeight - subjectHeight,
552
+ lastVisible: ({
553
+ subjectHeight
554
+ }) => -subjectHeight,
555
+ // The pinned position is only known while the element actually is
556
+ // pinned. That is exactly when progress along the pinned range is
557
+ // between 0 and 1, though: Before, the element's top edge coincides
558
+ // with the subject's, after, its bottom edge does. Both edges of the
559
+ // range therefore come out equally far off in those phases, which
560
+ // makes progress clamp to 0 respectively 1.
561
+ reachesPinnedPosition: ({
562
+ elementTop
563
+ }) => elementTop,
564
+ leavesPinnedPosition: ({
565
+ elementTop,
566
+ subjectHeight,
567
+ elementHeight
568
+ }) => elementTop - subjectHeight + elementHeight
569
+ };
570
+ const ranges = {
571
+ // Mirror the named ranges of CSS scroll driven animations.
572
+ cover: ['firstVisible', 'lastVisible'],
573
+ contain: ['firstContained', 'lastContained'],
574
+ entry: ['firstVisible', 'firstContained'],
575
+ exit: ['lastContained', 'lastVisible'],
576
+ // Same part of the page during which content elements become active
577
+ // and autoplayed videos play.
578
+ center: ['reachesCenter', 'leavesCenter'],
579
+ // Only elements that components like TwoColumn or
580
+ // ContentElementScrollSpace pin in the viewport have a pinned phase.
581
+ pinned: ['reachesPinnedPosition', 'leavesPinnedPosition']
582
+ };
583
+ const rangeAliases = {
584
+ // Elements that are pinned in the viewport hold the reader's
585
+ // attention while they stay in place. Elements that are not pinned
586
+ // at all do so while they pass the center of the viewport.
587
+ inFocus: hasPinnedPhase => hasPinnedPhase ? 'pinned' : 'center'
588
+ };
589
+ function getViewTimelineProgress({
590
+ range,
591
+ subjectRect,
592
+ elementRect,
593
+ viewportHeight
594
+ }) {
595
+ const hasPinnedPhase = subjectRect.height > elementRect.height;
596
+ const alias = rangeAliases[range];
597
+ const milestoneNames = ranges[alias ? alias(hasPinnedPhase) : range];
598
+ if (!milestoneNames) {
599
+ const supportedRanges = [...Object.keys(ranges), ...Object.keys(rangeAliases)];
600
+ throw new Error(`Unknown view timeline range '${range}'. ` + `Supported ranges: ${supportedRanges.join(', ')}.`);
601
+ }
602
+
603
+ // Without enough content next to it, a pinned element never reaches
604
+ // its pinned position and keeps moving with the page. Its own rect
605
+ // then is the subject covering the same range of the page.
606
+ const subject = hasPinnedPhase ? subjectRect : elementRect;
607
+ const [start, end] = orderEdges(milestoneNames.map(name => milestones[name]({
608
+ subjectHeight: subject.height,
609
+ elementTop: elementRect.top,
610
+ elementHeight: elementRect.height,
611
+ viewportHeight
612
+ })));
613
+ if (start === end) {
614
+ return subject.top <= start ? 1 : 0;
615
+ }
616
+ return clamp((start - subject.top) / (start - end));
617
+ }
618
+
619
+ // Milestones come out in reverse order for elements taller than the
620
+ // viewport: Such elements cover the viewport instead of being
621
+ // contained in it, so they stop being contained before they start
622
+ // being contained.
623
+ function orderEdges([start, end]) {
624
+ return start < end ? [end, start] : [start, end];
625
+ }
626
+ function clamp(value) {
627
+ return Math.min(Math.max(value, 0), 1);
628
+ }
629
+
630
+ const ContentElementViewTimelineContext = createContext();
631
+
632
+ // Lets components that pin content elements in the viewport for part
633
+ // of their scroll space pass a function returning the pinned element
634
+ // and the subject that keeps moving with the page. Pinned content
635
+ // elements would otherwise stop making progress along their view
636
+ // timeline for exactly the part of the page that the extra scroll
637
+ // space was added for.
638
+ //
639
+ // Passing a function instead of refs lets components resolve elements
640
+ // they do not render themselves: Sticky boxes in TwoColumn walk up the
641
+ // DOM to find their group.
642
+ const ViewTimelinePinContext = createContext();
643
+ function ViewTimelinePinProvider({
644
+ getPinnedElements,
645
+ children
646
+ }) {
647
+ return /*#__PURE__*/React.createElement(ViewTimelinePinContext.Provider, {
648
+ value: getPinnedElements,
649
+ children: children
650
+ });
651
+ }
652
+ function ContentElementViewTimelineProvider({
653
+ type,
654
+ children
655
+ }) {
656
+ const {
657
+ viewTimeline
658
+ } = api.contentElementTypes.getOptions(type);
659
+ if (viewTimeline) {
660
+ return /*#__PURE__*/React.createElement(ViewTimelineProvider, null, children);
661
+ } else {
662
+ return children;
663
+ }
664
+ }
665
+ function ViewTimelineProvider({
666
+ children
667
+ }) {
668
+ const getPinnedElements = useContext(ViewTimelinePinContext);
669
+ const ownElementRef = useRef();
670
+ const subscriptionsRef = useRef(new Set());
671
+
672
+ // Content element types can support view timelines without always
673
+ // observing scroll position. Only listen while there are
674
+ // subscriptions to prevent each of them from adding a handler.
675
+ const [hasSubscriptions, setHasSubscriptions] = useState(false);
676
+ const getElements = useCallback(() => getPinnedElements ? getPinnedElements() : {
677
+ subject: ownElementRef.current,
678
+ element: ownElementRef.current
679
+ }, [getPinnedElements]);
680
+ const viewTimeline = useMemo(() => ({
681
+ subscribe(range, callback) {
682
+ const subscription = {
683
+ range,
684
+ callback
685
+ };
686
+ subscriptionsRef.current.add(subscription);
687
+ setHasSubscriptions(true);
688
+ update(getElements(), [subscription]);
689
+ return () => {
690
+ subscriptionsRef.current.delete(subscription);
691
+ setHasSubscriptions(subscriptionsRef.current.size > 0);
692
+ };
693
+ }
694
+ }), [getElements]);
695
+ useEffect(() => {
696
+ if (!hasSubscriptions) {
697
+ return;
698
+ }
699
+ const subscriptions = subscriptionsRef.current;
700
+ let animationFrame;
701
+ function handle() {
702
+ if (animationFrame) {
703
+ return;
704
+ }
705
+ animationFrame = requestAnimationFrame(() => {
706
+ animationFrame = null;
707
+ update(getElements(), subscriptions);
708
+ });
709
+ }
710
+ window.addEventListener('scroll', handle);
711
+ window.addEventListener('resize', handle);
712
+ return () => {
713
+ cancelAnimationFrame(animationFrame);
714
+ window.removeEventListener('scroll', handle);
715
+ window.removeEventListener('resize', handle);
716
+ };
717
+ }, [getElements, hasSubscriptions]);
718
+ const content = /*#__PURE__*/React.createElement(ContentElementViewTimelineContext.Provider, {
719
+ value: viewTimeline
720
+ }, children);
721
+ if (getPinnedElements) {
722
+ return content;
723
+ }
724
+ return /*#__PURE__*/React.createElement("div", {
725
+ ref: ownElementRef
726
+ }, content);
727
+ }
728
+ function update({
729
+ subject,
730
+ element
731
+ }, subscriptions) {
732
+ const subjectRect = subject.getBoundingClientRect();
733
+ const elementRect = element === subject ? subjectRect : element.getBoundingClientRect();
734
+ const viewportHeight = window.innerHeight;
735
+ subscriptions.forEach(subscription => {
736
+ const progress = getViewTimelineProgress({
737
+ range: subscription.range,
738
+ subjectRect,
739
+ elementRect,
740
+ viewportHeight
741
+ });
742
+ if (progress !== subscription.lastProgress) {
743
+ subscription.lastProgress = progress;
744
+ subscription.callback(progress);
745
+ }
746
+ });
747
+ }
748
+
749
+ /**
750
+ * Invokes a callback with the progress of the content element along a
751
+ * range of its view timeline. Mirrors the concepts of CSS scroll
752
+ * driven animations: The content element acts as the subject of a
753
+ * view timeline of the page's scroll container. Requires the
754
+ * `viewTimeline` option to be set to true in the
755
+ * `frontend.contentElementTypes.register` call for the content
756
+ * element's type.
757
+ *
758
+ * Progress is passed to a callback instead of being returned to
759
+ * prevent rerendering the content element on every scroll frame.
760
+ *
761
+ * @param {Object} options
762
+ *
763
+ * @param {string} [options.range='cover'] -
764
+ * Which part of the content element's view timeline to measure:
765
+ *
766
+ * * `cover`: From the moment the content element starts entering
767
+ * the viewport until it has completely left it.
768
+ *
769
+ * * `contain`: While the content element is completely inside the
770
+ * viewport. For content elements taller than the viewport, while
771
+ * the content element completely covers the viewport.
772
+ *
773
+ * * `entry`: While the content element is entering the viewport.
774
+ *
775
+ * * `exit`: While the content element is leaving the viewport.
776
+ *
777
+ * * `center`: While the content element intersects the vertical
778
+ * center of the viewport, i.e. from its top edge passing the
779
+ * center until its bottom edge does.
780
+ *
781
+ * * `pinned`: While the content element stays pinned in the
782
+ * viewport, i.e. from the moment it reaches the position it is
783
+ * pinned at until it starts moving with the page again. Progress
784
+ * stays 1 for content elements that are not pinned at all.
785
+ *
786
+ * * `inFocus`: While the content element holds the reader's
787
+ * attention: `pinned` for content elements that are pinned in the
788
+ * viewport, `center` for all others.
789
+ *
790
+ * @param {Function} [options.onProgress] -
791
+ * Invoked with a number between 0 and 1 whenever progress along the
792
+ * range changes. Pass a falsy value to not observe scroll position
793
+ * at all.
794
+ *
795
+ * @example
796
+ *
797
+ * useContentElementViewTimelineProgress({
798
+ * range: 'cover',
799
+ * onProgress: progress => player.seekTo(progress)
800
+ * });
801
+ */
802
+ function useContentElementViewTimelineProgress({
803
+ range = 'cover',
804
+ onProgress
805
+ } = {}) {
806
+ const viewTimeline = useContext(ContentElementViewTimelineContext);
807
+ const onProgressRef = useRef();
808
+ onProgressRef.current = onProgress;
809
+ const enabled = !!onProgress;
810
+ useEffect(() => {
811
+ if (viewTimeline && enabled) {
812
+ return viewTimeline.subscribe(range, progress => onProgressRef.current(progress));
813
+ }
814
+ }, [viewTimeline, range, enabled]);
815
+ if (!viewTimeline) {
816
+ throw new Error('useContentElementViewTimelineProgress is only available in ' + 'content elements for which `viewTimeline: true` has ' + 'been passed to frontend.contentElementTypes.register');
817
+ }
818
+ }
819
+
820
+ const widths = {
821
+ xxs: -3,
822
+ xs: -2,
823
+ sm: -1,
824
+ md: 0,
825
+ lg: 1,
826
+ xl: 2,
827
+ full: 3
828
+ };
829
+ function widthName(width) {
830
+ return Object.keys(widths)[(width || 0) + 3];
831
+ }
832
+
833
+ const TrimDefaultMarginTopContext = createContext(false);
834
+ const TrimDefaultMarginTop = TrimDefaultMarginTopContext.Provider;
835
+ function useTrimDefaultMarginTop() {
836
+ return useContext(TrimDefaultMarginTopContext);
837
+ }
838
+
839
+ var styles$1 = {"wrapper":"ContentElementMargin-module_wrapper__20kIk","noTopMargin":"ContentElementMargin-module_noTopMargin__mB7t3"};
840
+
841
+ function ContentElementMargin({
842
+ width,
843
+ first,
844
+ defaultMarginTop,
845
+ top,
846
+ bottom,
847
+ previousBottom,
848
+ children
849
+ }) {
850
+ const trimDefaultMarginTop = useTrimDefaultMarginTop();
851
+ if (width === widths.full) {
852
+ return children;
853
+ }
854
+ return /*#__PURE__*/React.createElement("div", {
855
+ className: classNames(styles$1.wrapper, {
856
+ [styles$1.noTopMargin]: trimDefaultMarginTop && first && !top
857
+ }),
858
+ style: {
859
+ '--margin-top': scaleProperty(top) || defaultMarginTop,
860
+ '--margin-bottom': scaleProperty(bottom),
861
+ '--prev-margin-bottom': scaleProperty(previousBottom)
862
+ }
863
+ }, children);
864
+ }
865
+ function scaleProperty(value) {
866
+ return value && `var(--theme-content-element-margin-${value})`;
867
+ }
868
+
869
+ var styles$2 = {"missing":"ContentElement-module_missing__2_1j9"};
870
+
871
+ function ContentElementErrorBoundary({
872
+ typeName,
873
+ configuration,
874
+ children
875
+ }) {
876
+ const ErrorBoundary = api.contentElementErrorBoundary || DefaultErrorBoundary;
877
+ const fallback = () => /*#__PURE__*/React.createElement(DefaultFallback, {
878
+ type: typeName
879
+ });
880
+ return /*#__PURE__*/React.createElement(ErrorBoundary, {
881
+ typeName: typeName,
882
+ configuration: configuration,
883
+ fallback: fallback
884
+ }, children);
885
+ }
886
+ class DefaultErrorBoundary extends React.Component {
887
+ constructor(props) {
888
+ super(props);
889
+ this.state = {
890
+ hasError: false
891
+ };
892
+ }
893
+ static getDerivedStateFromError(error) {
894
+ return {
895
+ hasError: true
896
+ };
897
+ }
898
+ render() {
899
+ if (this.state.hasError) {
900
+ return this.props.fallback();
901
+ }
902
+ return this.props.children;
903
+ }
904
+ }
905
+ function DefaultFallback({
906
+ type
907
+ }) {
908
+ return /*#__PURE__*/React.createElement("div", {
909
+ className: styles$2.missing
910
+ }, "Error rendering element of type \"", type, "\"");
911
+ }
912
+
913
+ const ContentElement = React.memo(extensible('ContentElement', function ContentElement(props) {
914
+ const Component = api.contentElementTypes.getComponent(props.type);
915
+ const {
916
+ defaultMarginTop,
917
+ inlineComments
918
+ } = api.contentElementTypes.getOptions(props.type) || {};
919
+ if (Component) {
920
+ return /*#__PURE__*/React.createElement(ContentElementAttributesProvider, {
921
+ id: props.id,
922
+ permaId: props.permaId,
923
+ width: props.width,
924
+ position: props.position,
925
+ inlineComments: inlineComments
926
+ }, /*#__PURE__*/React.createElement(ContentElementLifecycleProvider, {
927
+ type: props.type,
928
+ override: props.lifecycleOverride
929
+ }, /*#__PURE__*/React.createElement(ContentElementMargin, {
930
+ width: props.width,
931
+ first: props.first,
932
+ defaultMarginTop: defaultMarginTop,
933
+ top: props.itemProps.marginTop,
934
+ bottom: props.marginBottom,
935
+ previousBottom: props.previousMarginBottom
936
+ }, /*#__PURE__*/React.createElement(ContentElementViewTimelineProvider, {
937
+ type: props.type
938
+ }, /*#__PURE__*/React.createElement(ContentElementErrorBoundary, {
939
+ typeName: props.type,
940
+ configuration: props.itemProps
941
+ }, /*#__PURE__*/React.createElement(Component, {
942
+ sectionProps: props.sectionProps,
943
+ customMargin: props.customMargin,
944
+ configuration: props.itemProps,
945
+ contentElementWidth: props.width,
946
+ contentElementId: props.id
947
+ }))))));
948
+ } else {
949
+ return /*#__PURE__*/React.createElement("div", {
950
+ className: styles$2.missing
951
+ }, "Element of unknown type \"", props.type, "\"");
952
+ }
953
+ }), arePropsEqual);
954
+ function arePropsEqual(prevProps, nextProps) {
955
+ return prevProps.id === nextProps.id && prevProps.permaId === nextProps.permaId && prevProps.type === nextProps.type && prevProps.first === nextProps.first && prevProps.position === nextProps.position && prevProps.width === nextProps.width && prevProps.itemProps === nextProps.itemProps && prevProps.marginBottom === nextProps.marginBottom && prevProps.previousMarginBottom === nextProps.previousMarginBottom && prevProps.customMargin === nextProps.customMargin && prevProps.sectionProps === nextProps.sectionProps && prevProps.lifecycleOverride === nextProps.lifecycleOverride;
956
+ }
957
+ ContentElement.defaultProps = {
958
+ itemProps: {}
959
+ };
960
+
961
+ var styles$3 = {"wrapper":"ContentElementScrollSpace-module_wrapper__2ZBwZ","inner":"ContentElementScrollSpace-module_inner__1FBgh"};
962
+
963
+ function ContentElementScrollSpace({
964
+ children
965
+ }) {
966
+ const ref = useRef();
967
+ const innerRef = useRef();
968
+ const getPinnedElements = useCallback(() => ({
969
+ subject: ref.current,
970
+ element: innerRef.current
971
+ }), []);
972
+ return /*#__PURE__*/React.createElement("div", {
973
+ className: styles$3.wrapper,
974
+ ref: ref
975
+ }, /*#__PURE__*/React.createElement(ViewTimelinePinProvider, {
976
+ getPinnedElements: getPinnedElements
977
+ }, /*#__PURE__*/React.createElement(Measure, {
978
+ bounds: true,
979
+ innerRef: innerRef
980
+ }, ({
981
+ measureRef,
982
+ contentRect
983
+ }) => /*#__PURE__*/React.createElement("div", {
984
+ ref: measureRef,
985
+ className: styles$3.inner,
986
+ style: {
987
+ '--height': contentRect.bounds.height / 2
988
+ }
989
+ }, children))));
990
+ }
991
+
992
+ function ContentElements(props) {
993
+ return /*#__PURE__*/React.createElement(React.Fragment, null, props.items.map((item, index) => props.children(item, renderScrollSpace(item, /*#__PURE__*/React.createElement(ContentElement, {
994
+ key: item.id,
995
+ id: item.id,
996
+ permaId: item.permaId,
997
+ type: item.type,
998
+ first: index === 0,
999
+ position: item.position,
1000
+ width: item.width,
1001
+ itemProps: item.props,
1002
+ marginBottom: item.marginBottom,
1003
+ previousMarginBottom: item.previousMarginBottom,
1004
+ customMargin: props.customMargin,
1005
+ sectionProps: props.sectionProps
1006
+ })), index)));
1007
+ }
1008
+ ContentElements.defaultProps = {
1009
+ children: (item, child) => child
1010
+ };
1011
+ function renderScrollSpace(item, children) {
1012
+ if (!item.standAlone) {
1013
+ return children;
1014
+ }
1015
+ return /*#__PURE__*/React.createElement(ContentElementScrollSpace, {
1016
+ key: item.id,
1017
+ children: children
1018
+ });
1019
+ }
1020
+
1021
+ function useMediaQuery(query, {
1022
+ active
1023
+ } = {
1024
+ active: true
1025
+ }) {
1026
+ const [doesMatch, setDoesMatch] = useState(false);
1027
+ useEffect(() => {
1028
+ if (!active) {
1029
+ return;
1030
+ }
1031
+ const onUpdateMatch = ({
1032
+ matches
1033
+ }) => {
1034
+ setDoesMatch(matches);
1035
+ };
1036
+ const matcher = window.matchMedia(query);
1037
+ matcher.addEventListener('change', onUpdateMatch);
1038
+ onUpdateMatch(matcher);
1039
+ return () => {
1040
+ matcher.removeEventListener('change', onUpdateMatch);
1041
+ };
1042
+ }, [query, setDoesMatch, active]);
1043
+ return active && doesMatch;
1044
+ }
1045
+
1046
+ var styles$4 = {"root":"TwoColumn-module_root__37EqL","group":"TwoColumn-module_group__3Hg2y","group-full":"TwoColumn-module_group-full__2OT4o","box":"TwoColumn-module_box__1Nils","inline":"TwoColumn-module_inline__1fPfM","width-lg":"TwoColumn-module_width-lg__2MD35","width-xl":"TwoColumn-module_width-xl__3Bxet","width-full":"TwoColumn-module_width-full__1QWYO","constrainContentWidth":"TwoColumn-module_constrainContentWidth__1cxa-","group-lg":"TwoColumn-module_group-lg__xgaPx","restrict-xxs":"TwoColumn-module_restrict-xxs__6il-H","restrict-xs":"TwoColumn-module_restrict-xs__AOezq","restrict-sm":"TwoColumn-module_restrict-sm__2rKty","align-left":"TwoColumn-module_align-left__QSe2G","align-right":"TwoColumn-module_align-right__3Dn4i","customMargin":"TwoColumn-module_customMargin__o0uxH","right":"TwoColumn-module_right__Fr52a","side":"TwoColumn-module_side__2xx0s","sticky":"TwoColumn-module_sticky__4LCDO TwoColumn-module_side__2xx0s"};
1047
+
1048
+ function TwoColumn(props) {
1049
+ const shouldInline = useShouldInlineSticky();
1050
+ return /*#__PURE__*/React.createElement("div", {
1051
+ className: classNames(styles$4.root, styles$4[props.align], {
1052
+ [styles$4.constrainContentWidth]: props.constrainContentWidth
1053
+ })
1054
+ }, /*#__PURE__*/React.createElement("div", {
1055
+ className: classNames(styles$4.group),
1056
+ key: props.align
1057
+ }, /*#__PURE__*/React.createElement("div", Object.assign({
1058
+ className: classNames(styles$4.box, styles$4.inline),
1059
+ ref: props.contentAreaRef
1060
+ }, TwoColumn.contentAreaProbeProps))), renderItems(props, shouldInline), renderPlaceholder(props.placeholder));
1061
+ }
1062
+ TwoColumn.defaultProps = {
1063
+ align: 'left'
1064
+ };
1065
+ function useShouldInlineSticky() {
1066
+ var _theme$options$proper;
1067
+ const theme = useTheme();
1068
+ const root = ((_theme$options$proper = theme.options.properties) === null || _theme$options$proper === void 0 ? void 0 : _theme$options$proper.root) || {};
1069
+ const shouldInline = {
1070
+ [widths.md]: useMediaQuery(`(max-width: ${root.twoColumnStickyBreakpoint || '950px'})`),
1071
+ [widths.lg]: useMediaQuery(`(max-width: ${root.twoColumnStickyLgBreakpoint || '1200px'})`),
1072
+ [widths.xl]: useMediaQuery(`(max-width: ${root.twoColumnStickyXlBreakpoint || '1400px'})`)
1073
+ };
1074
+ return function (width) {
1075
+ return width <= widths.md ? shouldInline[widths.md] : shouldInline[width];
1076
+ };
1077
+ }
1078
+
1079
+ // Used in tests to render markers around groups
1080
+ TwoColumn.GroupComponent = 'div';
1081
+
1082
+ // Used to set data-testids on probe element
1083
+ TwoColumn.contentAreaProbeProps = {};
1084
+ function renderItems(props, shouldInline) {
1085
+ const groups = groupItemsByPosition(props.items, shouldInline, props.isContentPadded);
1086
+ return groups.map((group, groupIndex) => /*#__PURE__*/React.createElement(TwoColumn.GroupComponent, {
1087
+ key: groupIndex,
1088
+ className: classNames(styles$4.group, styles$4[`group-${widthName(group.width)}`])
1089
+ }, group.boxes.map((box, boxIndex) => renderItemGroup(props, box, boxIndex))));
1090
+ }
1091
+ function renderItemGroup(props, box, key) {
1092
+ if (box.items.length) {
1093
+ return /*#__PURE__*/React.createElement(Box, {
1094
+ key: key,
1095
+ box: box
1096
+ }, props.children( /*#__PURE__*/React.createElement(ContentElements, {
1097
+ sectionProps: props.sectionProps,
1098
+ customMargin: box.customMargin,
1099
+ items: box.items
1100
+ }, (item, child) => restrictWidth(item.effectiveWidth, item.effectiveAlignment, child)), {
1101
+ position: box.position,
1102
+ width: box.width,
1103
+ customMargin: box.customMargin,
1104
+ lastMarginBottom: box.items[box.items.length - 1].marginBottom,
1105
+ openStart: box.openStart,
1106
+ openEnd: box.openEnd,
1107
+ atSectionStart: box.atSectionStart,
1108
+ atSectionEnd: box.atSectionEnd
1109
+ }));
1110
+ }
1111
+ }
1112
+ function Box({
1113
+ box,
1114
+ children
1115
+ }) {
1116
+ const ref = useRef();
1117
+ useStickyBoxHeight(ref, box.position === 'sticky');
1118
+ return /*#__PURE__*/React.createElement("div", {
1119
+ ref: ref,
1120
+ className: classNames(styles$4.box, styles$4[box.position], styles$4[`width-${widthName(box.width)}`], {
1121
+ [styles$4.customMargin]: box.customMargin
1122
+ })
1123
+ }, box.position === 'sticky' ? /*#__PURE__*/React.createElement(ViewTimelinePin, {
1124
+ boxRef: ref,
1125
+ children: children
1126
+ }) : children);
1127
+ }
1128
+ function useStickyBoxHeight(ref, sticky) {
1129
+ useEffect(() => {
1130
+ if (!sticky || typeof ResizeObserver === 'undefined') {
1131
+ return;
1132
+ }
1133
+ const box = ref.current;
1134
+ const observer = new ResizeObserver(entries => box.style.setProperty('--sticky-box-height', `${entries[entries.length - 1].contentRect.height}px`));
1135
+ observer.observe(box);
1136
+ return () => {
1137
+ observer.disconnect();
1138
+ box.style.removeProperty('--sticky-box-height');
1139
+ };
1140
+ }, [ref, sticky]);
1141
+ }
1142
+
1143
+ // Sticky boxes stay pinned while the rest of their group scrolls past.
1144
+ // The group therefore is the subject that drives view timelines of
1145
+ // content elements inside the box.
1146
+ function ViewTimelinePin({
1147
+ boxRef,
1148
+ children
1149
+ }) {
1150
+ const getPinnedElements = useCallback(() => ({
1151
+ subject: boxRef.current.closest(`.${styles$4.group}`),
1152
+ element: boxRef.current
1153
+ }), [boxRef]);
1154
+ return /*#__PURE__*/React.createElement(ViewTimelinePinProvider, {
1155
+ getPinnedElements: getPinnedElements,
1156
+ children: children
1157
+ });
1158
+ }
1159
+ function restrictWidth(width, alignment, children) {
1160
+ if (width >= 0) {
1161
+ return children;
1162
+ }
1163
+ return /*#__PURE__*/React.createElement("div", {
1164
+ className: classNames(styles$4[`restrict-${widthName(width)}`], styles$4[`align-${alignment}`])
1165
+ }, children);
1166
+ }
1167
+ function groupItemsByPosition(items, shouldInline, isContentPadded) {
1168
+ const groups = [];
1169
+ let currentGroup, currentBox;
1170
+ let firstInlineBox = null;
1171
+ let previousInlineBox = null;
1172
+ let previousInlineItem = null;
1173
+ let previousMarginBottom = null;
1174
+ items.reduce((previousPosition, item, index) => {
1175
+ var _item$props;
1176
+ let {
1177
+ customMargin: elementSupportsCustomMargin
1178
+ } = api.contentElementTypes.getOptions(item.type) || {};
1179
+ if (typeof elementSupportsCustomMargin === 'function') {
1180
+ elementSupportsCustomMargin = elementSupportsCustomMargin({
1181
+ configuration: item.props
1182
+ });
1183
+ }
1184
+ let width = item.width || 0;
1185
+ const position = onTheSide(item.position) && !shouldInline(width) ? item.position : 'inline';
1186
+ const customMargin = !!elementSupportsCustomMargin && width < widths.full;
1187
+ const alignment = item.position === 'inline' && width < 0 ? item.alignment : null;
1188
+ if (onTheSide(item.position) && position === 'inline' && width > widths.md) {
1189
+ width -= 1;
1190
+ }
1191
+ const boxWidth = position === 'inline' ? Math.max(width, widths.md) : width;
1192
+ if (!currentGroup || previousPosition !== position || onTheSide(position) && currentBox.customMargin !== customMargin || currentBox.width !== boxWidth) {
1193
+ currentBox = null;
1194
+ if (!(onTheSide(previousPosition) && position === 'inline' && width <= widths.md)) {
1195
+ currentGroup = {
1196
+ width: onTheSide(position) ? widths.md : boxWidth,
1197
+ boxes: []
1198
+ };
1199
+ groups.push(currentGroup);
1200
+ }
1201
+ }
1202
+ if (!currentBox || currentBox.customMargin !== customMargin) {
1203
+ currentBox = {
1204
+ customMargin,
1205
+ position,
1206
+ width: boxWidth,
1207
+ items: []
1208
+ };
1209
+ if (position === 'inline') {
1210
+ if (!continueInlineBoxes(previousInlineBox, currentBox)) {
1211
+ previousMarginBottom = null;
1212
+ }
1213
+ markFirstInlineBoxAsAtSectionStart(currentBox);
1214
+ }
1215
+ currentGroup.boxes.push(currentBox);
1216
+ }
1217
+ item = {
1218
+ ...item,
1219
+ effectiveWidth: width,
1220
+ effectiveAlignment: alignment,
1221
+ marginBottom: (_item$props = item.props) === null || _item$props === void 0 ? void 0 : _item$props.marginBottom
1222
+ };
1223
+ currentBox.items.push(item);
1224
+ if (position === 'inline') {
1225
+ assignPreviousMarginBottom(previousInlineItem, item);
1226
+ previousInlineItem = item;
1227
+ previousInlineBox = currentBox;
1228
+ }
1229
+ return position;
1230
+ }, null);
1231
+ if (currentBox) {
1232
+ currentBox.atSectionEnd = true;
1233
+ }
1234
+ return groups;
1235
+ function markFirstInlineBoxAsAtSectionStart(inlineBox) {
1236
+ if (!firstInlineBox) {
1237
+ inlineBox.atSectionStart = !isContentPadded;
1238
+ firstInlineBox = currentBox;
1239
+ }
1240
+ }
1241
+ function assignPreviousMarginBottom(previousInlineItem, item) {
1242
+ if (previousMarginBottom) {
1243
+ item.previousMarginBottom = previousMarginBottom;
1244
+ previousInlineItem.marginBottom = null;
1245
+ previousMarginBottom = null;
1246
+ }
1247
+ if (item.marginBottom) {
1248
+ previousMarginBottom = item.marginBottom;
1249
+ }
1250
+ }
1251
+ }
1252
+ function continueInlineBoxes(previousInlineBox, currentBox) {
1253
+ if (previousInlineBox && isContinuable(previousInlineBox) && isContinuable(currentBox)) {
1254
+ previousInlineBox.openEnd = true;
1255
+ currentBox.openStart = true;
1256
+ return true;
1257
+ }
1258
+ return false;
1259
+ }
1260
+ function isContinuable(box) {
1261
+ return box.width <= widths.md && !box.customMargin;
1262
+ }
1263
+ function onTheSide(position) {
1264
+ return position === 'side' || position === 'sticky';
1265
+ }
1266
+ function renderPlaceholder(placeholder) {
1267
+ if (!placeholder) {
1268
+ return null;
1269
+ }
1270
+ return /*#__PURE__*/React.createElement("div", {
1271
+ className: classNames(styles$4.group)
1272
+ }, /*#__PURE__*/React.createElement("div", {
1273
+ className: classNames(styles$4.box, styles$4.inline)
1274
+ }, placeholder));
1275
+ }
1276
+
1277
+ var styles$5 = {"outer":"Center-module_outer__3Rr0H","customMargin":"Center-module_customMargin__1es3t","outer-full":"Center-module_outer-full__3dknO","box":"Center-module_box__3pZbI","box-lg":"Center-module_box-lg__3t3-e","box-xl":"Center-module_box-xl__1zdnc","box-full":"Center-module_box-full__1YKgB","constrainContentWidth":"Center-module_constrainContentWidth__3BThN","outer-lg":"Center-module_outer-lg__fEKHC","clear":"Center-module_clear__jJEap","selfClear-left":"Center-module_selfClear-left__39q7E","selfClear-right":"Center-module_selfClear-right__1T_fm","selfClear-both":"Center-module_selfClear-both__2WDEC","inner-xxs":"Center-module_inner-xxs__1oroz","inner-xs":"Center-module_inner-xs__3FRT8","inner-sm":"Center-module_inner-sm__-oQ0E","align-left":"Center-module_align-left__1rYBX","align-right":"Center-module_align-right__1uglq","inner-left":"Center-module_inner-left__2z9Ea","inner-right":"Center-module_inner-right__KBkVt","sideBySide":"Center-module_sideBySide__-YsP0","inner-md":"Center-module_inner-md__3dLC3","inner-lg":"Center-module_inner-lg__2GQbs","inner-xl":"Center-module_inner-xl__3dOME"};
1278
+
1279
+ const floatedPositions = ['left', 'right'];
1280
+ function Center(props) {
1281
+ const groups = groupItems(props.items);
1282
+ return /*#__PURE__*/React.createElement("div", {
1283
+ className: classNames(styles$5.root, {
1284
+ [styles$5.constrainContentWidth]: props.constrainContentWidth
1285
+ })
1286
+ }, /*#__PURE__*/React.createElement("div", {
1287
+ ref: props.contentAreaRef
1288
+ }), groups.map((group, groupIndex) => {
1289
+ return /*#__PURE__*/React.createElement("div", {
1290
+ key: groupIndex,
1291
+ className: classNames(styles$5.outer, styles$5[`outer-${widthName(group.width)}`], {
1292
+ [styles$5.customMargin]: group.customMargin
1293
+ })
1294
+ }, /*#__PURE__*/React.createElement("div", {
1295
+ className: classNames(styles$5.box, styles$5[`box-${widthName(group.width)}`])
1296
+ }, props.children( /*#__PURE__*/React.createElement(ContentElements, {
1297
+ sectionProps: props.sectionProps,
1298
+ items: group.items,
1299
+ customMargin: group.customMargin
1300
+ }, (item, child, itemIndex) => {
1301
+ const alignment = getWidth(item) < 0 && item.position === 'inline' ? item.alignment : null;
1302
+ return /*#__PURE__*/React.createElement("div", {
1303
+ key: item.id,
1304
+ className: classNames(styles$5[`selfClear-${selfClear(group.items, itemIndex)}`])
1305
+ }, /*#__PURE__*/React.createElement("div", {
1306
+ className: classNames(styles$5[`inner-${item.position}`], styles$5[`inner-${widthName(item.width)}`], styles$5[`align-${alignment}`], {
1307
+ [styles$5.clear]: clearItem(group.items, itemIndex)
1308
+ }, {
1309
+ [styles$5[`sideBySide`]]: sideBySideFloat(group.items, itemIndex)
1310
+ })
1311
+ }, child));
1312
+ }), boxProps(groups, groupIndex, props.isContentPadded))));
1313
+ }), renderPlaceholder$1(props.placeholder));
1314
+ }
1315
+ function groupItems(items) {
1316
+ const groups = [];
1317
+ let currentGroup;
1318
+ items.forEach(item => {
1319
+ var _item$props;
1320
+ const width = isFloated(item) ? widths.md : groupWidth(item);
1321
+ const customMargin = hasCustomMargin(item);
1322
+ if (!currentGroup || currentGroup.width !== width || currentGroup.customMargin !== customMargin) {
1323
+ currentGroup = {
1324
+ items: [],
1325
+ position: 'inline',
1326
+ width,
1327
+ customMargin
1328
+ };
1329
+ groups.push(currentGroup);
1330
+ }
1331
+ currentGroup.items.push({
1332
+ ...item,
1333
+ marginBottom: (_item$props = item.props) === null || _item$props === void 0 ? void 0 : _item$props.marginBottom
1334
+ });
1335
+ });
1336
+ return groups;
1337
+ }
1338
+ function boxProps(groups, index, isContentPadded) {
1339
+ const group = groups[index];
1340
+ const lastItem = group.items[group.items.length - 1];
1341
+ return {
1342
+ position: group.position,
1343
+ width: group.width,
1344
+ customMargin: group.customMargin,
1345
+ lastMarginBottom: lastItem.marginBottom,
1346
+ atSectionStart: index === 0 && !isContentPadded,
1347
+ atSectionEnd: index === groups.length - 1
1348
+ };
1349
+ }
1350
+ function selfClear(items, index) {
1351
+ const item = items[index];
1352
+ const next = items[index + 1];
1353
+ if (supportsWrappingAroundFloats(item) || isFloated(item) && (!next || clearItem(items, index + 1))) {
1354
+ return 'both';
1355
+ } else if (isFloated(item)) {
1356
+ return item.position === 'left' ? 'right' : 'left';
1357
+ }
1358
+ return 'none';
1359
+ }
1360
+ function clearItem(items, index) {
1361
+ return supportsWrappingAroundFloats(items[index]) ? followsSideBySideElements(items, index) : !isFloatedFollowingOppositeFloated(items, index);
1362
+ }
1363
+ function followsSideBySideElements(items, index) {
1364
+ return index > 1 && (items[index - 1].position === 'left' && items[index - 2].position === 'right' || items[index - 1].position === 'right' && items[index - 2].position === 'left');
1365
+ }
1366
+ function sideBySideFloat(items, index) {
1367
+ return isFloatedFollowingOppositeFloated(items, index) || isFloatedFollowedByOppositeFloated(items, index);
1368
+ }
1369
+ function isFloatedFollowingOppositeFloated(items, index) {
1370
+ return index > 0 && isFloated(items[index]) && isFloated(items[index - 1]) && items[index].position !== items[index - 1].position;
1371
+ }
1372
+ function isFloatedFollowedByOppositeFloated(items, index) {
1373
+ return index < items.length - 1 && isFloated(items[index]) && isFloated(items[index + 1]) && items[index].position !== items[index + 1].position;
1374
+ }
1375
+ function isFloated(item) {
1376
+ return floatedPositions.includes(item.position);
1377
+ }
1378
+ function supportsWrappingAroundFloats(item) {
1379
+ const {
1380
+ supportsWrappingAroundFloats
1381
+ } = api.contentElementTypes.getOptions(item.type);
1382
+ return supportsWrappingAroundFloats;
1383
+ }
1384
+ function hasCustomMargin(item) {
1385
+ const position = item.position;
1386
+ let {
1387
+ customMargin: elementSupportsCustomMargin
1388
+ } = api.contentElementTypes.getOptions(item.type) || {};
1389
+ if (typeof elementSupportsCustomMargin === 'function') {
1390
+ elementSupportsCustomMargin = elementSupportsCustomMargin({
1391
+ configuration: item.props
1392
+ });
1393
+ }
1394
+ return !!(elementSupportsCustomMargin && position === 'inline' && getWidth(item) < widths.full);
1395
+ }
1396
+ function groupWidth(item) {
1397
+ return Math.max(getWidth(item), widths.md);
1398
+ }
1399
+ function getWidth(item) {
1400
+ return item.width || widths.md;
1401
+ }
1402
+ function renderPlaceholder$1(placeholder) {
1403
+ if (!placeholder) {
1404
+ return null;
1405
+ }
1406
+ return /*#__PURE__*/React.createElement("div", {
1407
+ className: classNames(styles$5.outer)
1408
+ }, /*#__PURE__*/React.createElement("div", {
1409
+ className: classNames(styles$5.item)
1410
+ }, placeholder));
1411
+ }
1412
+
1413
+ const Layout = React.memo(extensible('LayoutWithPlaceholder', LayoutWithoutInlineEditing), (prevProps, nextProps) => prevProps.sectionId === nextProps.sectionId && prevProps.items === nextProps.items && prevProps.appearance === nextProps.appearance && prevProps.contentAreaRef === nextProps.contentAreaRef && prevProps.sectionProps === nextProps.sectionProps && prevProps.isContentPadded === nextProps.isContentPadded && prevProps.constrainContentWidth === nextProps.constrainContentWidth);
1414
+ function LayoutWithoutInlineEditing(props) {
1415
+ if (props.sectionProps.layout === 'center' || props.sectionProps.layout === 'centerRagged') {
1416
+ return /*#__PURE__*/React.createElement(Center, props);
1417
+ } else if (props.sectionProps.layout === 'right') {
1418
+ return /*#__PURE__*/React.createElement(TwoColumn, Object.assign({
1419
+ align: "right"
1420
+ }, props));
1421
+ } else {
1422
+ return /*#__PURE__*/React.createElement(TwoColumn, props);
1423
+ }
1424
+ }
1425
+ Layout.defaultProps = {
1426
+ layout: 'left'
1427
+ };
1428
+
1429
+ function scrollToElement(element, {
1430
+ align,
1431
+ ifNeeded,
1432
+ behavior
1433
+ } = {}) {
1434
+ const rect = element.getBoundingClientRect();
1435
+ if (ifNeeded && isInViewport(align, rect)) {
1436
+ return;
1437
+ }
1438
+ window.scrollTo({
1439
+ top: rect.top + window.scrollY + getAlignOffset(align, rect),
1440
+ behavior: behavior || 'smooth'
1441
+ });
1442
+ }
1443
+ function getAlignOffset(align, rect) {
1444
+ if (align === 'start') {
1445
+ return 0;
1446
+ } else if (align === 'center') {
1447
+ return (rect.height - window.innerHeight) / 2;
1448
+ } else if (align === 'nearEnd') {
1449
+ return rect.height - window.innerHeight * 0.75;
1450
+ } else {
1451
+ return -window.innerHeight * 0.25;
1452
+ }
1453
+ }
1454
+ function isInViewport(align, rect) {
1455
+ if (align === 'nearEnd') {
1456
+ const bottom = rect.top + rect.height;
1457
+ return bottom > 0 && bottom <= window.innerHeight;
1458
+ } else {
1459
+ return rect.top >= 0 && rect.top < window.innerHeight;
1460
+ }
1461
+ }
1462
+
1463
+ const ScrollTargetEmitterContext = createContext();
1464
+ function ScrollTargetEmitterProvider({
1465
+ children
1466
+ }) {
1467
+ const emitter = useMemo(() => Object.assign({}, BackboneEvents), []);
1468
+ return /*#__PURE__*/React.createElement(ScrollTargetEmitterContext.Provider, {
1469
+ value: emitter
1470
+ }, children);
1471
+ }
1472
+ function useScrollToTarget() {
1473
+ const emitter = useContext(ScrollTargetEmitterContext);
1474
+ return useCallback(({
1475
+ id,
1476
+ align,
1477
+ ifNeeded,
1478
+ behavior
1479
+ }) => {
1480
+ emitter.trigger(id, {
1481
+ align,
1482
+ ifNeeded,
1483
+ behavior
1484
+ });
1485
+ }, [emitter]);
1486
+ }
1487
+ function useScrollTarget(id) {
1488
+ const ref = useRef();
1489
+ const emitter = useContext(ScrollTargetEmitterContext);
1490
+ useEffect(() => {
1491
+ const handler = options => {
1492
+ if (ref.current) {
1493
+ scrollToElement(ref.current, options);
1494
+ }
1495
+ };
1496
+ emitter.on(id, handler);
1497
+ return () => emitter.off(id, handler);
1498
+ }, [id, emitter]);
1499
+ return ref;
1500
+ }
1501
+
1502
+ const DarkBackgroundContext = createContext(true);
1503
+ function BackgroundColorProvider({
1504
+ dark,
1505
+ invert,
1506
+ children
1507
+ }) {
1508
+ const previousValue = useDarkBackground();
1509
+ return /*#__PURE__*/React.createElement(DarkBackgroundContext.Provider, {
1510
+ value: getValue({
1511
+ dark,
1512
+ invert,
1513
+ previousValue
1514
+ })
1515
+ }, children);
1516
+ }
1517
+ function getValue({
1518
+ dark,
1519
+ invert,
1520
+ previousValue
1521
+ }) {
1522
+ if (dark !== undefined) {
1523
+ return dark;
1524
+ } else if (invert === true) {
1525
+ return !previousValue;
1526
+ } else {
1527
+ return previousValue;
1528
+ }
1529
+ }
1530
+
1531
+ /**
1532
+ * Use to invert elements depending on whether they are rendered on a
1533
+ * dark or light background to ensure correct display in inverted
1534
+ * sections or in sections with card appearance.
1535
+ *
1536
+ * @return {boolean}
1537
+ */
1538
+ function useDarkBackground() {
1539
+ return useContext(DarkBackgroundContext);
1540
+ }
1541
+
1542
+ const ActiveExcursionContext = createContext({
1543
+ activeExcursion: undefined,
1544
+ activateExcursionOfSection: () => {},
1545
+ returnFromExcursion: () => {}
1546
+ });
1547
+ function ActiveExcursionProvider({
1548
+ children
1549
+ }) {
1550
+ const entryStructure = useEntryStructure();
1551
+ const scrollToTarget = useScrollToTarget();
1552
+ const [activeExcursionId, setActiveExcursionId] = useState();
1553
+ const [scrollTarget, setScrollTarget] = useState();
1554
+ const returnUrlRef = useRef(null);
1555
+ useEffect(() => {
1556
+ function handleHashChange(event) {
1557
+ const hash = window.__ACTIVE_EXCURSION__ ||
1558
+ // Used in Storybook
1559
+ window.location.hash.slice(1);
1560
+ const {
1561
+ excursion,
1562
+ sectionId
1563
+ } = findScrollTargetByHash(hash);
1564
+ if (excursion && (event === null || event === void 0 ? void 0 : event.oldURL) && !returnUrlRef.current) {
1565
+ returnUrlRef.current = event.oldURL;
1566
+ }
1567
+ setActiveExcursionId(prevExcursionId => {
1568
+ const excursionChanged = (excursion === null || excursion === void 0 ? void 0 : excursion.id) !== prevExcursionId;
1569
+ if (excursionChanged && sectionId) {
1570
+ setScrollTarget(sectionId);
1571
+ }
1572
+ return excursion === null || excursion === void 0 ? void 0 : excursion.id;
1573
+ });
1574
+ }
1575
+ function findScrollTargetByHash(hash) {
1576
+ if (hash.startsWith('section-')) {
1577
+ const permaId = parseInt(hash.replace('section-', ''), 10);
1578
+ for (const chapter of entryStructure.excursions) {
1579
+ const section = chapter.sections.find(s => s.permaId === permaId);
1580
+ if (section) {
1581
+ var _chapter$sections$;
1582
+ const isFirstSection = section.id === ((_chapter$sections$ = chapter.sections[0]) === null || _chapter$sections$ === void 0 ? void 0 : _chapter$sections$.id);
1583
+ return {
1584
+ excursion: chapter,
1585
+ sectionId: isFirstSection ? null : section.id
1586
+ };
1587
+ }
1588
+ }
1589
+ for (const chapter of entryStructure.main) {
1590
+ const section = chapter.sections.find(s => s.permaId === permaId);
1591
+ if (section) {
1592
+ return {
1593
+ excursion: null,
1594
+ sectionId: section.id
1595
+ };
1596
+ }
1597
+ }
1598
+ return {
1599
+ excursion: null,
1600
+ sectionId: null
1601
+ };
1602
+ }
1603
+ const excursion = entryStructure.excursions.find(chapter => chapter.chapterSlug === hash);
1604
+ if (excursion) {
1605
+ return {
1606
+ excursion,
1607
+ sectionId: null
1608
+ };
1609
+ }
1610
+ const mainChapter = entryStructure.main.find(chapter => chapter.chapterSlug === hash);
1611
+ if (mainChapter) {
1612
+ var _mainChapter$sections;
1613
+ return {
1614
+ excursion: null,
1615
+ sectionId: (_mainChapter$sections = mainChapter.sections[0]) === null || _mainChapter$sections === void 0 ? void 0 : _mainChapter$sections.id
1616
+ };
1617
+ }
1618
+ return {
1619
+ excursion: null,
1620
+ sectionId: null
1621
+ };
1622
+ }
1623
+ handleHashChange();
1624
+ window.addEventListener('hashchange', handleHashChange);
1625
+ return () => window.removeEventListener('hashchange', handleHashChange);
1626
+ }, [entryStructure]);
1627
+ useEffect(() => {
1628
+ if (!scrollTarget) {
1629
+ return;
1630
+ }
1631
+ setTimeout(() => {
1632
+ scrollToTarget({
1633
+ id: scrollTarget
1634
+ });
1635
+ }, 500);
1636
+ setScrollTarget(null);
1637
+ }, [scrollTarget, scrollToTarget]);
1638
+ const activateExcursionOfSection = useCallback(({
1639
+ id
1640
+ }) => {
1641
+ const excursion = entryStructure.excursions.find(chapter => chapter.sections.find(section => section.id === id));
1642
+ if (excursion) {
1643
+ returnUrlRef.current = returnUrlRef.current || window.location.href;
1644
+ window.history.replaceState(null, null, '#' + excursion.chapterSlug);
1645
+ }
1646
+ setActiveExcursionId(excursion === null || excursion === void 0 ? void 0 : excursion.id);
1647
+ }, [entryStructure]);
1648
+ const returnFromExcursion = useCallback(() => {
1649
+ setActiveExcursionId(undefined);
1650
+ if (returnUrlRef.current) {
1651
+ window.history.replaceState(null, null, returnUrlRef.current);
1652
+ returnUrlRef.current = null;
1653
+ }
1654
+ }, []);
1655
+ const activeExcursion = useMemo(() => {
1656
+ return entryStructure.excursions.find(excursion => excursion.id === activeExcursionId);
1657
+ }, [entryStructure, activeExcursionId]);
1658
+ const value = useMemo(() => ({
1659
+ activeExcursion,
1660
+ activateExcursionOfSection,
1661
+ returnFromExcursion
1662
+ }), [activeExcursion, activateExcursionOfSection, returnFromExcursion]);
1663
+ return /*#__PURE__*/React.createElement(ActiveExcursionContext.Provider, {
1664
+ value: value
1665
+ }, children);
1666
+ }
1667
+ function useActiveExcursion() {
1668
+ return useContext(ActiveExcursionContext);
1669
+ }
1670
+
1671
+ function camelize(snakeCase) {
1672
+ return snakeCase.replace(/[_-][a-z]/g, function (match) {
1673
+ return match[1].toUpperCase();
1674
+ });
1675
+ }
1676
+
1677
+ /**
1678
+ * Resolve a palette color to a CSS custom property.
1679
+ *
1680
+ * @example
1681
+ * <div style={{backgroundColor: paletteColor(configuration.backgroundColor)}}>
1682
+ */
1683
+ function paletteColor(value) {
1684
+ if (!value) {
1685
+ return undefined;
1686
+ }
1687
+ if (value[0] === '#') {
1688
+ return value;
1689
+ }
1690
+ return `var(--theme-palette-color-${value})`;
1691
+ }
1692
+
1693
+ var textStyles = {"text-2xs":"16px","text-xs":"18px","text-s":"20px","text-base":"22px","text-md":"33px","text-l":"40px","text-2l":"50px","text-xl":"66px","text-2xl":"88px","text-3xl":"110px","text-4xl":"200px","text-5xl":"350px","heading-lg":"Text-module_heading-lg__FKxzu typography-headingLg typography-heading","heading-md":"Text-module_heading-md__1q5Ss typography-headingMd typography-heading","heading-sm":"Text-module_heading-sm__2awaz typography-headingSm typography-heading","heading-xs":"Text-module_heading-xs__21nHy typography-headingXs typography-heading","headingTagline-lg":"Text-module_headingTagline-lg__1O2TQ typography-headingTaglineLg typography-headingTagline","headingTagline-md":"Text-module_headingTagline-md__2hrVS typography-headingTaglineMd typography-headingTagline","headingTagline-sm":"Text-module_headingTagline-sm__1Fw2J typography-headingTaglineSm typography-headingTagline","headingSubtitle-lg":"Text-module_headingSubtitle-lg__15kj0 typography-headingSubtitleLg typography-headingSubtitle","headingSubtitle-md":"Text-module_headingSubtitle-md__2_qtz typography-headingSubtitleMd typography-headingSubtitle","headingSubtitle-sm":"Text-module_headingSubtitle-sm__MOc_6 typography-headingSubtitleSm typography-headingSubtitle","body":"Text-module_body__4oWD- typography-body","caption":"Text-module_caption__3_6Au typography-caption","question":"Text-module_question__ByVAq typography-question","questionAnswer":"Text-module_questionAnswer__2Hdmc typography-questionAnswer","quoteText-lg":"Text-module_quoteText-lg__3ZnZi typography-quoteText typography-quoteTextLg","quoteText-md":"Text-module_quoteText-md__2eooO typography-quoteText typography-quoteTextMd","quoteText-sm":"Text-module_quoteText-sm__5nKex typography-quoteText typography-quoteTextSm","quoteText-xs":"Text-module_quoteText-xs__2p5on typography-quoteText typography-quoteTextXs","quoteAttribution":"Text-module_quoteAttribution__VBqLw typography-quoteAttribution","quoteAttribution-lg":"Text-module_quoteAttribution-lg__23pl- Text-module_quoteAttribution__VBqLw typography-quoteAttribution typography-quoteAttributionLg","quoteAttribution-md":"Text-module_quoteAttribution-md__2BnBN Text-module_quoteAttribution__VBqLw typography-quoteAttribution typography-quoteAttributionMd","quoteAttribution-sm":"Text-module_quoteAttribution-sm__17vbI Text-module_quoteAttribution__VBqLw typography-quoteAttribution typography-quoteAttributionSm","quoteAttribution-xs":"Text-module_quoteAttribution-xs__3v3BW Text-module_quoteAttribution__VBqLw typography-quoteAttribution typography-quoteAttributionXs","counterNumber":"Text-module_counterNumber__DkyhC typography-counterNumber","counterUnit":"Text-module_counterUnit__1N5v7 typography-counterUnit","counterDescription":"Text-module_counterDescription__34NjQ typography-counterDescription","hotspotsTooltipTitle":"Text-module_hotspotsTooltipTitle__2KROf typography-hotspotTooltipTitle","hotspotsTooltipDescription":"Text-module_hotspotsTooltipDescription__2l9v5 typography-hotspotTooltipDescription","hotspotsTooltipLink":"Text-module_hotspotsTooltipLink__2F2aj typography-hotspotTooltipLink typography-linkButton","teaserTitle-lg":"Text-module_teaserTitle-lg__K3MFG typography-externalLinkTitle typography-externalLinkTitleLg","teaserTitle-md":"Text-module_teaserTitle-md__tBZIn typography-externalLinkTitle typography-externalLinkTitleMd","teaserTitle-sm":"Text-module_teaserTitle-sm__ZXQCr typography-externalLinkTitle typography-externalLinkTitleSm","teaserTagline-lg":"Text-module_teaserTagline-lg__8JW7Z typography-externalLinkTagline typography-externalLinkTaglineLg","teaserTagline-md":"Text-module_teaserTagline-md__45HLL typography-externalLinkTagline typography-externalLinkTaglineMd","teaserTagline-sm":"Text-module_teaserTagline-sm__1vzOr typography-externalLinkTagline typography-externalLinkTaglineSm","teaserDescription-lg":"Text-module_teaserDescription-lg__1ysIZ typography-externalLinkDescription","teaserDescription-md":"Text-module_teaserDescription-md__1JtRs typography-externalLinkDescription","teaserDescription-sm":"Text-module_teaserDescription-sm__2VRao typography-externalLinkDescription","teaserLink":"Text-module_teaserLink__XEWHU typography-teaserLink typography-linkButton","infoTableLabel":"Text-module_infoTableLabel__w9kbv typography-infoTableLabel","infoTableValue":"Text-module_infoTableValue__2elOY typography-infoTableValue"};
1694
+
1695
+ /**
1696
+ * Render some text using the default typography scale.
1697
+ *
1698
+ * @param {Object} props
1699
+ * @param {string} props.scaleCategory -
1700
+ * One of the styles `'heading-lg'`, `'heading-md'`, `'heading-sm'`,`'heading-xs'`,
1701
+ * `'headingTagline-lg'`, `'headingTagline-md'`, `'headingTagline-sm'`,
1702
+ * `'headingSubtitle-lg'`, `'headingSubtitle-md'`, `'headingSubtitle-sm'`,
1703
+ * `'body'`, `'caption'`, `'question'`, `'questionAnswer'`,
1704
+ * `'quoteText-lg'`, `'quoteText-md'`, `'quoteText-sm'`, `'quoteText-xs'`, `'quoteAttribution'`,
1705
+ * `'counterNumber'`, `'counterUnit'`, `'counterDescription'`,
1706
+ * `'infoTableLabel'`, `'infoTableValue`'.
1707
+ * `'hotspotsTooltipTitle'`, `'hotspotsTooltipDescription`', `'hotspotsTooltipLink`',
1708
+ * `'teaserTitle-lg'`, `'teaserTitle-md'`, `'teaserTitle-sm'`,
1709
+ * `'teaserTagline-lg'`, `'teaserTagline-md'`, `'teaserTagline-sm'`,
1710
+ * `'teaserDescription-lg'`, `'teaserDescription-md'`, `'teaserDescription-sm'`,
1711
+ * `'teaserLink'`.
1712
+ * @param {string} [props.typographyVariant] - Suffix for variant class name.
1713
+ * @param {string} [props.typographySize] - Suffix for size class name.
1714
+ * @param {string} [props.inline] - Render a span instread of a div.
1715
+ * @param {string} props.children - Nodes to render with specified typography.
1716
+ */
1717
+ function Text({
1718
+ inline,
1719
+ scaleCategory,
1720
+ typographyVariant,
1721
+ typographySize,
1722
+ children
1723
+ }) {
1724
+ const variantClassName = typographyVariant && `typography-${scaleCategory.split('-')[0]}-${typographyVariant}`;
1725
+ const sizeClassName = typographySize && `typography-${scaleCategory}-${typographySize}`;
1726
+ return React.createElement(inline ? 'span' : 'div', {
1727
+ className: classNames(textStyles[scaleCategory], variantClassName, sizeClassName)
1728
+ }, children);
1729
+ }
1730
+
1731
+ function Link({
1732
+ attributes,
1733
+ children,
1734
+ href,
1735
+ openInNewTab
1736
+ }) {
1737
+ const embedOriginUrl = useEmbedOriginUrl();
1738
+ if (href === null || href === void 0 ? void 0 : href.chapter) {
1739
+ return /*#__PURE__*/React.createElement(ChapterLink, {
1740
+ attributes: attributes,
1741
+ chapterPermaId: href.chapter
1742
+ }, children);
1743
+ } else if (href === null || href === void 0 ? void 0 : href.section) {
1744
+ return /*#__PURE__*/React.createElement("a", Object.assign({}, attributes, {
1745
+ href: `#section-${href.section}`
1746
+ }), children);
1747
+ }
1748
+ if (href === null || href === void 0 ? void 0 : href.file) {
1749
+ return /*#__PURE__*/React.createElement(FileLink, {
1750
+ attributes: attributes,
1751
+ fileOptions: href.file
1752
+ }, children);
1753
+ } else {
1754
+ const targetAttributes = getTargetAttributes({
1755
+ href,
1756
+ openInNewTab,
1757
+ embedOriginUrl
1758
+ });
1759
+ return /*#__PURE__*/React.createElement("a", Object.assign({}, attributes, targetAttributes, {
1760
+ href: href
1761
+ }), children);
1762
+ }
1763
+ }
1764
+ function ChapterLink({
1765
+ attributes,
1766
+ children,
1767
+ chapterPermaId
1768
+ }) {
1769
+ const chapter = useChapter({
1770
+ permaId: chapterPermaId
1771
+ });
1772
+ return /*#__PURE__*/React.createElement("a", Object.assign({}, attributes, {
1773
+ href: `#${(chapter === null || chapter === void 0 ? void 0 : chapter.chapterSlug) || ''}`
1774
+ }), children);
1775
+ }
1776
+ function FileLink({
1777
+ attributes,
1778
+ children,
1779
+ fileOptions
1780
+ }) {
1781
+ const file = useDownloadableFile(fileOptions);
1782
+ return /*#__PURE__*/React.createElement("a", Object.assign({}, attributes, {
1783
+ target: "_blank",
1784
+ rel: "noopener noreferrer",
1785
+ href: file === null || file === void 0 ? void 0 : file.urls.download
1786
+ }), children);
1787
+ }
1788
+ function getTargetAttributes({
1789
+ href,
1790
+ openInNewTab,
1791
+ embedOriginUrl
1792
+ }) {
1793
+ if (openInNewTab) {
1794
+ return {
1795
+ target: '_blank',
1796
+ rel: 'noopener noreferrer'
1797
+ };
1798
+ }
1799
+ if (embedOriginUrl && typeof href === 'string' && !href.startsWith('#') && !href.startsWith(embedOriginUrl)) {
1800
+ return {
1801
+ target: '_top'
1802
+ };
1803
+ }
1804
+ return {};
1805
+ }
1806
+
1807
+ var textStyles$1 = {"darkContentTextColor":"var(--theme-dark-content-text-color, #222)","lightContentTextColor":"var(--theme-light-content-text-color, #fff)","contentLinkColor":"var(--content-link-color)","root":"EditableText-module_root__2v1tU","justify":"EditableText-module_justify__1pNdv","light":"EditableText-module_light__2c29h","dark":"EditableText-module_dark__2ym90","link":"EditableText-module_link__3vDbl typography-contentLink","bold":"EditableText-module_bold__tGw26","sub":"EditableText-module_sub__pNBwN","sup":"EditableText-module_sup__RJJlq"};
1808
+
1809
+ const defaultValue = [{
1810
+ type: 'paragraph',
1811
+ children: [{
1812
+ text: ''
1813
+ }]
1814
+ }];
1815
+ function PlainEditableText({
1816
+ value,
1817
+ className,
1818
+ scaleCategory = 'body',
1819
+ typographyVariant,
1820
+ typographySize
1821
+ }) {
1822
+ return /*#__PURE__*/React.createElement("div", {
1823
+ className: classNames(textStyles$1.root, className)
1824
+ }, /*#__PURE__*/React.createElement(Text, {
1825
+ scaleCategory: scaleCategory,
1826
+ typographyVariant: typographyVariant,
1827
+ typographySize: typographySize
1828
+ }, render(value || defaultValue)));
1829
+ }
1830
+ const EditableText = extensible('EditableText', PlainEditableText);
1831
+ function render(children) {
1832
+ return children.map((element, index) => {
1833
+ if (element.type && element.children) {
1834
+ return renderElement({
1835
+ attributes: {
1836
+ key: index
1837
+ },
1838
+ element,
1839
+ children: render(element.children)
1840
+ });
1841
+ } else {
1842
+ return renderLeaf({
1843
+ attributes: {
1844
+ key: index
1845
+ },
1846
+ leaf: element,
1847
+ children: children.length === 1 && element.text.trim() === '' ? '\uFEFF' : element.text
1848
+ });
1849
+ }
1850
+ });
1851
+ }
1852
+ function renderElement({
1853
+ attributes,
1854
+ children,
1855
+ element
1856
+ }) {
1857
+ const variantClassName = element.variant && ['typography-textBlock', camelize(element.type), element.variant].join('-');
1858
+ const sizeClassName = element.size && ['typography-textBlock', camelize(element.type), element.size].join('-');
1859
+ const className = classNames(variantClassName, sizeClassName, {
1860
+ [textStyles$1.justify]: element.textAlign === 'justify'
1861
+ });
1862
+ const inlineStyles = {
1863
+ ...(element.color && {
1864
+ color: paletteColor(element.color)
1865
+ })
1866
+ };
1867
+ switch (element.type) {
1868
+ case 'block-quote':
1869
+ return /*#__PURE__*/React.createElement("blockquote", Object.assign({}, attributes, {
1870
+ className: className,
1871
+ style: inlineStyles
1872
+ }), children);
1873
+ case 'bulleted-list':
1874
+ return /*#__PURE__*/React.createElement("ul", Object.assign({}, attributes, {
1875
+ className: className,
1876
+ style: inlineStyles
1877
+ }), children);
1878
+ case 'numbered-list':
1879
+ return /*#__PURE__*/React.createElement("ol", Object.assign({}, attributes, {
1880
+ className: className,
1881
+ style: inlineStyles
1882
+ }), children);
1883
+ case 'list-item':
1884
+ return /*#__PURE__*/React.createElement("li", attributes, children);
1885
+ case 'heading':
1886
+ const {
1887
+ key,
1888
+ ...otherAttributes
1889
+ } = attributes;
1890
+ return /*#__PURE__*/React.createElement(Heading, {
1891
+ key: key,
1892
+ attributes: otherAttributes,
1893
+ className: className,
1894
+ inlineStyles: inlineStyles
1895
+ }, children);
1896
+ case 'link':
1897
+ return renderLink({
1898
+ attributes,
1899
+ children,
1900
+ element
1901
+ });
1902
+ default:
1903
+ return /*#__PURE__*/React.createElement("p", Object.assign({}, attributes, {
1904
+ className: className,
1905
+ style: inlineStyles
1906
+ }), children);
1907
+ }
1908
+ }
1909
+ function Heading({
1910
+ attributes,
1911
+ className,
1912
+ inlineStyles,
1913
+ children
1914
+ }) {
1915
+ const darkBackground = useDarkBackground();
1916
+ return /*#__PURE__*/React.createElement("h2", Object.assign({}, attributes, {
1917
+ className: classNames(className, darkBackground ? textStyles$1.light : textStyles$1.dark, 'scope-headings', textStyles['heading-xs']),
1918
+ style: inlineStyles
1919
+ }), children);
1920
+ }
1921
+ function renderLink({
1922
+ attributes,
1923
+ children,
1924
+ element
1925
+ }) {
1926
+ const {
1927
+ key,
1928
+ ...otherAttributes
1929
+ } = attributes;
1930
+ return /*#__PURE__*/React.createElement(Link, {
1931
+ key: key,
1932
+ attributes: {
1933
+ ...otherAttributes,
1934
+ className: textStyles$1.link
1935
+ },
1936
+ href: element.href,
1937
+ openInNewTab: element.openInNewTab,
1938
+ children: children
1939
+ });
1940
+ }
1941
+ function renderLeaf({
1942
+ attributes,
1943
+ children,
1944
+ leaf
1945
+ }) {
1946
+ if (leaf.bold) {
1947
+ children = /*#__PURE__*/React.createElement("strong", {
1948
+ className: textStyles$1.bold
1949
+ }, children);
1950
+ }
1951
+ if (leaf.italic) {
1952
+ children = /*#__PURE__*/React.createElement("em", null, children);
1953
+ }
1954
+ if (leaf.underline) {
1955
+ children = /*#__PURE__*/React.createElement("u", null, children);
1956
+ }
1957
+ if (leaf.strikethrough) {
1958
+ children = /*#__PURE__*/React.createElement("s", null, children);
1959
+ }
1960
+ if (leaf.sub) {
1961
+ children = /*#__PURE__*/React.createElement("sub", {
1962
+ className: textStyles$1.sub
1963
+ }, children);
1964
+ }
1965
+ if (leaf.sup) {
1966
+ children = /*#__PURE__*/React.createElement("sup", {
1967
+ className: textStyles$1.sup
1968
+ }, children);
1969
+ }
1970
+ return /*#__PURE__*/React.createElement("span", attributes, children);
1971
+ }
1972
+
1973
+ const FloatingPortalRootContext = createContext();
1974
+ function useFloatingPortalRoot() {
1975
+ return useContext(FloatingPortalRootContext);
1976
+ }
1977
+ const FloatingPortalRootProvider = FloatingPortalRootContext.Provider;
1978
+
1979
+ export { useScrollTarget as A, BackgroundColorProvider as B, ContentElementAttributesProvider as C, Layout as D, EditableText as E, FloatingPortalRootProvider as F, ExtensionsProvider as G, ActiveExcursionProvider as H, StaticPreview as I, camelize as J, renderLink as K, Link as L, MainStorylineCoverageProvider as M, renderLeaf as N, OnScreenObserverRootProvider as O, scrollToElement as P, LayoutWithoutInlineEditing as Q, renderElement as R, ScrollTargetEmitterProvider as S, Text as T, PlainEditableText as U, textStyles$1 as V, useActiveExcursion as a, MainStorylineActivity as b, useMainStorylineCoverage as c, api as d, extensible as e, useDarkBackground as f, useContentElementAttributes as g, useContentElementLifecycle as h, ContentElementLifecycleContext as i, useContentElementViewTimelineProgress as j, ContentElementViewTimelineContext as k, getViewTimelineProgress as l, useIsStaticPreview as m, useStorylineActivity as n, useFloatingPortalRoot as o, provideExtensions as p, paletteColor as q, widthName as r, useOnScreen as s, createScrollPositionLifecycleProvider as t, useScrollToTarget as u, createScrollPositionLifecycleHook as v, widths as w, useMediaQuery as x, TrimDefaultMarginTop as y, ContentElement as z };