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
data/package/ui.js CHANGED
@@ -3,9 +3,9 @@ import _ from 'underscore';
3
3
  import $ from 'jquery';
4
4
  import I18n$1 from 'i18n-js';
5
5
  import Backbone from 'backbone';
6
+ import Sortable from 'sortablejs';
6
7
  import ChildViewContainer from 'backbone.babysitter';
7
8
  import IScroll from 'iscroll';
8
- import 'jquery.minicolors';
9
9
  import wysihtml5 from 'wysihtml5';
10
10
  import 'jquery-ui';
11
11
  import Cocktail from 'cocktail';
@@ -16,15 +16,12 @@ Marionette.Renderer.render = function (template, data) {
16
16
  if (_.isFunction(template)) {
17
17
  return template(data);
18
18
  }
19
-
20
19
  if (template.indexOf('templates/') === 0) {
21
20
  template = 'pageflow/editor/' + template;
22
21
  }
23
-
24
22
  if (!JST[template]) {
25
23
  throw "Template '" + template + "' not found!";
26
24
  }
27
-
28
25
  return JST[template](data);
29
26
  };
30
27
 
@@ -50,22 +47,19 @@ Marionette.Renderer.render = function (template, data) {
50
47
  * @memberof i18nUtils
51
48
  * @since 12.0
52
49
  */
53
-
54
50
  function attributeTranslationKeys(attributeName, keyName, options) {
55
51
  var result = [];
56
-
57
52
  if (options.prefixes) {
58
53
  result = result.concat(_(options.prefixes).map(function (prefix) {
59
54
  return prefix + '.' + attributeName + '.' + keyName;
60
55
  }, this));
61
56
  }
62
-
63
57
  if (options && options.fallbackPrefix) {
64
58
  result.push(options.fallbackPrefix + '.' + options.fallbackModelI18nKey + '.' + attributeName);
65
59
  }
66
-
67
60
  return result;
68
61
  }
62
+
69
63
  /**
70
64
  * Takes the same parameters as {@link
71
65
  * #i18nutilsattributetranslationkeys attributeTranslationKeys}, but returns the first existing
@@ -75,10 +69,10 @@ function attributeTranslationKeys(attributeName, keyName, options) {
75
69
  * @memberof i18nUtils
76
70
  * @since 12.0
77
71
  */
78
-
79
72
  function attributeTranslation(attributeName, keyName, options) {
80
73
  return findTranslation(attributeTranslationKeys(attributeName, keyName, options));
81
74
  }
75
+
82
76
  /**
83
77
  * Find the first key for which a translation exists and return the
84
78
  * translation.
@@ -97,19 +91,15 @@ function attributeTranslation(attributeName, keyName, options) {
97
91
  * @memberof i18nUtils
98
92
  * @return {string}
99
93
  */
100
-
101
94
  function findTranslation(keys, options) {
102
95
  options = options || {};
103
-
104
96
  if (options.html) {
105
97
  keys = translationKeysWithSuffix(keys, 'html');
106
98
  }
107
-
108
99
  return _.chain(keys).reverse().reduce(function (result, key) {
109
100
  var unescapedTranslation = I18n$1.t(key, _.extend({}, options, {
110
101
  defaultValue: result
111
102
  }));
112
-
113
103
  if (!options.html || key.match(/_html$/) || result == unescapedTranslation) {
114
104
  return unescapedTranslation;
115
105
  } else {
@@ -117,6 +107,7 @@ function findTranslation(keys, options) {
117
107
  }
118
108
  }, options.defaultValue).value();
119
109
  }
110
+
120
111
  /**
121
112
  * Return the first key for which a translation exists. Returns the
122
113
  * first if non of the keys has a translation.
@@ -127,7 +118,6 @@ function findTranslation(keys, options) {
127
118
  * @memberof i18nUtils
128
119
  * @return {string}
129
120
  */
130
-
131
121
  function findKeyWithTranslation(keys) {
132
122
  var missing = '_not_translated';
133
123
  return _(keys).detect(function (key) {
@@ -151,64 +141,6 @@ var i18nUtils = /*#__PURE__*/Object.freeze({
151
141
  translationKeysWithSuffix: translationKeysWithSuffix
152
142
  });
153
143
 
154
- function _arrayWithHoles(arr) {
155
- if (Array.isArray(arr)) return arr;
156
- }
157
-
158
- function _iterableToArrayLimit(arr, i) {
159
- if (typeof Symbol === "undefined" || !(Symbol.iterator in Object(arr))) return;
160
- var _arr = [];
161
- var _n = true;
162
- var _d = false;
163
- var _e = undefined;
164
-
165
- try {
166
- for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {
167
- _arr.push(_s.value);
168
-
169
- if (i && _arr.length === i) break;
170
- }
171
- } catch (err) {
172
- _d = true;
173
- _e = err;
174
- } finally {
175
- try {
176
- if (!_n && _i["return"] != null) _i["return"]();
177
- } finally {
178
- if (_d) throw _e;
179
- }
180
- }
181
-
182
- return _arr;
183
- }
184
-
185
- function _arrayLikeToArray(arr, len) {
186
- if (len == null || len > arr.length) len = arr.length;
187
-
188
- for (var i = 0, arr2 = new Array(len); i < len; i++) {
189
- arr2[i] = arr[i];
190
- }
191
-
192
- return arr2;
193
- }
194
-
195
- function _unsupportedIterableToArray(o, minLen) {
196
- if (!o) return;
197
- if (typeof o === "string") return _arrayLikeToArray(o, minLen);
198
- var n = Object.prototype.toString.call(o).slice(8, -1);
199
- if (n === "Object" && o.constructor) n = o.constructor.name;
200
- if (n === "Map" || n === "Set") return Array.from(n);
201
- if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
202
- }
203
-
204
- function _nonIterableRest() {
205
- throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
206
- }
207
-
208
- function _slicedToArray(arr, i) {
209
- return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest();
210
- }
211
-
212
144
  /**
213
145
  * Create object that can be passed to Marionette ui property from CSS
214
146
  * module object.
@@ -248,16 +180,13 @@ function _slicedToArray(arr, i) {
248
180
  *
249
181
  * @memberof cssModulesUtils
250
182
  */
251
- function ui(styles) {
252
- for (var _len = arguments.length, classNames = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
253
- classNames[_key - 1] = arguments[_key];
254
- }
255
-
256
- return classNames.reduce(function (result, className) {
183
+ function ui(styles, ...classNames) {
184
+ return classNames.reduce((result, className) => {
257
185
  result[className] = selector(styles, className);
258
186
  return result;
259
187
  }, {});
260
188
  }
189
+
261
190
  /**
262
191
  * Create object that can be passed to Marionette events property from CSS
263
192
  * module object.
@@ -295,18 +224,14 @@ function ui(styles) {
295
224
  *
296
225
  * @memberof cssModulesUtils
297
226
  */
298
-
299
227
  function events(styles, mapping) {
300
- return Object.keys(mapping).reduce(function (result, key) {
301
- var _key$split = key.split(' '),
302
- _key$split2 = _slicedToArray(_key$split, 2),
303
- event = _key$split2[0],
304
- className = _key$split2[1];
305
-
306
- result["".concat(event, " ").concat(selector(styles, className))] = mapping[key];
228
+ return Object.keys(mapping).reduce((result, key) => {
229
+ const [event, className] = key.split(' ');
230
+ result[`${event} ${selector(styles, className)}`] = mapping[key];
307
231
  return result;
308
232
  }, {});
309
233
  }
234
+
310
235
  /**
311
236
  * Generates a CSS selector from a CSS module rule.
312
237
  *
@@ -319,15 +244,12 @@ function events(styles, mapping) {
319
244
  * @return {String} CSS Selector
320
245
  * @memberof cssModulesUtils
321
246
  */
322
-
323
247
  function selector(styles, className) {
324
- var classNames = styles[className];
325
-
248
+ const classNames = styles[className];
326
249
  if (!classNames) {
327
- throw new Error("Unknown class name ".concat(className, " in mapping. Knwon names: ").concat(Object.keys(styles).join(', '), "."));
250
+ throw new Error(`Unknown class name ${className} in mapping. Knwon names: ${Object.keys(styles).join(', ')}.`);
328
251
  }
329
-
330
- return ".".concat(classNames.replace(/ /g, '.'));
252
+ return `.${classNames.replace(/ /g, '.')}`;
331
253
  }
332
254
 
333
255
  var cssModulesUtils = /*#__PURE__*/Object.freeze({
@@ -337,46 +259,92 @@ var cssModulesUtils = /*#__PURE__*/Object.freeze({
337
259
  selector: selector
338
260
  });
339
261
 
262
+ function setup({
263
+ binding,
264
+ model,
265
+ listener,
266
+ callback,
267
+ normalize = value => value,
268
+ option,
269
+ bindingValue
270
+ }) {
271
+ if (binding) {
272
+ _.flatten([binding]).forEach(attribute => {
273
+ listener.listenTo(model, 'change:' + attribute, update);
274
+ });
275
+ }
276
+ update();
277
+ function update() {
278
+ callback(resolve({
279
+ binding,
280
+ model,
281
+ normalize,
282
+ option,
283
+ bindingValue
284
+ }));
285
+ }
286
+ }
287
+ function resolve({
288
+ binding,
289
+ model,
290
+ normalize = value => value,
291
+ option,
292
+ bindingValue
293
+ }) {
294
+ const boundValue = Array.isArray(binding) ? binding.map(attribute => model.get(attribute)) : model.get(binding);
295
+ if (bindingValue !== undefined) {
296
+ return boundValue === bindingValue;
297
+ } else if (typeof option === 'function') {
298
+ return normalize(option(boundValue));
299
+ } else if (option !== undefined) {
300
+ return normalize(option);
301
+ } else if (binding) {
302
+ return normalize(boundValue);
303
+ }
304
+ }
305
+
306
+ var attributeBindingUtils = /*#__PURE__*/Object.freeze({
307
+ __proto__: null,
308
+ setup: setup,
309
+ resolve: resolve
310
+ });
311
+
312
+ // Class-y constructor by github.com/opensas
340
313
  // https://github.com/jashkenas/backbone/issues/2601
341
314
 
342
315
  function BaseObject(options) {
343
316
  this.initialize.apply(this, arguments);
344
317
  }
345
-
346
318
  _.extend(BaseObject.prototype, Backbone.Events, {
347
- initialize: function initialize(options) {}
348
- }); // The self-propagating extend function that Backbone classes use.
349
-
319
+ initialize: function (options) {}
320
+ });
350
321
 
322
+ // The self-propagating extend function that Backbone classes use.
351
323
  BaseObject.extend = Backbone.Model.extend;
352
324
 
353
- var serverSideValidation = {
354
- initialize: function initialize() {
355
- var _this = this;
356
-
325
+ const serverSideValidation = {
326
+ initialize() {
357
327
  this.validationErrors = {};
358
- this.listenTo(this, 'error', function (model, request) {
328
+ this.listenTo(this, 'error', (model, request) => {
359
329
  if (request.status === 422) {
360
- _this.validationErrors = JSON.parse(request.responseText).errors;
361
-
362
- _this.trigger('invalid');
330
+ this.validationErrors = JSON.parse(request.responseText).errors;
331
+ this.trigger('invalid');
363
332
  }
364
333
  });
365
- this.listenTo(this, 'sync', function () {
366
- _this.validationErrors = {};
334
+ this.listenTo(this, 'sync', () => {
335
+ this.validationErrors = {};
367
336
  });
368
337
  }
369
338
  };
370
339
 
371
- var CollectionView = Marionette.View.extend({
372
- initialize: function initialize() {
340
+ const CollectionView = Marionette.View.extend({
341
+ initialize: function () {
373
342
  this.rendered = false;
374
343
  this.itemViews = new ChildViewContainer();
375
344
  this.collection.map(this.addItem, this);
376
345
  this.listenTo(this.collection, 'add', this.addItem);
377
346
  this.listenTo(this.collection, 'remove', this.removeItem);
378
347
  this.listenTo(this.collection, 'sort', this.sort);
379
-
380
348
  if (this.options.loadingViewConstructor) {
381
349
  this.listenTo(this.collection, 'request', function () {
382
350
  this.loading = true;
@@ -388,7 +356,7 @@ var CollectionView = Marionette.View.extend({
388
356
  });
389
357
  }
390
358
  },
391
- render: function render() {
359
+ render: function () {
392
360
  if (!this.rendered) {
393
361
  this.$el.append(this.itemViews.map(function (itemView) {
394
362
  itemView.$el.data('view', itemView);
@@ -397,89 +365,84 @@ var CollectionView = Marionette.View.extend({
397
365
  this.togglePlaceHolder();
398
366
  this.rendered = true;
399
367
  }
400
-
401
368
  return this;
402
369
  },
403
- onClose: function onClose() {
370
+ onClose: function () {
404
371
  this.itemViews.call('close');
405
372
  this.closePlaceHolderView();
406
373
  },
407
- addItem: function addItem(item) {
374
+ addItem: function (item) {
408
375
  var view = new this.options.itemViewConstructor(_.extend({
409
376
  model: item
410
377
  }, this.getItemViewOptions(item)));
411
378
  this.itemViews.add(view);
412
-
413
379
  if (this.rendered) {
414
380
  var index = this.collection.indexOf(item);
415
381
  view.render();
416
382
  view.$el.data('view', view);
417
-
418
383
  if (index > 0) {
419
384
  this.$el.children().eq(index - 1).after(view.el);
420
385
  } else {
421
386
  this.$el.prepend(view.el);
422
387
  }
423
-
424
388
  this.togglePlaceHolder();
425
389
  }
426
390
  },
427
- removeItem: function removeItem(item) {
391
+ removeItem: function (item) {
428
392
  var view = this.itemViews.findByModel(item);
429
-
430
393
  if (view) {
431
394
  this.itemViews.remove(view);
432
395
  view.close();
433
396
  this.togglePlaceHolder();
434
397
  }
435
398
  },
436
- sort: function sort() {
399
+ sort: function () {
437
400
  var last = null;
438
401
  this.collection.each(function (item) {
439
402
  var itemView = this.itemViews.findByModel(item);
440
403
  var element;
441
-
442
404
  if (!itemView) {
443
405
  return;
444
406
  }
445
-
446
407
  element = itemView.$el;
447
-
448
408
  if (last) {
449
409
  last.after(element);
450
410
  } else {
451
411
  this.$el.prepend(element);
452
412
  }
453
-
454
413
  last = element;
455
414
  }, this);
456
415
  },
457
- getItemViewOptions: function getItemViewOptions(item) {
416
+ getItemViewOptions: function (item) {
458
417
  if (typeof this.options.itemViewOptions === 'function') {
459
418
  return this.options.itemViewOptions(item);
460
419
  } else {
461
420
  return this.options.itemViewOptions || {};
462
421
  }
463
422
  },
464
- closePlaceHolderView: function closePlaceHolderView() {
423
+ closePlaceHolderView: function () {
465
424
  if (this.placeHolderView) {
466
425
  this.placeHolderView.close();
467
426
  this.placeHolderView = null;
468
427
  }
469
428
  },
470
- togglePlaceHolder: function togglePlaceHolder() {
429
+ togglePlaceHolder: function () {
471
430
  var lastPlaceholderConstructor = this.placeHolderConstructor;
472
431
  this.placeHolderConstructor = this.getPlaceHolderConstructor();
473
-
474
432
  if (this.itemViews.length || !this.placeHolderConstructor) {
475
433
  this.closePlaceHolderView();
476
434
  } else if (!this.placeHolderView || lastPlaceholderConstructor !== this.placeHolderConstructor) {
477
435
  this.closePlaceHolderView();
478
- this.placeHolderView = new this.placeHolderConstructor();
436
+ this.placeHolderView = new this.placeHolderConstructor(this.getPlaceHolderViewOptions());
479
437
  this.$el.append(this.placeHolderView.render().el);
480
438
  }
481
439
  },
482
- getPlaceHolderConstructor: function getPlaceHolderConstructor() {
440
+ getPlaceHolderViewOptions: function () {
441
+ if (this.placeHolderConstructor === this.options.blankSlateViewConstructor) {
442
+ return this.options.blankSlateViewOptions;
443
+ }
444
+ },
445
+ getPlaceHolderConstructor: function () {
483
446
  if (this.loading && this.options.loadingViewConstructor) {
484
447
  return this.options.loadingViewConstructor;
485
448
  } else if (this.options.blankSlateViewConstructor) {
@@ -488,119 +451,135 @@ var CollectionView = Marionette.View.extend({
488
451
  }
489
452
  });
490
453
 
491
- var SortableCollectionView = CollectionView.extend({
492
- render: function render() {
454
+ const SortableCollectionView = CollectionView.extend({
455
+ render: function () {
493
456
  CollectionView.prototype.render.call(this);
494
- this.$el.sortable({
495
- connectWith: this.options.connectWith,
496
- placeholder: 'sortable-placeholder',
497
- forcePlaceholderSize: true,
498
- delay: 200,
499
- update: _.bind(function (event, ui) {
500
- if (ui.item.parent().is(this.el)) {
457
+ this.sortable = Sortable.create(this.el, {
458
+ group: this.options.connectWith,
459
+ animation: 150,
460
+ ghostClass: 'sortable-placeholder',
461
+ forceFallback: this.options.forceDraggableFallback,
462
+ fallbackTolerance: 3,
463
+ onEnd: event => {
464
+ const item = $(event.item);
465
+ if (item.parent().is(this.el)) {
501
466
  this.updateOrder();
502
467
  }
503
- }, this),
504
- receive: _.bind(function (event, ui) {
505
- var view = ui.item.data('view');
506
- this.reindexPositions();
507
- this.itemViews.add(view);
508
- this.collection.add(view.model);
509
- }, this),
510
- remove: _.bind(function (event, ui) {
511
- var view = ui.item.data('view');
468
+ },
469
+ onRemove: event => {
470
+ const view = $(event.item).data('view');
512
471
  this.itemViews.remove(view);
513
472
  this.collection.remove(view.model);
514
- }, this)
473
+ },
474
+ onSort: event => {
475
+ if (event.from !== event.to && event.to === this.el) {
476
+ const view = $(event.item).data('view');
477
+ this.reindexPositions();
478
+ this.itemViews.add(view);
479
+ this.collection.add(view.model);
480
+ this.collection.saveOrder();
481
+ }
482
+ }
515
483
  });
516
484
  return this;
517
485
  },
518
- addItem: function addItem(item) {
486
+ onClose() {
487
+ CollectionView.prototype.onClose.call(this);
488
+ this.sortable.destroy();
489
+ },
490
+ disableSorting() {
491
+ this.sortable.option('disabled', true);
492
+ },
493
+ enableSorting() {
494
+ this.sortable.option('disabled', false);
495
+ },
496
+ addItem: function (item) {
519
497
  if (!this.itemViews.findByModel(item)) {
520
498
  CollectionView.prototype.addItem.call(this, item);
521
499
  }
522
500
  },
523
- removeItem: function removeItem(item) {
501
+ removeItem: function (item) {
524
502
  if (this.itemViews.findByModel(item)) {
525
503
  CollectionView.prototype.removeItem.call(this, item);
526
504
  }
527
505
  },
528
- updateOrder: function updateOrder() {
506
+ updateOrder: function () {
529
507
  this.reindexPositions();
530
508
  this.collection.sort();
531
509
  this.collection.saveOrder();
532
510
  },
533
- reindexPositions: function reindexPositions() {
511
+ reindexPositions: function () {
534
512
  this.$el.children().each(function (index) {
535
513
  $(this).data('view').model.set('position', index);
536
514
  });
537
515
  }
538
516
  });
539
517
 
540
- var ConfigurationEditorTabView = Marionette.View.extend({
518
+ const ConfigurationEditorTabView = Marionette.View.extend({
541
519
  className: 'configuration_editor_tab',
542
- initialize: function initialize() {
520
+ initialize: function () {
543
521
  this.inputs = new ChildViewContainer();
544
522
  this.groups = this.options.groups || ConfigurationEditorTabView.groups;
545
523
  },
546
- input: function input(propertyName, view, options) {
524
+ input: function (propertyName, view, options) {
547
525
  this.view(view, _.extend({
548
526
  placeholderModel: this.options.placeholderModel,
549
527
  propertyName: propertyName,
550
528
  attributeTranslationKeyPrefixes: this.options.attributeTranslationKeyPrefixes
551
529
  }, options || {}));
552
530
  },
553
- view: function view(_view, options) {
554
- this.inputs.add(new _view(_.extend({
531
+ view: function (view, options) {
532
+ this.inputs.add(new view(_.extend({
555
533
  model: this.model,
556
534
  parentTab: this.options.tab
557
535
  }, options || {})));
558
536
  },
559
- group: function group(name, options) {
537
+ group: function (name, options) {
560
538
  this.groups.apply(name, this, options);
561
539
  },
562
- render: function render() {
540
+ render: function () {
563
541
  this.inputs.each(function (input) {
564
542
  this.$el.append(input.render().el);
565
543
  }, this);
566
544
  return this;
567
545
  },
568
- onClose: function onClose() {
546
+ onClose: function () {
569
547
  if (this.inputs) {
570
548
  this.inputs.call('close');
571
549
  }
572
550
  }
573
551
  });
574
-
575
552
  ConfigurationEditorTabView.Groups = function () {
576
553
  var groups = {};
577
-
578
554
  this.define = function (name, fn) {
579
555
  if (typeof fn !== 'function') {
580
556
  throw 'Group has to be function.';
581
557
  }
582
-
583
558
  groups[name] = fn;
584
559
  };
585
-
586
- this.apply = function (name, context, options) {
560
+ this.apply = function (name, context, {
561
+ ignoreUndefined,
562
+ ...options
563
+ } = {}) {
587
564
  if (!(name in groups)) {
565
+ if (ignoreUndefined) {
566
+ return;
567
+ }
588
568
  throw 'Undefined group named "' + name + '".';
589
569
  }
590
-
591
570
  groups[name].call(context, options || {});
592
571
  };
593
572
  };
594
-
595
573
  ConfigurationEditorTabView.groups = new ConfigurationEditorTabView.Groups();
596
574
 
597
575
  function template(data) {
598
576
  var __p = '';
599
- __p += '<div class="tabs_view-scroller">\n <ul class="tabs_view-headers"></ul>\n</div>\n<div class="tabs_view-container"></div>\n';
577
+ __p += '<div class="tabs_view-scroller">\n <div class="tabs_view-tabs" role="tablist" aria-orientation="horizontal"></div>\n</div>\n<div class="tabs_view-container"></div>\n';
600
578
  return __p
601
579
  }
602
580
 
603
581
  /*global pageflow*/
582
+
604
583
  /**
605
584
  * Switch between different views using tabs.
606
585
  *
@@ -621,41 +600,43 @@ return __p
621
600
  *
622
601
  * @class
623
602
  */
624
-
625
- var TabsView = Marionette.Layout.extend(
626
- /* @lends TabView.prototype */
627
- {
628
- template: template,
603
+ const TabsView = Marionette.Layout.extend( /* @lends TabView.prototype */{
604
+ template,
629
605
  className: 'tabs_view',
630
606
  ui: {
631
- headers: '.tabs_view-headers',
632
- scroller: '.tabs_view-scroller'
607
+ tabs: '.tabs_view-tabs',
608
+ scroller: '.tabs_view-scroller',
609
+ container: '.tabs_view-container'
633
610
  },
634
611
  regions: {
635
612
  container: '.tabs_view-container'
636
613
  },
637
614
  events: {
638
- 'click .tabs_view-headers > li': function clickTabs_viewHeadersLi(event) {
639
- this.changeTab($(event.target).data('tab-name'));
615
+ 'click .tabs_view-tab': function (event) {
616
+ this.changeTab($(event.currentTarget).data('tab-name'));
617
+ },
618
+ 'keydown .tabs_view-tab': function (event) {
619
+ this._handleKeyDown(event);
640
620
  }
641
621
  },
642
- initialize: function initialize() {
622
+ initialize: function () {
643
623
  this.tabFactoryFns = {};
644
624
  this.tabNames = [];
645
625
  this.currentTabName = null;
646
-
647
626
  this._refreshScrollerOnSideBarResize();
648
627
  },
649
- tab: function tab(name, factoryFn) {
628
+ tab: function (name, factoryFn) {
650
629
  this.tabFactoryFns[name] = factoryFn;
651
630
  this.tabNames.push(name);
652
631
  },
653
- onRender: function onRender() {
632
+ onRender: function () {
654
633
  _.each(this.tabNames, function (name) {
655
634
  var label = findTranslation(this._labelTranslationKeys(name));
656
- this.ui.headers.append($('<li />').attr('data-tab-name', name).text(label));
635
+ var tabId = this._tabId(name);
636
+ var button = $('<button />').attr('type', 'button').attr('role', 'tab').attr('id', tabId).attr('data-tab-name', name).attr('tabindex', '-1').attr('aria-selected', 'false').addClass('tabs_view-tab').text(label);
637
+ this.ui.tabs.append(button);
657
638
  }, this);
658
-
639
+ this.ui.container.attr('id', this._panelId()).attr('role', 'tabpanel');
659
640
  this.scroller = new IScroll(this.ui.scroller[0], {
660
641
  scrollX: true,
661
642
  scrollY: false,
@@ -663,71 +644,138 @@ var TabsView = Marionette.Layout.extend(
663
644
  mouseWheel: true,
664
645
  preventDefault: false
665
646
  });
666
- this.changeTab(this.defaultTab());
647
+ this.changeTab(this.defaultTab(), {
648
+ refresh: true
649
+ });
667
650
  },
668
- changeTab: function changeTab(name) {
669
- this.container.show(this.tabFactoryFns[name]());
670
-
671
- this._updateActiveHeader(name);
672
-
651
+ changeTab: function (name, options = {}) {
652
+ if (!this.tabFactoryFns[name]) {
653
+ return;
654
+ }
655
+ if (this.currentTabName !== name || options.refresh) {
656
+ this.container.show(this.tabFactoryFns[name]());
657
+ }
673
658
  this.currentTabName = name;
659
+ this._updateActiveTab(name, options.focusTab);
660
+ this._updateActivePanel(name);
674
661
  },
675
- defaultTab: function defaultTab() {
662
+ defaultTab: function () {
676
663
  if (_.include(this.tabNames, this.options.defaultTab)) {
677
664
  return this.options.defaultTab;
678
665
  } else {
679
666
  return _.first(this.tabNames);
680
667
  }
681
668
  },
682
-
683
669
  /**
684
670
  * Rerender current tab.
685
671
  */
686
- refresh: function refresh() {
687
- this.changeTab(this.currentTabName);
672
+ refresh: function () {
673
+ if (this.currentTabName) {
674
+ this.changeTab(this.currentTabName, {
675
+ refresh: true
676
+ });
677
+ }
688
678
  },
689
-
690
679
  /**
691
680
  * Adjust tabs scroller to changed width of view.
692
681
  */
693
- refreshScroller: function refreshScroller() {
682
+ refreshScroller: function () {
694
683
  this.scroller.refresh();
695
684
  },
696
- toggleSpinnerOnTab: function toggleSpinnerOnTab(name, visible) {
685
+ toggleSpinnerOnTab: function (name, visible) {
697
686
  this.$('[data-tab-name=' + name + ']').toggleClass('spinner', visible);
698
687
  },
699
- _labelTranslationKeys: function _labelTranslationKeys(name) {
688
+ _labelTranslationKeys: function (name) {
700
689
  var result = _.map(this.options.translationKeyPrefixes, function (prefix) {
701
690
  return prefix + '.' + name;
702
691
  });
703
-
704
692
  if (this.options.i18n) {
705
693
  result.push(this.options.i18n + '.' + name);
706
694
  }
707
-
708
695
  if (this.options.fallbackTranslationKeyPrefix) {
709
696
  result.push(this.options.fallbackTranslationKeyPrefix + '.' + name);
710
697
  }
711
-
712
698
  return result;
713
699
  },
714
- _updateActiveHeader: function _updateActiveHeader(activeTabName) {
700
+ _updateActiveTab: function (activeTabName, focusTab) {
715
701
  var scroller = this.scroller;
716
- this.ui.headers.children().each(function () {
717
- if ($(this).data('tab-name') === activeTabName) {
702
+ var panelId = this._panelId();
703
+ this.ui.tabs.children().each(function () {
704
+ var button = $(this);
705
+ var isActive = button.data('tab-name') === activeTabName;
706
+ if (isActive) {
718
707
  scroller.scrollToElement(this, 200, true);
719
- $(this).addClass('active');
708
+ button.addClass('active');
709
+ button.attr('aria-selected', 'true');
710
+ button.attr('aria-controls', panelId);
711
+ button.attr('tabindex', '0');
712
+ if (focusTab) {
713
+ button.focus();
714
+ }
720
715
  } else {
721
- $(this).removeClass('active');
716
+ button.removeClass('active');
717
+ button.attr('aria-selected', 'false');
718
+ button.attr('tabindex', '-1');
722
719
  }
723
720
  });
724
721
  },
725
- _refreshScrollerOnSideBarResize: function _refreshScrollerOnSideBarResize() {
722
+ _updateActivePanel: function (activeTabName) {
723
+ this.ui.container.attr('aria-labelledby', this._tabId(activeTabName));
724
+ },
725
+ _handleKeyDown: function (event) {
726
+ var tabName = $(event.currentTarget).data('tab-name');
727
+ var nextTabName;
728
+ switch (event.key) {
729
+ case 'ArrowLeft':
730
+ case 'ArrowUp':
731
+ nextTabName = this._previousTabName(tabName);
732
+ break;
733
+ case 'ArrowRight':
734
+ case 'ArrowDown':
735
+ nextTabName = this._nextTabName(tabName);
736
+ break;
737
+ case 'Home':
738
+ nextTabName = _.first(this.tabNames);
739
+ break;
740
+ case 'End':
741
+ nextTabName = _.last(this.tabNames);
742
+ break;
743
+ case 'Enter':
744
+ case ' ':
745
+ nextTabName = tabName;
746
+ break;
747
+ default:
748
+ return;
749
+ }
750
+ event.preventDefault();
751
+ if (nextTabName) {
752
+ this.changeTab(nextTabName, {
753
+ focusTab: true
754
+ });
755
+ }
756
+ },
757
+ _previousTabName: function (currentTabName) {
758
+ var currentIndex = _.indexOf(this.tabNames, currentTabName);
759
+ var previousIndex = (currentIndex - 1 + this.tabNames.length) % this.tabNames.length;
760
+ return this.tabNames[previousIndex];
761
+ },
762
+ _nextTabName: function (currentTabName) {
763
+ var currentIndex = _.indexOf(this.tabNames, currentTabName);
764
+ var nextIndex = (currentIndex + 1) % this.tabNames.length;
765
+ return this.tabNames[nextIndex];
766
+ },
767
+ _refreshScrollerOnSideBarResize: function () {
726
768
  if (pageflow.app) {
727
769
  this.listenTo(pageflow.app, 'resize', function () {
728
770
  this.scroller.refresh();
729
771
  });
730
772
  }
773
+ },
774
+ _tabId: function (name) {
775
+ return this.cid + '-tab-' + name;
776
+ },
777
+ _panelId: function () {
778
+ return this.cid + '-panel';
731
779
  }
732
780
  });
733
781
 
@@ -756,10 +804,9 @@ var TabsView = Marionette.Layout.extend(
756
804
  *
757
805
  * @class
758
806
  */
759
-
760
- var ConfigurationEditorView = Marionette.View.extend({
807
+ const ConfigurationEditorView = Marionette.View.extend({
761
808
  className: 'configuration_editor',
762
- initialize: function initialize() {
809
+ initialize: function () {
763
810
  this.tabsView = new TabsView({
764
811
  translationKeyPrefixes: this.options.tabTranslationKeyPrefixes || [this.options.tabTranslationKeyPrefix],
765
812
  fallbackTranslationKeyPrefix: 'pageflow.ui.configuration_editor.tabs',
@@ -767,10 +814,10 @@ var ConfigurationEditorView = Marionette.View.extend({
767
814
  });
768
815
  this.configure();
769
816
  },
770
- configure: function configure() {},
771
- tab: function tab(name, callbackOrOptions, callback) {
817
+ configure: function () {},
818
+ tab: function (name, callbackOrOptions, callback) {
772
819
  callback = callback || callbackOrOptions;
773
- var options = callback ? callbackOrOptions : {};
820
+ const options = callback ? callbackOrOptions : {};
774
821
  this.tabsView.tab(name, _.bind(function () {
775
822
  var tabView = new ConfigurationEditorTabView({
776
823
  model: options.model || this.model,
@@ -782,29 +829,26 @@ var ConfigurationEditorView = Marionette.View.extend({
782
829
  return tabView;
783
830
  }, this));
784
831
  },
785
-
786
832
  /**
787
833
  * Rerender current tab.
788
834
  */
789
- refresh: function refresh() {
835
+ refresh: function () {
790
836
  this.tabsView.refresh();
791
837
  },
792
-
793
838
  /**
794
839
  * Adjust tabs scroller to changed width of view.
795
840
  */
796
- refreshScroller: function refreshScroller() {
841
+ refreshScroller: function () {
797
842
  this.tabsView.refreshScroller();
798
843
  },
799
- render: function render() {
844
+ render: function () {
800
845
  this.$el.append(this.subview(this.tabsView).el);
801
846
  return this;
802
847
  }
803
848
  });
804
-
805
849
  _.extend(ConfigurationEditorView, {
806
850
  repository: {},
807
- register: function register(pageTypeName, prototype) {
851
+ register: function (pageTypeName, prototype) {
808
852
  this.repository[pageTypeName] = ConfigurationEditorView.extend(prototype);
809
853
  }
810
854
  });
@@ -829,44 +873,40 @@ return __p
829
873
  *
830
874
  * @since 12.0
831
875
  */
832
-
833
- var TableCellView = Marionette.ItemView.extend({
876
+ const TableCellView = Marionette.ItemView.extend({
834
877
  tagName: 'td',
835
878
  template: template$1,
836
- className: function className() {
879
+ className: function () {
837
880
  return this.options.className;
838
881
  },
839
- onRender: function onRender() {
882
+ onRender: function () {
840
883
  this.listenTo(this.getModel(), 'change:' + this.options.column.name, this.update);
841
884
  this.setupContentBinding();
842
885
  this.update();
843
886
  },
844
-
845
887
  /**
846
888
  * Override in concrete cell view.
847
889
  */
848
- update: function update() {
890
+ update: function () {
849
891
  throw 'Not implemented';
850
892
  },
851
-
852
893
  /**
853
894
  * Returns the column attribute's value in the row model.
854
895
  */
855
- attributeValue: function attributeValue() {
896
+ attributeValue: function () {
856
897
  if (typeof this.options.column.value == 'function') {
857
898
  return this.options.column.value(this.model);
858
899
  } else {
859
900
  return this.getModel().get(this.options.column.name);
860
901
  }
861
902
  },
862
- getModel: function getModel() {
903
+ getModel: function () {
863
904
  if (this.options.column.configurationAttribute) {
864
905
  return this.model.configuration;
865
906
  } else {
866
907
  return this.model;
867
908
  }
868
909
  },
869
-
870
910
  /**
871
911
  * Look up attribute specific translations based on
872
912
  * `attributeTranslationKeyPrefixes` of the the parent `TableView`.
@@ -885,15 +925,14 @@ var TableCellView = Marionette.ItemView.extend({
885
925
  * // Looks for keys of the form:
886
926
  * // <table_view_translation_key_prefix>.<column_attribute>.cell_title
887
927
  */
888
- attributeTranslation: function attributeTranslation(keyName, options) {
928
+ attributeTranslation: function (keyName, options) {
889
929
  return findTranslation(this.attributeTranslationKeys(keyName), options);
890
930
  },
891
- attributeTranslationKeys: function attributeTranslationKeys(keyName) {
931
+ attributeTranslationKeys: function (keyName) {
892
932
  return _(this.options.attributeTranslationKeyPrefixes || []).map(function (prefix) {
893
933
  return prefix + '.' + this.options.column.name + '.' + keyName;
894
934
  }, this);
895
935
  },
896
-
897
936
  /**
898
937
  * Set up content binding to update this view upon change of
899
938
  * specified attribute on this.getModel().
@@ -903,7 +942,7 @@ var TableCellView = Marionette.ItemView.extend({
903
942
  *
904
943
  * @protected
905
944
  */
906
- setupContentBinding: function setupContentBinding() {
945
+ setupContentBinding: function () {
907
946
  if (this.options.column.contentBinding) {
908
947
  this.listenTo(this.getModel(), 'change:' + this.options.column.contentBinding, this.update);
909
948
  this.update();
@@ -911,30 +950,30 @@ var TableCellView = Marionette.ItemView.extend({
911
950
  }
912
951
  });
913
952
 
914
- var TableHeaderCellView = TableCellView.extend({
953
+ const TableHeaderCellView = TableCellView.extend({
915
954
  tagName: 'th',
916
- render: function render() {
955
+ render: function () {
917
956
  this.$el.text(this.options.column.headerText || this.attributeTranslation('column_header'));
918
957
  this.$el.data('columnName', this.options.column.name);
919
958
  return this;
920
959
  }
921
960
  });
922
961
 
923
- var TableRowView = Marionette.View.extend({
962
+ const TableRowView = Marionette.View.extend({
924
963
  tagName: 'tr',
925
964
  events: {
926
- 'click': function click() {
965
+ 'click': function () {
927
966
  if (this.options.selection) {
928
967
  this.options.selection.set(this.selectionAttribute(), this.model);
929
968
  }
930
969
  }
931
970
  },
932
- initialize: function initialize() {
971
+ initialize: function () {
933
972
  if (this.options.selection) {
934
973
  this.listenTo(this.options.selection, 'change', this.updateClassName);
935
974
  }
936
975
  },
937
- render: function render() {
976
+ render: function () {
938
977
  _(this.options.columns).each(function (column) {
939
978
  this.appendSubview(new column.cellView(_.extend({
940
979
  model: this.model,
@@ -942,17 +981,16 @@ var TableRowView = Marionette.View.extend({
942
981
  attributeTranslationKeyPrefixes: this.options.attributeTranslationKeyPrefixes
943
982
  }, column.cellViewOptions || {})));
944
983
  }, this);
945
-
946
984
  this.updateClassName();
947
985
  return this;
948
986
  },
949
- updateClassName: function updateClassName() {
987
+ updateClassName: function () {
950
988
  this.$el.toggleClass('is_selected', this.isSelected());
951
989
  },
952
- isSelected: function isSelected() {
990
+ isSelected: function () {
953
991
  return this.options.selection && this.options.selection.get(this.selectionAttribute()) === this.model;
954
992
  },
955
- selectionAttribute: function selectionAttribute() {
993
+ selectionAttribute: function () {
956
994
  return this.options.selectionAttribute || 'current';
957
995
  }
958
996
  });
@@ -973,7 +1011,7 @@ __p += '<td colspan="' +
973
1011
  return __p
974
1012
  }
975
1013
 
976
- var TableView = Marionette.ItemView.extend({
1014
+ const TableView = Marionette.ItemView.extend({
977
1015
  tagName: 'table',
978
1016
  className: 'table_view',
979
1017
  template: template$2,
@@ -981,16 +1019,14 @@ var TableView = Marionette.ItemView.extend({
981
1019
  headRow: 'thead tr',
982
1020
  body: 'tbody'
983
1021
  },
984
- onRender: function onRender() {
1022
+ onRender: function () {
985
1023
  var view = this;
986
-
987
1024
  _(this.options.columns).each(function (column) {
988
1025
  this.ui.headRow.append(this.subview(new TableHeaderCellView({
989
1026
  column: column,
990
1027
  attributeTranslationKeyPrefixes: this.options.attributeTranslationKeyPrefixes
991
1028
  })).el);
992
1029
  }, this);
993
-
994
1030
  this.subview(new CollectionView({
995
1031
  el: this.ui.body,
996
1032
  collection: this.collection,
@@ -1005,7 +1041,7 @@ var TableView = Marionette.ItemView.extend({
1005
1041
  tagName: 'tr',
1006
1042
  className: 'blank_slate',
1007
1043
  template: blankSlateTemplate,
1008
- serializeData: function serializeData() {
1044
+ serializeData: function () {
1009
1045
  return {
1010
1046
  blankSlateText: view.options.blankSlateText,
1011
1047
  colSpan: view.options.columns.length
@@ -1022,18 +1058,18 @@ __p += '<span class="label">\n</span>\n';
1022
1058
  return __p
1023
1059
  }
1024
1060
 
1025
- var TooltipView = Marionette.ItemView.extend({
1061
+ const TooltipView = Marionette.ItemView.extend({
1026
1062
  template: template$3,
1027
1063
  className: 'tooltip',
1028
1064
  ui: {
1029
1065
  label: '.label'
1030
1066
  },
1031
- hide: function hide() {
1067
+ hide: function () {
1032
1068
  this.visible = false;
1033
1069
  clearTimeout(this.timeout);
1034
1070
  this.$el.removeClass('visible');
1035
1071
  },
1036
- show: function show(text, position, options) {
1072
+ show: function (text, position, options) {
1037
1073
  options = options || {};
1038
1074
  this.visible = true;
1039
1075
  clearTimeout(this.timeout);
@@ -1044,7 +1080,6 @@ var TooltipView = Marionette.ItemView.extend({
1044
1080
  this.$el.toggleClass('align_bottom_right', options.align === 'bottom right');
1045
1081
  this.$el.toggleClass('align_bottom_left', options.align === 'bottom left');
1046
1082
  this.$el.toggleClass('align_top_center', options.align === 'top center');
1047
-
1048
1083
  if (options.align === 'bottom right' || options.align === 'bottom left') {
1049
1084
  offsetTop = 10;
1050
1085
  offsetLeft = 0;
@@ -1055,7 +1090,6 @@ var TooltipView = Marionette.ItemView.extend({
1055
1090
  offsetTop = -17;
1056
1091
  offsetLeft = 10;
1057
1092
  }
1058
-
1059
1093
  this.$el.css({
1060
1094
  top: position.top + offsetTop + 'px',
1061
1095
  left: position.left + offsetLeft + 'px'
@@ -1065,104 +1099,36 @@ var TooltipView = Marionette.ItemView.extend({
1065
1099
  }
1066
1100
  });
1067
1101
 
1068
- function _defineProperty(obj, key, value) {
1069
- if (key in obj) {
1070
- Object.defineProperty(obj, key, {
1071
- value: value,
1072
- enumerable: true,
1073
- configurable: true,
1074
- writable: true
1075
- });
1076
- } else {
1077
- obj[key] = value;
1078
- }
1079
-
1080
- return obj;
1081
- }
1082
-
1083
- function ownKeys(object, enumerableOnly) {
1084
- var keys = Object.keys(object);
1085
-
1086
- if (Object.getOwnPropertySymbols) {
1087
- var symbols = Object.getOwnPropertySymbols(object);
1088
- if (enumerableOnly) symbols = symbols.filter(function (sym) {
1089
- return Object.getOwnPropertyDescriptor(object, sym).enumerable;
1090
- });
1091
- keys.push.apply(keys, symbols);
1092
- }
1093
-
1094
- return keys;
1095
- }
1096
-
1097
- function _objectSpread2(target) {
1098
- for (var i = 1; i < arguments.length; i++) {
1099
- var source = arguments[i] != null ? arguments[i] : {};
1100
-
1101
- if (i % 2) {
1102
- ownKeys(Object(source), true).forEach(function (key) {
1103
- _defineProperty(target, key, source[key]);
1104
- });
1105
- } else if (Object.getOwnPropertyDescriptors) {
1106
- Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
1107
- } else {
1108
- ownKeys(Object(source)).forEach(function (key) {
1109
- Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
1110
- });
1111
- }
1112
- }
1113
-
1114
- return target;
1115
- }
1116
-
1117
- var attributeBinding = {
1118
- setupBooleanAttributeBinding: function setupBooleanAttributeBinding(optionName, updateMethod) {
1102
+ const attributeBinding = {
1103
+ setupBooleanAttributeBinding(optionName, updateMethod) {
1119
1104
  this.setupAttributeBinding(optionName, updateMethod, Boolean);
1120
1105
  },
1121
- getBooleanAttributBoundOption: function getBooleanAttributBoundOption(optionName) {
1106
+ getBooleanAttributBoundOption(optionName) {
1122
1107
  return this.getAttributeBoundOption(optionName, Boolean);
1123
1108
  },
1124
- setupAttributeBinding: function setupAttributeBinding(optionName, updateMethod) {
1125
- var _this = this;
1126
-
1127
- var normalize = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : function (value) {
1128
- return value;
1129
- };
1130
- var binding = this.options["".concat(optionName, "Binding")];
1131
- var view = this;
1132
-
1133
- if (binding) {
1134
- _.flatten([binding]).forEach(function (attribute) {
1135
- _this.listenTo(_this.model, 'change:' + attribute, update);
1136
- });
1137
- }
1138
-
1139
- update();
1140
-
1141
- function update() {
1142
- updateMethod.call(view, view.getAttributeBoundOption(optionName, normalize));
1143
- }
1109
+ setupAttributeBinding(optionName, updateMethod, normalize = value => value) {
1110
+ const binding = this.options[`${optionName}Binding`];
1111
+ const model = this.options[`${optionName}BindingModel`] || this.model;
1112
+ setup({
1113
+ binding,
1114
+ model,
1115
+ listener: this,
1116
+ normalize,
1117
+ option: this.options[optionName],
1118
+ bindingValue: this.options[`${optionName}BindingValue`],
1119
+ callback: value => updateMethod.call(this, value)
1120
+ });
1144
1121
  },
1145
- getAttributeBoundOption: function getAttributeBoundOption(optionName) {
1146
- var _this2 = this;
1147
-
1148
- var normalize = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : function (value) {
1149
- return value;
1150
- };
1151
- var binding = this.options["".concat(optionName, "Binding")];
1152
- var bindingValueOptionName = "".concat(optionName, "BindingValue");
1153
- var value = Array.isArray(binding) ? binding.map(function (attribute) {
1154
- return _this2.model.get(attribute);
1155
- }) : this.model.get(binding);
1156
-
1157
- if (bindingValueOptionName in this.options) {
1158
- return value === this.options[bindingValueOptionName];
1159
- } else if (typeof this.options[optionName] === 'function') {
1160
- return normalize(this.options[optionName](value));
1161
- } else if (optionName in this.options) {
1162
- return normalize(this.options[optionName]);
1163
- } else if (binding) {
1164
- return normalize(value);
1165
- }
1122
+ getAttributeBoundOption(optionName, normalize = value => value) {
1123
+ const binding = this.options[`${optionName}Binding`];
1124
+ const model = this.options[`${optionName}BindingModel`] || this.model;
1125
+ return resolve({
1126
+ binding,
1127
+ model,
1128
+ normalize,
1129
+ option: this.options[optionName],
1130
+ bindingValue: this.options[`${optionName}BindingValue`]
1131
+ });
1166
1132
  }
1167
1133
  };
1168
1134
 
@@ -1255,6 +1221,11 @@ var attributeBinding = {
1255
1221
  * An array of prefixes to lookup translations for labels and
1256
1222
  * inline help texts based on attribute names.
1257
1223
  *
1224
+ * @param {string} [options.attributeTranslationPropertyName]
1225
+ * Use this property name instead of `propertyName` for looking up
1226
+ * translations. Useful when the property name has been transformed
1227
+ * but translations should use the original name.
1228
+ *
1258
1229
  * @param {string} [options.additionalInlineHelpText]
1259
1230
  * A text that will be appended to the translation based inline
1260
1231
  * text.
@@ -1276,6 +1247,9 @@ var attributeBinding = {
1276
1247
  * Input will be disabled whenever the value of the `disabledBinding`
1277
1248
  * attribute equals the value of this option.
1278
1249
  *
1250
+ * @param {Backbone.Model} [options.disabledBindingModel]
1251
+ * Alternative model to bind to.
1252
+ *
1279
1253
  * @param {string|string[]} [options.visibleBinding]
1280
1254
  * Name of an attribute to control whether the input is visible. If
1281
1255
  * the `visible` and `visibleBindingValue` options are not set,
@@ -1292,16 +1266,18 @@ var attributeBinding = {
1292
1266
  * Input will be visible whenever the value of the `visibleBinding`
1293
1267
  * attribute equals the value of this option.
1294
1268
  *
1269
+ * @param {Backbone.Model} [options.visibleBindingModel]
1270
+ * Alternative model to bind to.
1271
+ *
1295
1272
  * @mixin
1296
1273
  */
1297
-
1298
- var inputView = _objectSpread2(_objectSpread2({}, attributeBinding), {}, {
1274
+ const inputView = {
1275
+ ...attributeBinding,
1299
1276
  ui: {
1300
1277
  label: 'label',
1301
1278
  labelText: 'label .name',
1302
1279
  inlineHelp: 'label .inline_help'
1303
1280
  },
1304
-
1305
1281
  /**
1306
1282
  * Returns an array of translation keys based on the
1307
1283
  * `attributeTranslationKeyPrefixes` option and the given keyName.
@@ -1324,92 +1300,89 @@ var inputView = _objectSpread2(_objectSpread2({}, attributeBinding), {}, {
1324
1300
  * @since 0.9
1325
1301
  * @member
1326
1302
  */
1327
- attributeTranslationKeys: function attributeTranslationKeys$1(keyName, options) {
1328
- return attributeTranslationKeys(this.options.propertyName, keyName, _.extend({
1303
+ attributeTranslationKeys: function (keyName, options) {
1304
+ return attributeTranslationKeys(this.options.attributeTranslationPropertyName || this.options.propertyName, keyName, _.extend({
1329
1305
  prefixes: this.options.attributeTranslationKeyPrefixes,
1330
1306
  fallbackModelI18nKey: this.model.i18nKey
1331
1307
  }, options || {}));
1332
1308
  },
1333
- onRender: function onRender() {
1309
+ onRender: function () {
1334
1310
  this.$el.addClass('input');
1335
1311
  this.$el.addClass(this.model.modelName + '_' + this.options.propertyName);
1336
1312
  this.$el.data('inputPropertyName', this.options.propertyName);
1337
1313
  this.$el.data('labelText', this.labelText());
1338
1314
  this.$el.data('inlineHelpText', this.inlineHelpText());
1339
1315
  this.ui.labelText.text(this.labelText());
1316
+ if (this.options.hideLabel) {
1317
+ this.ui.label.addClass('visually_hidden');
1318
+ }
1340
1319
  this.updateInlineHelp();
1341
1320
  this.setLabelFor();
1342
1321
  this.setupBooleanAttributeBinding('disabled', this.updateDisabled);
1343
1322
  this.setupBooleanAttributeBinding('visible', this.updateVisible);
1344
1323
  },
1345
-
1346
1324
  /**
1347
1325
  * The label to display in the form.
1348
1326
  * @return {string}
1349
1327
  */
1350
- labelText: function labelText() {
1328
+ labelText: function () {
1351
1329
  return this.options.label || this.localizedAttributeName();
1352
1330
  },
1353
- localizedAttributeName: function localizedAttributeName() {
1331
+ localizedAttributeName: function () {
1354
1332
  return findTranslation(this.attributeTranslationKeys('label', {
1355
1333
  fallbackPrefix: 'activerecord.attributes'
1356
1334
  }));
1357
1335
  },
1358
- updateInlineHelp: function updateInlineHelp() {
1336
+ updateInlineHelp: function () {
1359
1337
  this.ui.inlineHelp.html(this.inlineHelpText());
1360
-
1361
1338
  if (!this.inlineHelpText()) {
1362
1339
  this.ui.inlineHelp.hide();
1363
1340
  }
1364
1341
  },
1365
-
1366
1342
  /**
1367
1343
  * The inline help text for the form field.
1368
1344
  * @return {string}
1369
1345
  */
1370
- inlineHelpText: function inlineHelpText() {
1346
+ inlineHelpText: function () {
1371
1347
  var keys = this.attributeTranslationKeys('inline_help', {
1372
1348
  fallbackPrefix: 'pageflow.ui.inline_help'
1373
1349
  });
1374
-
1375
1350
  if (this.isDisabled()) {
1376
1351
  keys = translationKeysWithSuffix(keys, 'disabled');
1377
1352
  }
1378
-
1379
1353
  return _.compact([findTranslation(keys, {
1380
1354
  defaultValue: '',
1381
1355
  html: true
1382
1356
  }), this.options.additionalInlineHelpText]).join(' ');
1383
1357
  },
1384
- setLabelFor: function setLabelFor() {
1358
+ setLabelFor() {
1385
1359
  if (this.ui.input && this.ui.label.length === 1 && !this.ui.input.attr('id')) {
1386
- var id = 'input_' + this.model.modelName + '_' + this.options.propertyName;
1360
+ const id = 'input_' + this.model.modelName + '_' + this.options.propertyName;
1387
1361
  this.ui.input.attr('id', id);
1388
1362
  this.ui.label.attr('for', id);
1389
1363
  }
1390
1364
  },
1391
- isDisabled: function isDisabled() {
1365
+ isDisabled: function () {
1392
1366
  return this.getBooleanAttributBoundOption('disabled');
1393
1367
  },
1394
- updateDisabled: function updateDisabled() {
1368
+ updateDisabled: function () {
1395
1369
  this.$el.toggleClass('input-disabled', !!this.isDisabled());
1396
1370
  this.updateInlineHelp();
1397
-
1398
1371
  if (this.ui.input) {
1399
1372
  this.updateDisabledAttribute(this.ui.input);
1400
1373
  }
1401
1374
  },
1402
- updateDisabledAttribute: function updateDisabledAttribute(element) {
1375
+ updateDisabledAttribute: function (element) {
1403
1376
  if (this.isDisabled()) {
1404
1377
  element.attr('disabled', true);
1405
1378
  } else {
1406
1379
  element.removeAttr('disabled');
1407
1380
  }
1408
1381
  },
1409
- updateVisible: function updateVisible() {
1382
+ updateVisible: function () {
1410
1383
  this.$el.toggleClass('hidden_via_binding', this.getBooleanAttributBoundOption('visible') === false);
1411
1384
  }
1412
- });
1385
+ };
1413
1386
 
1414
1387
  function template$4(data) {
1415
1388
  var __p = '';
@@ -1425,8 +1398,7 @@ return __p
1425
1398
  *
1426
1399
  * @class
1427
1400
  */
1428
-
1429
- var CheckBoxGroupInputView = Marionette.ItemView.extend({
1401
+ const CheckBoxGroupInputView = Marionette.ItemView.extend({
1430
1402
  mixins: [inputView],
1431
1403
  template: template$4,
1432
1404
  className: 'check_box_group_input',
@@ -1437,7 +1409,7 @@ var CheckBoxGroupInputView = Marionette.ItemView.extend({
1437
1409
  label: 'label',
1438
1410
  container: '.check_boxes_container'
1439
1411
  },
1440
- initialize: function initialize() {
1412
+ initialize: function () {
1441
1413
  if (!this.options.texts) {
1442
1414
  if (!this.options.translationKeys) {
1443
1415
  var translationKeyPrefix = this.options.translationKeyPrefix || findKeyWithTranslation(this.attributeTranslationKeys('values', {
@@ -1447,34 +1419,31 @@ var CheckBoxGroupInputView = Marionette.ItemView.extend({
1447
1419
  return translationKeyPrefix + '.' + value;
1448
1420
  }, this);
1449
1421
  }
1450
-
1451
1422
  this.options.texts = _.map(this.options.translationKeys, function (key) {
1452
1423
  return I18n$1.t(key);
1453
1424
  });
1454
1425
  }
1455
1426
  },
1456
- onRender: function onRender() {
1427
+ onRender: function () {
1457
1428
  this.ui.label.attr('for', this.cid);
1458
1429
  this.appendOptions();
1459
1430
  this.load();
1460
1431
  this.listenTo(this.model, 'change:' + this.options.propertyName, this.load);
1461
1432
  },
1462
- appendOptions: function appendOptions() {
1433
+ appendOptions: function () {
1463
1434
  _.each(this.options.values, function (value, index) {
1464
1435
  var option = '<div class="check_box">' + '<label><input type="checkbox" name="' + value + '" />' + this.options.texts[index] + '</label></div>';
1465
1436
  this.ui.container.append($(option));
1466
1437
  }, this);
1467
1438
  },
1468
- save: function save() {
1439
+ save: function () {
1469
1440
  var configured = {};
1470
-
1471
1441
  _.each(this.ui.container.find('input'), function (input) {
1472
1442
  configured[$(input).attr('name')] = $(input).prop('checked');
1473
1443
  });
1474
-
1475
1444
  this.model.set(this.options.propertyName, configured);
1476
1445
  },
1477
- load: function load() {
1446
+ load: function () {
1478
1447
  if (!this.isClosed) {
1479
1448
  _.each(this.options.values, function (value) {
1480
1449
  this.ui.container.find('input[name="' + value + '"]').prop('checked', this.model.get(this.options.propertyName)[value]);
@@ -1502,8 +1471,7 @@ return __p
1502
1471
  *
1503
1472
  * @class
1504
1473
  */
1505
-
1506
- var UrlDisplayView = Marionette.ItemView.extend({
1474
+ const UrlDisplayView = Marionette.ItemView.extend({
1507
1475
  mixins: [inputView],
1508
1476
  template: template$5,
1509
1477
  ui: {
@@ -1513,43 +1481,21 @@ var UrlDisplayView = Marionette.ItemView.extend({
1513
1481
  'change': 'update'
1514
1482
  },
1515
1483
  events: {
1516
- 'click a': function clickA(event) {
1484
+ 'click a': function (event) {
1517
1485
  // Ensure default is not prevented by parent event listener.
1518
1486
  event.stopPropagation();
1519
1487
  }
1520
1488
  },
1521
- onRender: function onRender() {
1489
+ onRender: function () {
1522
1490
  this.update();
1523
1491
  },
1524
- update: function update() {
1525
- var url = this.model.get('original_url');
1492
+ update: function () {
1493
+ var url = this.model.get(this.options.propertyName || 'original_url');
1526
1494
  this.$el.toggle(this.model.isUploaded() && !_.isEmpty(url));
1527
1495
  this.ui.link.attr('href', url);
1528
1496
  }
1529
1497
  });
1530
1498
 
1531
- function _classCallCheck(instance, Constructor) {
1532
- if (!(instance instanceof Constructor)) {
1533
- throw new TypeError("Cannot call a class as a function");
1534
- }
1535
- }
1536
-
1537
- function _defineProperties(target, props) {
1538
- for (var i = 0; i < props.length; i++) {
1539
- var descriptor = props[i];
1540
- descriptor.enumerable = descriptor.enumerable || false;
1541
- descriptor.configurable = true;
1542
- if ("value" in descriptor) descriptor.writable = true;
1543
- Object.defineProperty(target, descriptor.key, descriptor);
1544
- }
1545
- }
1546
-
1547
- function _createClass(Constructor, protoProps, staticProps) {
1548
- if (protoProps) _defineProperties(Constructor.prototype, protoProps);
1549
- if (staticProps) _defineProperties(Constructor, staticProps);
1550
- return Constructor;
1551
- }
1552
-
1553
1499
  /**
1554
1500
  * Input view for a number.
1555
1501
  *
@@ -1562,90 +1508,73 @@ function _createClass(Constructor, protoProps, staticProps) {
1562
1508
  *
1563
1509
  * @class
1564
1510
  */
1565
-
1566
- var NumberInputView = Marionette.ItemView.extend({
1511
+ const NumberInputView = Marionette.ItemView.extend({
1567
1512
  mixins: [inputView],
1568
- template: function template() {
1569
- return "\n <label>\n <span class=\"name\"></span>\n <span class=\"inline_help\"></span>\n </label>\n <input type=\"text\" dir=\"auto\" />\n ";
1570
- },
1513
+ template: () => `
1514
+ <label>
1515
+ <span class="name"></span>
1516
+ <span class="inline_help"></span>
1517
+ </label>
1518
+ <input type="text" dir="auto" />
1519
+ `,
1571
1520
  ui: {
1572
1521
  input: 'input'
1573
1522
  },
1574
1523
  events: {
1575
1524
  'change': 'onChange'
1576
1525
  },
1577
- initialize: function initialize() {
1526
+ initialize() {
1578
1527
  this.parser = new NumberParser(this.options.locale);
1579
1528
  },
1580
- onRender: function onRender() {
1529
+ onRender: function () {
1581
1530
  this.load();
1582
1531
  this.listenTo(this.model, 'change:' + this.options.propertyName, this.load);
1583
1532
  },
1584
- onChange: function onChange() {
1533
+ onChange: function () {
1585
1534
  this.save();
1586
1535
  this.load();
1587
1536
  },
1588
- onClose: function onClose() {
1537
+ onClose: function () {
1589
1538
  this.save();
1590
1539
  },
1591
- save: function save() {
1592
- var inputValue = this.ui.input.val();
1540
+ save: function () {
1541
+ const inputValue = this.ui.input.val();
1593
1542
  this.model.set(this.options.propertyName, this.parser.parse(inputValue) || 0);
1594
1543
  },
1595
- load: function load() {
1596
- var input = this.ui.input;
1597
- var value = this.model.get(this.options.propertyName) || 0;
1544
+ load: function () {
1545
+ const input = this.ui.input;
1546
+ const value = this.model.get(this.options.propertyName) || 0;
1598
1547
  input.val(value.toLocaleString(this.options.locale, {
1599
1548
  useGrouping: false
1600
1549
  }));
1601
1550
  },
1602
- displayValidationError: function displayValidationError(message) {
1551
+ displayValidationError: function (message) {
1603
1552
  this.$el.addClass('invalid');
1604
1553
  this.ui.input.attr('title', message);
1605
1554
  },
1606
- resetValidationError: function resetValidationError(message) {
1555
+ resetValidationError: function (message) {
1607
1556
  this.$el.removeClass('invalid');
1608
1557
  this.ui.input.attr('title', '');
1609
1558
  }
1610
1559
  });
1611
-
1612
- var NumberParser = /*#__PURE__*/function () {
1613
- function NumberParser(locale) {
1614
- _classCallCheck(this, NumberParser);
1615
-
1616
- var format = new Intl.NumberFormat(locale);
1617
- var parts = format.formatToParts(12345.6);
1618
- var numerals = Array.from({
1560
+ class NumberParser {
1561
+ constructor(locale) {
1562
+ const format = new Intl.NumberFormat(locale);
1563
+ const parts = format.formatToParts(12345.6);
1564
+ const numerals = Array.from({
1619
1565
  length: 10
1620
- }).map(function (_, i) {
1621
- return format.format(i);
1622
- });
1623
- var index = new Map(numerals.map(function (d, i) {
1624
- return [d, i];
1625
- }));
1626
- this._group = new RegExp("[".concat(parts.find(function (d) {
1627
- return d.type === "group";
1628
- }).value, "]"), "g");
1629
- this._decimal = new RegExp("[".concat(parts.find(function (d) {
1630
- return d.type === "decimal";
1631
- }).value, "]"));
1632
- this._numeral = new RegExp("[".concat(numerals.join(""), "]"), "g");
1633
-
1634
- this._index = function (d) {
1635
- return index.get(d);
1636
- };
1566
+ }).map((_, i) => format.format(i));
1567
+ const index = new Map(numerals.map((d, i) => [d, i]));
1568
+ this._group = new RegExp(`[${parts.find(d => d.type === "group").value}]`, "g");
1569
+ this._decimal = new RegExp(`[${parts.find(d => d.type === "decimal").value}]`);
1570
+ this._numeral = new RegExp(`[${numerals.join("")}]`, "g");
1571
+ this._index = d => index.get(d);
1637
1572
  }
1638
-
1639
- _createClass(NumberParser, [{
1640
- key: "parse",
1641
- value: function parse(string) {
1642
- string = string.trim().replace(this._group, "").replace(this._decimal, ".").replace(this._numeral, this._index);
1643
- return string ? +string : NaN;
1644
- }
1645
- }]);
1646
-
1647
- return NumberParser;
1648
- }();
1573
+ parse(string) {
1574
+ string = string.trim().replace(this._group, "").replace(this._decimal, ".").replace(this._numeral, this._index);
1575
+ return string ? +string : NaN;
1576
+ }
1577
+ }
1649
1578
 
1650
1579
  /**
1651
1580
  * Text based input view that can display a placeholder.
@@ -1668,21 +1597,20 @@ var NumberParser = /*#__PURE__*/function () {
1668
1597
  * Obtain placeholder by looking up the configured `propertyName`
1669
1598
  * inside a given model.
1670
1599
  */
1671
- var inputWithPlaceholderText = {
1672
- onRender: function onRender() {
1600
+ const inputWithPlaceholderText = {
1601
+ onRender: function () {
1673
1602
  this.updatePlaceholder();
1674
-
1675
1603
  if (this.options.placeholderBinding) {
1676
1604
  this.listenTo(this.model, 'change:' + this.options.placeholderBinding, this.updatePlaceholder);
1677
1605
  }
1678
1606
  },
1679
- updateDisabled: function updateDisabled() {
1607
+ updateDisabled: function () {
1680
1608
  this.updatePlaceholder();
1681
1609
  },
1682
- updatePlaceholder: function updatePlaceholder() {
1610
+ updatePlaceholder: function () {
1683
1611
  this.ui.input.attr('placeholder', this.placeholderText());
1684
1612
  },
1685
- placeholderText: function placeholderText() {
1613
+ placeholderText: function () {
1686
1614
  if (!this.isDisabled() || !this.options.hidePlaceholderIfDisabled) {
1687
1615
  if (this.options.placeholder) {
1688
1616
  if (typeof this.options.placeholder == 'function') {
@@ -1694,30 +1622,24 @@ var inputWithPlaceholderText = {
1694
1622
  return this.placeholderModelValue();
1695
1623
  }
1696
1624
  }
1697
-
1698
1625
  return '';
1699
1626
  },
1700
- placeholderModelValue: function placeholderModelValue() {
1627
+ placeholderModelValue: function () {
1701
1628
  return this.options.placeholderModel && this.options.placeholderModel.get(this.options.propertyName);
1702
1629
  }
1703
1630
  };
1704
1631
 
1705
- var viewWithValidationErrorMessages = {
1706
- onRender: function onRender() {
1632
+ const viewWithValidationErrorMessages = {
1633
+ onRender() {
1707
1634
  this.listenTo(this.model, 'invalid sync', this.updateValidationErrorMessages);
1708
1635
  this.updateValidationErrorMessages();
1709
1636
  },
1710
- updateValidationErrorMessages: function updateValidationErrorMessages() {
1711
- var _this = this;
1712
-
1713
- var errors = this.model.validationErrors && this.model.validationErrors[this.options.propertyName] || [];
1714
-
1637
+ updateValidationErrorMessages() {
1638
+ const errors = this.model.validationErrors && this.model.validationErrors[this.options.propertyName] || [];
1715
1639
  if (errors.length) {
1716
1640
  this.validationErrorList = this.validationErrorList || $('<ul class="validation_error_messages" />').appendTo(this.el);
1717
1641
  this.validationErrorList.html('');
1718
- errors.forEach(function (error) {
1719
- return _this.validationErrorList.append("<li>".concat(error, "</li>"));
1720
- });
1642
+ errors.forEach(error => this.validationErrorList.append(`<li>${error}</li>`));
1721
1643
  this.$el.addClass('invalid');
1722
1644
  } else if (this.validationErrorList) {
1723
1645
  this.validationErrorList.remove();
@@ -1752,8 +1674,7 @@ return __p
1752
1674
  *
1753
1675
  * @class
1754
1676
  */
1755
-
1756
- var TextInputView = Marionette.ItemView.extend({
1677
+ const TextInputView = Marionette.ItemView.extend({
1757
1678
  mixins: [inputView, inputWithPlaceholderText, viewWithValidationErrorMessages],
1758
1679
  template: template$6,
1759
1680
  ui: {
@@ -1762,41 +1683,40 @@ var TextInputView = Marionette.ItemView.extend({
1762
1683
  events: {
1763
1684
  'change': 'onChange'
1764
1685
  },
1765
- onRender: function onRender() {
1686
+ onRender: function () {
1766
1687
  this.load();
1767
1688
  this.validate();
1768
1689
  this.listenTo(this.model, 'change:' + this.options.propertyName, this.load);
1769
1690
  },
1770
- onChange: function onChange() {
1691
+ onChange: function () {
1771
1692
  if (this.validate()) {
1772
1693
  this.save();
1773
1694
  }
1774
1695
  },
1775
- onClose: function onClose() {
1696
+ onClose: function () {
1776
1697
  if (this.validate()) {
1777
1698
  this.save();
1778
1699
  }
1779
1700
  },
1780
- save: function save() {
1701
+ save: function () {
1781
1702
  this.model.set(this.options.propertyName, this.ui.input.val());
1782
1703
  },
1783
- load: function load() {
1704
+ load: function () {
1784
1705
  var input = this.ui.input;
1785
- input.val(this.model.get(this.options.propertyName)); // set mysql varchar length as default for non-legacy data
1706
+ input.val(this.model.get(this.options.propertyName));
1786
1707
 
1787
- this.options.maxLength = this.options.maxLength || 255; // do not validate legacy data which length exceeds the specified maximum
1708
+ // set mysql varchar length as default for non-legacy data
1709
+ this.options.maxLength = this.options.maxLength || 255;
1710
+ // do not validate legacy data which length exceeds the specified maximum
1788
1711
  // for new and maxLength-conforming data: add validation
1789
-
1790
1712
  this.validateMaxLength = input.val().length <= this.options.maxLength;
1791
1713
  },
1792
- validate: function validate() {
1714
+ validate: function () {
1793
1715
  var input = this.ui.input;
1794
-
1795
1716
  if (this.options.required && !input.val()) {
1796
1717
  this.displayValidationError(I18n$1.t('pageflow.ui.views.inputs.text_input_view.required_field'));
1797
1718
  return false;
1798
1719
  }
1799
-
1800
1720
  if (this.validateMaxLength && input.val().length > this.options.maxLength) {
1801
1721
  this.displayValidationError(I18n$1.t('pageflow.ui.views.inputs.text_input_view.max_characters_exceeded', {
1802
1722
  max_length: this.options.maxLength
@@ -1807,112 +1727,763 @@ var TextInputView = Marionette.ItemView.extend({
1807
1727
  return true;
1808
1728
  }
1809
1729
  },
1810
- displayValidationError: function displayValidationError(message) {
1730
+ displayValidationError: function (message) {
1811
1731
  this.$el.addClass('invalid');
1812
1732
  this.ui.input.attr('title', message);
1813
1733
  },
1814
- resetValidationError: function resetValidationError(message) {
1734
+ resetValidationError: function (message) {
1815
1735
  this.$el.removeClass('invalid');
1816
1736
  this.ui.input.attr('title', '');
1817
1737
  }
1818
1738
  });
1819
1739
 
1820
- /**
1821
- * Input view for a color value in hex representation.
1822
- * See {@link inputView} for further options
1823
- *
1824
- * @param {Object} [options]
1825
- *
1826
- * @param {string|function} [options.defaultValue]
1827
- * Color value to display by default. The corresponding value is not
1828
- * stored in the model. Selecting the default value when a different
1829
- * value was set before, unsets the attribute in the model.
1830
- *
1831
- * @param {string} [options.defaultValueBinding]
1832
- * Name of an attribute the default value depends on. If a function
1833
- * is used as defaultValue option, it will be passed the value of the
1834
- * defaultValueBinding attribute each time it changes. If no
1835
- * defaultValue option is set, the value of the defaultValueBinding
1836
- * attribute will be used as default value.
1837
- *
1838
- * @param {string[]} [options.swatches]
1839
- * Preset color values to be displayed inside the picker drop
1840
- * down. The default value, if present, is always used as the
1841
- * first swatch automatically.
1842
- *
1843
- * @class
1844
- */
1740
+ function template$7(data) {
1741
+ var __p = '';
1742
+ __p += '<label>\n <span class="name"></span>\n <span class="inline_help"></span>\n</label>\n<div class="file_name_input-wrapper">\n <input type="text" dir="auto" />\n <span class="file_name_input-extension"></span>\n</div>\n';
1743
+ return __p
1744
+ }
1845
1745
 
1846
- var ColorInputView = Marionette.ItemView.extend({
1847
- mixins: [inputView],
1848
- template: template$6,
1849
- className: 'color_input',
1850
- ui: {
1851
- input: 'input'
1852
- },
1853
- events: {
1854
- 'mousedown': 'refreshPicker'
1855
- },
1856
- onRender: function onRender() {
1857
- this.ui.input.minicolors({
1858
- changeDelay: 200,
1859
- change: _.bind(function (color) {
1860
- this._saving = true;
1746
+ const FileNameInputView = TextInputView.extend({
1747
+ template: template$7,
1748
+ className: 'file_name_input',
1749
+ ui: Object.assign({}, TextInputView.prototype.ui, {
1750
+ extension: '.file_name_input-extension'
1751
+ }),
1752
+ onRender: function () {
1753
+ TextInputView.prototype.onRender.call(this);
1754
+ },
1755
+ save: function () {
1756
+ const baseName = this.ui.input.val();
1757
+ const extension = this.ui.extension.text();
1758
+ this.model.set(this.options.propertyName, baseName + extension);
1759
+ },
1760
+ load: function () {
1761
+ const fullName = this.model.get(this.options.propertyName) || '';
1762
+ const match = fullName.match(/^(.*?)(\.[^.]+)?$/);
1763
+ const baseName = match ? match[1] : fullName;
1764
+ const extension = match && match[2] ? match[2] : '';
1765
+ this.ui.input.val(baseName);
1766
+ this.ui.extension.text(extension);
1767
+ this.options.maxLength = this.options.maxLength || 255;
1768
+ }
1769
+ });
1861
1770
 
1862
- if (color === this.defaultValue()) {
1863
- this.model.unset(this.options.propertyName);
1864
- } else {
1865
- this.model.set(this.options.propertyName, color);
1866
- }
1771
+ // Inspired by https://github.com/mdbassit/Coloris
1772
+ const ctx = typeof OffscreenCanvas !== 'undefined' && new OffscreenCanvas(1, 1).getContext('2d');
1773
+ const DEFAULT_DISPLAY_COLOR = {
1774
+ r: 255,
1775
+ g: 255,
1776
+ b: 255,
1777
+ a: 1
1778
+ };
1779
+ let nextDescriptionId = 0;
1780
+ const PICKER_HTML = '<div class="color_picker-gradient" role="application">' + '<div class="color_picker-marker" tabindex="0"></div>' + '</div>' + '<div class="color_picker-hue">' + '<input type="range" min="0" max="360" step="1">' + '<div></div>' + '</div>' + '<div class="color_picker-alpha">' + '<input type="range" min="0" max="100" step="1">' + '<div></div>' + '<span></span>' + '</div>' + '<div class="color_picker-swatches"></div>';
1781
+ class ColorPicker {
1782
+ constructor(input, options = {}) {
1783
+ this._input = input;
1784
+ this._colorAreaDims = {};
1785
+ this._alpha = options.alpha || false;
1786
+ this._onChange = options.onChange;
1787
+ this._defaultColor = strToRGBA(options.defaultValue);
1788
+ this._fallbackColor = strToRGBA(options.fallbackColor);
1789
+ this._fallbackColorDescription = options.fallbackColorDescription;
1790
+ this._swatches = options.swatches || [];
1791
+ this._wrapInput();
1792
+ this._createPicker();
1793
+ this._renderSwatches();
1794
+ this._bindEvents();
1795
+ this._updateColor(strToRGBA(this._input.value), {
1796
+ silent: true
1797
+ });
1798
+ }
1799
+ setValue(str) {
1800
+ this._input.value = str || '';
1801
+ this._updateColor(strToRGBA(str), {
1802
+ silent: true
1803
+ });
1804
+ }
1805
+ update(options) {
1806
+ if ('defaultValue' in options) {
1807
+ this._defaultColor = strToRGBA(options.defaultValue);
1808
+ }
1809
+ if ('fallbackColor' in options) {
1810
+ this._fallbackColor = strToRGBA(options.fallbackColor);
1811
+ this._updateColor(this._currentColor, {
1812
+ silent: true
1813
+ });
1814
+ }
1815
+ if (options.swatches) {
1816
+ this._swatches = options.swatches;
1817
+ this._renderSwatches();
1818
+ }
1819
+ }
1820
+ destroy() {
1821
+ this._close();
1822
+ this._unbindEvents();
1823
+ this._picker.remove();
1824
+ this._unwrapInput();
1825
+ }
1867
1826
 
1868
- this._saving = false;
1869
- }, this)
1827
+ // Setup
1828
+
1829
+ _createPicker() {
1830
+ this._picker = document.createElement('div');
1831
+ this._picker.className = 'color_picker';
1832
+ this._picker.classList.toggle('color_picker-no_alpha', !this._alpha);
1833
+ this._picker.innerHTML = PICKER_HTML;
1834
+ this._input.parentNode.appendChild(this._picker);
1835
+ this._colorArea = this._picker.querySelector('.color_picker-gradient');
1836
+ this._colorMarker = this._picker.querySelector('.color_picker-marker');
1837
+ this._hueSlider = this._picker.querySelector('.color_picker-hue input');
1838
+ this._hueMarker = this._picker.querySelector('.color_picker-hue div');
1839
+ this._alphaSlider = this._picker.querySelector('.color_picker-alpha input');
1840
+ this._alphaMarker = this._picker.querySelector('.color_picker-alpha div');
1841
+ this._swatchesContainer = this._picker.querySelector('.color_picker-swatches');
1842
+ this._hueSlider.setAttribute('aria-label', I18n$1.t('pageflow.ui.color_picker.hue'));
1843
+ if (this._alpha) {
1844
+ this._alphaSlider.setAttribute('aria-label', I18n$1.t('pageflow.ui.color_picker.opacity'));
1845
+ }
1846
+ }
1847
+ _wrapInput() {
1848
+ const parent = this._input.parentNode;
1849
+ if (!parent.classList.contains('color_picker-field')) {
1850
+ const wrapper = document.createElement('div');
1851
+ wrapper.innerHTML = '<span></span>';
1852
+ parent.insertBefore(wrapper, this._input);
1853
+ wrapper.className = 'color_picker-field';
1854
+ wrapper.appendChild(this._input);
1855
+ if (this._fallbackColorDescription) {
1856
+ this._descriptionElement = document.createElement('span');
1857
+ this._descriptionElement.hidden = true;
1858
+ this._descriptionElement.id = 'color_picker_desc_' + nextDescriptionId++;
1859
+ wrapper.appendChild(this._descriptionElement);
1860
+ this._input.setAttribute('aria-describedby', this._descriptionElement.id);
1861
+ }
1862
+ }
1863
+ }
1864
+ _unwrapInput() {
1865
+ const wrapper = this._input.parentNode;
1866
+ if (wrapper && wrapper.classList.contains('color_picker-field')) {
1867
+ const parent = wrapper.parentNode;
1868
+ parent.insertBefore(this._input, wrapper);
1869
+ parent.removeChild(wrapper);
1870
+ }
1871
+ }
1872
+ _renderSwatches() {
1873
+ const swatches = this._swatches.map(normalizeSwatch).filter(swatch => this._alpha || !isTranslucentSwatch(swatch.value));
1874
+ this._swatchesContainer.textContent = '';
1875
+ this._swatchesContainer.classList.toggle('color_picker-empty', !swatches.length);
1876
+ if (!swatches.length) {
1877
+ return;
1878
+ }
1879
+ groupSwatches(swatches).forEach((group, index) => {
1880
+ if (index > 0) {
1881
+ const divider = document.createElement('span');
1882
+ divider.className = 'color_picker-swatch_divider';
1883
+ this._swatchesContainer.appendChild(divider);
1884
+ }
1885
+ group.swatches.forEach(({
1886
+ value,
1887
+ text
1888
+ }) => {
1889
+ const button = document.createElement('button');
1890
+ button.setAttribute('type', 'button');
1891
+ button.title = text;
1892
+ button.style.color = value;
1893
+ button.textContent = value;
1894
+ this._swatchesContainer.appendChild(button);
1895
+ });
1870
1896
  });
1871
- this.listenTo(this.model, 'change:' + this.options.propertyName, this.load);
1897
+ }
1872
1898
 
1873
- if (this.options.defaultValueBinding) {
1874
- this.listenTo(this.model, 'change:' + this.options.defaultValueBinding, this.updateSettings);
1899
+ // Lifecycle
1900
+
1901
+ _open() {
1902
+ if (this._picker.classList.contains('color_picker-open')) {
1903
+ return;
1904
+ }
1905
+ this._picker.classList.add('color_picker-open');
1906
+ this._updatePosition();
1907
+ this._setColorFromStr(this._input.value);
1908
+ this._addDocListeners();
1909
+ }
1910
+ _close() {
1911
+ if (!this._picker.classList.contains('color_picker-open')) {
1912
+ return;
1913
+ }
1914
+ this._picker.classList.remove('color_picker-open');
1915
+ this._removeDocListeners();
1916
+ }
1917
+ _updatePosition() {
1918
+ const margin = 2;
1919
+ const pickerHeight = this._picker.offsetHeight;
1920
+ const pickerWidth = this._picker.offsetWidth;
1921
+ const inputRect = this._input.getBoundingClientRect();
1922
+ const clipRect = getClipRect(this._input);
1923
+ const spaceBelow = clipRect.bottom - inputRect.bottom;
1924
+ const flipTop = pickerHeight + margin > spaceBelow && pickerHeight + margin <= inputRect.top - clipRect.top;
1925
+ this._picker.classList.toggle('color_picker-top', flipTop);
1926
+ if (flipTop) {
1927
+ this._picker.style.top = 'auto';
1928
+ this._picker.style.bottom = `calc(100% + ${margin}px)`;
1929
+ } else {
1930
+ this._picker.style.top = `calc(100% + ${margin}px)`;
1931
+ this._picker.style.bottom = '';
1932
+ }
1933
+ if (inputRect.left + pickerWidth > clipRect.right) {
1934
+ this._picker.style.left = 'auto';
1935
+ this._picker.style.right = '0';
1936
+ } else {
1937
+ this._picker.style.left = '0';
1938
+ this._picker.style.right = '';
1939
+ }
1940
+ const areaRect = this._colorArea.getBoundingClientRect();
1941
+ this._colorAreaDims = {
1942
+ width: this._colorArea.offsetWidth,
1943
+ height: this._colorArea.offsetHeight,
1944
+ x: areaRect.x + window.scrollX,
1945
+ y: areaRect.y + window.scrollY
1946
+ };
1947
+ }
1948
+
1949
+ // Color interaction
1950
+
1951
+ _setColorFromStr(str, options) {
1952
+ this._updateColor(strToRGBA(str), options);
1953
+ const {
1954
+ h,
1955
+ s,
1956
+ v,
1957
+ a
1958
+ } = rgbaToHSVA(this._displayColor);
1959
+ this._hueSlider.value = h;
1960
+ this._picker.style.color = `hsl(${h}, 100%, 50%)`;
1961
+ this._hueMarker.style.left = `${h / 360 * 100}%`;
1962
+ this._colorMarker.style.left = `${this._colorAreaDims.width * s / 100}px`;
1963
+ this._colorMarker.style.top = `${this._colorAreaDims.height - this._colorAreaDims.height * v / 100}px`;
1964
+ this._alphaSlider.value = a * 100;
1965
+ this._alphaMarker.style.left = `${a * 100}%`;
1966
+ }
1967
+ _moveMarker(event) {
1968
+ let x = event.pageX - this._colorAreaDims.x;
1969
+ let y = event.pageY - this._colorAreaDims.y;
1970
+ this._setMarkerPosition(x, y);
1971
+ event.preventDefault();
1972
+ event.stopPropagation();
1973
+ }
1974
+ _moveMarkerOnKeydown(dx, dy) {
1975
+ let x = this._colorMarker.style.left.replace('px', '') * 1 + dx;
1976
+ let y = this._colorMarker.style.top.replace('px', '') * 1 + dy;
1977
+ this._setMarkerPosition(x, y);
1978
+ }
1979
+ _setHue() {
1980
+ const hue = this._hueSlider.value * 1;
1981
+ const x = this._colorMarker.style.left.replace('px', '') * 1;
1982
+ const y = this._colorMarker.style.top.replace('px', '') * 1;
1983
+ this._picker.style.color = `hsl(${hue}, 100%, 50%)`;
1984
+ this._hueMarker.style.left = `${hue / 360 * 100}%`;
1985
+ this._setColorAtPosition(x, y);
1986
+ }
1987
+ _setAlpha() {
1988
+ const alpha = this._alphaSlider.value / 100;
1989
+ this._alphaMarker.style.left = `${alpha * 100}%`;
1990
+ this._updateColor({
1991
+ a: alpha
1992
+ });
1993
+ this._syncInput();
1994
+ }
1995
+ _setMarkerPosition(x, y) {
1996
+ x = x < 0 ? 0 : x > this._colorAreaDims.width ? this._colorAreaDims.width : x;
1997
+ y = y < 0 ? 0 : y > this._colorAreaDims.height ? this._colorAreaDims.height : y;
1998
+ this._colorMarker.style.left = `${x}px`;
1999
+ this._colorMarker.style.top = `${y}px`;
2000
+ this._setColorAtPosition(x, y);
2001
+ this._colorMarker.focus();
2002
+ }
2003
+ _setColorAtPosition(x, y) {
2004
+ const hsva = {
2005
+ h: this._hueSlider.value * 1,
2006
+ s: x / this._colorAreaDims.width * 100,
2007
+ v: 100 - y / this._colorAreaDims.height * 100,
2008
+ a: this._alphaSlider.value / 100
2009
+ };
2010
+ const rgba = hsvaToRGBA(hsva);
2011
+ this._updateColor(rgba);
2012
+ this._syncInput();
2013
+ }
2014
+ _updateColor(rgba, {
2015
+ silent
2016
+ } = {}) {
2017
+ rgba = rgba || this._defaultColor;
2018
+ if (rgba && !this._alpha) rgba.a = 1;
2019
+ this._currentColor = rgba && {
2020
+ ...this._displayColor,
2021
+ ...rgba
2022
+ };
2023
+ this._displayColor = this._currentColor || this._fallbackColor || DEFAULT_DISPLAY_COLOR;
2024
+ const hex = rgbaToHex(this._displayColor);
2025
+ const opaqueHex = hex.substring(0, 7);
2026
+ this._colorMarker.style.color = opaqueHex;
2027
+ this._alphaMarker.parentNode.style.color = opaqueHex;
2028
+ this._alphaMarker.style.color = hex;
2029
+ const formatted = this._formatHex(this._currentColor);
2030
+ const fallbackHex = this._formatHex(this._fallbackColor);
2031
+ const wrapper = this._input.parentNode;
2032
+ if (wrapper && wrapper.classList.contains('color_picker-field')) {
2033
+ wrapper.style.color = formatted || fallbackHex || '';
2034
+ }
2035
+ if (this._descriptionElement) {
2036
+ this._descriptionElement.textContent = !formatted && fallbackHex ? `${this._fallbackColorDescription}: ${fallbackHex}` : '';
2037
+ }
2038
+
2039
+ // Force repaint the color and alpha gradients (Chrome workaround)
2040
+ this._colorArea.style.display = 'none';
2041
+ this._colorArea.offsetHeight;
2042
+ this._colorArea.style.display = '';
2043
+ this._alphaMarker.nextElementSibling.style.display = 'none';
2044
+ this._alphaMarker.nextElementSibling.offsetHeight;
2045
+ this._alphaMarker.nextElementSibling.style.display = '';
2046
+ if (!silent && this._onChange) this._onChange(formatted);
2047
+ }
2048
+ _syncInput() {
2049
+ this._input.value = this._formatHex(this._currentColor) || '';
2050
+ }
2051
+ _formatHex(rgba) {
2052
+ if (!rgba) return null;
2053
+ const hex = rgbaToHex(rgba);
2054
+ return rgba.a < 1 ? hex : hex.substring(0, 7);
2055
+ }
2056
+ _normalizeInputValue() {
2057
+ const hex = this._formatHex(this._currentColor) || '';
2058
+ if (hex !== this._input.value) {
2059
+ this._input.value = hex;
1875
2060
  }
2061
+ }
2062
+
2063
+ // Event wiring
1876
2064
 
1877
- this.updateSettings();
2065
+ _bindEvents() {
2066
+ this._onInputClick = () => {
2067
+ this._open();
2068
+ };
2069
+ this._onInputKeydown = event => {
2070
+ if (event.key === 'Enter') {
2071
+ this._normalizeInputValue();
2072
+ }
2073
+ if (this._picker.classList.contains('color_picker-open')) {
2074
+ return;
2075
+ }
2076
+ if (event.key === 'Enter' || event.key === 'ArrowDown' && event.altKey) {
2077
+ this._open();
2078
+ event.stopPropagation();
2079
+ }
2080
+ };
2081
+ this._onPickerMousedown = event => {
2082
+ this._picker.classList.remove('color_picker-keyboard_nav');
2083
+ event.stopPropagation();
2084
+ };
2085
+ this._onAreaPointerdown = event => {
2086
+ event.preventDefault();
2087
+ this._colorArea.setPointerCapture(event.pointerId);
2088
+ this._dragging = true;
2089
+ };
2090
+ this._onAreaPointermove = event => {
2091
+ if (this._dragging) {
2092
+ this._moveMarker(event);
2093
+ }
2094
+ };
2095
+ this._onAreaPointerup = () => {
2096
+ this._dragging = false;
2097
+ };
2098
+ this._onInputEvent = () => {
2099
+ if (this._picker.classList.contains('color_picker-open')) {
2100
+ this._setColorFromStr(this._input.value);
2101
+ } else {
2102
+ this._updateColor(strToRGBA(this._input.value));
2103
+ }
2104
+ };
2105
+ this._onSwatchClick = event => {
2106
+ if (event.target.closest('.color_picker-swatches button')) {
2107
+ this._setColorFromStr(event.target.closest('.color_picker-swatches button').textContent);
2108
+ this._syncInput();
2109
+ }
2110
+ };
2111
+ this._onMarkerKeydown = event => {
2112
+ const movements = {
2113
+ ArrowUp: [0, -1],
2114
+ ArrowDown: [0, 1],
2115
+ ArrowLeft: [-1, 0],
2116
+ ArrowRight: [1, 0]
2117
+ };
2118
+ if (movements[event.key]) {
2119
+ this._moveMarkerOnKeydown(...movements[event.key]);
2120
+ event.preventDefault();
2121
+ }
2122
+ };
2123
+ this._onAreaClick = event => this._moveMarker(event);
2124
+ this._onHueInput = () => this._setHue();
2125
+ this._onAlphaInput = () => this._setAlpha();
2126
+ const wrapper = this._input.parentNode;
2127
+ this._onFocusout = event => {
2128
+ if (event.target === this._input) {
2129
+ this._normalizeInputValue();
2130
+ }
2131
+ if (this._picker.classList.contains('color_picker-open')) {
2132
+ if (event.relatedTarget && !wrapper.contains(event.relatedTarget)) {
2133
+ this._close();
2134
+ }
2135
+ }
2136
+ };
2137
+ this._input.addEventListener('click', this._onInputClick);
2138
+ this._input.addEventListener('keydown', this._onInputKeydown);
2139
+ this._input.addEventListener('input', this._onInputEvent);
2140
+ wrapper.addEventListener('focusout', this._onFocusout);
2141
+ this._picker.addEventListener('mousedown', this._onPickerMousedown);
2142
+ this._colorArea.addEventListener('pointerdown', this._onAreaPointerdown);
2143
+ this._colorArea.addEventListener('pointermove', this._onAreaPointermove);
2144
+ this._colorArea.addEventListener('pointerup', this._onAreaPointerup);
2145
+ this._swatchesContainer.addEventListener('click', this._onSwatchClick);
2146
+ this._colorMarker.addEventListener('keydown', this._onMarkerKeydown);
2147
+ this._colorArea.addEventListener('click', this._onAreaClick);
2148
+ this._hueSlider.addEventListener('input', this._onHueInput);
2149
+ this._alphaSlider.addEventListener('input', this._onAlphaInput);
2150
+ }
2151
+ _unbindEvents() {
2152
+ this._input.removeEventListener('click', this._onInputClick);
2153
+ this._input.removeEventListener('keydown', this._onInputKeydown);
2154
+ this._input.removeEventListener('input', this._onInputEvent);
2155
+ const wrapper = this._input.parentNode;
2156
+ if (wrapper && wrapper.classList.contains('color_picker-field')) {
2157
+ wrapper.removeEventListener('focusout', this._onFocusout);
2158
+ }
2159
+ this._picker.removeEventListener('mousedown', this._onPickerMousedown);
2160
+ this._colorArea.removeEventListener('pointerdown', this._onAreaPointerdown);
2161
+ this._colorArea.removeEventListener('pointermove', this._onAreaPointermove);
2162
+ this._colorArea.removeEventListener('pointerup', this._onAreaPointerup);
2163
+ this._swatchesContainer.removeEventListener('click', this._onSwatchClick);
2164
+ this._colorMarker.removeEventListener('keydown', this._onMarkerKeydown);
2165
+ this._colorArea.removeEventListener('click', this._onAreaClick);
2166
+ this._hueSlider.removeEventListener('input', this._onHueInput);
2167
+ this._alphaSlider.removeEventListener('input', this._onAlphaInput);
2168
+ this._removeDocListeners();
2169
+ }
2170
+ _addDocListeners() {
2171
+ this._onDocMousedown = event => {
2172
+ this._picker.classList.remove('color_picker-keyboard_nav');
2173
+ if (!this._input.parentNode.contains(event.target)) {
2174
+ this._close();
2175
+ }
2176
+ };
2177
+ this._onDocKeydown = event => {
2178
+ if (event.key === 'Escape' || event.key === 'Enter' && event.target.tagName !== 'BUTTON') {
2179
+ this._close();
2180
+ this._input.focus({
2181
+ preventScroll: true
2182
+ });
2183
+ return;
2184
+ }
2185
+ const navKeys = ['Tab', 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'];
2186
+ if (navKeys.includes(event.key)) {
2187
+ this._picker.classList.add('color_picker-keyboard_nav');
2188
+ }
2189
+ };
2190
+ document.addEventListener('mousedown', this._onDocMousedown);
2191
+ document.addEventListener('keydown', this._onDocKeydown);
2192
+ }
2193
+ _removeDocListeners() {
2194
+ if (this._onDocMousedown) {
2195
+ document.removeEventListener('mousedown', this._onDocMousedown);
2196
+ document.removeEventListener('keydown', this._onDocKeydown);
2197
+ }
2198
+ }
2199
+ }
2200
+ function strToRGBA(str) {
2201
+ if (!str) return null;
2202
+ const regex = /^((rgba)|rgb)[\D]+([\d.]+)[\D]+([\d.]+)[\D]+([\d.]+)[\D]*?([\d.]+|$)/i;
2203
+ let match, rgba;
2204
+ if (!ctx) {
2205
+ match = regex.exec(str);
2206
+ if (match) {
2207
+ return {
2208
+ r: match[3] * 1,
2209
+ g: match[4] * 1,
2210
+ b: match[5] * 1,
2211
+ a: match[6] * 1 || 1
2212
+ };
2213
+ }
2214
+ if (!/^#[0-9a-f]{3,8}$/i.test(str)) return null;
2215
+ let hex = str.replace('#', '');
2216
+ if (hex.length === 3 || hex.length === 4) {
2217
+ hex = hex.split('').map(c => c + c).join('');
2218
+ }
2219
+ if (hex.length !== 6 && hex.length !== 8) return null;
2220
+ match = hex.match(/.{2}/g).map(h => parseInt(h, 16));
2221
+ return {
2222
+ r: match[0],
2223
+ g: match[1],
2224
+ b: match[2],
2225
+ a: match[3] !== undefined ? match[3] / 255 : 1
2226
+ };
2227
+ }
2228
+ ctx.fillStyle = '#010101';
2229
+ ctx.fillStyle = str;
2230
+ if (ctx.fillStyle === '#010101') {
2231
+ ctx.fillStyle = '#020202';
2232
+ ctx.fillStyle = str;
2233
+ if (ctx.fillStyle === '#020202') return null;
2234
+ }
2235
+ match = regex.exec(ctx.fillStyle);
2236
+ if (match) {
2237
+ rgba = {
2238
+ r: match[3] * 1,
2239
+ g: match[4] * 1,
2240
+ b: match[5] * 1,
2241
+ a: match[6] * 1
2242
+ };
2243
+ } else {
2244
+ match = ctx.fillStyle.replace('#', '').match(/.{2}/g).map(h => parseInt(h, 16));
2245
+ rgba = {
2246
+ r: match[0],
2247
+ g: match[1],
2248
+ b: match[2],
2249
+ a: 1
2250
+ };
2251
+ }
2252
+ return rgba;
2253
+ }
2254
+ function rgbaToHSVA(rgba) {
2255
+ const red = rgba.r / 255;
2256
+ const green = rgba.g / 255;
2257
+ const blue = rgba.b / 255;
2258
+ const xmax = Math.max(red, green, blue);
2259
+ const xmin = Math.min(red, green, blue);
2260
+ const chroma = xmax - xmin;
2261
+ const value = xmax;
2262
+ let hue = 0;
2263
+ let saturation = 0;
2264
+ if (chroma) {
2265
+ if (xmax === red) {
2266
+ hue = (green - blue) / chroma;
2267
+ }
2268
+ if (xmax === green) {
2269
+ hue = 2 + (blue - red) / chroma;
2270
+ }
2271
+ if (xmax === blue) {
2272
+ hue = 4 + (red - green) / chroma;
2273
+ }
2274
+ if (xmax) {
2275
+ saturation = chroma / xmax;
2276
+ }
2277
+ }
2278
+ hue = Math.floor(hue * 60);
2279
+ return {
2280
+ h: hue < 0 ? hue + 360 : hue,
2281
+ s: Math.round(saturation * 100),
2282
+ v: Math.round(value * 100),
2283
+ a: rgba.a
2284
+ };
2285
+ }
2286
+ function hsvaToRGBA(hsva) {
2287
+ const saturation = hsva.s / 100;
2288
+ const value = hsva.v / 100;
2289
+ let chroma = saturation * value;
2290
+ let hueBy60 = hsva.h / 60;
2291
+ let x = chroma * (1 - Math.abs(hueBy60 % 2 - 1));
2292
+ let m = value - chroma;
2293
+ chroma = chroma + m;
2294
+ x = x + m;
2295
+ const index = Math.floor(hueBy60) % 6;
2296
+ const red = [chroma, x, m, m, x, chroma][index];
2297
+ const green = [x, chroma, chroma, x, m, m][index];
2298
+ const blue = [m, m, x, chroma, chroma, x][index];
2299
+ return {
2300
+ r: Math.round(red * 255),
2301
+ g: Math.round(green * 255),
2302
+ b: Math.round(blue * 255),
2303
+ a: hsva.a
2304
+ };
2305
+ }
2306
+ function rgbaToHex(rgba) {
2307
+ let R = rgba.r.toString(16);
2308
+ let G = rgba.g.toString(16);
2309
+ let B = rgba.b.toString(16);
2310
+ let A = Math.round(rgba.a * 255).toString(16);
2311
+ if (rgba.r < 16) {
2312
+ R = '0' + R;
2313
+ }
2314
+ if (rgba.g < 16) {
2315
+ G = '0' + G;
2316
+ }
2317
+ if (rgba.b < 16) {
2318
+ B = '0' + B;
2319
+ }
2320
+ if (rgba.a * 255 < 16) {
2321
+ A = '0' + A;
2322
+ }
2323
+ return '#' + R + G + B + A;
2324
+ }
2325
+ function getClipRect(element) {
2326
+ const viewport = {
2327
+ top: 0,
2328
+ left: 0,
2329
+ right: document.documentElement.clientWidth,
2330
+ bottom: document.documentElement.clientHeight
2331
+ };
2332
+ let ancestor = element.parentElement;
2333
+ while (ancestor && ancestor !== document.documentElement) {
2334
+ const overflow = getComputedStyle(ancestor).overflow;
2335
+ if (overflow !== 'visible') {
2336
+ const rect = ancestor.getBoundingClientRect();
2337
+ return {
2338
+ top: Math.max(viewport.top, rect.top),
2339
+ left: Math.max(viewport.left, rect.left),
2340
+ right: Math.min(viewport.right, rect.right),
2341
+ bottom: Math.min(viewport.bottom, rect.bottom)
2342
+ };
2343
+ }
2344
+ ancestor = ancestor.parentElement;
2345
+ }
2346
+ return viewport;
2347
+ }
2348
+ function isTranslucentSwatch(str) {
2349
+ return str.length > 7 && str.slice(-2).toLowerCase() !== 'ff';
2350
+ }
2351
+ function normalizeSwatch(swatch) {
2352
+ return typeof swatch === 'string' ? {
2353
+ value: swatch,
2354
+ text: swatch
2355
+ } : {
2356
+ value: swatch.value,
2357
+ text: swatch.text || swatch.value,
2358
+ group: swatch.group
2359
+ };
2360
+ }
2361
+ function groupSwatches(swatches) {
2362
+ const groups = [];
2363
+ const groupsByKey = new Map();
2364
+ swatches.forEach(swatch => {
2365
+ const key = swatch.group || '';
2366
+ let group = groupsByKey.get(key);
2367
+ if (!group) {
2368
+ group = {
2369
+ key,
2370
+ swatches: []
2371
+ };
2372
+ groupsByKey.set(key, group);
2373
+ groups.push(group);
2374
+ }
2375
+ group.swatches.push(swatch);
2376
+ });
2377
+ return groups;
2378
+ }
2379
+
2380
+ function template$8(data) {
2381
+ var __p = '';
2382
+ __p += '<label>\n <span class="name"></span>\n <span class="inline_help"></span>\n</label>\n<input type="text" dir="auto" autocomplete="off" />\n';
2383
+ return __p
2384
+ }
2385
+
2386
+ /**
2387
+ * Input view for a color value in hex representation.
2388
+ *
2389
+ * See {@link inputWithPlaceholderText} for placeholder related
2390
+ * further options. See {@link inputView} for further options.
2391
+ *
2392
+ * @param {Object} [options]
2393
+ *
2394
+ * @param {string|function} [options.defaultValue]
2395
+ * Color value to display by default. The corresponding value is not
2396
+ * stored in the model. Selecting the default value when a different
2397
+ * value was set before, unsets the attribute in the model.
2398
+ *
2399
+ * @param {string} [options.defaultValueBinding]
2400
+ * Name of an attribute the default value depends on. If a function
2401
+ * is used as defaultValue option, it will be passed the value of the
2402
+ * defaultValueBinding attribute each time it changes. If no
2403
+ * defaultValue option is set, the value of the defaultValueBinding
2404
+ * attribute will be used as default value.
2405
+ *
2406
+ * @param {string|function} [options.placeholderColor]
2407
+ * Color to display in swatch by default.
2408
+ *
2409
+ * @param {string} [options.placeholderColorBinding]
2410
+ * Name of an attribute the placeholder color depends on. If a function
2411
+ * is used as placeholderColor option, it will be passed the value of the
2412
+ * placeholderColorBinding attribute each time it changes.
2413
+ *
2414
+ * @param {boolean} [options.alpha]
2415
+ * Allow picking colors with alpha channel. When enabled, translucent
2416
+ * colors are stored in `#rrggbbaa` format. Fully opaque colors still
2417
+ * use `#rrggbb`.
2418
+ *
2419
+ * @param {Array<string|{value: string, text: string, group: string}>} [options.swatches]
2420
+ * Preset colors to be displayed inside the picker drop down. Each
2421
+ * entry is either a color string or an object with a `value` color,
2422
+ * a `text` label shown as the swatch's tooltip and an optional
2423
+ * `group` key. Consecutive swatches with a different `group` are
2424
+ * separated by a divider in the picker. The default value, if
2425
+ * present, is always used as the first swatch automatically. Swatches
2426
+ * are deduplicated by value.
2427
+ *
2428
+ * @class
2429
+ */
2430
+ const ColorInputView = Marionette.ItemView.extend({
2431
+ mixins: [inputView, inputWithPlaceholderText],
2432
+ template: template$8,
2433
+ className: 'color_input',
2434
+ ui: {
2435
+ input: 'input'
1878
2436
  },
1879
- updateSettings: function updateSettings() {
1880
- this.resetSwatchesInStoredSettings();
1881
- this.ui.input.minicolors('settings', {
2437
+ onRender: function () {
2438
+ this.setupAttributeBinding('placeholderColor', this.updatePlaceholderColor);
2439
+ this._colorPicker = new ColorPicker(this.ui.input[0], {
2440
+ alpha: this.options.alpha,
1882
2441
  defaultValue: this.defaultValue(),
1883
- swatches: this.getSwatches()
2442
+ fallbackColor: this.getAttributeBoundOption('placeholderColor'),
2443
+ fallbackColorDescription: this.options.placeholderColorDescription,
2444
+ swatches: this.getSwatches(),
2445
+ onChange: this._debouncedOnChange()
1884
2446
  });
2447
+ this.listenTo(this.model, 'change:' + this.options.propertyName, this.load);
2448
+ if (this.options.defaultValueBinding) {
2449
+ this.listenTo(this.model, 'change:' + this.options.defaultValueBinding, this.updateSettings);
2450
+ }
1885
2451
  this.load();
1886
2452
  },
1887
- // see https://github.com/claviska/jquery-minicolors/issues/287
1888
- resetSwatchesInStoredSettings: function resetSwatchesInStoredSettings() {
1889
- var settings = this.ui.input.data('minicolors-settings');
1890
-
1891
- if (settings) {
1892
- delete settings.swatches;
1893
- this.ui.input.data('minicolors-settings', settings);
2453
+ updatePlaceholderColor(value) {
2454
+ if (this._colorPicker) {
2455
+ this._colorPicker.update({
2456
+ fallbackColor: value
2457
+ });
1894
2458
  }
1895
2459
  },
1896
- load: function load() {
2460
+ updateSettings: function () {
2461
+ this._colorPicker.update({
2462
+ defaultValue: this.defaultValue(),
2463
+ swatches: this.getSwatches()
2464
+ });
2465
+ this.load();
2466
+ },
2467
+ load: function () {
2468
+ var color = this.model.get(this.options.propertyName) || this.defaultValue() || '';
1897
2469
  if (!this._saving) {
1898
- this.ui.input.minicolors('value', this.model.get(this.options.propertyName) || this.defaultValue());
2470
+ this._colorPicker.setValue(color);
1899
2471
  }
1900
-
1901
2472
  this.$el.toggleClass('is_default', !this.model.has(this.options.propertyName));
1902
2473
  },
1903
- refreshPicker: function refreshPicker() {
1904
- this.ui.input.minicolors('value', {});
2474
+ onBeforeClose: function () {
2475
+ this._colorPicker.destroy();
1905
2476
  },
1906
- getSwatches: function getSwatches() {
1907
- return _.chain([this.defaultValue(), this.options.swatches]).flatten().uniq().compact().value();
2477
+ getSwatches: function () {
2478
+ return _.chain([this.defaultValue(), this.options.swatches]).flatten().compact().map(swatch => typeof swatch === 'string' ? {
2479
+ value: swatch
2480
+ } : swatch).uniq(swatch => swatch.value).value();
1908
2481
  },
1909
- defaultValue: function defaultValue() {
2482
+ defaultValue: function () {
1910
2483
  var bindingValue;
1911
-
1912
2484
  if (this.options.defaultValueBinding) {
1913
2485
  bindingValue = this.model.get(this.options.defaultValueBinding);
1914
2486
  }
1915
-
1916
2487
  if (typeof this.options.defaultValue === 'function') {
1917
2488
  return this.options.defaultValue(bindingValue);
1918
2489
  } else if ('defaultValue' in this.options) {
@@ -1920,10 +2491,23 @@ var ColorInputView = Marionette.ItemView.extend({
1920
2491
  } else {
1921
2492
  return bindingValue;
1922
2493
  }
2494
+ },
2495
+ _debouncedOnChange: function () {
2496
+ const handler = _.bind(this._onChange, this);
2497
+ return this.options.disableChangeDebounce ? handler : _.debounce(handler, 200);
2498
+ },
2499
+ _onChange: function (color) {
2500
+ this._saving = true;
2501
+ if (!color || color === this.defaultValue()) {
2502
+ this.model.unset(this.options.propertyName);
2503
+ } else {
2504
+ this.model.set(this.options.propertyName, color);
2505
+ }
2506
+ this._saving = false;
1923
2507
  }
1924
2508
  });
1925
2509
 
1926
- function template$7(data) {
2510
+ function template$9(data) {
1927
2511
  var __p = '';
1928
2512
  __p += '<label>\n <span class="name"></span>\n <span class="inline_help"></span>\n</label>\n<select></select>';
1929
2513
  return __p
@@ -1935,8 +2519,12 @@ return __p
1935
2519
  *
1936
2520
  * @param {Object} [options]
1937
2521
  *
1938
- * @param {string[]} [options.values]
1939
- * List of possible values to persist in the attribute.
2522
+ * @param {string[]|function} [options.values]
2523
+ * List of possible values to persist in the attribute. Pass a
2524
+ * function returning the list to defer reading the values.
2525
+ *
2526
+ * @param {number} [options.defaultValue]
2527
+ * Default value to display if property is not set.
1940
2528
  *
1941
2529
  * @param {string[]} [options.texts]
1942
2530
  * List of display texts for drop down items.
@@ -1985,7 +2573,10 @@ return __p
1985
2573
  * Display text for the blank item.
1986
2574
  *
1987
2575
  * @param {string} [options.blankTranslationKey]
1988
- * Translation key to obtain display text for blank item.
2576
+ * Translation key to obtain display text for blank item. If neither
2577
+ * `blankText` nor `blankTranslationKey` are provided, the blank text
2578
+ * will be determined using `attributeTranslationKeyPrefixes` with
2579
+ * the suffix `blank`, similar to how labels are determined.
1989
2580
  *
1990
2581
  * @param {string} [options.placeholderValue]
1991
2582
  * Include an item that sets the value of the attribute to a blank
@@ -2007,10 +2598,9 @@ return __p
2007
2598
  *
2008
2599
  * @class
2009
2600
  */
2010
-
2011
- var SelectInputView = Marionette.ItemView.extend({
2601
+ const SelectInputView = Marionette.ItemView.extend({
2012
2602
  mixins: [inputView],
2013
- template: template$7,
2603
+ template: template$9,
2014
2604
  events: {
2015
2605
  'change': 'save'
2016
2606
  },
@@ -2018,23 +2608,21 @@ var SelectInputView = Marionette.ItemView.extend({
2018
2608
  select: 'select',
2019
2609
  input: 'select'
2020
2610
  },
2021
- initialize: function initialize() {
2611
+ initialize: function () {
2612
+ this.options.values = _.result(this.options, 'values');
2022
2613
  if (this.options.collection) {
2023
2614
  this.options.values = _.pluck(this.options.collection, this.options.valueProperty);
2024
-
2025
2615
  if (this.options.textProperty) {
2026
2616
  this.options.texts = _.pluck(this.options.collection, this.options.textProperty);
2027
2617
  } else if (this.options.translationKeyProperty) {
2028
2618
  this.options.translationKeys = _.pluck(this.options.collection, this.options.translationKeyProperty);
2029
2619
  }
2030
-
2031
2620
  if (this.options.groupProperty) {
2032
2621
  this.options.groups = _.pluck(this.options.collection, this.options.groupProperty);
2033
2622
  } else if (this.options.groupTranslationKeyProperty) {
2034
2623
  this.options.groupTanslationKeys = _.pluck(this.options.collection, this.options.groupTranslationKeyProperty);
2035
2624
  }
2036
2625
  }
2037
-
2038
2626
  if (!this.options.texts) {
2039
2627
  if (!this.options.translationKeys) {
2040
2628
  var translationKeyPrefix = this.options.translationKeyPrefix || findKeyWithTranslation(this.attributeTranslationKeys('values', {
@@ -2044,53 +2632,50 @@ var SelectInputView = Marionette.ItemView.extend({
2044
2632
  return translationKeyPrefix + '.' + value;
2045
2633
  }, this);
2046
2634
  }
2047
-
2048
2635
  this.options.texts = _.map(this.options.translationKeys, function (key) {
2049
2636
  return I18n$1.t(key);
2050
2637
  });
2051
2638
  }
2052
-
2053
2639
  if (!this.options.groups) {
2054
2640
  this.options.groups = _.map(this.options.groupTanslationKeys, function (key) {
2055
2641
  return I18n$1.t(key);
2056
2642
  });
2057
2643
  }
2058
-
2059
2644
  this.optGroups = {};
2060
2645
  },
2061
- onRender: function onRender() {
2646
+ onRender: function () {
2062
2647
  this.appendBlank();
2063
2648
  this.appendPlaceholder();
2064
2649
  this.appendOptions();
2065
2650
  this.load();
2066
2651
  this.listenTo(this.model, 'change:' + this.options.propertyName, this.load);
2067
-
2068
2652
  if (this.options.ensureValueDefined && !this.model.has(this.options.propertyName)) {
2069
2653
  this.save();
2070
2654
  }
2071
2655
  },
2072
- appendBlank: function appendBlank() {
2656
+ appendBlank: function () {
2073
2657
  if (!this.options.includeBlank) {
2074
2658
  return;
2075
2659
  }
2076
-
2660
+ var blankText = this.options.blankText;
2077
2661
  if (this.options.blankTranslationKey) {
2078
- this.options.blankText = I18n$1.t(this.options.blankTranslationKey);
2662
+ blankText = I18n$1.t(this.options.blankTranslationKey);
2663
+ } else if (!blankText) {
2664
+ blankText = findTranslation(this.attributeTranslationKeys('blank'), {
2665
+ defaultValue: I18n$1.t('pageflow.ui.views.inputs.select_input_view.none')
2666
+ });
2079
2667
  }
2080
-
2081
2668
  var option = document.createElement('option');
2082
2669
  option.value = '';
2083
- option.text = this.options.blankText || I18n$1.t('pageflow.ui.views.inputs.select_input_view.none');
2670
+ option.text = blankText;
2084
2671
  this.ui.select.append(option);
2085
2672
  },
2086
- appendPlaceholder: function appendPlaceholder() {
2673
+ appendPlaceholder: function () {
2087
2674
  if (!this.options.placeholderModel && !this.options.placeholderValue) {
2088
2675
  return;
2089
2676
  }
2090
-
2091
2677
  var placeholderValue = this.options.placeholderValue || this.options.placeholderModel.get(this.options.propertyName);
2092
2678
  var placeholderIndex = this.options.values.indexOf(placeholderValue);
2093
-
2094
2679
  if (placeholderIndex >= 0) {
2095
2680
  var option = document.createElement('option');
2096
2681
  option.value = '';
@@ -2100,17 +2685,15 @@ var SelectInputView = Marionette.ItemView.extend({
2100
2685
  this.ui.select.append(option);
2101
2686
  }
2102
2687
  },
2103
- appendOptions: function appendOptions() {
2688
+ appendOptions: function () {
2104
2689
  _.each(this.options.values, function (value, index) {
2105
2690
  var option = document.createElement('option');
2106
2691
  var group = this.options.groups[index];
2107
2692
  option.value = value;
2108
2693
  option.text = this.options.texts[index];
2109
-
2110
2694
  if (this.options.optionDisabled && this.options.optionDisabled(value)) {
2111
2695
  option.setAttribute('disabled', true);
2112
2696
  }
2113
-
2114
2697
  if (group) {
2115
2698
  option.setAttribute('data-group', group);
2116
2699
  this.findOrCreateOptGroup(group).append(option);
@@ -2119,24 +2702,29 @@ var SelectInputView = Marionette.ItemView.extend({
2119
2702
  }
2120
2703
  }, this);
2121
2704
  },
2122
- findOrCreateOptGroup: function findOrCreateOptGroup(label) {
2705
+ findOrCreateOptGroup: function (label) {
2123
2706
  if (!this.optGroups[label]) {
2124
2707
  this.optGroups[label] = $('<optgroup />', {
2125
2708
  label: label
2126
2709
  }).appendTo(this.ui.select);
2127
2710
  }
2128
-
2129
2711
  return this.optGroups[label];
2130
2712
  },
2131
- save: function save() {
2132
- this.model.set(this.options.propertyName, this.ui.select.val());
2713
+ save: function () {
2714
+ const value = this.ui.select.val();
2715
+ if ('defaultValue' in this.options && value === this.options.defaultValue) {
2716
+ this.model.unset(this.options.propertyName);
2717
+ } else {
2718
+ this.model.set(this.options.propertyName, value);
2719
+ }
2133
2720
  },
2134
- load: function load() {
2721
+ load: function () {
2135
2722
  if (!this.isClosed) {
2136
2723
  var value = this.model.get(this.options.propertyName);
2137
-
2138
2724
  if (this.model.has(this.options.propertyName) && this.ui.select.find('option[value="' + value + '"]:not([disabled])').length) {
2139
2725
  this.ui.select.val(value);
2726
+ } else if ('defaultValue' in this.options) {
2727
+ this.ui.select.val(this.options.defaultValue);
2140
2728
  } else {
2141
2729
  this.ui.select.val(this.ui.select.find('option:not([disabled]):first').val());
2142
2730
  }
@@ -2144,11 +2732,10 @@ var SelectInputView = Marionette.ItemView.extend({
2144
2732
  }
2145
2733
  });
2146
2734
 
2147
- var ExtendedSelectInputView = SelectInputView.extend({
2735
+ const ExtendedSelectInputView = SelectInputView.extend({
2148
2736
  className: 'extended_select_input',
2149
- initialize: function initialize() {
2737
+ initialize: function () {
2150
2738
  SelectInputView.prototype.initialize.apply(this, arguments);
2151
-
2152
2739
  if (this.options.collection) {
2153
2740
  if (this.options.descriptionProperty) {
2154
2741
  this.options.descriptions = _.pluck(this.options.collection, this.options.descriptionProperty);
@@ -2156,47 +2743,42 @@ var ExtendedSelectInputView = SelectInputView.extend({
2156
2743
  this.options.descriptionTanslationKeys = _.pluck(this.options.collection, this.options.descriptionTranslationKeyProperty);
2157
2744
  }
2158
2745
  }
2159
-
2160
2746
  if (!this.options.descriptions) {
2161
2747
  this.options.descriptions = _.map(this.options.descriptionTanslationKeys, function (key) {
2162
2748
  return I18n$1.t(key);
2163
2749
  });
2164
2750
  }
2165
2751
  },
2166
- onRender: function onRender() {
2752
+ onRender: function () {
2167
2753
  var view = this,
2168
- options = this.options;
2754
+ options = this.options;
2169
2755
  SelectInputView.prototype.onRender.apply(this, arguments);
2170
2756
  $.widget("custom.extendedselectmenu", $.ui.selectmenu, {
2171
- _renderItem: function _renderItem(ul, item) {
2757
+ _renderItem: function (ul, item) {
2172
2758
  var widget = this;
2173
2759
  var li = $('<li>', {
2174
- "class": item.value
2760
+ class: item.value
2175
2761
  });
2176
2762
  var container = $('<div>', {
2177
- "class": 'text-container'
2763
+ class: 'text-container'
2178
2764
  }).appendTo(li);
2179
2765
  var index = options.values.indexOf(item.value);
2180
-
2181
2766
  if (item.disabled) {
2182
2767
  li.addClass('ui-state-disabled');
2183
2768
  }
2184
-
2185
2769
  if (options.pictogramClass) {
2186
2770
  $('<span>', {
2187
- "class": options.pictogramClass
2771
+ class: options.pictogramClass
2188
2772
  }).prependTo(li);
2189
2773
  }
2190
-
2191
2774
  $('<p>', {
2192
2775
  text: item.label,
2193
- "class": 'item-text'
2776
+ class: 'item-text'
2194
2777
  }).appendTo(container);
2195
2778
  $('<p>', {
2196
2779
  text: options.descriptions[index],
2197
- "class": 'item-description'
2780
+ class: 'item-description'
2198
2781
  }).appendTo(container);
2199
-
2200
2782
  if (options.helpLinkClicked) {
2201
2783
  $('<a>', {
2202
2784
  href: '#',
@@ -2207,15 +2789,13 @@ var ExtendedSelectInputView = SelectInputView.extend({
2207
2789
  return false;
2208
2790
  }).appendTo(li);
2209
2791
  }
2210
-
2211
2792
  return li.appendTo(ul);
2212
2793
  },
2213
- _resizeMenu: function _resizeMenu() {
2794
+ _resizeMenu: function () {
2214
2795
  this.menuWrap.addClass('extended_select_input_menu');
2215
2796
  var menuHeight = this.menu.height(),
2216
- menuOffset = this.button.offset().top + this.button.outerHeight(),
2217
- bodyHeight = $('body').height();
2218
-
2797
+ menuOffset = this.button.offset().top + this.button.outerHeight(),
2798
+ bodyHeight = $('body').height();
2219
2799
  if (menuHeight + menuOffset > bodyHeight) {
2220
2800
  this.menuWrap.outerHeight(bodyHeight - menuOffset - 5).css({
2221
2801
  'overflow-y': 'scroll'
@@ -2237,13 +2817,13 @@ var ExtendedSelectInputView = SelectInputView.extend({
2237
2817
  }
2238
2818
  });
2239
2819
  },
2240
- select: function select(event, ui) {
2820
+ select: function (event, ui) {
2241
2821
  this.ui.select.val(ui.item.value);
2242
2822
  this.save();
2243
2823
  }
2244
2824
  });
2245
2825
 
2246
- function template$8(data) {
2826
+ function template$a(data) {
2247
2827
  var __t, __p = '';
2248
2828
  __p += '<label>\n <span class="name"></span>\n <span class="inline_help"></span>\n</label>\n\n<!-- inline style for wysihtml5 to pick up -->\n<textarea style="width: 100%;" dir="auto"></textarea>\n\n<div class="toolbar">\n <a data-wysihtml5-command="bold" title="' +
2249
2829
  ((__t = ( I18n.t('pageflow.ui.templates.inputs.text_area_input.bold') )) == null ? '' : __t) +
@@ -2300,10 +2880,9 @@ return __p
2300
2880
  *
2301
2881
  * @class
2302
2882
  */
2303
-
2304
- var TextAreaInputView = Marionette.ItemView.extend({
2883
+ const TextAreaInputView = Marionette.ItemView.extend({
2305
2884
  mixins: [inputView, inputWithPlaceholderText],
2306
- template: template$8,
2885
+ template: template$a,
2307
2886
  className: 'text_area_input',
2308
2887
  ui: {
2309
2888
  input: 'textarea',
@@ -2327,7 +2906,7 @@ var TextAreaInputView = Marionette.ItemView.extend({
2327
2906
  'change .open_in_new_tab': 'setTargetFromOpenInNewTabCheckBox',
2328
2907
  'change .display_url': 'setUrlFromDisplayUrl'
2329
2908
  },
2330
- onRender: function onRender() {
2909
+ onRender: function () {
2331
2910
  this.ui.input.addClass(this.options.size);
2332
2911
  this.load();
2333
2912
  this.updatePlaceholder();
@@ -2376,7 +2955,6 @@ var TextAreaInputView = Marionette.ItemView.extend({
2376
2955
  }
2377
2956
  }
2378
2957
  });
2379
-
2380
2958
  if (this.options.disableRichtext) {
2381
2959
  this.ui.toolbar.find('a[data-wysihtml5-command="bold"]').hide();
2382
2960
  this.ui.toolbar.find('a[data-wysihtml5-command="italic"]').hide();
@@ -2384,36 +2962,42 @@ var TextAreaInputView = Marionette.ItemView.extend({
2384
2962
  this.ui.toolbar.find('a[data-wysihtml5-command="insertOrderedList"]').hide();
2385
2963
  this.ui.toolbar.find('a[data-wysihtml5-command="insertUnorderedList"]').hide();
2386
2964
  }
2387
-
2388
2965
  if (!this.options.enableLists) {
2389
2966
  this.ui.toolbar.find('a[data-wysihtml5-command="insertOrderedList"]').hide();
2390
2967
  this.ui.toolbar.find('a[data-wysihtml5-command="insertUnorderedList"]').hide();
2391
2968
  }
2392
-
2393
2969
  if (this.options.disableLinks) {
2394
2970
  this.ui.toolbar.find('a[data-wysihtml5-command="createLink"]').hide();
2395
2971
  } else {
2396
2972
  this.setupUrlLinkPanel();
2397
2973
  this.setupFragmentLinkPanel();
2398
2974
  }
2399
-
2400
2975
  this.editor.on('change', _.bind(this.save, this));
2401
2976
  this.editor.on('aftercommand:composer', _.bind(this.save, this));
2402
2977
  },
2403
- onClose: function onClose() {
2978
+ onClose: function () {
2404
2979
  this.editor.fire('destroy:composer');
2405
2980
  },
2406
- save: function save() {
2981
+ updateDisabled: function () {
2982
+ inputView.updateDisabled.call(this);
2983
+ if (this.isDisabled()) {
2984
+ this.editor.disable();
2985
+ this.$el.attr('inert', true);
2986
+ } else {
2987
+ this.editor.enable();
2988
+ this.$el.removeAttr('inert');
2989
+ }
2990
+ },
2991
+ save: function () {
2407
2992
  this.model.set(this.options.propertyName, this.editor.getValue());
2408
2993
  },
2409
- load: function load() {
2994
+ load: function () {
2410
2995
  this.ui.input.val(this.model.get(this.options.propertyName));
2411
2996
  },
2412
- setupUrlLinkPanel: function setupUrlLinkPanel() {
2997
+ setupUrlLinkPanel: function () {
2413
2998
  this.editor.on('show:dialog', _.bind(function () {
2414
2999
  this.ui.linkDialog.toggleClass('for_existing_link', this.ui.linkButton.hasClass('wysihtml5-command-active'));
2415
3000
  var currentUrl = this.ui.urlInput.val();
2416
-
2417
3001
  if (currentUrl.startsWith('#')) {
2418
3002
  this.ui.displayUrlInput.val('');
2419
3003
  this.ui.openInNewTabCheckBox.prop('checked', true);
@@ -2423,7 +3007,7 @@ var TextAreaInputView = Marionette.ItemView.extend({
2423
3007
  }
2424
3008
  }, this));
2425
3009
  },
2426
- setupFragmentLinkPanel: function setupFragmentLinkPanel() {
3010
+ setupFragmentLinkPanel: function () {
2427
3011
  if (this.options.fragmentLinkInputView) {
2428
3012
  this.fragmentLinkModel = new Backbone.Model();
2429
3013
  this.listenTo(this.fragmentLinkModel, 'change', function (model, options) {
@@ -2451,37 +3035,36 @@ var TextAreaInputView = Marionette.ItemView.extend({
2451
3035
  this.ui.fragmentLinkPanel.hide();
2452
3036
  }
2453
3037
  },
2454
- initLinkTypePanels: function initLinkTypePanels(isUrlLink) {
3038
+ initLinkTypePanels: function (isUrlLink) {
2455
3039
  if (isUrlLink) {
2456
3040
  this.ui.urlLinkRadioButton.prop('checked', true);
2457
3041
  } else {
2458
3042
  this.ui.fragmentLinkRadioButton.prop('checked', true);
2459
3043
  }
2460
-
2461
3044
  this.ui.toolbar.toggleClass('fragment_link_panel_active', !isUrlLink);
2462
3045
  },
2463
- showUrlLinkPanel: function showUrlLinkPanel() {
3046
+ showUrlLinkPanel: function () {
2464
3047
  this.ui.toolbar.removeClass('fragment_link_panel_active');
2465
3048
  this.setUrlFromDisplayUrl();
2466
3049
  this.setTargetFromOpenInNewTabCheckBox();
2467
3050
  },
2468
- showFragmentLinkPanel: function showFragmentLinkPanel() {
3051
+ showFragmentLinkPanel: function () {
2469
3052
  this.ui.toolbar.addClass('fragment_link_panel_active');
2470
3053
  this.setInputsFromFragmentLinkModel();
2471
3054
  },
2472
- setInputsFromFragmentLinkModel: function setInputsFromFragmentLinkModel() {
3055
+ setInputsFromFragmentLinkModel: function () {
2473
3056
  this.ui.urlInput.val('#' + (this.fragmentLinkModel.get('linkId') || ''));
2474
3057
  this.ui.targetInput.val('_self');
2475
3058
  },
2476
- setUrlFromDisplayUrl: function setUrlFromDisplayUrl() {
3059
+ setUrlFromDisplayUrl: function () {
2477
3060
  this.ui.urlInput.val(this.ui.displayUrlInput.val());
2478
3061
  },
2479
- setTargetFromOpenInNewTabCheckBox: function setTargetFromOpenInNewTabCheckBox() {
3062
+ setTargetFromOpenInNewTabCheckBox: function () {
2480
3063
  this.ui.targetInput.val(this.ui.openInNewTabCheckBox.is(':checked') ? '_blank' : '_self');
2481
3064
  }
2482
3065
  });
2483
3066
 
2484
- function template$9(data) {
3067
+ function template$b(data) {
2485
3068
  var __p = '';
2486
3069
  __p += '<label>\n <span class="name"></span>\n <span class="inline_help"></span>\n</label>\n<input type="text" />\n<div class="validation"></div>\n';
2487
3070
  return __p
@@ -2504,12 +3087,9 @@ return __p
2504
3087
  *
2505
3088
  * @class
2506
3089
  */
2507
-
2508
- var UrlInputView = Marionette.Layout.extend(
2509
- /** @lends UrlInputView.prototype */
2510
- {
3090
+ const UrlInputView = Marionette.Layout.extend( /** @lends UrlInputView.prototype */{
2511
3091
  mixins: [inputView],
2512
- template: template$9,
3092
+ template: template$b,
2513
3093
  ui: {
2514
3094
  input: 'input',
2515
3095
  validation: '.validation'
@@ -2517,95 +3097,95 @@ var UrlInputView = Marionette.Layout.extend(
2517
3097
  events: {
2518
3098
  'change': 'onChange'
2519
3099
  },
2520
- onRender: function onRender() {
3100
+ onRender: function () {
2521
3101
  this.ui.validation.hide();
2522
3102
  this.load();
2523
3103
  this.validate();
3104
+ this.listenTo(this.model, 'change:' + this.options.displayPropertyName, this.load);
2524
3105
  },
2525
- onChange: function onChange() {
2526
- var _this = this;
2527
-
2528
- this.validate().then(function () {
2529
- return _this.save();
2530
- }, function () {
2531
- return _this.saveDisplayProperty();
2532
- });
3106
+ onChange: function () {
3107
+ var value = this.ui.input.val();
3108
+ this.validate().then(() => this.save(value), () => this.saveDisplayProperty(value));
2533
3109
  },
2534
- saveDisplayProperty: function saveDisplayProperty() {
3110
+ saveDisplayProperty: function (value) {
2535
3111
  this.model.unset(this.options.propertyName, {
2536
3112
  silent: true
2537
3113
  });
2538
- this.model.set(this.options.displayPropertyName, this.ui.input.val());
3114
+ this.model.set(this.options.displayPropertyName, value);
2539
3115
  },
2540
- save: function save() {
2541
- var _this2 = this;
2542
-
2543
- var value = this.ui.input.val();
2544
- $.when(this.transformPropertyValue(value)).then(function (transformedValue) {
2545
- var _this2$model$set;
2546
-
2547
- _this2.model.set((_this2$model$set = {}, _defineProperty(_this2$model$set, _this2.options.displayPropertyName, value), _defineProperty(_this2$model$set, _this2.options.propertyName, transformedValue), _this2$model$set));
3116
+ save: function (value) {
3117
+ $.when(this.transformPropertyValue(value, this.lastValidationResult)).then(transformedValue => {
3118
+ this.model.set({
3119
+ [this.options.displayPropertyName]: value,
3120
+ [this.options.propertyName]: transformedValue
3121
+ });
2548
3122
  });
2549
3123
  },
2550
- load: function load() {
3124
+ load: function () {
2551
3125
  this.ui.input.val(this.model.has(this.options.displayPropertyName) ? this.model.get(this.options.displayPropertyName) : this.model.get(this.options.propertyName));
2552
3126
  this.onLoad();
2553
3127
  },
2554
-
2555
3128
  /**
2556
3129
  * Override to be notified when the input has been loaded.
2557
3130
  */
2558
- onLoad: function onLoad() {},
2559
-
3131
+ onLoad: function () {},
2560
3132
  /**
2561
3133
  * Override to validate the untransformed url. Validation error
2562
3134
  * message can be passed as rejected promise. Progress notifications
2563
3135
  * are displayed. Only valid urls are stored in the configuration.
2564
3136
  *
3137
+ * Can optionally return a value when the promise resolves. This
3138
+ * value will be passed to transformPropertyValue as the second
3139
+ * parameter.
3140
+ *
2565
3141
  * @return Promise
2566
3142
  */
2567
- validateUrl: function validateUrl(url) {
3143
+ validateUrl: function (url) {
2568
3144
  return $.Deferred().resolve().promise();
2569
3145
  },
2570
-
2571
3146
  /**
2572
3147
  * Override to transform the property value before it is stored.
2573
3148
  *
3149
+ * @param {String} value - The URL value to transform
3150
+ * @param {*} validationResult - Optional result returned by validateUrl
2574
3151
  * @return Promise | String
2575
3152
  */
2576
- transformPropertyValue: function transformPropertyValue(value) {
3153
+ transformPropertyValue: function (value, validationResult) {
2577
3154
  return value;
2578
3155
  },
2579
-
2580
3156
  /**
2581
3157
  * Override to change the list of supported host names.
2582
3158
  */
2583
- supportedHosts: function supportedHosts() {
3159
+ supportedHosts: function () {
2584
3160
  return this.options.supportedHosts;
2585
3161
  },
3162
+ /**
3163
+ * Override to enable HTTPS URLs by default.
3164
+ * @return {boolean}
3165
+ */
3166
+ permitHttps: function () {
3167
+ return this.options.permitHttps;
3168
+ },
2586
3169
  // Host names used to be expected to include protocols. Remove
2587
3170
  // protocols for backwards compatilbity. Since supportedHosts
2588
3171
  // is supposed to be overridden in subclasses, we do it in a
2589
3172
  // separate method.
2590
- supportedHostsWithoutLegacyProtocols: function supportedHostsWithoutLegacyProtocols() {
3173
+ supportedHostsWithoutLegacyProtocols: function () {
2591
3174
  return _.map(this.supportedHosts(), function (host) {
2592
3175
  return host.replace(/^https?:\/\//, '');
2593
3176
  });
2594
3177
  },
2595
- validate: function validate(success) {
3178
+ validate: function (success) {
2596
3179
  var view = this;
2597
3180
  var options = this.options;
2598
3181
  var value = this.ui.input.val();
2599
-
2600
3182
  if (options.required && !value) {
2601
3183
  displayValidationError(I18n$1.t('pageflow.ui.views.inputs.url_input_view.required_field'));
2602
3184
  } else if (value && !isValidUrl(value)) {
2603
3185
  var errorMessage = I18n$1.t('pageflow.ui.views.inputs.url_input_view.url_hint');
2604
-
2605
- if (options.permitHttps) {
3186
+ if (view.permitHttps()) {
2606
3187
  errorMessage = I18n$1.t('pageflow.ui.views.inputs.url_input_view.url_hint_https');
2607
3188
  }
2608
-
2609
3189
  displayValidationError(errorMessage);
2610
3190
  } else if (value && !hasSupportedHost(value)) {
2611
3191
  displayValidationError(I18n$1.t('pageflow.ui.views.inputs.url_input_view.supported_vendors') + _.map(view.supportedHosts(), function (url) {
@@ -2613,41 +3193,44 @@ var UrlInputView = Marionette.Layout.extend(
2613
3193
  }).join(''));
2614
3194
  } else {
2615
3195
  return view.validateUrl(value).progress(function (message) {
2616
- displayValidationPending(message);
2617
- }).done(function () {
2618
- resetValidationError();
3196
+ if (!view.isClosed) {
3197
+ displayValidationPending(message);
3198
+ }
3199
+ }).done(function (result) {
3200
+ view.lastValidationResult = result;
3201
+ if (!view.isClosed) {
3202
+ resetValidationError();
3203
+ }
2619
3204
  }).fail(function (error) {
2620
- displayValidationError(error);
3205
+ view.lastValidationResult = undefined;
3206
+ if (!view.isClosed) {
3207
+ displayValidationError(error);
3208
+ }
2621
3209
  });
2622
3210
  }
2623
-
2624
3211
  return $.Deferred().reject().promise();
2625
-
2626
3212
  function isValidUrl(url) {
2627
- return options.permitHttps ? url.match(/^https?:\/\//i) : url.match(/^http:\/\//i);
3213
+ return view.permitHttps() ? url.match(/^https?:\/\//i) : url.match(/^http:\/\//i);
2628
3214
  }
2629
-
2630
3215
  function hasSupportedHost(url) {
2631
3216
  return _.any(view.supportedHostsWithoutLegacyProtocols(), function (host) {
2632
3217
  return url.match(new RegExp('^https?://' + host));
2633
3218
  });
2634
3219
  }
2635
-
2636
3220
  function displayValidationError(message) {
2637
3221
  view.$el.addClass('invalid');
2638
3222
  view.ui.input.attr('aria-invalid', 'true');
2639
3223
  view.ui.validation.removeClass('pending').addClass('failed').html(message).show();
2640
3224
  }
2641
-
2642
3225
  function displayValidationPending(message) {
2643
3226
  view.$el.removeClass('invalid');
2644
3227
  view.ui.input.removeAttr('aria-invalid');
2645
3228
  view.ui.validation.removeClass('failed').addClass('pending').html(message).show();
2646
3229
  }
2647
-
2648
3230
  function resetValidationError(message) {
2649
3231
  view.$el.removeClass('invalid');
2650
3232
  view.ui.input.attr('aria-invalid', 'false');
3233
+ view.ui.validation.text('');
2651
3234
  view.ui.validation.hide();
2652
3235
  }
2653
3236
  }
@@ -2699,12 +3282,9 @@ var UrlInputView = Marionette.Layout.extend(
2699
3282
  *
2700
3283
  * @class
2701
3284
  */
2702
-
2703
- var ProxyUrlInputView = UrlInputView.extend(
2704
- /** @lends ProxyUrlInputView.prototype */
2705
- {
3285
+ const ProxyUrlInputView = UrlInputView.extend( /** @lends ProxyUrlInputView.prototype */{
2706
3286
  // @override
2707
- validateUrl: function validateUrl(url) {
3287
+ validateUrl: function (url) {
2708
3288
  var view = this;
2709
3289
  return $.Deferred(function (deferred) {
2710
3290
  deferred.notify(I18n$1.t('pageflow.ui.views.inputs.proxy_url_input_view.url_validation'));
@@ -2719,25 +3299,28 @@ var ProxyUrlInputView = UrlInputView.extend(
2719
3299
  }).promise();
2720
3300
  },
2721
3301
  // override
2722
- transformPropertyValue: function transformPropertyValue(url) {
3302
+ transformPropertyValue: function (url) {
2723
3303
  return this.rewriteUrl(url);
2724
3304
  },
2725
3305
  // override
2726
- supportedHosts: function supportedHosts() {
3306
+ supportedHosts: function () {
2727
3307
  return _.pluck(this.options.proxies, 'url');
2728
3308
  },
2729
- rewriteUrl: function rewriteUrl(url) {
3309
+ rewriteUrl: function (url) {
2730
3310
  _.each(this.options.proxies, function (proxy) {
2731
3311
  url = url.replace(new RegExp('^' + proxy.url + '/?'), proxy.base_path + '/');
2732
3312
  });
2733
-
2734
3313
  return url;
2735
3314
  }
2736
3315
  });
2737
3316
 
2738
- function template$a(data) {
2739
- var __p = '';
2740
- __p += '<label>\n <span class="name"></span>\n <span class="inline_help"></span>\n</label>\n<div class="value"></div>\n<div class="slider"></div>\n';
3317
+ function template$c(data) {
3318
+ var __t, __p = '';
3319
+ __p += '<label>\n <span class="name"></span>\n <span class="inline_help"></span>\n</label>\n<div class="slider_wrapper">\n ';
3320
+ if (data.icon) {__p += '<img class="icon" src="' +
3321
+ ((__t = ( data.icon )) == null ? '' : __t) +
3322
+ '" width="18" height="18" />';
3323
+ }__p += '\n <div class="value"></div>\n <div class="slider"></div>\n</div>\n';
2741
3324
  return __p
2742
3325
  }
2743
3326
 
@@ -2748,7 +3331,7 @@ return __p
2748
3331
  * @param {Object} [options]
2749
3332
  *
2750
3333
  * @param {number} [options.defaultValue]
2751
- * Defaults value to display if property is not set.
3334
+ * Default value to display if property is not set.
2752
3335
  *
2753
3336
  * @param {number} [options.minValue=0]
2754
3337
  * Value when dragging slider to the very left.
@@ -2766,92 +3349,116 @@ return __p
2766
3349
  * Already update the model while dragging the handle - not only after
2767
3350
  * handle has been released.
2768
3351
  *
3352
+ * @param {string} [options.icon]
3353
+ * Path to an icon image to display before the label.
3354
+ *
2769
3355
  * @class
2770
3356
  */
2771
-
2772
- var SliderInputView = Marionette.ItemView.extend({
3357
+ const SliderInputView = Marionette.ItemView.extend({
2773
3358
  mixins: [inputView],
2774
3359
  className: 'slider_input',
2775
- template: template$a,
3360
+ template: template$c,
3361
+ serializeData() {
3362
+ return {
3363
+ icon: this.options.icon
3364
+ };
3365
+ },
2776
3366
  ui: {
2777
3367
  widget: '.slider',
2778
3368
  value: '.value'
2779
3369
  },
2780
3370
  events: {
3371
+ 'slidestart': 'handleSlideStart',
2781
3372
  'slidechange': 'save',
2782
3373
  'slide': 'handleSlide'
2783
3374
  },
2784
- onRender: function onRender() {
2785
- var _this = this;
2786
-
3375
+ onRender: function () {
2787
3376
  this.ui.widget.slider({
2788
3377
  animate: 'fast'
2789
3378
  });
2790
- this.setupAttributeBinding('minValue', function (value) {
2791
- return _this.updateSliderOption('min', value || 0);
2792
- });
2793
- this.setupAttributeBinding('maxValue', function (value) {
2794
- return _this.updateSliderOption('max', value || 100);
2795
- });
3379
+ if (this.options.values) {
3380
+ this.ui.widget.slider('option', 'min', 0);
3381
+ this.ui.widget.slider('option', 'max', this.options.values.length - 1);
3382
+ } else {
3383
+ this.setupAttributeBinding('minValue', value => this.updateSliderOption('min', value || 0));
3384
+ this.setupAttributeBinding('maxValue', value => this.updateSliderOption('max', value !== undefined ? value : 100));
3385
+ }
2796
3386
  this.load();
2797
3387
  this.listenTo(this.model, 'change:' + this.options.propertyName, this.load);
2798
3388
  },
2799
- updateSliderOption: function updateSliderOption(name, value) {
3389
+ updateSliderOption(name, value) {
2800
3390
  this.ui.widget.slider('option', name, value);
2801
3391
  this.updateText(this.ui.widget.slider('value'));
2802
3392
  },
2803
- updateDisabled: function updateDisabled(disabled) {
3393
+ updateDisabled: function (disabled) {
2804
3394
  this.$el.toggleClass('disabled', !!disabled);
2805
-
2806
3395
  if (disabled) {
2807
3396
  this.ui.widget.slider('disable');
2808
3397
  } else {
2809
3398
  this.ui.widget.slider('enable');
2810
3399
  }
2811
3400
  },
2812
- handleSlide: function handleSlide(event, ui) {
2813
- this.updateText(ui.value);
2814
-
3401
+ handleSlideStart() {
3402
+ if (this.options.onInteractionStart) {
3403
+ this.options.onInteractionStart();
3404
+ }
3405
+ },
3406
+ handleSlide(event, ui) {
3407
+ var value = this.options.values ? this.options.values[ui.value] : ui.value;
3408
+ this.updateText(value);
2815
3409
  if (this.options.saveOnSlide) {
2816
3410
  this.save(event, ui);
2817
3411
  }
2818
3412
  },
2819
- save: function save(event, ui) {
2820
- this.model.set(this.options.propertyName, ui.value);
3413
+ save: function (event, ui) {
3414
+ if (this.loading) {
3415
+ return;
3416
+ }
3417
+ var value = this.options.values ? this.options.values[ui.value] : ui.value;
3418
+ this.model.set(this.options.propertyName, value);
2821
3419
  },
2822
- load: function load() {
3420
+ load: function () {
2823
3421
  var value;
2824
-
2825
3422
  if (this.model.has(this.options.propertyName)) {
2826
3423
  value = this.model.get(this.options.propertyName);
2827
3424
  } else {
2828
3425
  value = 'defaultValue' in this.options ? this.options.defaultValue : 0;
2829
3426
  }
2830
-
2831
- this.ui.widget.slider('option', 'value', this.clampValue(value));
3427
+ var sliderValue = this.options.values ? this.options.values.indexOf(value) : value;
3428
+ this.loading = true;
3429
+ this.ui.widget.slider('option', 'value', this.clampValue(sliderValue));
3430
+ this.loading = false;
2832
3431
  this.updateText(value);
2833
3432
  },
2834
- clampValue: function clampValue(value) {
2835
- var min = this.ui.widget.slider('option', 'min');
2836
- var max = this.ui.widget.slider('option', 'max');
3433
+ clampValue(value) {
3434
+ const min = this.ui.widget.slider('option', 'min');
3435
+ const max = this.ui.widget.slider('option', 'max');
2837
3436
  return Math.min(max, Math.max(min, value));
2838
3437
  },
2839
- updateText: function updateText(value) {
2840
- var unit = 'unit' in this.options ? this.options.unit : '%';
2841
- var text = 'displayText' in this.options ? this.options.displayText(value) : value + unit;
3438
+ updateText: function (value) {
3439
+ var text;
3440
+ if (this.options.texts) {
3441
+ var index = this.options.values.indexOf(value);
3442
+ text = this.options.texts[index];
3443
+ } else if ('displayText' in this.options) {
3444
+ text = this.options.displayText(value);
3445
+ } else {
3446
+ var unit = 'unit' in this.options ? this.options.unit : '%';
3447
+ text = value + unit;
3448
+ }
2842
3449
  this.ui.value.text(text);
2843
3450
  }
2844
3451
  });
2845
3452
 
2846
- function template$b(data) {
3453
+ function template$d(data) {
2847
3454
  var __p = '';
2848
3455
  __p += '<label>\n <span class="name"></span>\n <span class="inline_help"></span>\n</label>\n\n<textarea></textarea>\n';
2849
3456
  return __p
2850
3457
  }
2851
3458
 
2852
- var JsonInputView = Marionette.ItemView.extend({
3459
+ const JsonInputView = Marionette.ItemView.extend({
2853
3460
  mixins: [inputView],
2854
- template: template$b,
3461
+ template: template$d,
2855
3462
  className: 'json_input',
2856
3463
  ui: {
2857
3464
  input: 'textarea'
@@ -2860,32 +3467,31 @@ var JsonInputView = Marionette.ItemView.extend({
2860
3467
  'change': 'onChange',
2861
3468
  'keyup': 'validate'
2862
3469
  },
2863
- onRender: function onRender() {
3470
+ onRender: function () {
2864
3471
  this.load();
2865
3472
  this.validate();
2866
3473
  this.listenTo(this.model, 'change:' + this.options.propertyName, this.load);
2867
3474
  },
2868
- onChange: function onChange() {
3475
+ onChange: function () {
2869
3476
  if (this.validate()) {
2870
3477
  this.save();
2871
3478
  }
2872
3479
  },
2873
- onClose: function onClose() {
3480
+ onClose: function () {
2874
3481
  if (this.validate()) {
2875
3482
  this.save();
2876
3483
  }
2877
3484
  },
2878
- save: function save() {
3485
+ save: function () {
2879
3486
  this.model.set(this.options.propertyName, this.ui.input.val() ? JSON.parse(this.ui.input.val()) : null);
2880
3487
  },
2881
- load: function load() {
3488
+ load: function () {
2882
3489
  var input = this.ui.input;
2883
3490
  var value = this.model.get(this.options.propertyName);
2884
3491
  input.val(value ? JSON.stringify(value, null, 2) : '');
2885
3492
  },
2886
- validate: function validate() {
3493
+ validate: function () {
2887
3494
  var input = this.ui.input;
2888
-
2889
3495
  if (input.val() && !this.isValidJson(input.val())) {
2890
3496
  this.displayValidationError(I18n$1.t('pageflow.ui.views.inputs.json_input_view.invalid'));
2891
3497
  return false;
@@ -2894,15 +3500,15 @@ var JsonInputView = Marionette.ItemView.extend({
2894
3500
  return true;
2895
3501
  }
2896
3502
  },
2897
- displayValidationError: function displayValidationError(message) {
3503
+ displayValidationError: function (message) {
2898
3504
  this.$el.addClass('invalid');
2899
3505
  this.ui.input.attr('title', message);
2900
3506
  },
2901
- resetValidationError: function resetValidationError(message) {
3507
+ resetValidationError: function (message) {
2902
3508
  this.$el.removeClass('invalid');
2903
3509
  this.ui.input.attr('title', '');
2904
3510
  },
2905
- isValidJson: function isValidJson(text) {
3511
+ isValidJson: function (text) {
2906
3512
  try {
2907
3513
  JSON.parse(text);
2908
3514
  return true;
@@ -2912,7 +3518,7 @@ var JsonInputView = Marionette.ItemView.extend({
2912
3518
  }
2913
3519
  });
2914
3520
 
2915
- function template$c(data) {
3521
+ function template$e(data) {
2916
3522
  var __p = '';
2917
3523
  __p += '<input type="checkbox" />\n<label>\n <span class="name"></span>\n <span class="inline_help"></span>\n</label>';
2918
3524
  return __p
@@ -2939,10 +3545,9 @@ return __p
2939
3545
  *
2940
3546
  * @class
2941
3547
  */
2942
-
2943
- var CheckBoxInputView = Marionette.ItemView.extend({
3548
+ const CheckBoxInputView = Marionette.ItemView.extend({
2944
3549
  mixins: [inputView],
2945
- template: template$c,
3550
+ template: template$e,
2946
3551
  className: 'check_box_input',
2947
3552
  events: {
2948
3553
  'change': 'save'
@@ -2951,19 +3556,18 @@ var CheckBoxInputView = Marionette.ItemView.extend({
2951
3556
  input: 'input',
2952
3557
  label: 'label'
2953
3558
  },
2954
- onRender: function onRender() {
3559
+ onRender: function () {
2955
3560
  this.ui.label.attr('for', this.cid);
2956
3561
  this.ui.input.attr('id', this.cid);
2957
3562
  this.load();
2958
3563
  this.listenTo(this.model, 'change:' + this.options.propertyName, this.load);
2959
3564
  },
2960
- updateDisabled: function updateDisabled() {
3565
+ updateDisabled: function () {
2961
3566
  this.load();
2962
3567
  },
2963
- save: function save() {
3568
+ save: function () {
2964
3569
  if (!this.isDisabled()) {
2965
- var value = this.ui.input.is(':checked');
2966
-
3570
+ const value = this.ui.input.is(':checked');
2967
3571
  if (this.options.storeInverted) {
2968
3572
  this.model.set(this.options.storeInverted, !value);
2969
3573
  } else {
@@ -2971,12 +3575,12 @@ var CheckBoxInputView = Marionette.ItemView.extend({
2971
3575
  }
2972
3576
  }
2973
3577
  },
2974
- load: function load() {
3578
+ load: function () {
2975
3579
  if (!this.isClosed) {
2976
- this.ui.input.prop('checked', this.displayValue());
3580
+ this.ui.input.prop('checked', !!this.displayValue());
2977
3581
  }
2978
3582
  },
2979
- displayValue: function displayValue() {
3583
+ displayValue: function () {
2980
3584
  if (this.isDisabled() && this.options.displayUncheckedIfDisabled) {
2981
3585
  return false;
2982
3586
  } else if (this.isDisabled() && this.options.displayCheckedIfDisabled) {
@@ -2989,6 +3593,86 @@ var CheckBoxInputView = Marionette.ItemView.extend({
2989
3593
  }
2990
3594
  });
2991
3595
 
3596
+ /**
3597
+ * Input view for selecting one value from a small set of options
3598
+ * using radio buttons.
3599
+ * See {@link inputView} for further options.
3600
+ *
3601
+ * @param {Object} [options]
3602
+ *
3603
+ * @param {Array} options.values
3604
+ * Array of possible values the property can be set to.
3605
+ *
3606
+ * @param {Array} [options.texts]
3607
+ * Array of display texts for the values. If not provided,
3608
+ * translations are looked up based on the model's i18nKey.
3609
+ *
3610
+ * @class
3611
+ */
3612
+ const RadioButtonGroupInputView = Marionette.ItemView.extend({
3613
+ mixins: [inputView],
3614
+ template: () => `
3615
+ <label>
3616
+ <span class="name"></span>
3617
+ <span class="inline_help"></span>
3618
+ </label>
3619
+ `,
3620
+ className: 'radio_button_input',
3621
+ events: {
3622
+ 'change': 'save'
3623
+ },
3624
+ initialize: function () {
3625
+ if (!this.options.texts) {
3626
+ var translationKeyPrefix = findKeyWithTranslation(this.attributeTranslationKeys('values', {
3627
+ fallbackPrefix: 'activerecord.values'
3628
+ }));
3629
+ this.options.texts = _.map(this.options.values, function (value) {
3630
+ return I18n$1.t(translationKeyPrefix + '.' + value);
3631
+ });
3632
+ }
3633
+ },
3634
+ onRender: function () {
3635
+ this.appendOptions();
3636
+ this.load();
3637
+ this.listenTo(this.model, 'change:' + this.options.propertyName, this.load);
3638
+ },
3639
+ updateDisabled: function () {
3640
+ this.$el.find('input').prop('disabled', this.isDisabled());
3641
+ },
3642
+ save: function () {
3643
+ var index = this.$el.find('input').index(this.$el.find('input:checked'));
3644
+ this.model.set(this.options.propertyName, this.options.values[index]);
3645
+ },
3646
+ appendOptions: function () {
3647
+ _.each(this.options.values, function (value, index) {
3648
+ var id = this.cid + '_' + value;
3649
+ var wrapper = document.createElement('div');
3650
+ wrapper.className = 'radio_button';
3651
+ var input = document.createElement('input');
3652
+ input.type = 'radio';
3653
+ input.name = this.options.propertyName;
3654
+ input.value = value;
3655
+ input.id = id;
3656
+ var label = document.createElement('label');
3657
+ label.htmlFor = id;
3658
+ var nameSpan = document.createElement('span');
3659
+ nameSpan.className = 'name';
3660
+ nameSpan.textContent = this.options.texts[index];
3661
+ label.appendChild(nameSpan);
3662
+ wrapper.appendChild(input);
3663
+ wrapper.appendChild(document.createTextNode(' '));
3664
+ wrapper.appendChild(label);
3665
+ this.$el.append(wrapper);
3666
+ }, this);
3667
+ },
3668
+ load: function () {
3669
+ if (!this.isClosed) {
3670
+ var value = this.model.get(this.options.propertyName);
3671
+ this.$el.find('input[value="' + value + '"]').prop('checked', true);
3672
+ }
3673
+ }
3674
+ });
3675
+
2992
3676
  /**
2993
3677
  * Render a separator in a {@link ConfigurationEditorView} tab.
2994
3678
  *
@@ -2998,8 +3682,7 @@ var CheckBoxInputView = Marionette.ItemView.extend({
2998
3682
  *
2999
3683
  * @class
3000
3684
  */
3001
-
3002
- var SeparatorView = Marionette.View.extend({
3685
+ const SeparatorView = Marionette.View.extend({
3003
3686
  className: 'separator'
3004
3687
  });
3005
3688
 
@@ -3011,12 +3694,14 @@ var SeparatorView = Marionette.View.extend({
3011
3694
  *
3012
3695
  * @class
3013
3696
  */
3014
-
3015
- var LabelOnlyView = Marionette.ItemView.extend({
3697
+ const LabelOnlyView = Marionette.ItemView.extend({
3016
3698
  mixins: [inputView],
3017
- template: function template() {
3018
- return "\n <label>\n <span class=\"name\"></span>\n <span class=\"inline_help\"></span>\n </label>\n ";
3019
- },
3699
+ template: () => `
3700
+ <label>
3701
+ <span class="name"></span>
3702
+ <span class="inline_help"></span>
3703
+ </label>
3704
+ `,
3020
3705
  ui: {
3021
3706
  label: 'label'
3022
3707
  }
@@ -3037,10 +3722,9 @@ var LabelOnlyView = Marionette.ItemView.extend({
3037
3722
  *
3038
3723
  * @since 12.0
3039
3724
  */
3040
-
3041
- var EnumTableCellView = TableCellView.extend({
3725
+ const EnumTableCellView = TableCellView.extend({
3042
3726
  className: 'enum_table_cell',
3043
- update: function update() {
3727
+ update: function () {
3044
3728
  this.$el.text(this.attributeTranslation('cell_text.' + (this.attributeValue() || 'blank')));
3045
3729
  this.$el.attr('title', this.attributeTranslation('cell_title.' + (this.attributeValue() || 'blank'), {
3046
3730
  defaultValue: ''
@@ -3048,7 +3732,7 @@ var EnumTableCellView = TableCellView.extend({
3048
3732
  }
3049
3733
  });
3050
3734
 
3051
- function template$d(data) {
3735
+ function template$f(data) {
3052
3736
  var __t, __p = '';
3053
3737
  __p += '<a class="remove" title="' +
3054
3738
  ((__t = ( I18n.t('pageflow.editor.templates.row.destroy') )) == null ? '' : __t) +
@@ -3078,24 +3762,22 @@ return __p
3078
3762
  *
3079
3763
  * @since 12.0
3080
3764
  */
3081
-
3082
- var DeleteRowTableCellView = TableCellView.extend({
3765
+ const DeleteRowTableCellView = TableCellView.extend({
3083
3766
  className: 'delete_row_table_cell',
3084
- template: template$d,
3767
+ template: template$f,
3085
3768
  ui: {
3086
3769
  removeButton: '.remove'
3087
3770
  },
3088
3771
  events: {
3089
3772
  'click .remove': 'destroy',
3090
- 'click': function click() {
3773
+ 'click': function () {
3091
3774
  return false;
3092
3775
  }
3093
3776
  },
3094
- showButton: function showButton() {
3777
+ showButton: function () {
3095
3778
  if (this.options.toggleDeleteButton) {
3096
3779
  var context = this.getModel();
3097
3780
  var toggle = context[this.options.toggleDeleteButton].apply(context);
3098
-
3099
3781
  if (this.options.invertToggleDeleteButton) {
3100
3782
  return !toggle;
3101
3783
  } else {
@@ -3105,11 +3787,11 @@ var DeleteRowTableCellView = TableCellView.extend({
3105
3787
  return true;
3106
3788
  }
3107
3789
  },
3108
- update: function update() {
3790
+ update: function () {
3109
3791
  this.ui.removeButton.toggleClass('remove', this.showButton());
3110
3792
  this.ui.removeButton.attr('title', this.attributeTranslation('cell_title'));
3111
3793
  },
3112
- destroy: function destroy() {
3794
+ destroy: function () {
3113
3795
  this.getModel().destroy();
3114
3796
  }
3115
3797
  });
@@ -3130,10 +3812,9 @@ var DeleteRowTableCellView = TableCellView.extend({
3130
3812
  *
3131
3813
  * @since 12.0
3132
3814
  */
3133
-
3134
- var PresenceTableCellView = TableCellView.extend({
3815
+ const PresenceTableCellView = TableCellView.extend({
3135
3816
  className: 'presence_table_cell',
3136
- update: function update() {
3817
+ update: function () {
3137
3818
  var isPresent = !!this.attributeValue();
3138
3819
  this.$el.attr('title', isPresent ? this.attributeTranslation('cell_title.present', {
3139
3820
  value: this.attributeValue()
@@ -3154,18 +3835,18 @@ var PresenceTableCellView = TableCellView.extend({
3154
3835
  *
3155
3836
  * @param {Object} [options]
3156
3837
  *
3157
- * @param {string[]} [options.icons]
3838
+ * @param {string[]|function} [options.icons]
3158
3839
  * An array of all possible attribute values to be mapped to HTML
3159
3840
  * classes of the same name. A global mapping from those classes to
3160
3841
  * icon mixins is provided in
3161
- * pageflow/ui/table_cells/icon_table_cell.scss.
3842
+ * pageflow/ui/table_cells/icon_table_cell.scss. Pass a function
3843
+ * returning the array to defer reading the values.
3162
3844
  *
3163
3845
  * @since 12.0
3164
3846
  */
3165
-
3166
- var IconTableCellView = TableCellView.extend({
3847
+ const IconTableCellView = TableCellView.extend({
3167
3848
  className: 'icon_table_cell',
3168
- update: function update() {
3849
+ update: function () {
3169
3850
  var icon = this.attributeValue();
3170
3851
  var isPresent = !!this.attributeValue();
3171
3852
  this.removeExistingIcons();
@@ -3174,8 +3855,8 @@ var IconTableCellView = TableCellView.extend({
3174
3855
  }) : this.attributeTranslation('cell_title.blank'));
3175
3856
  this.$el.addClass(icon);
3176
3857
  },
3177
- removeExistingIcons: function removeExistingIcons() {
3178
- this.$el.removeClass(this.options.icons.join(' '));
3858
+ removeExistingIcons: function () {
3859
+ this.$el.removeClass(_.result(this.options, 'icons').join(' '));
3179
3860
  }
3180
3861
  });
3181
3862
 
@@ -3198,65 +3879,59 @@ var IconTableCellView = TableCellView.extend({
3198
3879
  *
3199
3880
  * @since 12.0
3200
3881
  */
3201
-
3202
- var TextTableCellView = TableCellView.extend({
3882
+ const TextTableCellView = TableCellView.extend({
3203
3883
  className: 'text_table_cell',
3204
- update: function update() {
3884
+ update: function () {
3205
3885
  this.$el.text(this._updateText());
3206
3886
  },
3207
- _updateText: function _updateText() {
3887
+ _updateText: function () {
3208
3888
  if (this.attributeValue()) {
3209
3889
  return this.attributeValue();
3210
- } else if (typeof this.options.column["default"] === 'function') {
3890
+ } else if (typeof this.options.column.default === 'function') {
3211
3891
  var options = {};
3212
-
3213
3892
  if (this.options.column.contentBinding) {
3214
3893
  options = {
3215
3894
  contentBinding: this.options.column.contentBinding,
3216
3895
  model: this.getModel()
3217
3896
  };
3218
3897
  }
3219
-
3220
- return this.options.column["default"](options);
3898
+ return this.options.column.default(options);
3221
3899
  } else if ('default' in this.options.column) {
3222
- return this.options.column["default"];
3900
+ return this.options.column.default;
3223
3901
  } else {
3224
3902
  return I18n$1.t('pageflow.ui.text_table_cell_view.empty');
3225
3903
  }
3226
3904
  }
3227
3905
  });
3228
3906
 
3229
- var subviewContainer = {
3230
- subview: function subview(view) {
3907
+ const subviewContainer = {
3908
+ subview: function (view) {
3231
3909
  this.subviews = this.subviews || new ChildViewContainer();
3232
3910
  this.subviews.add(view.render());
3233
3911
  return view;
3234
3912
  },
3235
- appendSubview: function appendSubview(view) {
3236
- var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
3237
- to = _ref.to;
3238
-
3913
+ appendSubview: function (view, {
3914
+ to
3915
+ } = {}) {
3239
3916
  return (to || this.$el).append(this.subview(view).el);
3240
3917
  },
3241
- onClose: function onClose() {
3918
+ onClose: function () {
3242
3919
  if (this.subviews) {
3243
3920
  this.subviews.call('close');
3244
3921
  }
3245
3922
  }
3246
3923
  };
3247
-
3248
3924
  if (!Marionette.View.prototype.appendSubview) {
3249
3925
  Cocktail.mixin(Marionette.View, subviewContainer);
3250
3926
  }
3251
3927
 
3252
- var tooltipContainer = {
3928
+ const tooltipContainer = {
3253
3929
  events: {
3254
- 'mouseover [data-tooltip]': function mouseoverDataTooltip(event) {
3930
+ 'mouseover [data-tooltip]': function (event) {
3255
3931
  if (!this.tooltip.visible) {
3256
3932
  var target = $(event.currentTarget);
3257
3933
  var key = target.attr('data-tooltip');
3258
3934
  var position;
3259
-
3260
3935
  if (target.data('tooltipAlign') === 'bottom left') {
3261
3936
  position = {
3262
3937
  left: target.position().left,
@@ -3278,19 +3953,18 @@ var tooltipContainer = {
3278
3953
  top: target.position().top + target.outerHeight() / 2
3279
3954
  };
3280
3955
  }
3281
-
3282
3956
  this.tooltip.show(I18n$1.t(key), position, {
3283
3957
  align: target.data('tooltipAlign')
3284
3958
  });
3285
3959
  }
3286
3960
  },
3287
- 'mouseleave [data-tooltip]': function mouseleaveDataTooltip() {
3961
+ 'mouseleave [data-tooltip]': function () {
3288
3962
  this.tooltip.hide();
3289
3963
  }
3290
3964
  },
3291
- onRender: function onRender() {
3965
+ onRender: function () {
3292
3966
  this.appendSubview(this.tooltip = new TooltipView());
3293
3967
  }
3294
3968
  };
3295
3969
 
3296
- export { CheckBoxGroupInputView, CheckBoxInputView, CollectionView, ColorInputView, ConfigurationEditorTabView, ConfigurationEditorView, DeleteRowTableCellView, EnumTableCellView, ExtendedSelectInputView, IconTableCellView, JsonInputView, LabelOnlyView, NumberInputView, BaseObject as Object, PresenceTableCellView, ProxyUrlInputView, SelectInputView, SeparatorView, SliderInputView, SortableCollectionView, TableCellView, TableHeaderCellView, TableRowView, TableView, TabsView, TextAreaInputView, TextInputView, TextTableCellView, TooltipView, UrlDisplayView, UrlInputView, attributeBinding, cssModulesUtils, i18nUtils, inputView, inputWithPlaceholderText, serverSideValidation, subviewContainer, tooltipContainer, viewWithValidationErrorMessages };
3970
+ export { CheckBoxGroupInputView, CheckBoxInputView, CollectionView, ColorInputView, ColorPicker, ConfigurationEditorTabView, ConfigurationEditorView, DeleteRowTableCellView, EnumTableCellView, ExtendedSelectInputView, FileNameInputView, IconTableCellView, JsonInputView, LabelOnlyView, NumberInputView, BaseObject as Object, PresenceTableCellView, ProxyUrlInputView, RadioButtonGroupInputView, SelectInputView, SeparatorView, SliderInputView, SortableCollectionView, TableCellView, TableHeaderCellView, TableRowView, TableView, TabsView, TextAreaInputView, TextInputView, TextTableCellView, TooltipView, UrlDisplayView, UrlInputView, attributeBinding, attributeBindingUtils, cssModulesUtils, i18nUtils, inputView, inputWithPlaceholderText, serverSideValidation, subviewContainer, tooltipContainer, viewWithValidationErrorMessages };