pageflow 17.1.0 → 17.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +319 -345
- data/README.md +3 -4
- data/admins/pageflow/accounts.rb +6 -6
- data/admins/pageflow/entry.rb +10 -8
- data/admins/pageflow/membership.rb +11 -11
- data/admins/pageflow/translations.rb +5 -5
- data/app/assets/images/pageflow/admin/icons/comment.svg +1 -0
- data/app/assets/javascripts/pageflow/dist/ui.js +1350 -630
- data/app/assets/javascripts/pageflow/editor/vendor.js +0 -1
- data/app/assets/javascripts/pageflow/ui.js +0 -1
- data/app/assets/stylesheets/pageflow/admin/entry_comments_indicator.scss +45 -0
- data/app/assets/stylesheets/pageflow/admin/permalink_input.scss +8 -1
- data/app/assets/stylesheets/pageflow/admin.scss +1 -0
- data/app/assets/stylesheets/pageflow/animations/spin.scss +9 -0
- data/app/assets/stylesheets/pageflow/animations.scss +1 -0
- data/app/assets/stylesheets/pageflow/editor/background_positioning.scss +21 -5
- data/app/assets/stylesheets/pageflow/editor/base.scss +9 -1
- data/app/assets/stylesheets/pageflow/editor/drop_down_button.scss +13 -1
- data/app/assets/stylesheets/pageflow/editor/edit_configuration_view.scss +5 -0
- data/app/assets/stylesheets/pageflow/editor/file_folders.scss +127 -0
- data/app/assets/stylesheets/pageflow/editor/file_meta_data.scss +0 -1
- data/app/assets/stylesheets/pageflow/editor/file_meta_data_overlay.scss +45 -0
- data/app/assets/stylesheets/pageflow/editor/file_preview.scss +138 -0
- data/app/assets/stylesheets/pageflow/editor/file_references.scss +68 -0
- data/app/assets/stylesheets/pageflow/editor/file_settings_dialog.scss +1 -1
- data/app/assets/stylesheets/pageflow/editor/file_stage_icons.scss +17 -0
- data/app/assets/stylesheets/pageflow/editor/file_stages.scss +25 -32
- data/app/assets/stylesheets/pageflow/editor/file_thumbnails.scss +31 -48
- data/app/assets/stylesheets/pageflow/editor/file_type_pills.scss +73 -0
- data/app/assets/stylesheets/pageflow/editor/files.scss +230 -50
- data/app/assets/stylesheets/pageflow/editor/filtered_files.scss +142 -14
- data/app/assets/stylesheets/pageflow/editor/folder_breadcrumb.scss +40 -0
- data/app/assets/stylesheets/pageflow/editor/info_box.scss +12 -0
- data/app/assets/stylesheets/pageflow/editor/inputs/edit_defaults_button.scss +15 -0
- data/app/assets/stylesheets/pageflow/editor/inputs.scss +1 -0
- data/app/assets/stylesheets/pageflow/editor/list.scss +88 -38
- data/app/assets/stylesheets/pageflow/editor/manage_files.scss +9 -0
- data/app/assets/stylesheets/pageflow/editor/menu.scss +14 -0
- data/app/assets/stylesheets/pageflow/editor/move_to_folder_dialog.scss +83 -0
- data/app/assets/stylesheets/pageflow/mixins/pageflow.scss +9 -5
- data/app/assets/stylesheets/pageflow/ui/color_picker.scss +230 -0
- data/app/assets/stylesheets/pageflow/ui/forms.scss +34 -4
- data/app/assets/stylesheets/pageflow/ui/functions.scss +12 -2
- data/app/assets/stylesheets/pageflow/ui/input/color_input.scss +9 -16
- data/app/assets/stylesheets/pageflow/ui/input/text_area_input.scss +15 -0
- data/app/assets/stylesheets/pageflow/ui/overrides.scss +8 -0
- data/app/assets/stylesheets/pageflow/ui/properties.scss +29 -0
- data/app/assets/stylesheets/pageflow/ui/tabs_view.scss +8 -3
- data/app/assets/stylesheets/pageflow/ui.scss +4 -2
- data/app/controllers/pageflow/editor/entries_controller.rb +1 -0
- data/app/controllers/pageflow/editor/file_folders_controller.rb +80 -0
- data/app/controllers/pageflow/editor/file_import_controller.rb +1 -0
- data/app/controllers/pageflow/editor/files_controller.rb +15 -6
- data/app/controllers/pageflow/editor/oembeds_controller.rb +21 -0
- data/app/controllers/pageflow/entries_controller.rb +17 -13
- data/app/controllers/pageflow/review/comment_thread_reads_controller.rb +30 -0
- data/app/controllers/pageflow/review/comment_threads_controller.rb +60 -0
- data/app/controllers/pageflow/review/comments_controller.rb +53 -0
- data/app/controllers/pageflow/revisions_controller.rb +4 -1
- data/app/helpers/pageflow/admin/entries_helper.rb +40 -0
- data/app/helpers/pageflow/admin/widgets_helper.rb +2 -2
- data/app/helpers/pageflow/common_entry_seed_helper.rb +5 -3
- data/app/helpers/pageflow/editor/config_helper.rb +3 -1
- data/app/helpers/pageflow/entries_helper.rb +24 -42
- data/app/helpers/pageflow/entry_json_seed_helper.rb +4 -4
- data/app/helpers/pageflow/public_i18n_helper.rb +9 -8
- data/app/helpers/pageflow/structured_data_helper.rb +4 -1
- data/app/helpers/pageflow/themes_helper.rb +1 -3
- data/app/helpers/pageflow/widgets_helper.rb +2 -1
- data/app/inputs/pageflow_permalink_input.rb +6 -2
- data/app/models/concerns/pageflow/auto_generated_perma_id.rb +11 -4
- data/app/models/concerns/pageflow/reusable_file.rb +1 -1
- data/app/models/concerns/pageflow/serialization_blacklist.rb +2 -4
- data/app/models/concerns/pageflow/translated_attributes.rb +101 -0
- data/app/models/concerns/pageflow/uploadable_file.rb +1 -1
- data/app/models/pageflow/comment.rb +30 -0
- data/app/models/pageflow/comment_thread.rb +65 -0
- data/app/models/pageflow/comment_thread_read.rb +22 -0
- data/app/models/pageflow/customized_theme.rb +3 -2
- data/app/models/pageflow/draft_entry.rb +20 -8
- data/app/models/pageflow/entry.rb +2 -0
- data/app/models/pageflow/entry_at_revision.rb +9 -1
- data/app/models/pageflow/entry_comment_summary.rb +114 -0
- data/app/models/pageflow/file_folder.rb +74 -0
- data/app/models/pageflow/file_reuse.rb +3 -2
- data/app/models/pageflow/file_usage.rb +8 -0
- data/app/models/pageflow/image_file.rb +24 -20
- data/app/models/pageflow/image_file_url_templates.rb +3 -3
- data/app/models/pageflow/legal_info.rb +38 -0
- data/app/models/pageflow/nested_revision_component_copy.rb +1 -1
- data/app/models/pageflow/permalink.rb +1 -0
- data/app/models/pageflow/revision.rb +6 -2
- data/app/models/pageflow/site.rb +10 -0
- data/app/models/pageflow/used_file.rb +8 -2
- data/app/models/pageflow/widget.rb +2 -2
- data/app/views/pageflow/audio_files/_audio_file.json.jbuilder +0 -1
- data/app/views/pageflow/editor/config/_seeds.json.jbuilder +4 -2
- data/app/views/pageflow/editor/entries/_entry.json.jbuilder +2 -1
- data/app/views/pageflow/editor/entries/seed.json.erb +1 -0
- data/app/views/pageflow/editor/file_folders/_file_folder.json.jbuilder +5 -0
- data/app/views/pageflow/editor/file_folders/create.json.jbuilder +1 -0
- data/app/views/pageflow/editor/file_folders/index.json.jbuilder +3 -0
- data/app/views/pageflow/editor/files/_file.json.jbuilder +2 -1
- data/app/views/pageflow/editor/image_files/_image_file.json.jbuilder +3 -0
- data/app/views/pageflow/editor/video_files/_video_file.json.jbuilder +3 -0
- data/app/views/pageflow/files/_file.json.jbuilder +2 -0
- data/app/views/pageflow/image_files/_image_file.json.jbuilder +0 -1
- data/app/views/pageflow/review/comment_threads/_comment_thread.json.jbuilder +20 -0
- data/app/views/pageflow/review/comment_threads/create.json.jbuilder +2 -0
- data/app/views/pageflow/review/comment_threads/index.json.jbuilder +13 -0
- data/app/views/pageflow/review/comments/_comment.json.jbuilder +13 -0
- data/app/views/pageflow/review/comments/create.json.jbuilder +1 -0
- data/app/views/pageflow/structured_data/_entry.json.jbuilder +2 -3
- data/app/views/pageflow/video_files/_video_file.json.jbuilder +0 -1
- data/config/locales/de.yml +152 -14
- data/config/locales/en.yml +150 -14
- data/config/routes.rb +14 -0
- data/db/migrate/20140418225525_setup_schema.rb +2 -2
- data/db/migrate/20251015000000_add_structured_data_type_name_to_revisions.rb +5 -0
- data/db/migrate/20251029142849_convert_legacy_social_embeds_to_unified_social_embed.rb +38 -0
- data/db/migrate/20251119083347_fix_main_storyline_positions.rb +16 -0
- data/db/migrate/20251119083348_ensure_scrolled_entries_have_excursion_storyline.rb +15 -0
- data/db/migrate/20260303000000_add_layout_version_to_revisions.rb +6 -0
- data/db/migrate/20260323000000_create_comment_threads_and_comments.rb +30 -0
- data/db/migrate/20260414000000_add_subject_range_to_comment_threads.rb +5 -0
- data/db/migrate/20260415000000_add_section_perma_id_to_comment_threads.rb +5 -0
- data/db/migrate/20260727000000_add_quote_to_comments.rb +5 -0
- data/db/migrate/20260728000000_create_file_folders.rb +17 -0
- data/db/migrate/20260805000000_add_attribute_translations_to_sites.rb +5 -0
- data/db/migrate/20260806000000_add_edited_at_to_comments.rb +5 -0
- data/db/migrate/20260810000000_create_pageflow_scrolled_lottie_files.rb +24 -0
- data/db/migrate/20260817000000_create_comment_thread_reads.rb +16 -0
- data/db/migrate/20260819000000_add_unread_comments_since_at_to_users.rb +18 -0
- data/entry_types/paged/app/assets/javascripts/pageflow_paged/dist/editor.js +8665 -3890
- data/entry_types/paged/app/assets/javascripts/pageflow_paged/dist/frontend.js +979 -3571
- data/entry_types/paged/app/assets/javascripts/pageflow_paged/dist/react-client.js +20199 -28120
- data/entry_types/paged/app/assets/javascripts/pageflow_paged/dist/react-server.js +18652 -26584
- data/entry_types/scrolled/app/assets/stylesheets/pageflow_scrolled/ui.scss +2 -0
- data/entry_types/scrolled/app/controllers/pageflow_scrolled/editor/content_elements_controller.rb +33 -3
- data/entry_types/scrolled/app/controllers/pageflow_scrolled/editor/sections_controller.rb +2 -3
- data/entry_types/scrolled/app/controllers/pageflow_scrolled/entries_controller.rb +21 -1
- data/entry_types/scrolled/app/helpers/pageflow_scrolled/cache_helper.rb +3 -3
- data/entry_types/scrolled/app/helpers/pageflow_scrolled/editor/entry_json_seed_helper.rb +7 -5
- data/entry_types/scrolled/app/helpers/pageflow_scrolled/editor/seed_html_helper.rb +5 -2
- data/entry_types/scrolled/app/helpers/pageflow_scrolled/entry_json_seed_helper.rb +13 -5
- data/entry_types/scrolled/app/helpers/pageflow_scrolled/i18n_helper.rb +28 -13
- data/entry_types/scrolled/app/helpers/pageflow_scrolled/packs_helper.rb +26 -11
- data/entry_types/scrolled/app/helpers/pageflow_scrolled/react_server_side_rendering_helper.rb +2 -2
- data/entry_types/scrolled/app/helpers/pageflow_scrolled/sprockets_helper.rb +16 -0
- data/entry_types/scrolled/app/helpers/pageflow_scrolled/themes_helper.rb +174 -3
- data/entry_types/scrolled/app/models/pageflow_scrolled/content_element.rb +49 -7
- data/entry_types/scrolled/app/models/pageflow_scrolled/lottie_file.rb +6 -0
- data/entry_types/scrolled/app/models/pageflow_scrolled/lottie_file_url_templates.rb +16 -0
- data/entry_types/scrolled/app/models/pageflow_scrolled/storyline.rb +1 -0
- data/entry_types/scrolled/app/views/pageflow_scrolled/entries/show.html.erb +13 -12
- data/entry_types/scrolled/app/views/pageflow_scrolled/entry_json_seed/_entry.json.jbuilder +30 -10
- data/entry_types/scrolled/app/views/pageflow_scrolled/entry_json_seed/_theme.json.jbuilder +9 -0
- data/entry_types/scrolled/app/views/pageflow_scrolled/favicons/_entry.html.erb +1 -1
- data/entry_types/scrolled/config/configuration_schemas/pageflow-scrolled.json +414 -0
- data/entry_types/scrolled/config/locales/de.yml +512 -65
- data/entry_types/scrolled/config/locales/en.yml +518 -52
- data/entry_types/scrolled/lib/pageflow_scrolled/additional_seed_data.rb +2 -2
- data/entry_types/scrolled/lib/pageflow_scrolled/configuration.rb +51 -6
- data/entry_types/scrolled/lib/pageflow_scrolled/configuration_schemas.rb +91 -0
- data/entry_types/scrolled/lib/pageflow_scrolled/engine.rb +2 -1
- data/entry_types/scrolled/lib/pageflow_scrolled/entry_file_reference_locations.rb +71 -0
- data/entry_types/scrolled/lib/pageflow_scrolled/entry_structured_data_types/faq_page.rb +58 -0
- data/entry_types/scrolled/lib/pageflow_scrolled/file_reference_locations.rb +69 -0
- data/entry_types/scrolled/lib/pageflow_scrolled/plugin.rb +205 -22
- data/entry_types/scrolled/lib/pageflow_scrolled/react_widget_type.rb +2 -4
- data/entry_types/scrolled/lib/pageflow_scrolled/seeds.rb +56 -36
- data/entry_types/scrolled/lib/pageflow_scrolled/theme_options_default_scale.rb +35 -0
- data/entry_types/scrolled/lib/pageflow_scrolled.rb +13 -0
- data/entry_types/scrolled/lib/tasks/pageflow_scrolled/storybook.rake +19 -1
- data/entry_types/scrolled/package/config/configurationSchemas.js +70 -0
- data/entry_types/scrolled/package/config/rollup/configurationSchemas.js +19 -0
- data/entry_types/scrolled/package/config/webpack.js +72 -0
- data/entry_types/scrolled/package/contentElements/hotspots-frontend.css +1 -1
- data/entry_types/scrolled/package/contentElements/hotspots-frontend.js +1181 -915
- data/entry_types/scrolled/package/contentElements/inlineBeforeAfter-frontend.css +1 -0
- data/entry_types/scrolled/package/contentElements/inlineBeforeAfter-frontend.js +219 -0
- data/entry_types/scrolled/package/contentElements/lottieAnimation-frontend.css +1 -0
- data/entry_types/scrolled/package/contentElements/lottieAnimation-frontend.js +244 -0
- data/entry_types/scrolled/package/contentElements/socialEmbed-frontend.css +1 -0
- data/entry_types/scrolled/package/contentElements/socialEmbed-frontend.js +823 -0
- data/entry_types/scrolled/package/contentElements/tikTokEmbed-frontend.js +71 -21
- data/entry_types/scrolled/package/contentElements/twitterEmbed-frontend.css +1 -0
- data/entry_types/scrolled/package/contentElements/twitterEmbed-frontend.js +277 -0
- data/entry_types/scrolled/package/contentElements/videoEmbed-frontend.css +1 -0
- data/entry_types/scrolled/package/contentElements/videoEmbed-frontend.js +244 -0
- data/entry_types/scrolled/package/contentElements-editor.js +1915 -1097
- data/entry_types/scrolled/package/contentElements-frontend.css +1 -1
- data/entry_types/scrolled/package/contentElements-frontend.js +3093 -1679
- data/entry_types/scrolled/package/editor.css +1 -0
- data/entry_types/scrolled/package/editor.js +8667 -4077
- data/entry_types/scrolled/package/entryState.js +1600 -0
- data/entry_types/scrolled/package/frontend/FloatingPortalRootProvider-20c600de.js +1979 -0
- data/entry_types/scrolled/package/frontend/FloatingPortalRootProvider-58189220.js +2538 -0
- data/entry_types/scrolled/package/frontend/{PhonePlatformContext-9f76033e.js → PhonePlatformContext-035a99fa.js} +7 -12
- data/entry_types/scrolled/package/frontend/PhonePlatformContext-9da43797.js +33 -0
- data/entry_types/scrolled/package/frontend/Placeholder.module-bf0966b4.js +5267 -0
- data/entry_types/scrolled/package/frontend/Placeholder.module-d22a9335.js +4180 -0
- data/entry_types/scrolled/package/frontend/ThemeIcon-ab834849.js +754 -0
- data/entry_types/scrolled/package/frontend/ThemeIcon-f6cb3a8f.js +773 -0
- data/entry_types/scrolled/package/frontend/{ToggleFullscreenCornerButton-788e9cdb.js → ToggleFullscreenCornerButton-30fd1124.js} +6 -6
- data/entry_types/scrolled/package/frontend/ToggleFullscreenCornerButton-61c25224.js +72 -0
- data/entry_types/scrolled/package/frontend/Viewer-1074fd59.js +147 -0
- data/entry_types/scrolled/package/frontend/Viewer-1482ca34.js +480 -0
- data/entry_types/scrolled/package/frontend/Viewer-70504231.js +210 -0
- data/entry_types/scrolled/package/frontend/{Viewer-0345ce57.js → Viewer-cc8d4072.js} +84 -92
- data/entry_types/scrolled/package/frontend/Wavesurfer-656e3c36.js +302 -0
- data/entry_types/scrolled/package/frontend/Wavesurfer-b753b838.js +385 -0
- data/entry_types/scrolled/package/frontend/commenting.css +1 -0
- data/entry_types/scrolled/package/frontend/extensions-167ae143.js +1455 -0
- data/entry_types/scrolled/package/frontend/extensions-2318f92b.js +5918 -0
- data/entry_types/scrolled/package/frontend/extensions-a461fd13.js +4890 -0
- data/entry_types/scrolled/package/frontend/extensions-c98269fd.js +1982 -0
- data/entry_types/scrolled/package/frontend/i18n-493cd2a6.js +93 -0
- data/entry_types/scrolled/package/frontend/i18n-e06cac14.js +100 -0
- data/entry_types/scrolled/package/frontend/{index-eb670c2f.js → index-2e5546f7.js} +36 -44
- data/entry_types/scrolled/package/frontend/index-de2b418d.js +130 -0
- data/entry_types/scrolled/package/frontend/index.css +1 -1
- data/entry_types/scrolled/package/frontend/index.js +1870 -875
- data/entry_types/scrolled/package/frontend/inlineEditing.css +1 -0
- data/entry_types/scrolled/package/frontend/useContentElementEditorState-a084912e.js +25 -0
- data/entry_types/scrolled/package/frontend/useDelayedBoolean-a387d85b.js +24 -0
- data/entry_types/scrolled/package/frontend/{usePhonePlatform-c2ba875d.js → usePhonePlatform-4ecb96d4.js} +1 -1
- data/entry_types/scrolled/package/frontend/usePhonePlatform-ce9ff08d.js +8 -0
- data/entry_types/scrolled/package/frontend-server.js +20 -209
- data/entry_types/scrolled/package/package.json +5 -3
- data/entry_types/scrolled/package/review.css +1 -0
- data/entry_types/scrolled/package/review.js +3306 -0
- data/entry_types/scrolled/package/testHelpers.js +836 -359
- data/entry_types/scrolled/package/widgets/consentBar.css +1 -1
- data/entry_types/scrolled/package/widgets/consentBar.js +269 -290
- data/entry_types/scrolled/package/widgets/defaultNavigation.css +2 -2
- data/entry_types/scrolled/package/widgets/defaultNavigation.js +911 -352
- data/entry_types/scrolled/package/widgets/excursionSheet.css +1 -1
- data/entry_types/scrolled/package/widgets/excursionSheet.js +87 -124
- data/entry_types/scrolled/package/widgets/iconInlineFileRights.css +1 -1
- data/entry_types/scrolled/package/widgets/iconInlineFileRights.js +62 -29
- data/entry_types/scrolled/package/widgets/iconScrollIndicator.js +32 -13
- data/entry_types/scrolled/package/widgets/mainStorylineSheet.css +1 -1
- data/entry_types/scrolled/package/widgets/mainStorylineSheet.js +56 -100
- data/entry_types/scrolled/package/widgets/textInlineFileRights.css +1 -1
- data/entry_types/scrolled/package/widgets/textInlineFileRights.js +33 -28
- data/entry_types/scrolled/package/widgets-server.js +1 -0
- data/entry_types/scrolled/spec/factories/lottie_files.rb +29 -0
- data/entry_types/scrolled/spec/fixtures/animation.lottie +0 -0
- data/entry_types/scrolled/spec/fixtures/font.woff +0 -0
- data/entry_types/scrolled/spec/fixtures/font.woff2 +0 -0
- data/lib/generators/pageflow/resque/resque_generator.rb +0 -2
- data/lib/pageflow/configuration.rb +49 -7
- data/lib/pageflow/deprecation.rb +19 -0
- data/lib/pageflow/engine.rb +1 -1
- data/lib/pageflow/entries_controller_env_helper.rb +32 -2
- data/lib/pageflow/entry_export_import/entry_serialization.rb +4 -4
- data/lib/pageflow/entry_export_import/page_type_versions.rb +2 -2
- data/lib/pageflow/entry_export_import/revision_serialization/import.rb +10 -0
- data/lib/pageflow/entry_export_import/revision_serialization.rb +3 -0
- data/lib/pageflow/entry_structured_data_types.rb +43 -0
- data/lib/pageflow/entry_type_configuration.rb +1 -0
- data/lib/pageflow/file_type.rb +2 -2
- data/lib/pageflow/file_types.rb +1 -1
- data/lib/pageflow/oembed_providers.rb +42 -0
- data/lib/pageflow/primary_domain_entry_redirect.rb +6 -0
- data/lib/pageflow/rails_version.rb +13 -2
- data/lib/pageflow/themes.rb +41 -0
- data/lib/pageflow/user_mixin.rb +12 -0
- data/lib/pageflow/version.rb +1 -1
- data/lib/pageflow.rb +1 -0
- data/package/config/eslint-rules/documented-in-toc.js +125 -0
- data/package/config/jest/index.js +0 -1
- data/package/config/webpack.js +0 -1
- data/package/config/webpack5.js +0 -1
- data/package/editor.js +4378 -1897
- data/package/frontend.js +647 -886
- data/package/package.json +2 -1
- data/package/review.js +384 -0
- data/package/testHelpers.js +248 -188
- data/package/ui.js +1346 -630
- data/package/vendor/stubs/wysihtml5.js +2 -0
- data/spec/factories/comment_thread_reads.rb +10 -0
- data/spec/factories/comment_threads.rb +10 -0
- data/spec/factories/comments.rb +9 -0
- data/spec/factories/file_folders.rb +8 -0
- data/spec/factories/published_entries.rb +2 -0
- metadata +188 -67
- data/app/assets/stylesheets/pageflow/editor/select_button.scss +0 -51
- data/entry_types/scrolled/app/helpers/pageflow_scrolled/generated_media_queries_helper.rb +0 -55
- data/entry_types/scrolled/package/frontend/FloatingPortalRootProvider-51914be7.js +0 -5439
- data/entry_types/scrolled/package/frontend/ThemeIcon-81f2f066.js +0 -1932
- data/entry_types/scrolled/package/frontend/Viewer-cdc549cc.js +0 -147
- data/entry_types/scrolled/package/frontend/Wavesurfer-1cdc3925.js +0 -313
- data/entry_types/scrolled/package/frontend/components-96660ffd.js +0 -3803
- data/entry_types/scrolled/package/frontend/inherits-539844a6.js +0 -99
- data/entry_types/scrolled/package/frontend/useContentElementEditorState-77fe6c79.js +0 -51
- data/entry_types/scrolled/package/widgets/excursionDialog.css +0 -1
- data/entry_types/scrolled/package/widgets/excursionDialog.js +0 -109
- data/lib/generators/pageflow/resque/templates/resque_enqueue_after_commit_patch.rb +0 -25
- data/package/vendor/jquery.minicolors.js +0 -1108
|
@@ -0,0 +1,1600 @@
|
|
|
1
|
+
import React, { useReducer, useMemo, useCallback } from 'react';
|
|
2
|
+
import { createContext, useContextSelector } from 'use-context-selector';
|
|
3
|
+
import { createSelectorCreator, defaultMemoize, createSelector } from 'reselect';
|
|
4
|
+
import slugify from 'slugify';
|
|
5
|
+
import { features } from 'pageflow/frontend';
|
|
6
|
+
|
|
7
|
+
const PREFIX = 'PAGEFLOW_SCROLLED_COLLECTION';
|
|
8
|
+
const RESET = `${PREFIX}_RESET`;
|
|
9
|
+
const ADD = `${PREFIX}_ADD`;
|
|
10
|
+
const CHANGE = `${PREFIX}_CHANGE`;
|
|
11
|
+
const PATCH_CONFIGURATION = `${PREFIX}_PATCH_CONFIGURATION`;
|
|
12
|
+
const REMOVE = `${PREFIX}_REMOVE`;
|
|
13
|
+
const SORT = `${PREFIX}_SORT`;
|
|
14
|
+
function useCollections(seed = {}, {
|
|
15
|
+
keyAttribute
|
|
16
|
+
} = {}) {
|
|
17
|
+
return useReducer(reducer, Object.keys(seed).reduce((result, key) => {
|
|
18
|
+
result[key] = init(seed[key], keyAttribute);
|
|
19
|
+
return result;
|
|
20
|
+
}, {}));
|
|
21
|
+
}
|
|
22
|
+
function reducer(state, action) {
|
|
23
|
+
const collectionName = action.payload.collectionName;
|
|
24
|
+
const keyAttribute = action.payload.keyAttribute;
|
|
25
|
+
switch (action.type) {
|
|
26
|
+
case RESET:
|
|
27
|
+
return {
|
|
28
|
+
...state,
|
|
29
|
+
[collectionName]: init(action.payload.items, keyAttribute)
|
|
30
|
+
};
|
|
31
|
+
case ADD:
|
|
32
|
+
return {
|
|
33
|
+
...state,
|
|
34
|
+
[collectionName]: {
|
|
35
|
+
order: action.payload.order,
|
|
36
|
+
items: {
|
|
37
|
+
...state[collectionName].items,
|
|
38
|
+
[action.payload.attributes[keyAttribute]]: action.payload.attributes
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
case CHANGE:
|
|
43
|
+
return {
|
|
44
|
+
...state,
|
|
45
|
+
[collectionName]: {
|
|
46
|
+
order: state[collectionName].order,
|
|
47
|
+
items: {
|
|
48
|
+
...state[collectionName].items,
|
|
49
|
+
[action.payload.attributes[keyAttribute]]: action.payload.attributes
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
case PATCH_CONFIGURATION:
|
|
54
|
+
const key = action.payload.key;
|
|
55
|
+
return {
|
|
56
|
+
...state,
|
|
57
|
+
[collectionName]: {
|
|
58
|
+
order: state[collectionName].order,
|
|
59
|
+
items: {
|
|
60
|
+
...state[collectionName].items,
|
|
61
|
+
[key]: {
|
|
62
|
+
...state[collectionName].items[key],
|
|
63
|
+
configuration: {
|
|
64
|
+
...state[collectionName].items[key].configuration,
|
|
65
|
+
...action.payload.configuration
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
case REMOVE:
|
|
72
|
+
const clonedItems = {
|
|
73
|
+
...state[collectionName].items
|
|
74
|
+
};
|
|
75
|
+
delete clonedItems[action.payload.key];
|
|
76
|
+
return {
|
|
77
|
+
...state,
|
|
78
|
+
[collectionName]: {
|
|
79
|
+
order: action.payload.order,
|
|
80
|
+
items: clonedItems
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
case SORT:
|
|
84
|
+
return {
|
|
85
|
+
...state,
|
|
86
|
+
[collectionName]: {
|
|
87
|
+
order: action.payload.order,
|
|
88
|
+
items: state[collectionName].items
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
default:
|
|
92
|
+
return state;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function init(items, keyAttribute = 'id') {
|
|
96
|
+
items = items.filter(item => item[keyAttribute]);
|
|
97
|
+
return {
|
|
98
|
+
order: items.map(item => item[keyAttribute]),
|
|
99
|
+
items: items.reduce((result, item) => {
|
|
100
|
+
result[item[keyAttribute]] = item;
|
|
101
|
+
return result;
|
|
102
|
+
}, {})
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
function updateConfiguration({
|
|
106
|
+
dispatch,
|
|
107
|
+
name,
|
|
108
|
+
key,
|
|
109
|
+
configuration
|
|
110
|
+
}) {
|
|
111
|
+
dispatch({
|
|
112
|
+
type: PATCH_CONFIGURATION,
|
|
113
|
+
payload: {
|
|
114
|
+
collectionName: name,
|
|
115
|
+
key,
|
|
116
|
+
configuration
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
function collectionSnapshot(collection, {
|
|
121
|
+
attributes,
|
|
122
|
+
includeConfiguration
|
|
123
|
+
}) {
|
|
124
|
+
return collection.map(model => getAttributes(model, {
|
|
125
|
+
attributeNames: attributes,
|
|
126
|
+
includeConfiguration
|
|
127
|
+
}));
|
|
128
|
+
}
|
|
129
|
+
function watchCollection(collection, {
|
|
130
|
+
name,
|
|
131
|
+
dispatch,
|
|
132
|
+
attributes,
|
|
133
|
+
includeConfiguration,
|
|
134
|
+
keyAttribute = 'id'
|
|
135
|
+
}) {
|
|
136
|
+
const handle = {};
|
|
137
|
+
const options = {
|
|
138
|
+
attributeNames: attributes,
|
|
139
|
+
includeConfiguration
|
|
140
|
+
};
|
|
141
|
+
let tearingDown = false;
|
|
142
|
+
const watchedAttributeNames = getWatchedAttributeNames(attributes);
|
|
143
|
+
const sourceKeyAttribute = findSourceAttributeName(attributes, keyAttribute);
|
|
144
|
+
dispatch({
|
|
145
|
+
type: RESET,
|
|
146
|
+
payload: {
|
|
147
|
+
collectionName: name,
|
|
148
|
+
keyAttribute: keyAttribute,
|
|
149
|
+
items: collection.map(model => getAttributes(model, options))
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
collection.on('add change:id', model => {
|
|
153
|
+
if (!model.isNew()) {
|
|
154
|
+
dispatch({
|
|
155
|
+
type: ADD,
|
|
156
|
+
payload: {
|
|
157
|
+
collectionName: name,
|
|
158
|
+
keyAttribute: keyAttribute,
|
|
159
|
+
order: collection.pluck(sourceKeyAttribute).filter(Boolean),
|
|
160
|
+
attributes: getAttributes(model, options)
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}, handle);
|
|
165
|
+
collection.on('change', model => {
|
|
166
|
+
if (hasChangedAttributes(model, watchedAttributeNames) && !model.isNew()) {
|
|
167
|
+
dispatch({
|
|
168
|
+
type: CHANGE,
|
|
169
|
+
payload: {
|
|
170
|
+
collectionName: name,
|
|
171
|
+
keyAttribute: keyAttribute,
|
|
172
|
+
attributes: getAttributes(model, options)
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}, handle);
|
|
177
|
+
if (includeConfiguration) {
|
|
178
|
+
collection.on('change:configuration', (model, value, {
|
|
179
|
+
ignoreInWatchCollection
|
|
180
|
+
} = {}) => {
|
|
181
|
+
if (!model.isNew() && !ignoreInWatchCollection) {
|
|
182
|
+
dispatch({
|
|
183
|
+
type: CHANGE,
|
|
184
|
+
payload: {
|
|
185
|
+
collectionName: name,
|
|
186
|
+
keyAttribute: keyAttribute,
|
|
187
|
+
attributes: getAttributes(model, options)
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
}, handle);
|
|
192
|
+
}
|
|
193
|
+
collection.on('remove', model => {
|
|
194
|
+
if (!tearingDown && !model.isNew()) {
|
|
195
|
+
dispatch({
|
|
196
|
+
type: REMOVE,
|
|
197
|
+
payload: {
|
|
198
|
+
collectionName: name,
|
|
199
|
+
order: collection.pluck(sourceKeyAttribute).filter(Boolean),
|
|
200
|
+
key: model.attributes[sourceKeyAttribute]
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
}, handle);
|
|
205
|
+
collection.on('sort', model => dispatch({
|
|
206
|
+
type: SORT,
|
|
207
|
+
payload: {
|
|
208
|
+
collectionName: name,
|
|
209
|
+
order: collection.pluck(sourceKeyAttribute).filter(Boolean)
|
|
210
|
+
}
|
|
211
|
+
}), handle);
|
|
212
|
+
return function () {
|
|
213
|
+
tearingDown = true;
|
|
214
|
+
collection.off(null, null, handle);
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
function findSourceAttributeName(attributeNames, targetAttributeName) {
|
|
218
|
+
const mapping = attributeNames.find(attributeName => typeof attributeName === 'object' && mappedAttributeTarget(attributeName) === targetAttributeName);
|
|
219
|
+
return mapping ? mappedAttributeSource(mapping) : targetAttributeName;
|
|
220
|
+
}
|
|
221
|
+
function hasChangedAttributes(model, attributeNames) {
|
|
222
|
+
return attributeNames.some(attributeName => model.hasChanged(attributeName));
|
|
223
|
+
}
|
|
224
|
+
function getWatchedAttributeNames(attributeNames) {
|
|
225
|
+
return attributeNames.flatMap(attributeName => typeof attributeName == 'object' ? mappedAttributeSource(attributeName) : attributeName);
|
|
226
|
+
}
|
|
227
|
+
function mappedAttributeSource(mapping) {
|
|
228
|
+
const attributeName = Object.keys(mapping)[0];
|
|
229
|
+
const source = mapping[attributeName];
|
|
230
|
+
if (typeof source === 'string') {
|
|
231
|
+
return source;
|
|
232
|
+
} else if (Array.isArray(source)) {
|
|
233
|
+
return source.slice(0, -1);
|
|
234
|
+
} else {
|
|
235
|
+
return attributeName;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
function mappedAttributeTarget(attributeName) {
|
|
239
|
+
return Object.keys(attributeName)[0];
|
|
240
|
+
}
|
|
241
|
+
function getAttributes(model, {
|
|
242
|
+
attributeNames,
|
|
243
|
+
includeConfiguration
|
|
244
|
+
}) {
|
|
245
|
+
const result = attributeNames.reduce((result, attributeName) => {
|
|
246
|
+
if (typeof attributeName == 'object') {
|
|
247
|
+
const key = Object.keys(attributeName)[0];
|
|
248
|
+
const value = attributeName[key];
|
|
249
|
+
if (Array.isArray(value)) {
|
|
250
|
+
const attributeNames = value.slice(0, -1);
|
|
251
|
+
const fn = value[value.length - 1];
|
|
252
|
+
result[key] = fn.apply(null, attributeNames.map(attributeName => model.get(attributeName)));
|
|
253
|
+
} else if (typeof value == 'function') {
|
|
254
|
+
result[key] = value(model.get(key));
|
|
255
|
+
} else {
|
|
256
|
+
result[key] = model.get(value);
|
|
257
|
+
}
|
|
258
|
+
} else {
|
|
259
|
+
result[attributeName] = model.get(attributeName);
|
|
260
|
+
}
|
|
261
|
+
return result;
|
|
262
|
+
}, {});
|
|
263
|
+
if (includeConfiguration) {
|
|
264
|
+
result.configuration = {
|
|
265
|
+
...model.configuration.attributes
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
return result;
|
|
269
|
+
}
|
|
270
|
+
function getItem(state, collectionName, key) {
|
|
271
|
+
if (state[collectionName]) {
|
|
272
|
+
return state[collectionName].items[key];
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
function createMultipleItemsSelector(collectionNames, filter) {
|
|
276
|
+
return createSelector(...collectionNames.map(collectionName => collections => collections[collectionName]), (...collections) => {
|
|
277
|
+
return collectionNames.reduce((result, collectionName, index) => {
|
|
278
|
+
result[collectionName] = toOrderedItems(collections[index]);
|
|
279
|
+
return result;
|
|
280
|
+
}, {});
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
function createItemsSelector(collectionName, filter) {
|
|
284
|
+
if (filter) {
|
|
285
|
+
const itemsSelector = createItemsSelector(collectionName);
|
|
286
|
+
return createShallowEqualArraysSelector(collections => itemsSelector(collections).filter(filter), items => items);
|
|
287
|
+
}
|
|
288
|
+
return createSelector(collections => collections[collectionName], toOrderedItems);
|
|
289
|
+
}
|
|
290
|
+
function toOrderedItems(collection) {
|
|
291
|
+
if (collection) {
|
|
292
|
+
const items = collection.items;
|
|
293
|
+
return collection.order.map(key => items[key]);
|
|
294
|
+
} else {
|
|
295
|
+
return [];
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
const createShallowEqualArraysSelector = createSelectorCreator(defaultMemoize, shallowEqualArrays);
|
|
299
|
+
function shallowEqualArrays(a, b) {
|
|
300
|
+
return a.length === b.length && a.every((item, index) => item === b[index]);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
var _jsxFileName = "/home/tfischbach/code/pageflow/entry_types/scrolled/package/src/entryState/EntryStateProvider.js";
|
|
304
|
+
const Context = createContext();
|
|
305
|
+
function EntryStateProvider({
|
|
306
|
+
seed,
|
|
307
|
+
children
|
|
308
|
+
}) {
|
|
309
|
+
const [collections, dispatch] = useCollections(seed.collections, {
|
|
310
|
+
keyAttribute: 'permaId'
|
|
311
|
+
});
|
|
312
|
+
const value = useMemo(() => ({
|
|
313
|
+
entryState: {
|
|
314
|
+
collections,
|
|
315
|
+
config: seed.config
|
|
316
|
+
},
|
|
317
|
+
dispatch
|
|
318
|
+
}), [collections, dispatch, seed]);
|
|
319
|
+
return /*#__PURE__*/React.createElement(Context.Provider, {
|
|
320
|
+
value: value,
|
|
321
|
+
__self: this,
|
|
322
|
+
__source: {
|
|
323
|
+
fileName: _jsxFileName,
|
|
324
|
+
lineNumber: 25,
|
|
325
|
+
columnNumber: 5
|
|
326
|
+
}
|
|
327
|
+
}, children);
|
|
328
|
+
}
|
|
329
|
+
function useEntryState(selector = entryState => entryState) {
|
|
330
|
+
return useContextSelector(Context, value => selector(value.entryState));
|
|
331
|
+
}
|
|
332
|
+
function useEntryStateDispatch() {
|
|
333
|
+
return useContextSelector(Context, value => value.dispatch);
|
|
334
|
+
}
|
|
335
|
+
function useEntryStateConfig() {
|
|
336
|
+
return useEntryState(entryState => entryState.config);
|
|
337
|
+
}
|
|
338
|
+
function useEntryStateCollectionItem(collectionName, key) {
|
|
339
|
+
return useEntryState(entryState => getItem(entryState.collections, collectionName, key));
|
|
340
|
+
}
|
|
341
|
+
function useEntryStateCollectionItems(collectionName, filter) {
|
|
342
|
+
const itemsSelector = useMemo(() => createItemsSelector(collectionName, filter), [collectionName, filter]);
|
|
343
|
+
return useEntryState(entryState => itemsSelector(entryState.collections));
|
|
344
|
+
}
|
|
345
|
+
function useMultipleEntryStateCollectionItems(collectionNames) {
|
|
346
|
+
const multipleItemsSelector = useMemo(() => createMultipleItemsSelector(collectionNames), [collectionNames]);
|
|
347
|
+
return useEntryState(entryState => multipleItemsSelector(entryState.collections));
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Returns data generated by a Ruby lambda registered server
|
|
352
|
+
* side via `config.additional_frontend_seed_data.register`.
|
|
353
|
+
*
|
|
354
|
+
* @example
|
|
355
|
+
*
|
|
356
|
+
* const data = useAdditionalSeedData('someSeed');
|
|
357
|
+
*/
|
|
358
|
+
function useAdditionalSeedData(name) {
|
|
359
|
+
const config = useEntryStateConfig();
|
|
360
|
+
if (!config.additionalSeedData[name]) {
|
|
361
|
+
throw new Error(`Could not find additional seed data with name '${name}'.`);
|
|
362
|
+
}
|
|
363
|
+
return config.additionalSeedData[name];
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function useContentElementConsentVendor({
|
|
367
|
+
contentElementId
|
|
368
|
+
}) {
|
|
369
|
+
const config = useEntryStateConfig();
|
|
370
|
+
const vendorName = config.contentElementConsentVendors[contentElementId];
|
|
371
|
+
return config.consentVendors.find(vendor => vendor.name === vendorName);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function useCutOff() {
|
|
375
|
+
const config = useEntryStateConfig();
|
|
376
|
+
return config.cutOff;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function useEmbedOriginUrl() {
|
|
380
|
+
const config = useEntryStateConfig();
|
|
381
|
+
return config.embed ? config.originUrl : undefined;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Returns an object containing theme asset paths.
|
|
386
|
+
*
|
|
387
|
+
* @example
|
|
388
|
+
*
|
|
389
|
+
* const theme = useTheme();
|
|
390
|
+
* theme // =>
|
|
391
|
+
* {
|
|
392
|
+
* assets: {
|
|
393
|
+
* logoDesktop: 'path/to/logoDesktop.svg',
|
|
394
|
+
* logoMobile: 'path/to/logoMobile.svg'
|
|
395
|
+
* },
|
|
396
|
+
* options: {
|
|
397
|
+
* // options passed to `themes.register` in `pageflow.rb` initializer
|
|
398
|
+
* // with camleized keys.
|
|
399
|
+
* }
|
|
400
|
+
* }
|
|
401
|
+
*/
|
|
402
|
+
function useTheme() {
|
|
403
|
+
const config = useEntryStateConfig();
|
|
404
|
+
return config.theme;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Returns a nested data structure representing the metadata of the entry.
|
|
409
|
+
*
|
|
410
|
+
* @example
|
|
411
|
+
*
|
|
412
|
+
* const metaData = useEntryMetadata();
|
|
413
|
+
* metaData // =>
|
|
414
|
+
* {
|
|
415
|
+
* id: 5,
|
|
416
|
+
* locale: 'en',
|
|
417
|
+
* shareProviders: {email: false, facebook: true},
|
|
418
|
+
* share_url: 'http://test.host/test',
|
|
419
|
+
* credits: 'Credits: Pageflow',
|
|
420
|
+
* configuration: {darkWidgets: true}
|
|
421
|
+
* }
|
|
422
|
+
*/
|
|
423
|
+
function useEntryMetadata() {
|
|
424
|
+
const entries = useEntryStateCollectionItems('entries');
|
|
425
|
+
return useMemo(() => {
|
|
426
|
+
return entries[0];
|
|
427
|
+
}, [entries]);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Returns boolean indicating whether dark variant has been activated for
|
|
432
|
+
* the widgets of the entry.
|
|
433
|
+
*/
|
|
434
|
+
function useDarkWidgets() {
|
|
435
|
+
const theme = useTheme();
|
|
436
|
+
return useEntryMetadata().configuration.darkWidgets || theme.options.darkWidgets;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Returns a list of attributes (iconName, name and url) of all configured share
|
|
441
|
+
* providers of the entry. The url provides a %<url>s placeholder where the link
|
|
442
|
+
* can be inserted. iconName can be passed to ThemeIcon to render a theme
|
|
443
|
+
* specific icon.
|
|
444
|
+
*
|
|
445
|
+
* @example
|
|
446
|
+
*
|
|
447
|
+
* const shareProviders = useShareProviders(options);
|
|
448
|
+
* shareProviders // =>
|
|
449
|
+
* [
|
|
450
|
+
* {
|
|
451
|
+
* iconName: 'facebook',
|
|
452
|
+
* name: 'Facebook',
|
|
453
|
+
* url: http://www.facebook.com/sharer/sharer.php?u=%<url>s
|
|
454
|
+
* },
|
|
455
|
+
* {
|
|
456
|
+
* iconName: 'twitter',
|
|
457
|
+
* name: 'Twitter',
|
|
458
|
+
* url: https://x.com/intent/post?url=%<url>s
|
|
459
|
+
* }
|
|
460
|
+
* ]
|
|
461
|
+
*/
|
|
462
|
+
function useShareProviders({
|
|
463
|
+
isPhonePlatform
|
|
464
|
+
}) {
|
|
465
|
+
const config = useEntryStateConfig();
|
|
466
|
+
const entryMetadata = useEntryMetadata();
|
|
467
|
+
const shareProviders = (entryMetadata === null || entryMetadata === void 0 ? void 0 : entryMetadata.shareProviders) || {};
|
|
468
|
+
const urlTemplates = config.shareUrlTemplates;
|
|
469
|
+
return useMemo(() => {
|
|
470
|
+
const sharing = {
|
|
471
|
+
bluesky: {
|
|
472
|
+
iconName: 'bluesky',
|
|
473
|
+
name: 'Bluesky',
|
|
474
|
+
url: urlTemplates.bluesky
|
|
475
|
+
},
|
|
476
|
+
email: {
|
|
477
|
+
iconName: 'email',
|
|
478
|
+
name: 'Mail',
|
|
479
|
+
url: urlTemplates.email
|
|
480
|
+
},
|
|
481
|
+
facebook: {
|
|
482
|
+
iconName: 'facebook',
|
|
483
|
+
name: 'Facebook',
|
|
484
|
+
url: urlTemplates.facebook
|
|
485
|
+
},
|
|
486
|
+
linked_in: {
|
|
487
|
+
iconName: 'linkedIn',
|
|
488
|
+
name: 'LinkedIn',
|
|
489
|
+
url: urlTemplates.linked_in
|
|
490
|
+
},
|
|
491
|
+
telegram: {
|
|
492
|
+
iconName: 'telegram',
|
|
493
|
+
name: 'Telegram',
|
|
494
|
+
url: urlTemplates.telegram
|
|
495
|
+
},
|
|
496
|
+
threads: {
|
|
497
|
+
iconName: 'threads',
|
|
498
|
+
name: 'threads',
|
|
499
|
+
url: urlTemplates.threads
|
|
500
|
+
},
|
|
501
|
+
twitter: {
|
|
502
|
+
iconName: 'twitter',
|
|
503
|
+
name: 'X',
|
|
504
|
+
url: urlTemplates.twitter
|
|
505
|
+
},
|
|
506
|
+
whats_app: {
|
|
507
|
+
iconName: 'whatsApp',
|
|
508
|
+
name: 'WhatsApp',
|
|
509
|
+
url: urlTemplates.whats_app
|
|
510
|
+
}
|
|
511
|
+
};
|
|
512
|
+
return activeShareProviders(shareProviders, isPhonePlatform).map(provider => {
|
|
513
|
+
const config = sharing[provider];
|
|
514
|
+
return {
|
|
515
|
+
name: config.name,
|
|
516
|
+
iconName: config.iconName,
|
|
517
|
+
url: config.url
|
|
518
|
+
};
|
|
519
|
+
});
|
|
520
|
+
}, [shareProviders, isPhonePlatform, urlTemplates]);
|
|
521
|
+
}
|
|
522
|
+
function activeShareProviders(shareProvidersConfig, isPhonePlatform) {
|
|
523
|
+
const providers = filterShareProviders(shareProvidersConfig, isPhonePlatform);
|
|
524
|
+
return providers.filter(function (provider) {
|
|
525
|
+
return shareProvidersConfig[provider] !== false;
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
function filterShareProviders(shareProvidersConfig, isPhonePlatform) {
|
|
529
|
+
if (!isPhonePlatform) {
|
|
530
|
+
return Object.keys(shareProvidersConfig).filter(function (provider) {
|
|
531
|
+
return provider !== 'telegram' && provider !== 'whats_app';
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
return Object.keys(shareProvidersConfig);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* Returns the share url of the entry.
|
|
539
|
+
*
|
|
540
|
+
* @example
|
|
541
|
+
*
|
|
542
|
+
* const shareUrl = useShareUrl();
|
|
543
|
+
* shareUrl // => "http://test.host/test"
|
|
544
|
+
*/
|
|
545
|
+
function useShareUrl() {
|
|
546
|
+
const entryMetadata = useEntryMetadata();
|
|
547
|
+
const config = useEntryStateConfig();
|
|
548
|
+
if (entryMetadata) {
|
|
549
|
+
return entryMetadata.shareUrl ? entryMetadata.shareUrl : config.prettyUrl;
|
|
550
|
+
} else {
|
|
551
|
+
return config.shareUrl;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
function useEntryTranslations() {
|
|
556
|
+
const config = useEntryStateConfig();
|
|
557
|
+
return config.entryTranslations;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
function getChapterSlugs(chapters) {
|
|
561
|
+
const result = {};
|
|
562
|
+
const usedSlugs = {};
|
|
563
|
+
chapters.forEach(chapter => {
|
|
564
|
+
let chapterSlug = chapter.configuration.title;
|
|
565
|
+
if (chapterSlug) {
|
|
566
|
+
chapterSlug = slugify(chapterSlug, {
|
|
567
|
+
lower: true,
|
|
568
|
+
locale: 'de',
|
|
569
|
+
strict: true
|
|
570
|
+
});
|
|
571
|
+
if (usedSlugs[chapterSlug]) {
|
|
572
|
+
chapterSlug = chapterSlug + '-' + chapter.permaId;
|
|
573
|
+
}
|
|
574
|
+
usedSlugs[chapterSlug] = true;
|
|
575
|
+
} else {
|
|
576
|
+
chapterSlug = 'chapter-' + chapter.permaId;
|
|
577
|
+
}
|
|
578
|
+
result[chapter.permaId] = chapterSlug;
|
|
579
|
+
});
|
|
580
|
+
return result;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
/**
|
|
584
|
+
* Returns a nested data structure representing the chapters and sections
|
|
585
|
+
* of the entry.
|
|
586
|
+
*
|
|
587
|
+
* @private
|
|
588
|
+
*
|
|
589
|
+
* @example
|
|
590
|
+
*
|
|
591
|
+
* const structure = useEntryStructure();
|
|
592
|
+
* structure // =>
|
|
593
|
+
* {
|
|
594
|
+
* main: [
|
|
595
|
+
* {
|
|
596
|
+
* permaId: 5,
|
|
597
|
+
* title: 'Chapter 1',
|
|
598
|
+
* summary: 'An introductory chapter',
|
|
599
|
+
* sections: [
|
|
600
|
+
* {
|
|
601
|
+
* id: 1,
|
|
602
|
+
* permaId: 101,
|
|
603
|
+
* chapterId: 3,
|
|
604
|
+
* sectionIndex: 0,
|
|
605
|
+
* transition: 'scroll',
|
|
606
|
+
*
|
|
607
|
+
* // references to parent chapter
|
|
608
|
+
* chapter: { ... },
|
|
609
|
+
*
|
|
610
|
+
* // references to adjacent section objects
|
|
611
|
+
* previousSection: { ... },
|
|
612
|
+
* nextSection: { ... },
|
|
613
|
+
* }
|
|
614
|
+
* ],
|
|
615
|
+
* }
|
|
616
|
+
* ],
|
|
617
|
+
* excursions: [ ... ],
|
|
618
|
+
* mainSectionsCount: 2
|
|
619
|
+
* }
|
|
620
|
+
*/
|
|
621
|
+
function useEntryStructure() {
|
|
622
|
+
const mainStoryline = useMainStoryline();
|
|
623
|
+
const chapters = useChapters();
|
|
624
|
+
const sections = useEntryStateCollectionItems('sections');
|
|
625
|
+
return useMemo(() => buildEntryStructure({
|
|
626
|
+
mainStoryline,
|
|
627
|
+
chapters,
|
|
628
|
+
sections
|
|
629
|
+
}), [mainStoryline, chapters, sections]);
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
/**
|
|
633
|
+
* Like {@link useEntryStructure}, but additionally nests an ordered
|
|
634
|
+
* `contentElements` array (including backdrop elements) into each
|
|
635
|
+
* section. Kept separate so the more frequently used
|
|
636
|
+
* `useEntryStructure` does not re-derive when content elements change.
|
|
637
|
+
*
|
|
638
|
+
* @private
|
|
639
|
+
*/
|
|
640
|
+
function useEntryStructureWithContentElements() {
|
|
641
|
+
const mainStoryline = useMainStoryline();
|
|
642
|
+
const chapters = useChapters();
|
|
643
|
+
const sections = useEntryStateCollectionItems('sections');
|
|
644
|
+
const contentElements = useEntryStateCollectionItems('contentElements');
|
|
645
|
+
return useMemo(() => {
|
|
646
|
+
const contentElementsBySectionId = {};
|
|
647
|
+
contentElements.forEach(contentElement => {
|
|
648
|
+
const sectionContentElements = contentElementsBySectionId[contentElement.sectionId] || (contentElementsBySectionId[contentElement.sectionId] = []);
|
|
649
|
+
sectionContentElements.push(contentElementSubjectData(contentElement));
|
|
650
|
+
});
|
|
651
|
+
return buildEntryStructure({
|
|
652
|
+
mainStoryline,
|
|
653
|
+
chapters,
|
|
654
|
+
sections,
|
|
655
|
+
contentElementsBySectionId
|
|
656
|
+
});
|
|
657
|
+
}, [mainStoryline, chapters, sections, contentElements]);
|
|
658
|
+
}
|
|
659
|
+
function buildEntryStructure({
|
|
660
|
+
mainStoryline,
|
|
661
|
+
chapters,
|
|
662
|
+
sections,
|
|
663
|
+
contentElementsBySectionId
|
|
664
|
+
}) {
|
|
665
|
+
const enrichedSections = sections.map(section => ({
|
|
666
|
+
...sectionData(section),
|
|
667
|
+
...(contentElementsBySectionId && {
|
|
668
|
+
contentElements: contentElementsBySectionId[section.id] || []
|
|
669
|
+
})
|
|
670
|
+
}));
|
|
671
|
+
const main = [];
|
|
672
|
+
const excursions = [];
|
|
673
|
+
chapters.forEach(chapter => {
|
|
674
|
+
const chapterSections = enrichedSections.filter(item => item.chapterId === chapter.id);
|
|
675
|
+
const isExcursion = chapter.storylineId !== mainStoryline.id;
|
|
676
|
+
chapter = {
|
|
677
|
+
...chapter,
|
|
678
|
+
isExcursion,
|
|
679
|
+
sections: chapterSections
|
|
680
|
+
};
|
|
681
|
+
chapterSections.forEach(section => section.chapter = chapter);
|
|
682
|
+
if (isExcursion) {
|
|
683
|
+
excursions.push(chapter);
|
|
684
|
+
} else {
|
|
685
|
+
main.push(chapter);
|
|
686
|
+
}
|
|
687
|
+
});
|
|
688
|
+
const mainSections = main.flatMap(chapter => chapter.sections);
|
|
689
|
+
linkAndIndexSections(mainSections);
|
|
690
|
+
excursions.forEach(excursion => linkAndIndexSections(excursion.sections));
|
|
691
|
+
return {
|
|
692
|
+
main,
|
|
693
|
+
excursions,
|
|
694
|
+
mainSectionsCount: mainSections.length
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
function contentElementSubjectData(contentElement) {
|
|
698
|
+
return {
|
|
699
|
+
id: contentElement.id,
|
|
700
|
+
permaId: contentElement.permaId,
|
|
701
|
+
sectionId: contentElement.sectionId,
|
|
702
|
+
type: contentElement.typeName
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
function linkAndIndexSections(sections) {
|
|
706
|
+
sections.forEach((section, index) => {
|
|
707
|
+
section.sectionIndex = index;
|
|
708
|
+
section.previousSection = sections[index - 1];
|
|
709
|
+
section.nextSection = sections[index + 1];
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
/**
|
|
714
|
+
* Returns an array of sections each with a chapter property containing
|
|
715
|
+
* data about the parent chapter.
|
|
716
|
+
*
|
|
717
|
+
* @private
|
|
718
|
+
*
|
|
719
|
+
* @example
|
|
720
|
+
*
|
|
721
|
+
* const sections = useSectionsWithChapter();
|
|
722
|
+
* sections // =>
|
|
723
|
+
* [
|
|
724
|
+
* {
|
|
725
|
+
* id: 1,
|
|
726
|
+
* permaId: 101,
|
|
727
|
+
* chapterId: 3,
|
|
728
|
+
* transition: 'scroll',
|
|
729
|
+
* chapter: {
|
|
730
|
+
* id: 3,
|
|
731
|
+
* permaId: 5,
|
|
732
|
+
* title: 'Chapter 1',
|
|
733
|
+
* summary: 'An introductory chapter',
|
|
734
|
+
* chapterSlug: 'chapter-1'
|
|
735
|
+
* },
|
|
736
|
+
* }
|
|
737
|
+
* ]
|
|
738
|
+
*/
|
|
739
|
+
function useSectionsWithChapter() {
|
|
740
|
+
const chapters = useChapters();
|
|
741
|
+
const sections = useEntryStateCollectionItems('sections');
|
|
742
|
+
const chaptersById = useMemo(() => chapters.reduce((result, chapter) => {
|
|
743
|
+
result[chapter.id] = chapter;
|
|
744
|
+
return result;
|
|
745
|
+
}, {}), [chapters]);
|
|
746
|
+
return useMemo(() => {
|
|
747
|
+
return sections.map((section, sectionIndex) => ({
|
|
748
|
+
sectionIndex,
|
|
749
|
+
...sectionData(section),
|
|
750
|
+
chapter: chaptersById[section.chapterId]
|
|
751
|
+
}));
|
|
752
|
+
}, [chaptersById, sections]);
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
/**
|
|
756
|
+
* Returns a nested data structure representing the content elements
|
|
757
|
+
* of section.
|
|
758
|
+
*
|
|
759
|
+
* @param {Object} options
|
|
760
|
+
* @param {number} options.sectionPermaId
|
|
761
|
+
*
|
|
762
|
+
* @private
|
|
763
|
+
*
|
|
764
|
+
* @example
|
|
765
|
+
*
|
|
766
|
+
* const section = useSection({sectionPermaId: 4});
|
|
767
|
+
* section // =>
|
|
768
|
+
* {
|
|
769
|
+
* id: 100,
|
|
770
|
+
* permaId: 4,
|
|
771
|
+
* chapterId: 1,
|
|
772
|
+
* transition: 'scroll'
|
|
773
|
+
* }
|
|
774
|
+
*/
|
|
775
|
+
function useSection({
|
|
776
|
+
sectionPermaId
|
|
777
|
+
}) {
|
|
778
|
+
const section = useEntryStateCollectionItem('sections', sectionPermaId);
|
|
779
|
+
return sectionData(section);
|
|
780
|
+
}
|
|
781
|
+
function sectionData(section) {
|
|
782
|
+
return section && {
|
|
783
|
+
permaId: section.permaId,
|
|
784
|
+
id: section.id,
|
|
785
|
+
chapterId: section.chapterId,
|
|
786
|
+
...normalizeSectionConfigurationData(section.configuration)
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
function normalizeSectionConfigurationData(configuration) {
|
|
790
|
+
return {
|
|
791
|
+
...configuration,
|
|
792
|
+
...(configuration.backdropType === 'contentElement' ? {
|
|
793
|
+
fullHeight: true
|
|
794
|
+
} : {})
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
function useSectionForegroundContentElements({
|
|
798
|
+
sectionId,
|
|
799
|
+
layout,
|
|
800
|
+
phoneLayout
|
|
801
|
+
}) {
|
|
802
|
+
const filter = useCallback(contentElement => contentElement.sectionId === sectionId && contentElement.configuration.position !== 'backdrop', [sectionId]);
|
|
803
|
+
const contentElements = useEntryStateCollectionItems('contentElements', filter);
|
|
804
|
+
return contentElements.map(contentElement => contentElementData(contentElement, layout, phoneLayout));
|
|
805
|
+
}
|
|
806
|
+
function useContentElement({
|
|
807
|
+
permaId,
|
|
808
|
+
layout
|
|
809
|
+
}) {
|
|
810
|
+
const contentElement = useEntryStateCollectionItem('contentElements', permaId);
|
|
811
|
+
return useMemo(() => contentElement && contentElementData(contentElement, layout), [contentElement, layout]);
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
/**
|
|
815
|
+
* Returns the perma id of the section a comment subject lives in: the
|
|
816
|
+
* subject's perma id itself for a section subject, the parent section's
|
|
817
|
+
* perma id for a content element subject. Persisted with new threads so
|
|
818
|
+
* they stay grouped under their section once the subject is deleted.
|
|
819
|
+
*
|
|
820
|
+
* @private
|
|
821
|
+
*/
|
|
822
|
+
function useSectionPermaIdOfSubject({
|
|
823
|
+
subjectType,
|
|
824
|
+
subjectId
|
|
825
|
+
}) {
|
|
826
|
+
const contentElement = useEntryStateCollectionItem('contentElements', subjectId);
|
|
827
|
+
const sections = useEntryStateCollectionItems('sections');
|
|
828
|
+
return useMemo(() => {
|
|
829
|
+
var _sections$find;
|
|
830
|
+
switch (subjectType) {
|
|
831
|
+
case 'Section':
|
|
832
|
+
return subjectId;
|
|
833
|
+
case 'ContentElement':
|
|
834
|
+
return (_sections$find = sections.find(section => section.id === (contentElement === null || contentElement === void 0 ? void 0 : contentElement.sectionId))) === null || _sections$find === void 0 ? void 0 : _sections$find.permaId;
|
|
835
|
+
default:
|
|
836
|
+
return undefined;
|
|
837
|
+
}
|
|
838
|
+
}, [subjectType, subjectId, contentElement, sections]);
|
|
839
|
+
}
|
|
840
|
+
function contentElementData(contentElement, layout, phoneLayout) {
|
|
841
|
+
const position = getPosition(contentElement, layout, phoneLayout);
|
|
842
|
+
return {
|
|
843
|
+
id: contentElement.id,
|
|
844
|
+
permaId: contentElement.permaId,
|
|
845
|
+
sectionId: contentElement.sectionId,
|
|
846
|
+
type: contentElement.typeName,
|
|
847
|
+
position,
|
|
848
|
+
width: getWidth(contentElement, position, phoneLayout),
|
|
849
|
+
standAlone: contentElement.configuration.position === 'standAlone',
|
|
850
|
+
alignment: contentElement.configuration.alignment,
|
|
851
|
+
props: contentElement.configuration
|
|
852
|
+
};
|
|
853
|
+
}
|
|
854
|
+
const supportedPositions = {
|
|
855
|
+
center: ['inline', 'left', 'right'],
|
|
856
|
+
centerRagged: ['inline', 'left', 'right'],
|
|
857
|
+
left: ['inline', 'side', 'sticky'],
|
|
858
|
+
right: ['inline', 'side', 'sticky'],
|
|
859
|
+
backdrop: ['backdrop']
|
|
860
|
+
};
|
|
861
|
+
function getPosition(contentElement, layout, phoneLayout) {
|
|
862
|
+
const position = contentElement.configuration.position;
|
|
863
|
+
if (contentElement.configuration.fullWidthInPhoneLayout && phoneLayout) {
|
|
864
|
+
return 'inline';
|
|
865
|
+
}
|
|
866
|
+
return supportedPositions[layout || 'left'].includes(position) ? position : 'inline';
|
|
867
|
+
}
|
|
868
|
+
const legacyPositionWidths = {
|
|
869
|
+
wide: 2,
|
|
870
|
+
full: 3
|
|
871
|
+
};
|
|
872
|
+
const clampedWidthPositions = ['sticky', 'left', 'right'];
|
|
873
|
+
function getWidth(contentElement, position, phoneLayout) {
|
|
874
|
+
const width = typeof contentElement.configuration.width === 'number' ? contentElement.configuration.width : legacyPositionWidths[contentElement.configuration.position] || 0;
|
|
875
|
+
if (contentElement.configuration.fullWidthInPhoneLayout && phoneLayout) {
|
|
876
|
+
return 3;
|
|
877
|
+
} else if (clampedWidthPositions.includes(position)) {
|
|
878
|
+
return Math.min(Math.max(width || 0, -2), 2);
|
|
879
|
+
} else {
|
|
880
|
+
return width;
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
function useChapter({
|
|
884
|
+
permaId
|
|
885
|
+
}) {
|
|
886
|
+
const chapters = useChapters();
|
|
887
|
+
return useMemo(() => chapters.find(chapter => chapter.permaId === permaId), [chapters, permaId]);
|
|
888
|
+
}
|
|
889
|
+
function useChapters() {
|
|
890
|
+
const chapters = useEntryStateCollectionItems('chapters');
|
|
891
|
+
return useMemo(() => {
|
|
892
|
+
const chapterSlugs = getChapterSlugs(chapters);
|
|
893
|
+
return chapters.map((chapter, index) => ({
|
|
894
|
+
id: chapter.id,
|
|
895
|
+
permaId: chapter.permaId,
|
|
896
|
+
storylineId: chapter.storylineId,
|
|
897
|
+
chapterSlug: chapterSlugs[chapter.permaId],
|
|
898
|
+
index,
|
|
899
|
+
...chapter.configuration
|
|
900
|
+
}));
|
|
901
|
+
}, [chapters]);
|
|
902
|
+
}
|
|
903
|
+
function useMainChapters() {
|
|
904
|
+
const chapters = useChapters();
|
|
905
|
+
const mainStoryline = useMainStoryline();
|
|
906
|
+
return useMemo(() => chapters.filter(chapter => chapter.storylineId === mainStoryline.id), [chapters, mainStoryline]);
|
|
907
|
+
}
|
|
908
|
+
function useMainStoryline() {
|
|
909
|
+
const storylines = useEntryStateCollectionItems('storylines');
|
|
910
|
+
return useMemo(() => storylines.find(storyline => storyline.configuration.main), [storylines]);
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
function extendFile(collectionName, file, config) {
|
|
914
|
+
if (!file) {
|
|
915
|
+
return null;
|
|
916
|
+
}
|
|
917
|
+
const variants = file.variants ? ['original', ...file.variants] : Object.keys(config.fileUrlTemplates[collectionName] || {});
|
|
918
|
+
return {
|
|
919
|
+
...file,
|
|
920
|
+
modelType: resolveModelType(collectionName, config.fileModelTypes),
|
|
921
|
+
urls: buildUrls(collectionName, file, variants, config.fileUrlTemplates),
|
|
922
|
+
variantWidths: computeVariantWidths(collectionName, file, variants)
|
|
923
|
+
};
|
|
924
|
+
}
|
|
925
|
+
function resolveModelType(collectionName, modelTypes) {
|
|
926
|
+
if (!modelTypes[collectionName]) {
|
|
927
|
+
throw new Error(`Could not find model type for collection name ${collectionName}`);
|
|
928
|
+
}
|
|
929
|
+
return modelTypes[collectionName];
|
|
930
|
+
}
|
|
931
|
+
function buildUrls(collectionName, file, variants, urlTemplates) {
|
|
932
|
+
if (!urlTemplates[collectionName]) {
|
|
933
|
+
throw new Error(`No file url templates found for ${collectionName}`);
|
|
934
|
+
}
|
|
935
|
+
return variants.reduce((result, variant) => {
|
|
936
|
+
const url = getFileUrl(collectionName, file, variant, urlTemplates);
|
|
937
|
+
if (url) {
|
|
938
|
+
result[variant] = url;
|
|
939
|
+
}
|
|
940
|
+
return result;
|
|
941
|
+
}, {});
|
|
942
|
+
}
|
|
943
|
+
function getFileUrl(collectionName, file, quality, urlTemplates) {
|
|
944
|
+
const templates = urlTemplates[collectionName];
|
|
945
|
+
const template = templates[quality];
|
|
946
|
+
if (template) {
|
|
947
|
+
return template.replace(':id_partition', idPartition(file.id)).replace(':basename', file.basename).replace(':extension', file.extension).replace(':processed_extension', file.processedExtension).replace(':pageflow_hls_qualities', () => hlsQualities(file));
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
function idPartition(id) {
|
|
951
|
+
return partition(pad(id, 9));
|
|
952
|
+
}
|
|
953
|
+
function partition(string, separator) {
|
|
954
|
+
return string.replace(/./g, function (c, i, a) {
|
|
955
|
+
return i && (a.length - i) % 3 === 0 ? '/' + c : c;
|
|
956
|
+
});
|
|
957
|
+
}
|
|
958
|
+
function pad(string, size) {
|
|
959
|
+
return (Array(size).fill(0).join('') + string).slice(-size);
|
|
960
|
+
}
|
|
961
|
+
function hlsQualities(file) {
|
|
962
|
+
return ['low', 'medium', 'high', 'fullhd', '4k'].filter(quality => file.variants.includes(quality)).join(',');
|
|
963
|
+
}
|
|
964
|
+
const variantGeometries = {
|
|
965
|
+
imageFiles: {
|
|
966
|
+
medium: 1024,
|
|
967
|
+
large: 1920,
|
|
968
|
+
ultra: 3840
|
|
969
|
+
}
|
|
970
|
+
};
|
|
971
|
+
function computeVariantWidths(collectionName, file, variants) {
|
|
972
|
+
const geometries = variantGeometries[collectionName];
|
|
973
|
+
if (!geometries) {
|
|
974
|
+
return undefined;
|
|
975
|
+
}
|
|
976
|
+
const widthToVariant = {};
|
|
977
|
+
variants.forEach(variant => {
|
|
978
|
+
const geometrySize = geometries[variant];
|
|
979
|
+
if (geometrySize) {
|
|
980
|
+
const key = variantWidth(file, geometrySize) + 'w';
|
|
981
|
+
if (!widthToVariant[key]) {
|
|
982
|
+
widthToVariant[key] = variant;
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
});
|
|
986
|
+
return Object.entries(widthToVariant);
|
|
987
|
+
}
|
|
988
|
+
function variantWidth(file, geometrySize) {
|
|
989
|
+
const {
|
|
990
|
+
width,
|
|
991
|
+
height
|
|
992
|
+
} = file;
|
|
993
|
+
if (!width || !height) {
|
|
994
|
+
return geometrySize;
|
|
995
|
+
}
|
|
996
|
+
const scale = Math.min(geometrySize / width, geometrySize / height, 1);
|
|
997
|
+
return Math.round(width * scale);
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
/**
|
|
1001
|
+
* Look up a file by its collection and perma id.
|
|
1002
|
+
*
|
|
1003
|
+
* @param {Object} options
|
|
1004
|
+
* @param {String} options.collectionName - Collection name of file type to look for (in camel case).
|
|
1005
|
+
* @param {String} options.permaId - Perma id of file look up
|
|
1006
|
+
*
|
|
1007
|
+
* @example
|
|
1008
|
+
* const imageFile = useFile({collectionName: 'imageFiles', permaId: 5});
|
|
1009
|
+
* imageFile // =>
|
|
1010
|
+
* {
|
|
1011
|
+
* id: 102,
|
|
1012
|
+
* permaId: 5,
|
|
1013
|
+
* width: 1000,
|
|
1014
|
+
* height: 500,
|
|
1015
|
+
* urls: {
|
|
1016
|
+
* large: 'https://...'
|
|
1017
|
+
* },
|
|
1018
|
+
* configuration: {
|
|
1019
|
+
* alt: '...'
|
|
1020
|
+
* }
|
|
1021
|
+
* }
|
|
1022
|
+
*/
|
|
1023
|
+
function useFile({
|
|
1024
|
+
collectionName,
|
|
1025
|
+
permaId
|
|
1026
|
+
}) {
|
|
1027
|
+
const file = useEntryStateCollectionItem(collectionName, permaId);
|
|
1028
|
+
const config = useEntryStateConfig();
|
|
1029
|
+
return useMemo(() => extendFile(collectionName, file, config), [collectionName, file, config]);
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
function useFileWithInlineRights({
|
|
1033
|
+
configuration,
|
|
1034
|
+
collectionName,
|
|
1035
|
+
propertyName
|
|
1036
|
+
}) {
|
|
1037
|
+
const file = useFile({
|
|
1038
|
+
collectionName,
|
|
1039
|
+
permaId: configuration[propertyName]
|
|
1040
|
+
});
|
|
1041
|
+
const config = useEntryStateConfig();
|
|
1042
|
+
return file && {
|
|
1043
|
+
...file,
|
|
1044
|
+
license: file.configuration.license && config.fileLicenses[file.configuration.license],
|
|
1045
|
+
inlineRights: file.configuration.rights_display === 'inline' && !configuration[propertyName === 'id' ? 'inlineRightsHidden' : `${propertyName.replace('Id', '')}InlineRightsHidden`]
|
|
1046
|
+
};
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
function useDownloadableFile(options) {
|
|
1050
|
+
const file = useFile(options);
|
|
1051
|
+
return file && {
|
|
1052
|
+
...file,
|
|
1053
|
+
urls: {
|
|
1054
|
+
...file.urls,
|
|
1055
|
+
download: `${file.urls.original}?download=${encodeURIComponent(file.displayName)}`
|
|
1056
|
+
}
|
|
1057
|
+
};
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
function useNestedFiles({
|
|
1061
|
+
collectionName,
|
|
1062
|
+
parent
|
|
1063
|
+
}) {
|
|
1064
|
+
const config = useEntryStateConfig();
|
|
1065
|
+
const files = useEntryStateCollectionItems(collectionName, file => {
|
|
1066
|
+
return parent && file.parentFileId === parent.id && file.parentFileModelType === parent.modelType;
|
|
1067
|
+
});
|
|
1068
|
+
return files.map(file => extendFile(collectionName, file, config));
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
function getFileUrlTemplateHost(seed, collectionName, variant) {
|
|
1072
|
+
const hlsUrlTemplate = seed.config.fileUrlTemplates[collectionName][variant];
|
|
1073
|
+
return hlsUrlTemplate.split('//')[1].split('/')[0];
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
/**
|
|
1077
|
+
* Find the file references a configuration contains.
|
|
1078
|
+
*
|
|
1079
|
+
* @param {Object} options
|
|
1080
|
+
* @param {Array} options.locations - File reference locations of the subject's schema.
|
|
1081
|
+
* @param {Object} options.configuration - Configuration to read perma ids from.
|
|
1082
|
+
*
|
|
1083
|
+
* @private
|
|
1084
|
+
*/
|
|
1085
|
+
function collectFileReferences({
|
|
1086
|
+
locations,
|
|
1087
|
+
configuration
|
|
1088
|
+
}) {
|
|
1089
|
+
return locations.flatMap(location => valuesAt(configuration, location.path).map(({
|
|
1090
|
+
path,
|
|
1091
|
+
value
|
|
1092
|
+
}) => ({
|
|
1093
|
+
path,
|
|
1094
|
+
permaId: toPermaId(value)
|
|
1095
|
+
})).filter(({
|
|
1096
|
+
permaId
|
|
1097
|
+
}) => permaId).map(({
|
|
1098
|
+
path,
|
|
1099
|
+
permaId
|
|
1100
|
+
}) => ({
|
|
1101
|
+
collectionName: location.collection,
|
|
1102
|
+
permaId,
|
|
1103
|
+
path,
|
|
1104
|
+
active: isActive(location.activeIf, configuration)
|
|
1105
|
+
})));
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
// Backdrops of legacy sections store a color in the property that
|
|
1109
|
+
// otherwise holds an image perma id.
|
|
1110
|
+
function toPermaId(value) {
|
|
1111
|
+
const permaId = Number(value);
|
|
1112
|
+
return Number.isInteger(permaId) && permaId > 0 ? permaId : null;
|
|
1113
|
+
}
|
|
1114
|
+
function valuesAt(value, path, resolvedPath = []) {
|
|
1115
|
+
if (value === null || value === undefined) {
|
|
1116
|
+
return [];
|
|
1117
|
+
}
|
|
1118
|
+
if (!path.length) {
|
|
1119
|
+
return [{
|
|
1120
|
+
path: resolvedPath,
|
|
1121
|
+
value
|
|
1122
|
+
}];
|
|
1123
|
+
}
|
|
1124
|
+
const [segment, ...rest] = path;
|
|
1125
|
+
if (segment === '*') {
|
|
1126
|
+
return Object.entries(value).flatMap(([key, item]) => valuesAt(item, rest, [...resolvedPath, key]));
|
|
1127
|
+
}
|
|
1128
|
+
return valuesAt(value[segment], rest, [...resolvedPath, segment]);
|
|
1129
|
+
}
|
|
1130
|
+
function isActive(activeIf, configuration) {
|
|
1131
|
+
if (!activeIf) {
|
|
1132
|
+
return true;
|
|
1133
|
+
}
|
|
1134
|
+
return [activeIf].flat().every(condition => matches(condition, configuration));
|
|
1135
|
+
}
|
|
1136
|
+
function matches(condition, configuration) {
|
|
1137
|
+
const value = valueAt(configuration, condition.path);
|
|
1138
|
+
if ('present' in condition) {
|
|
1139
|
+
return (value !== null && value !== undefined) === condition.present;
|
|
1140
|
+
}
|
|
1141
|
+
if ('not' in condition) {
|
|
1142
|
+
return ![condition.not].flat().includes(value);
|
|
1143
|
+
}
|
|
1144
|
+
return [condition.value].flat().includes(value);
|
|
1145
|
+
}
|
|
1146
|
+
function valueAt(configuration, path) {
|
|
1147
|
+
return path.reduce((value, segment) => value === null || value === void 0 ? void 0 : value[segment], configuration);
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
/**
|
|
1151
|
+
* Find the file references of an entry, indexed by file.
|
|
1152
|
+
*
|
|
1153
|
+
* @param {Object} options
|
|
1154
|
+
* @param {Object} options.collections - Entry state collections.
|
|
1155
|
+
* @param {Object} options.locations - File reference locations by subject.
|
|
1156
|
+
* @param {Object} options.fileModelTypes - Model type by file collection name.
|
|
1157
|
+
*
|
|
1158
|
+
* @example
|
|
1159
|
+
*
|
|
1160
|
+
* const references = collectEntryFileReferences({collections, locations, fileModelTypes});
|
|
1161
|
+
* references.of('imageFiles', 5)
|
|
1162
|
+
* // => [{subject: {model: 'section', permaId: 12},
|
|
1163
|
+
* // path: ['backdrop', 'image'], active: true}]
|
|
1164
|
+
*
|
|
1165
|
+
* @private
|
|
1166
|
+
*/
|
|
1167
|
+
function collectEntryFileReferences({
|
|
1168
|
+
collections,
|
|
1169
|
+
locations,
|
|
1170
|
+
fileModelTypes
|
|
1171
|
+
}) {
|
|
1172
|
+
const references = {};
|
|
1173
|
+
subjects(collections, locations).forEach(subject => collectFileReferences({
|
|
1174
|
+
locations: subject.locations,
|
|
1175
|
+
configuration: subject.configuration
|
|
1176
|
+
}).forEach(reference => add(references, reference, subject.subject)));
|
|
1177
|
+
addNestedFileReferences(references, collections, fileModelTypes);
|
|
1178
|
+
return {
|
|
1179
|
+
of(collectionName, permaId) {
|
|
1180
|
+
return references[key({
|
|
1181
|
+
collectionName,
|
|
1182
|
+
permaId
|
|
1183
|
+
})] || [];
|
|
1184
|
+
}
|
|
1185
|
+
};
|
|
1186
|
+
}
|
|
1187
|
+
function subjects({
|
|
1188
|
+
entries = [],
|
|
1189
|
+
sections = [],
|
|
1190
|
+
contentElements = []
|
|
1191
|
+
}, locations) {
|
|
1192
|
+
const bySectionId = groupBySectionId(contentElements);
|
|
1193
|
+
return [...entries.map(entry => ({
|
|
1194
|
+
subject: {
|
|
1195
|
+
model: 'entry'
|
|
1196
|
+
},
|
|
1197
|
+
configuration: entry,
|
|
1198
|
+
locations: locations.entry || []
|
|
1199
|
+
})), ...sections.flatMap(section => [{
|
|
1200
|
+
subject: {
|
|
1201
|
+
model: 'section',
|
|
1202
|
+
permaId: section.permaId
|
|
1203
|
+
},
|
|
1204
|
+
configuration: section.configuration,
|
|
1205
|
+
locations: locations.sections || []
|
|
1206
|
+
}, ...(bySectionId[section.id] || []).map(contentElement => ({
|
|
1207
|
+
subject: {
|
|
1208
|
+
model: 'contentElement',
|
|
1209
|
+
permaId: contentElement.permaId
|
|
1210
|
+
},
|
|
1211
|
+
configuration: contentElement.configuration,
|
|
1212
|
+
locations: (locations.contentElements || {})[contentElement.typeName] || []
|
|
1213
|
+
}))])];
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
// Text tracks and the like are not referenced by any configuration.
|
|
1217
|
+
function addNestedFileReferences(references, collections, fileModelTypes = {}) {
|
|
1218
|
+
const filesById = indexFilesById(collections, fileModelTypes);
|
|
1219
|
+
Object.keys(fileModelTypes).forEach(collectionName => (collections[collectionName] || []).forEach(file => {
|
|
1220
|
+
var _references$key;
|
|
1221
|
+
if (!file.parentFileId) {
|
|
1222
|
+
return;
|
|
1223
|
+
}
|
|
1224
|
+
const parent = filesById[modelTypeAndId(file.parentFileModelType, file.parentFileId)];
|
|
1225
|
+
(_references$key = references[key(parent || {})]) === null || _references$key === void 0 ? void 0 : _references$key.forEach(({
|
|
1226
|
+
subject,
|
|
1227
|
+
path,
|
|
1228
|
+
active
|
|
1229
|
+
}) => add(references, {
|
|
1230
|
+
collectionName,
|
|
1231
|
+
permaId: file.permaId,
|
|
1232
|
+
path,
|
|
1233
|
+
active
|
|
1234
|
+
}, subject));
|
|
1235
|
+
}));
|
|
1236
|
+
}
|
|
1237
|
+
function indexFilesById(collections, fileModelTypes) {
|
|
1238
|
+
return Object.entries(fileModelTypes).reduce((result, [collectionName, modelType]) => {
|
|
1239
|
+
(collections[collectionName] || []).forEach(file => {
|
|
1240
|
+
result[modelTypeAndId(modelType, file.id)] = {
|
|
1241
|
+
collectionName,
|
|
1242
|
+
permaId: file.permaId
|
|
1243
|
+
};
|
|
1244
|
+
});
|
|
1245
|
+
return result;
|
|
1246
|
+
}, {});
|
|
1247
|
+
}
|
|
1248
|
+
function add(references, {
|
|
1249
|
+
collectionName,
|
|
1250
|
+
permaId,
|
|
1251
|
+
path,
|
|
1252
|
+
active
|
|
1253
|
+
}, subject) {
|
|
1254
|
+
const list = references[key({
|
|
1255
|
+
collectionName,
|
|
1256
|
+
permaId
|
|
1257
|
+
})] = references[key({
|
|
1258
|
+
collectionName,
|
|
1259
|
+
permaId
|
|
1260
|
+
})] || [];
|
|
1261
|
+
list.push({
|
|
1262
|
+
subject,
|
|
1263
|
+
path,
|
|
1264
|
+
active
|
|
1265
|
+
});
|
|
1266
|
+
}
|
|
1267
|
+
function groupBySectionId(contentElements) {
|
|
1268
|
+
return contentElements.reduce((result, contentElement) => {
|
|
1269
|
+
(result[contentElement.sectionId] = result[contentElement.sectionId] || []).push(contentElement);
|
|
1270
|
+
return result;
|
|
1271
|
+
}, {});
|
|
1272
|
+
}
|
|
1273
|
+
function key({
|
|
1274
|
+
collectionName,
|
|
1275
|
+
permaId
|
|
1276
|
+
}) {
|
|
1277
|
+
return `${collectionName}/${permaId}`;
|
|
1278
|
+
}
|
|
1279
|
+
function modelTypeAndId(modelType, id) {
|
|
1280
|
+
return `${modelType}/${id}`;
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
/**
|
|
1284
|
+
* Returns the file references of the entry, indexed by file.
|
|
1285
|
+
*
|
|
1286
|
+
* @example
|
|
1287
|
+
*
|
|
1288
|
+
* const references = useFileReferences();
|
|
1289
|
+
* references.of('imageFiles', 5)
|
|
1290
|
+
* // => [{subject: {model: 'section', permaId: 12}, active: true}]
|
|
1291
|
+
*
|
|
1292
|
+
* @private
|
|
1293
|
+
*/
|
|
1294
|
+
function useFileReferences() {
|
|
1295
|
+
const config = useEntryStateConfig();
|
|
1296
|
+
const entry = useEntryMetadata();
|
|
1297
|
+
const sections = useEntryStateCollectionItems('sections');
|
|
1298
|
+
const contentElements = useEntryStateCollectionItems('contentElements');
|
|
1299
|
+
const collectionNames = useMemo(() => Object.keys(config.fileModelTypes), [config]);
|
|
1300
|
+
const files = useMultipleEntryStateCollectionItems(collectionNames);
|
|
1301
|
+
return useMemo(() => collectEntryFileReferences({
|
|
1302
|
+
collections: {
|
|
1303
|
+
entries: [entry],
|
|
1304
|
+
sections,
|
|
1305
|
+
contentElements,
|
|
1306
|
+
...files
|
|
1307
|
+
},
|
|
1308
|
+
locations: config.fileReferenceLocations || {},
|
|
1309
|
+
fileModelTypes: config.fileModelTypes
|
|
1310
|
+
}), [config, entry, sections, contentElements, files]);
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
/**
|
|
1314
|
+
* Returns a function telling whether a file is referenced in the entry.
|
|
1315
|
+
*
|
|
1316
|
+
* Considers every file referenced unless the file_rights_from_references
|
|
1317
|
+
* feature is enabled, since schemas do not cover all content element
|
|
1318
|
+
* types yet.
|
|
1319
|
+
*
|
|
1320
|
+
* @example
|
|
1321
|
+
*
|
|
1322
|
+
* const isFileReferenced = useIsFileReferenced();
|
|
1323
|
+
* isFileReferenced('imageFiles', 5) // => true
|
|
1324
|
+
*
|
|
1325
|
+
* @private
|
|
1326
|
+
*/
|
|
1327
|
+
function useIsFileReferenced() {
|
|
1328
|
+
const references = useFileReferences();
|
|
1329
|
+
return useMemo(() => {
|
|
1330
|
+
if (!features.isEnabled('file_rights_from_references')) {
|
|
1331
|
+
return () => true;
|
|
1332
|
+
}
|
|
1333
|
+
return (collectionName, permaId) => references.of(collectionName, permaId).some(({
|
|
1334
|
+
active
|
|
1335
|
+
}) => active);
|
|
1336
|
+
}, [references]);
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
/**
|
|
1340
|
+
* Returns a collection of rights and source urls of the files
|
|
1341
|
+
* referenced in the entry. If none of the files has a rights attribute
|
|
1342
|
+
* configured, it falls back to the default file rights of the
|
|
1343
|
+
* entry's site, otherwise returns an empty array.
|
|
1344
|
+
*
|
|
1345
|
+
* Lists all files of the entry unless the file_rights_from_references
|
|
1346
|
+
* feature is enabled, since schemas do not cover all content element
|
|
1347
|
+
* types yet.
|
|
1348
|
+
*
|
|
1349
|
+
* @example
|
|
1350
|
+
*
|
|
1351
|
+
* const fileRights = useFileRights();
|
|
1352
|
+
* fileRights // => [{text: 'author of image 1', urls: ['https://example.com/source-url']}]
|
|
1353
|
+
*/
|
|
1354
|
+
function useFileRights() {
|
|
1355
|
+
var _config$defaultFileRi;
|
|
1356
|
+
const config = useEntryStateConfig();
|
|
1357
|
+
const fileCollectionNames = Object.keys(config.fileModelTypes);
|
|
1358
|
+
const files = useMultipleEntryStateCollectionItems(fileCollectionNames);
|
|
1359
|
+
const isFileReferenced = useIsFileReferenced();
|
|
1360
|
+
const defaultFileRights = (_config$defaultFileRi = config.defaultFileRights) === null || _config$defaultFileRi === void 0 ? void 0 : _config$defaultFileRi.trim();
|
|
1361
|
+
const items = {};
|
|
1362
|
+
Object.keys(files).forEach(collectionName => files[collectionName].filter(file => file.configuration.rights_display !== 'inline').filter(file => isFileReferenced(collectionName, file.permaId)).forEach(file => {
|
|
1363
|
+
var _file$rights;
|
|
1364
|
+
const text = ((_file$rights = file.rights) === null || _file$rights === void 0 ? void 0 : _file$rights.trim()) || defaultFileRights;
|
|
1365
|
+
if (text) {
|
|
1366
|
+
var _file$configuration$s;
|
|
1367
|
+
items[text] = items[text] || {
|
|
1368
|
+
text,
|
|
1369
|
+
urls: new Set()
|
|
1370
|
+
};
|
|
1371
|
+
if ((_file$configuration$s = file.configuration.source_url) === null || _file$configuration$s === void 0 ? void 0 : _file$configuration$s.trim()) {
|
|
1372
|
+
items[text].urls.add(file.configuration.source_url);
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
}));
|
|
1376
|
+
return Object.values(items).map(item => ({
|
|
1377
|
+
...item,
|
|
1378
|
+
urls: Array.from(item.urls).sort()
|
|
1379
|
+
})).sort((a, b) => a.text.localeCompare(b.text));
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
/**
|
|
1383
|
+
* Returns a nested data structure representing the legal info of the entry.
|
|
1384
|
+
* Each legal info is separated into label and url to use in links.
|
|
1385
|
+
* Both label and url can be blank, depending on the configuration.
|
|
1386
|
+
*
|
|
1387
|
+
* @example
|
|
1388
|
+
*
|
|
1389
|
+
* const legalInfo = useLegalInfo();
|
|
1390
|
+
* legalInfo // =>
|
|
1391
|
+
* {
|
|
1392
|
+
* imprint: {
|
|
1393
|
+
* label: '',
|
|
1394
|
+
* url: ''
|
|
1395
|
+
* },
|
|
1396
|
+
* copyright: {
|
|
1397
|
+
* label: '',
|
|
1398
|
+
* url: ''
|
|
1399
|
+
* },
|
|
1400
|
+
* privacy: {
|
|
1401
|
+
* label: '',
|
|
1402
|
+
* url: ''
|
|
1403
|
+
* }
|
|
1404
|
+
* }
|
|
1405
|
+
*/
|
|
1406
|
+
function useLegalInfo() {
|
|
1407
|
+
const config = useEntryStateConfig();
|
|
1408
|
+
return config.legalInfo;
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
/**
|
|
1412
|
+
* Returns the credits string (rich text) of the entry.
|
|
1413
|
+
*
|
|
1414
|
+
* @example
|
|
1415
|
+
*
|
|
1416
|
+
* const credits = useCredits();
|
|
1417
|
+
* credits // => "Credits: <a href="http://pageflow.com">pageflow.com</a>"
|
|
1418
|
+
*/
|
|
1419
|
+
function useCredits() {
|
|
1420
|
+
const entryMetadata = useEntryMetadata();
|
|
1421
|
+
let credits = '';
|
|
1422
|
+
if (entryMetadata) {
|
|
1423
|
+
credits = entryMetadata.credits;
|
|
1424
|
+
}
|
|
1425
|
+
return credits;
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
const qualities = ['medium', 'fullhd', '4k'];
|
|
1429
|
+
function useAvailableQualities(file) {
|
|
1430
|
+
if (!file) {
|
|
1431
|
+
return [];
|
|
1432
|
+
}
|
|
1433
|
+
return ['auto', ...qualities.filter(name => file.variants.includes(name))];
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
function useWidget({
|
|
1437
|
+
role
|
|
1438
|
+
}) {
|
|
1439
|
+
const widgets = useEntryStateCollectionItems('widgets');
|
|
1440
|
+
return useMemo(() => widgets.find(widget => widget.role === role && widget.typeName), [role, widgets]);
|
|
1441
|
+
}
|
|
1442
|
+
function useActiveWidgets() {
|
|
1443
|
+
const widgets = useEntryStateCollectionItems('widgets');
|
|
1444
|
+
return useMemo(() => widgets.filter(widget => widget.typeName), [widgets]);
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
/**
|
|
1448
|
+
* Entry state collections of an entry, taken from its Backbone models
|
|
1449
|
+
* once. Use `watchCollections` instead to keep collections in sync.
|
|
1450
|
+
*
|
|
1451
|
+
* @private
|
|
1452
|
+
*/
|
|
1453
|
+
function collectionsSnapshot(entry) {
|
|
1454
|
+
return collectionSpecs(entry).reduce((result, {
|
|
1455
|
+
collection,
|
|
1456
|
+
name,
|
|
1457
|
+
...spec
|
|
1458
|
+
}) => {
|
|
1459
|
+
result[name] = collectionSnapshot(collection, spec);
|
|
1460
|
+
return result;
|
|
1461
|
+
}, {});
|
|
1462
|
+
}
|
|
1463
|
+
function watchCollections(entry, {
|
|
1464
|
+
dispatch
|
|
1465
|
+
}) {
|
|
1466
|
+
const teardownFns = collectionSpecs(entry).map(({
|
|
1467
|
+
collection,
|
|
1468
|
+
...spec
|
|
1469
|
+
}) => watchCollection(collection, {
|
|
1470
|
+
...spec,
|
|
1471
|
+
dispatch
|
|
1472
|
+
}));
|
|
1473
|
+
return function () {
|
|
1474
|
+
teardownFns.forEach(fn => fn());
|
|
1475
|
+
};
|
|
1476
|
+
}
|
|
1477
|
+
function collectionSpecs(entry) {
|
|
1478
|
+
const {
|
|
1479
|
+
storylines,
|
|
1480
|
+
chapters,
|
|
1481
|
+
sections,
|
|
1482
|
+
contentElements,
|
|
1483
|
+
widgets,
|
|
1484
|
+
files
|
|
1485
|
+
} = entry;
|
|
1486
|
+
return [{
|
|
1487
|
+
collection: new window.Backbone.Collection([entry.metadata]),
|
|
1488
|
+
name: 'entries',
|
|
1489
|
+
attributes: ['locale', {
|
|
1490
|
+
id: () => entry.id
|
|
1491
|
+
}, {
|
|
1492
|
+
permaId: () => entry.id
|
|
1493
|
+
},
|
|
1494
|
+
// Make sure key attribute is present
|
|
1495
|
+
{
|
|
1496
|
+
shareProviders: 'share_providers'
|
|
1497
|
+
}, {
|
|
1498
|
+
shareUrl: 'share_url'
|
|
1499
|
+
}, {
|
|
1500
|
+
shareImageId: 'share_image_id'
|
|
1501
|
+
}, 'credits'],
|
|
1502
|
+
keyAttribute: 'permaId',
|
|
1503
|
+
includeConfiguration: true
|
|
1504
|
+
}, {
|
|
1505
|
+
collection: storylines,
|
|
1506
|
+
name: 'storylines',
|
|
1507
|
+
attributes: ['id', 'permaId'],
|
|
1508
|
+
keyAttribute: 'permaId',
|
|
1509
|
+
includeConfiguration: true
|
|
1510
|
+
}, {
|
|
1511
|
+
collection: chapters,
|
|
1512
|
+
name: 'chapters',
|
|
1513
|
+
attributes: ['id', 'permaId', 'storylineId'],
|
|
1514
|
+
keyAttribute: 'permaId',
|
|
1515
|
+
includeConfiguration: true
|
|
1516
|
+
}, {
|
|
1517
|
+
collection: sections,
|
|
1518
|
+
name: 'sections',
|
|
1519
|
+
attributes: ['id', 'permaId', 'chapterId'],
|
|
1520
|
+
keyAttribute: 'permaId',
|
|
1521
|
+
includeConfiguration: true
|
|
1522
|
+
}, {
|
|
1523
|
+
collection: contentElements,
|
|
1524
|
+
name: 'contentElements',
|
|
1525
|
+
attributes: ['id', 'permaId', 'typeName', 'sectionId'],
|
|
1526
|
+
keyAttribute: 'permaId',
|
|
1527
|
+
includeConfiguration: true
|
|
1528
|
+
}, {
|
|
1529
|
+
// Only sync widgets whose frontend packs the server loads in the editor.
|
|
1530
|
+
// Editor-disabled widgets have no registered widget type here, so rendering
|
|
1531
|
+
// them in the preview would fail.
|
|
1532
|
+
collection: widgets.withWidgetType({
|
|
1533
|
+
insertPoint: 'react',
|
|
1534
|
+
enabledInEditor: true
|
|
1535
|
+
}),
|
|
1536
|
+
name: 'widgets',
|
|
1537
|
+
attributes: [{
|
|
1538
|
+
typeName: 'type_name'
|
|
1539
|
+
}, 'role', {
|
|
1540
|
+
permaId: 'role'
|
|
1541
|
+
}],
|
|
1542
|
+
keyAttribute: 'permaId',
|
|
1543
|
+
includeConfiguration: true
|
|
1544
|
+
}, ...Object.keys(files).map(collectionName => ({
|
|
1545
|
+
collection: files[collectionName],
|
|
1546
|
+
name: camelize(collectionName),
|
|
1547
|
+
attributes: ['id', {
|
|
1548
|
+
permaId: 'perma_id'
|
|
1549
|
+
}, 'width', 'height', 'basename', 'extension', 'rights', {
|
|
1550
|
+
displayName: ['display_name', 'file_name', (displayName, fileName) => displayName || fileName]
|
|
1551
|
+
}, {
|
|
1552
|
+
processedExtension: 'processed_extension'
|
|
1553
|
+
}, {
|
|
1554
|
+
isReady: 'is_ready'
|
|
1555
|
+
}, {
|
|
1556
|
+
variants: variants => variants && variants.map(variant => camelize(variant))
|
|
1557
|
+
}, {
|
|
1558
|
+
durationInMs: 'duration_in_ms'
|
|
1559
|
+
}, {
|
|
1560
|
+
parentFileId: 'parent_file_id'
|
|
1561
|
+
}, {
|
|
1562
|
+
parentFileModelType: 'parent_file_model_type'
|
|
1563
|
+
}],
|
|
1564
|
+
keyAttribute: 'permaId',
|
|
1565
|
+
includeConfiguration: true
|
|
1566
|
+
}))];
|
|
1567
|
+
}
|
|
1568
|
+
function camelize(snakeCase) {
|
|
1569
|
+
return snakeCase.replace(/_[a-z]/g, function (match) {
|
|
1570
|
+
return match[1].toUpperCase();
|
|
1571
|
+
});
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
function updateContentElementConfiguration({
|
|
1575
|
+
dispatch,
|
|
1576
|
+
permaId,
|
|
1577
|
+
configuration
|
|
1578
|
+
}) {
|
|
1579
|
+
updateConfiguration({
|
|
1580
|
+
dispatch,
|
|
1581
|
+
name: 'contentElements',
|
|
1582
|
+
key: permaId,
|
|
1583
|
+
configuration
|
|
1584
|
+
});
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
function updateWidgetConfiguration({
|
|
1588
|
+
dispatch,
|
|
1589
|
+
role,
|
|
1590
|
+
configuration
|
|
1591
|
+
}) {
|
|
1592
|
+
updateConfiguration({
|
|
1593
|
+
dispatch,
|
|
1594
|
+
name: 'widgets',
|
|
1595
|
+
key: role,
|
|
1596
|
+
configuration
|
|
1597
|
+
});
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
export { EntryStateProvider, collectionsSnapshot, getFileUrlTemplateHost, normalizeSectionConfigurationData, updateContentElementConfiguration, updateWidgetConfiguration, useActiveWidgets, useAdditionalSeedData, useAvailableQualities, useChapter, useChapters, useContentElement, useContentElementConsentVendor, useCredits, useCutOff, useDarkWidgets, useDownloadableFile, useEmbedOriginUrl, useEntryMetadata, useEntryStateDispatch, useEntryStructure, useEntryStructureWithContentElements, useEntryTranslations, useFile, useFileReferences, useFileRights, useFileWithInlineRights, useIsFileReferenced, useLegalInfo, useMainChapters, useMainStoryline, useNestedFiles, useSection, useSectionForegroundContentElements, useSectionPermaIdOfSubject, useSectionsWithChapter, useShareProviders, useShareUrl, useTheme, useWidget, watchCollections };
|