blacklight-spotlight 5.3.2 → 6.0.0.rc2

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 (211) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +41 -4
  3. data/Rakefile +1 -2
  4. data/app/assets/javascripts/spotlight/spotlight.esm.js +2168 -4560
  5. data/app/assets/javascripts/spotlight/spotlight.esm.js.map +1 -1
  6. data/app/assets/javascripts/spotlight/spotlight.js +2167 -4559
  7. data/app/assets/javascripts/spotlight/spotlight.js.map +1 -1
  8. data/app/assets/stylesheets/spotlight/_blacklight_overrides.scss +4 -0
  9. data/app/assets/stylesheets/spotlight/_browse.scss +0 -4
  10. data/app/assets/stylesheets/spotlight/{_collapse_toggle.scss → _collapse_toggle.css} +2 -2
  11. data/app/assets/stylesheets/spotlight/_exhibit_admin.scss +8 -0
  12. data/app/assets/stylesheets/spotlight/_exhibits_index.scss +2 -1
  13. data/app/assets/stylesheets/spotlight/_footer.scss +2 -2
  14. data/app/assets/stylesheets/spotlight/_header.scss +8 -8
  15. data/app/assets/stylesheets/spotlight/_pages.scss +1 -1
  16. data/app/assets/stylesheets/spotlight/_slideshow_block.scss +15 -0
  17. data/app/assets/stylesheets/spotlight/_spotlight.scss +1 -1
  18. data/app/assets/stylesheets/spotlight/_translations.scss +3 -3
  19. data/app/assets/stylesheets/spotlight/_uploaded_items_block.css +7 -0
  20. data/app/assets/stylesheets/spotlight/_variables.css +15 -0
  21. data/app/assets/stylesheets/spotlight/browse_group_categories_block.scss +0 -76
  22. data/app/components/blacklight/icons/pause_component.rb +15 -0
  23. data/app/components/blacklight/icons/play_arrow_component.rb +15 -0
  24. data/app/components/spotlight/analytics/dashboard_component.html.erb +1 -1
  25. data/app/components/spotlight/custom_field_form_group_component.rb +40 -0
  26. data/app/components/spotlight/document_admin_table_component.html.erb +2 -2
  27. data/app/components/spotlight/edit_sidecar_component.html.erb +18 -0
  28. data/app/components/spotlight/edit_sidecar_component.rb +41 -0
  29. data/app/components/spotlight/masthead_component.html.erb +18 -0
  30. data/app/components/spotlight/masthead_component.rb +46 -0
  31. data/app/components/spotlight/save_search_component.html.erb +1 -1
  32. data/app/components/spotlight/sidecar_form_field_component.html.erb +18 -0
  33. data/app/components/spotlight/sidecar_form_field_component.rb +71 -0
  34. data/app/components/spotlight/solr_document_legacy_embed_component.html.erb +0 -4
  35. data/app/components/spotlight/solr_document_legacy_embed_component.rb +2 -2
  36. data/app/components/spotlight/tag_selector_component.html.erb +1 -1
  37. data/app/components/spotlight/tag_selector_component.rb +3 -2
  38. data/app/components/spotlight/upload_field_form_group_component.rb +33 -0
  39. data/app/controllers/concerns/spotlight/controller.rb +15 -14
  40. data/app/controllers/concerns/spotlight/search_helper.rb +2 -5
  41. data/app/controllers/spotlight/browse_controller.rb +1 -6
  42. data/app/controllers/spotlight/catalog_controller.rb +2 -3
  43. data/app/controllers/spotlight/concerns/application_controller.rb +4 -6
  44. data/app/controllers/spotlight/exhibits_controller.rb +22 -10
  45. data/app/controllers/spotlight/lock_controller.rb +1 -1
  46. data/app/controllers/spotlight/pages_controller.rb +11 -11
  47. data/app/controllers/spotlight/resources/upload_controller.rb +1 -1
  48. data/app/controllers/spotlight/resources_controller.rb +1 -1
  49. data/app/controllers/spotlight/solr_controller.rb +5 -1
  50. data/app/helpers/spotlight/application_helper.rb +14 -26
  51. data/app/helpers/spotlight/crud_link_helpers.rb +0 -12
  52. data/app/helpers/spotlight/main_app_helpers.rb +2 -2
  53. data/app/javascript/spotlight/admin/add_another.js +37 -25
  54. data/app/javascript/spotlight/admin/add_image_selector.js +43 -26
  55. data/app/javascript/spotlight/admin/add_new_button.js +51 -36
  56. data/app/javascript/spotlight/admin/blacklight_configuration.js +76 -54
  57. data/app/javascript/spotlight/admin/block_mixins/autocompleteable.js +102 -101
  58. data/app/javascript/spotlight/admin/block_mixins/formable.js +91 -75
  59. data/app/javascript/spotlight/admin/block_mixins/plustextable.js +46 -48
  60. data/app/javascript/spotlight/admin/blocks/block.js +27 -28
  61. data/app/javascript/spotlight/admin/blocks/browse_block.js +14 -10
  62. data/app/javascript/spotlight/admin/blocks/browse_group_categories_block.js +14 -10
  63. data/app/javascript/spotlight/admin/blocks/pages_block.js +5 -4
  64. data/app/javascript/spotlight/admin/blocks/resources_block.js +61 -41
  65. data/app/javascript/spotlight/admin/blocks/search_result_block.js +24 -7
  66. data/app/javascript/spotlight/admin/blocks/solr_documents_base_block.js +103 -50
  67. data/app/javascript/spotlight/admin/blocks/solr_documents_carousel_block.js +50 -26
  68. data/app/javascript/spotlight/admin/blocks/solr_documents_features_block.js +47 -23
  69. data/app/javascript/spotlight/admin/blocks/uploaded_items_block.js +35 -19
  70. data/app/javascript/spotlight/admin/crop.js +146 -66
  71. data/app/javascript/spotlight/admin/croppable.js +5 -4
  72. data/app/javascript/spotlight/admin/croppable_modal.js +67 -32
  73. data/app/javascript/spotlight/admin/edit_in_place.js +69 -54
  74. data/app/javascript/spotlight/admin/exhibits.js +92 -45
  75. data/app/javascript/spotlight/admin/form_observer.js +92 -78
  76. data/app/javascript/spotlight/admin/multi_image_selector.js +201 -153
  77. data/app/javascript/spotlight/admin/pages.js +13 -7
  78. data/app/javascript/spotlight/admin/progress_monitor.js +65 -50
  79. data/app/javascript/spotlight/admin/readonly_checkbox.js +7 -3
  80. data/app/javascript/spotlight/admin/search_typeahead.js +20 -11
  81. data/app/javascript/spotlight/admin/select_related_input.js +23 -23
  82. data/app/javascript/spotlight/admin/serialize_json.js +120 -0
  83. data/app/javascript/spotlight/admin/sir-trevor/block_controls.js +24 -11
  84. data/app/javascript/spotlight/admin/sir-trevor/block_limits.js +9 -6
  85. data/app/javascript/spotlight/admin/sir-trevor/locales.js +2 -2
  86. data/app/javascript/spotlight/admin/spotlight_nestable.js +2 -5
  87. data/app/javascript/spotlight/admin/translation_progress.js +18 -13
  88. data/app/javascript/spotlight/admin/users.js +151 -64
  89. data/app/javascript/spotlight/user/carousel.js +104 -32
  90. data/app/javascript/spotlight/user/clear_form_button.js +24 -14
  91. data/app/javascript/spotlight/user/index.js +0 -2
  92. data/app/javascript/spotlight/user/zpr_links.js +99 -40
  93. data/app/jobs/concerns/spotlight/gather_documents.rb +1 -1
  94. data/app/jobs/spotlight/add_uploads_from_csv.rb +31 -8
  95. data/app/jobs/spotlight/cleanup_job_trackers_job.rb +5 -3
  96. data/app/models/concerns/spotlight/access_controls_enforcement_search_builder.rb +13 -2
  97. data/app/models/concerns/spotlight/browse_category_search_builder.rb +3 -3
  98. data/app/models/concerns/spotlight/exhibit_documents.rb +2 -2
  99. data/app/models/concerns/spotlight/sitemap.rb +2 -2
  100. data/app/models/concerns/spotlight/solr_document/finder.rb +2 -2
  101. data/app/models/concerns/spotlight/solr_document.rb +1 -47
  102. data/app/models/sir_trevor_rails/blocks/browse_block.rb +0 -4
  103. data/app/models/sir_trevor_rails/blocks/solr_documents_block.rb +2 -1
  104. data/app/models/spotlight/blacklight_configuration.rb +5 -3
  105. data/app/models/spotlight/contact_email.rb +2 -2
  106. data/app/models/spotlight/custom_field.rb +17 -0
  107. data/app/models/spotlight/exhibit.rb +28 -0
  108. data/app/models/spotlight/featured_image.rb +1 -1
  109. data/app/models/spotlight/field_metadata.rb +10 -5
  110. data/app/models/spotlight/job_tracker.rb +0 -18
  111. data/app/models/spotlight/main_navigation.rb +2 -2
  112. data/app/models/spotlight/page.rb +3 -3
  113. data/app/models/spotlight/resources/iiif_harvester.rb +7 -4
  114. data/app/models/spotlight/resources/iiif_manifest.rb +0 -4
  115. data/app/models/spotlight/resources/iiif_manifest_metadata.rb +1 -3
  116. data/app/models/spotlight/resources/iiif_service.rb +21 -6
  117. data/app/models/spotlight/resources/upload.rb +6 -0
  118. data/app/models/spotlight/search.rb +5 -9
  119. data/app/models/spotlight/solr_document_sidecar.rb +18 -0
  120. data/app/services/spotlight/bulk_updates_csv_template_service.rb +1 -1
  121. data/app/services/spotlight/etl/pipeline.rb +2 -2
  122. data/app/services/spotlight/etl/step.rb +2 -2
  123. data/app/views/catalog/_remove_tags.html.erb +2 -2
  124. data/app/views/layouts/spotlight/base.html.erb +3 -6
  125. data/app/views/layouts/spotlight/spotlight.html.erb +2 -2
  126. data/app/views/shared/_analytics.html.erb +4 -3
  127. data/app/views/shared/_masthead.html.erb +2 -18
  128. data/app/views/spotlight/about_pages/_empty.html.erb +1 -1
  129. data/app/views/spotlight/about_pages/_sidebar.html.erb +10 -8
  130. data/app/views/spotlight/browse/_search.html.erb +2 -2
  131. data/app/views/spotlight/browse/_search_box.html.erb +6 -14
  132. data/app/views/spotlight/browse/index.html.erb +1 -1
  133. data/app/views/spotlight/browse/show.html.erb +1 -1
  134. data/app/views/spotlight/catalog/_document_admin_table.html.erb +1 -1
  135. data/app/views/spotlight/catalog/_edit_default.html.erb +1 -1
  136. data/app/views/spotlight/catalog/edit.html.erb +1 -1
  137. data/app/views/spotlight/dashboards/_page.html.erb +1 -1
  138. data/app/views/spotlight/exhibits/_exhibit_card.html.erb +3 -1
  139. data/app/views/spotlight/exhibits/_export.html.erb +17 -1
  140. data/app/views/spotlight/exhibits/_form.html.erb +20 -4
  141. data/app/views/spotlight/exhibits/_missing_exhibits.html.erb +3 -3
  142. data/app/views/spotlight/exhibits/index.html.erb +21 -8
  143. data/app/views/spotlight/feature_pages/_empty.html.erb +1 -1
  144. data/app/views/spotlight/feature_pages/_sidebar.html.erb +17 -15
  145. data/app/views/spotlight/home_pages/_empty.html.erb +7 -7
  146. data/app/views/spotlight/pages/show.html.erb +1 -0
  147. data/app/views/spotlight/resources/upload/_form.html.erb +2 -2
  148. data/app/views/spotlight/sir_trevor/blocks/_browse_group_categories_block.html.erb +5 -16
  149. data/app/views/spotlight/sir_trevor/blocks/_embedded_document.html.erb +3 -5
  150. data/app/views/spotlight/sir_trevor/blocks/_link_to_search_block.html.erb +1 -1
  151. data/app/views/spotlight/sir_trevor/blocks/_solr_documents_carousel_block.html.erb +11 -2
  152. data/app/views/spotlight/sites/_exhibit.html.erb +1 -1
  153. data/config/i18n-tasks.yml +1 -1
  154. data/config/locales/spotlight.ar.yml +1 -2
  155. data/config/locales/spotlight.de.yml +1 -2
  156. data/config/locales/spotlight.en.yml +39 -10
  157. data/config/locales/spotlight.es.yml +1 -2
  158. data/config/locales/spotlight.fr.yml +1 -2
  159. data/config/locales/spotlight.it.yml +1 -2
  160. data/config/locales/spotlight.ja.yml +39 -0
  161. data/config/locales/spotlight.zh.yml +1 -2
  162. data/db/migrate/20261007133748_add_discovery_enabled_to_spotlight_exhibits.rb +5 -0
  163. data/lib/generators/spotlight/assets/importmap_generator.rb +2 -10
  164. data/lib/generators/spotlight/assets/propshaft_generator.rb +5 -4
  165. data/lib/generators/spotlight/install_generator.rb +2 -4
  166. data/lib/generators/spotlight/templates/catalog_controller.rb +0 -1
  167. data/lib/generators/spotlight/templates/config/initializers/spotlight_initializer.rb +26 -4
  168. data/lib/generators/spotlight/templates/solr/conf/schema.xml +4 -3
  169. data/lib/generators/spotlight/templates/solr/conf/solrconfig.xml +3 -8
  170. data/lib/migration/page_language.rb +0 -2
  171. data/lib/spotlight/engine.rb +28 -9
  172. data/lib/spotlight/search_state.rb +7 -11
  173. data/lib/spotlight/upload_field_config.rb +19 -1
  174. data/lib/spotlight/version.rb +1 -1
  175. data/lib/spotlight.rb +1 -1
  176. data/spec/support/features/capybara_wait_metadata_helper.rb +3 -1
  177. data/spec/support/features/test_features_helpers.rb +24 -1
  178. data/spec/support/iiif_manifest_fixtures.rb +33 -0
  179. data/spec/support/stub_dns_resolution.rb +12 -0
  180. metadata +50 -80
  181. data/app/assets/stylesheets/spotlight/_uploaded_items_block.scss +0 -7
  182. data/app/assets/stylesheets/spotlight/_variables.scss +0 -13
  183. data/app/components/blacklight/icons/arrow_alt_circle_left_component.rb +0 -15
  184. data/app/components/blacklight/icons/arrow_alt_circle_right_component.rb +0 -15
  185. data/app/components/blacklight/icons/info_component.rb +0 -15
  186. data/app/components/blacklight/icons/zoom_in_component.rb +0 -15
  187. data/app/components/blacklight/icons/zoom_out_component.rb +0 -15
  188. data/app/components/spotlight/icon_component.rb +0 -13
  189. data/app/javascript/spotlight/user/browse_group_categories.js +0 -54
  190. data/app/mailers/spotlight/invitation_mailer.rb +0 -21
  191. data/app/models/concerns/spotlight/resources/web.rb +0 -38
  192. data/app/services/spotlight/iiif_resource_resolver.rb +0 -78
  193. data/app/views/catalog/_image_default.html.erb +0 -6
  194. data/app/views/shared/_exhibit_navbar.html.erb +0 -2
  195. data/app/views/shared/_header_navbar.html.erb +0 -6
  196. data/app/views/spotlight/catalog/_admin_index_header_default.html.erb +0 -23
  197. data/app/views/spotlight/catalog/_admin_thumbnail_default.html.erb +0 -7
  198. data/app/views/spotlight/catalog/_curation_mode_toggle_default.html.erb +0 -1
  199. data/app/views/spotlight/catalog/_document_row.html.erb +0 -2
  200. data/app/views/spotlight/catalog/_edit_sidecar.html.erb +0 -24
  201. data/app/views/spotlight/catalog/_index_compact_default.html.erb +0 -21
  202. data/app/views/spotlight/custom_fields/form_group/_text.html.erb +0 -23
  203. data/app/views/spotlight/custom_fields/form_group/_vocab.html.erb +0 -23
  204. data/app/views/spotlight/invitation_mailer/exhibit_invitation_notification.html.erb +0 -12
  205. data/lib/generators/spotlight/templates/javascript/jquery-shim.js +0 -1
  206. data/vendor/assets/javascripts/tiny-slider.js +0 -3221
  207. data/vendor/assets/stylesheets/tiny-slider.css +0 -1
  208. /data/app/assets/stylesheets/spotlight/{_breadcrumbs.scss → _breadcrumbs.css} +0 -0
  209. /data/app/assets/stylesheets/spotlight/{_croppable.scss → _croppable.css} +0 -0
  210. /data/app/assets/stylesheets/spotlight/{_modals.scss → _modals.css} +0 -0
  211. /data/app/assets/stylesheets/spotlight/{_utilities.scss → _utilities.css} +0 -0
@@ -3,75 +3,90 @@
3
3
  */
4
4
  export default class {
5
5
  connect() {
6
- $("[data-in-place-edit-target]").each(function () {
7
- $(this).on("click.inplaceedit", function () {
8
- var $label = $(this).find($(this).data("in-place-edit-target"))
9
- var $input = $(this).find($(this).data("in-place-edit-field-target"))
6
+ document
7
+ .querySelectorAll("[data-in-place-edit-target]")
8
+ .forEach(function (container) {
9
+ var label = container.querySelector(container.dataset.inPlaceEditTarget)
10
+ var input = container.querySelector(
11
+ container.dataset.inPlaceEditFieldTarget,
12
+ )
13
+ if (!label || !input) return
10
14
 
11
- // hide the edit-in-place affordance icon while in edit mode
12
- $(this).addClass("hide-edit-icon")
13
- $label.hide()
14
- $input.val($label.text())
15
- $input.attr("type", "text")
16
- $input.select()
17
- $input.focus()
15
+ container.addEventListener("click", function (e) {
16
+ // hide the edit-in-place affordance icon while in edit mode
17
+ container.classList.add("hide-edit-icon")
18
+ label.style.display = "none"
19
+ input.value = label.textContent
20
+ input.setAttribute("type", "text")
21
+ input.select()
22
+ input.focus()
23
+ e.preventDefault()
24
+ e.stopPropagation()
25
+ })
18
26
 
19
- $input.on("keypress", function (e) {
20
- if (e.which == 13) {
21
- $input.trigger("blur.inplaceedit")
22
- return false
27
+ input.addEventListener("keypress", function (e) {
28
+ if (e.key === "Enter") {
29
+ input.blur()
30
+ e.preventDefault()
31
+ e.stopPropagation()
23
32
  }
24
33
  })
25
34
 
26
- $input.on("blur.inplaceedit", function () {
27
- var value = $input.val()
35
+ input.addEventListener("blur", function () {
36
+ var value = input.value
28
37
 
29
- if ($.trim(value).length == 0) {
30
- $input.val($label.text())
38
+ if (value.trim().length == 0) {
39
+ input.value = label.textContent
31
40
  } else {
32
- $label.text(value)
41
+ label.textContent = value
33
42
  }
34
43
 
35
- $label.show()
36
- $input.attr("type", "hidden")
44
+ label.style.display = ""
45
+ input.setAttribute("type", "hidden")
37
46
  // when leaving edit mode, should no longer hide edit-in-place affordance icon
38
- $("[data-in-place-edit-target]").removeClass("hide-edit-icon")
39
-
40
- return false
47
+ document
48
+ .querySelectorAll("[data-in-place-edit-target]")
49
+ .forEach(function (el) {
50
+ el.classList.remove("hide-edit-icon")
51
+ })
41
52
  })
42
-
43
- return false
44
53
  })
45
- })
46
54
 
47
- $("[data-behavior='restore-default']").each(function () {
48
- var hidden = $("[data-default-value]", $(this))
49
- var value = $(
50
- $("[data-in-place-edit-target]", $(this)).data("in-place-edit-target"),
51
- $(this),
52
- )
53
- var button = $("[data-restore-default]", $(this))
55
+ document
56
+ .querySelectorAll("[data-behavior='restore-default']")
57
+ .forEach(function (container) {
58
+ var hidden = container.querySelector("[data-default-value]")
59
+ var inPlaceEditContainer = container.querySelector(
60
+ "[data-in-place-edit-target]",
61
+ )
62
+ var button = container.querySelector("[data-restore-default]")
63
+ if (!hidden || !inPlaceEditContainer || !button) return
54
64
 
55
- hidden.on("keypress", function (e) {
56
- if (e.which == 13) {
57
- hidden.trigger("blur")
58
- return false
59
- }
60
- })
65
+ var value = container.querySelector(
66
+ inPlaceEditContainer.dataset.inPlaceEditTarget,
67
+ )
61
68
 
62
- hidden.on("blur", function () {
63
- if ($(this).val() == $(this).data("default-value")) {
64
- button.addClass("d-none")
65
- } else {
66
- button.removeClass("d-none")
67
- }
68
- })
69
- button.on("click", function (e) {
70
- e.preventDefault()
71
- hidden.val(hidden.data("default-value"))
72
- value.text(hidden.data("default-value"))
73
- button.hide()
69
+ hidden.addEventListener("keypress", function (e) {
70
+ if (e.key === "Enter") {
71
+ hidden.blur()
72
+ e.preventDefault()
73
+ }
74
+ })
75
+
76
+ hidden.addEventListener("blur", function () {
77
+ if (hidden.value == hidden.dataset.defaultValue) {
78
+ button.classList.add("d-none")
79
+ } else {
80
+ button.classList.remove("d-none")
81
+ }
82
+ })
83
+
84
+ button.addEventListener("click", function (e) {
85
+ e.preventDefault()
86
+ hidden.value = hidden.dataset.defaultValue
87
+ if (value) value.textContent = hidden.dataset.defaultValue
88
+ button.style.display = "none"
89
+ })
74
90
  })
75
- })
76
91
  }
77
92
  }
@@ -3,54 +3,90 @@ import { URLify } from "parameterize"
3
3
  export default class {
4
4
  connect() {
5
5
  // auto-fill the exhibit slug on the new exhibit form
6
- $("#new_exhibit").each(function () {
7
- $("#exhibit_title").on("change keyup", function () {
8
- $("#exhibit_slug").attr(
9
- "placeholder",
10
- URLify($(this).val(), $(this).val().length),
11
- )
12
- })
6
+ const newExhibit = document.getElementById("new_exhibit")
7
+ if (newExhibit) {
8
+ const exhibitTitle = document.getElementById("exhibit_title")
9
+ const exhibitSlug = document.getElementById("exhibit_slug")
13
10
 
14
- $("#exhibit_slug").on("focus", function () {
15
- if ($(this).val() === "") {
16
- $(this).val($(this).attr("placeholder"))
11
+ if (exhibitTitle && exhibitSlug) {
12
+ const updatePlaceholder = () => {
13
+ const val = exhibitTitle.value || ""
14
+ exhibitSlug.placeholder = URLify(val, val.length)
17
15
  }
18
- })
19
- })
20
16
 
21
- $("#another-email").on("click", function (e) {
22
- e.preventDefault()
17
+ exhibitTitle.addEventListener("change", updatePlaceholder)
18
+ exhibitTitle.addEventListener("keyup", updatePlaceholder)
23
19
 
24
- var container = $(this).closest(".form-group")
25
- var contacts = container.find(".contact")
26
- var inputContainer = contacts.first().clone()
20
+ exhibitSlug.addEventListener("focus", () => {
21
+ if (exhibitSlug.value === "") {
22
+ exhibitSlug.value = exhibitSlug.placeholder || ""
23
+ }
24
+ })
25
+ }
26
+ }
27
27
 
28
- // wipe out any values from the inputs
29
- inputContainer.find("input").each(function () {
30
- $(this).val("")
31
- $(this).attr("id", $(this).attr("id").replace("0", contacts.length))
32
- $(this).attr("name", $(this).attr("name").replace("0", contacts.length))
33
- if ($(this).attr("aria-label")) {
34
- $(this).attr(
35
- "aria-label",
36
- $(this)
37
- .attr("aria-label")
38
- .replace("1", contacts.length + 1),
39
- )
40
- }
41
- })
28
+ const anotherEmail = document.getElementById("another-email")
29
+ if (anotherEmail) {
30
+ anotherEmail.addEventListener("click", (e) => {
31
+ e.preventDefault()
32
+
33
+ const container = anotherEmail.closest(".form-group")
34
+ if (!container) return
42
35
 
43
- inputContainer.find(".contact-email-delete-wrapper").remove()
44
- inputContainer.find(".confirmation-status").remove()
36
+ const contacts = container.querySelectorAll(".contact")
37
+ if (contacts.length === 0) return
45
38
 
46
- // bootstrap does not render input-groups with only one value in them correctly.
47
- inputContainer
48
- .find(".input-group input:only-child")
49
- .closest(".input-group")
50
- .removeClass("input-group")
39
+ const firstContact = contacts[0]
40
+ const inputContainer = firstContact.cloneNode(true)
51
41
 
52
- $(inputContainer).insertAfter(contacts.last())
53
- })
42
+ // wipe out any values from the inputs
43
+ const inputs = inputContainer.querySelectorAll("input")
44
+ inputs.forEach((input) => {
45
+ input.value = ""
46
+ const originalId = input.getAttribute("id")
47
+ if (originalId) {
48
+ input.setAttribute(
49
+ "id",
50
+ originalId.replace("0", contacts.length.toString()),
51
+ )
52
+ }
53
+ const originalName = input.getAttribute("name")
54
+ if (originalName) {
55
+ input.setAttribute(
56
+ "name",
57
+ originalName.replace("0", contacts.length.toString()),
58
+ )
59
+ }
60
+ const originalAriaLabel = input.getAttribute("aria-label")
61
+ if (originalAriaLabel) {
62
+ input.setAttribute(
63
+ "aria-label",
64
+ originalAriaLabel.replace("1", (contacts.length + 1).toString()),
65
+ )
66
+ }
67
+ })
68
+
69
+ inputContainer
70
+ .querySelectorAll(".contact-email-delete-wrapper")
71
+ .forEach((el) => el.remove())
72
+ inputContainer
73
+ .querySelectorAll(".confirmation-status")
74
+ .forEach((el) => el.remove())
75
+
76
+ // bootstrap does not render input-groups with only one value in them correctly.
77
+ const onlyChildInputs = inputContainer.querySelectorAll(
78
+ ".input-group input:only-child",
79
+ )
80
+ onlyChildInputs.forEach((input) => {
81
+ const group = input.closest(".input-group")
82
+ if (group) {
83
+ group.classList.remove("input-group")
84
+ }
85
+ })
86
+
87
+ contacts[contacts.length - 1].after(inputContainer)
88
+ })
89
+ }
54
90
 
55
91
  if (document.getElementById("another-email")) {
56
92
  document.addEventListener(
@@ -60,9 +96,15 @@ export default class {
60
96
  }
61
97
 
62
98
  // Put focus in saved search title input when Save this search modal is shown
63
- $("#save-modal").on("shown.bs.modal", function () {
64
- $("#search_title").focus()
65
- })
99
+ const saveModal = document.getElementById("save-modal")
100
+ if (saveModal) {
101
+ saveModal.addEventListener("shown.bs.modal", () => {
102
+ const searchTitle = document.getElementById("search_title")
103
+ if (searchTitle) {
104
+ searchTitle.focus()
105
+ }
106
+ })
107
+ }
66
108
  }
67
109
 
68
110
  contactToDeleteNotFoundHandler(e) {
@@ -70,8 +112,13 @@ export default class {
70
112
  e.detail.formSubmission?.delegate?.element?.querySelector(".contact")
71
113
  if (contact && e.detail?.fetchResponse?.response?.status === 404) {
72
114
  const error = contact.querySelector(".contact-email-delete-error")
73
- error.style.display = "block"
74
- error.querySelector(".error-msg").textContent = "Not Found"
115
+ if (error) {
116
+ error.style.display = "block"
117
+ const errorMsg = error.querySelector(".error-msg")
118
+ if (errorMsg) {
119
+ errorMsg.textContent = "Not Found"
120
+ }
121
+ }
75
122
  }
76
123
  }
77
124
  }
@@ -1,94 +1,108 @@
1
- ;(function ($, _) {
2
- "use strict"
1
+ /*
2
+ * SerializedForm is built as a singleton. It needs to be able to
3
+ * handle instantiation from multiple sources, and use the [data-form-observer]
4
+ * as global state object.
5
+ */
3
6
 
4
- /*
5
- * SerializedForm is built as a singleton jQuery plugin. It needs to be able to
6
- * handle instantiation from multiple sources, and use the [data-form-observer]
7
- * as global state object.
8
- */
9
- $.SerializedForm = function () {
10
- var $serializedForm
11
- var plugin = this
7
+ // Per-form state (replaces jQuery's .data() storage)
8
+ const formState = new WeakMap()
12
9
 
13
- // Store form serialization in data attribute
14
- function serializeFormStatus() {
15
- $serializedForm.data(
16
- "serialized-form",
17
- formSerialization($serializedForm),
18
- )
19
- }
10
+ function getState(form) {
11
+ if (!formState.has(form)) {
12
+ formState.set(form, {})
13
+ }
14
+ return formState.get(form)
15
+ }
20
16
 
21
- // Do custom serialization of the sir-trevor form data. This needs to be a
22
- // passed in argument for comparison later on.
23
- function formSerialization(form) {
24
- var content_editable = []
25
- var i = 0
26
- $("[contenteditable='true']", form).each(function () {
27
- content_editable.push("&contenteditable_" + i + "=" + $(this).text())
28
- })
29
- return form.serialize() + content_editable.join()
30
- }
17
+ // Do custom serialization of the sir-trevor form data. This needs to be a
18
+ // passed in argument for comparison later on.
19
+ function formSerialization(form) {
20
+ var params = new URLSearchParams()
21
+ for (const element of form.elements) {
22
+ if (!element.name || element.disabled) continue
23
+ const type = (element.type || "").toLowerCase()
24
+ if (
25
+ type === "file" ||
26
+ type === "submit" ||
27
+ type === "button" ||
28
+ type === "reset" ||
29
+ type === "image"
30
+ )
31
+ continue
32
+ if ((type === "checkbox" || type === "radio") && !element.checked) continue
33
+ params.append(element.name, element.value)
34
+ }
31
35
 
32
- // Unbind observing form on submit (which we have to do because of turbolinks)
33
- function unbindObservedFormSubmit() {
34
- $serializedForm.on("submit", function () {
35
- $(this).data("being-submitted", true)
36
- })
37
- }
36
+ var content_editable = []
37
+ var i = 0
38
+ form.querySelectorAll("[contenteditable='true']").forEach((element) => {
39
+ content_editable.push("&contenteditable_" + i + "=" + element.textContent)
40
+ i++
41
+ })
42
+ return params.toString() + content_editable.join("")
43
+ }
38
44
 
39
- // Get the stored serialized form status
40
- function serializedFormStatus() {
41
- return $serializedForm.data("serialized-form")
42
- }
45
+ // Unbind observing form on submit (which we have to do because of turbolinks)
46
+ function bindObservedFormSubmit(form) {
47
+ var state = getState(form)
48
+ if (state.submitBound) return
49
+ state.submitBound = true
50
+ form.addEventListener("submit", () => {
51
+ getState(form).beingSubmitted = true
52
+ })
53
+ }
43
54
 
44
- // Check all observed forms on page for status change
45
- plugin.observedFormsStatusHasChanged = function () {
46
- var unsaved_changes = false
47
- $("[data-form-observer]").each(function () {
48
- if (!$(this).data("being-submitted")) {
49
- if (serializedFormStatus() != formSerialization($(this))) {
50
- unsaved_changes = true
51
- }
52
- }
53
- })
54
- return unsaved_changes
55
- }
55
+ export const SerializedForm = {
56
+ // Store form serialization in state and bind submit handlers
57
+ init() {
58
+ document.querySelectorAll("[data-form-observer]").forEach((form) => {
59
+ getState(form).serialized = formSerialization(form)
60
+ bindObservedFormSubmit(form)
61
+ })
62
+ return this
63
+ },
56
64
 
57
- function init() {
58
- $serializedForm = $("[data-form-observer]")
59
- serializeFormStatus()
60
- unbindObservedFormSubmit()
61
- }
65
+ // Check all observed forms on page for status change
66
+ observedFormsStatusHasChanged() {
67
+ return Array.from(document.querySelectorAll("[data-form-observer]")).some(
68
+ (form) => {
69
+ var state = getState(form)
70
+ if (state.beingSubmitted) return false
71
+ return state.serialized !== formSerialization(form)
72
+ },
73
+ )
74
+ },
75
+ }
62
76
 
63
- init()
77
+ var UNSAVED_CHANGES_MESSAGE =
78
+ "You have unsaved changes. Are you sure you want to leave this page?"
64
79
 
65
- return plugin
80
+ // Don't handle the same event twice #turbolinks
81
+ function handleNavigationEvent(event) {
82
+ if (event.handled === true) return
83
+ if (!SerializedForm.observedFormsStatusHasChanged()) return
84
+ event.handled = true
85
+
86
+ // There are variations in how Webkit browsers may handle this:
87
+ // https://developer.mozilla.org/en-US/docs/Web/Events/beforeunload
88
+ if (event.type === "beforeunload") {
89
+ event.preventDefault()
90
+ event.returnValue = UNSAVED_CHANGES_MESSAGE
91
+ return UNSAVED_CHANGES_MESSAGE
92
+ } else {
93
+ if (!confirm(UNSAVED_CHANGES_MESSAGE)) {
94
+ event.preventDefault()
95
+ }
66
96
  }
67
- })(jQuery)
97
+ }
68
98
 
69
99
  export default class {
70
100
  connect() {
71
101
  // Instantiate the singleton SerializedForm plugin
72
- var serializedForm = $.SerializedForm()
73
- $(window).on(
74
- "beforeunload page:before-change turbolinks:before-visit turbo:before-visit",
75
- function (event) {
76
- // Don't handle the same event twice #turbolinks
77
- if (event.handled !== true) {
78
- if (serializedForm.observedFormsStatusHasChanged()) {
79
- event.handled = true
80
- var message =
81
- "You have unsaved changes. Are you sure you want to leave this page?"
82
- // There are variations in how Webkit browsers may handle this:
83
- // https://developer.mozilla.org/en-US/docs/Web/Events/beforeunload
84
- if (event.type == "beforeunload") {
85
- return message
86
- } else {
87
- return confirm(message)
88
- }
89
- }
90
- }
91
- },
92
- )
102
+ SerializedForm.init()
103
+ window.addEventListener("beforeunload", handleNavigationEvent)
104
+ document.addEventListener("page:before-change", handleNavigationEvent)
105
+ document.addEventListener("turbolinks:before-visit", handleNavigationEvent)
106
+ document.addEventListener("turbo:before-visit", handleNavigationEvent)
93
107
  }
94
108
  }