spotlight-frontend 3.5.0-beta.1

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 (153) hide show
  1. package/LICENSE +13 -0
  2. package/README.md +114 -0
  3. package/app/assets/images/blacklight/arrow-alt-circle-left.svg +1 -0
  4. package/app/assets/images/blacklight/arrow-alt-circle-right.svg +1 -0
  5. package/app/assets/images/blacklight/arrow_back_ios.svg +1 -0
  6. package/app/assets/images/blacklight/arrow_forward_ios.svg +1 -0
  7. package/app/assets/images/blacklight/check.svg +1 -0
  8. package/app/assets/images/blacklight/check_circle.svg +1 -0
  9. package/app/assets/images/blacklight/chevron_right.svg +1 -0
  10. package/app/assets/images/blacklight/close.svg +1 -0
  11. package/app/assets/images/blacklight/edit.svg +1 -0
  12. package/app/assets/images/blacklight/error.svg +1 -0
  13. package/app/assets/images/blacklight/highlight_off.svg +1 -0
  14. package/app/assets/images/blacklight/info.svg +1 -0
  15. package/app/assets/images/blacklight/warning.svg +1 -0
  16. package/app/assets/images/blacklight/zoom_in.svg +1 -0
  17. package/app/assets/images/blacklight/zoom_out.svg +1 -0
  18. package/app/assets/images/spotlight/.keep +0 -0
  19. package/app/assets/images/spotlight/blocks/sir-trevor-icons.svg +320 -0
  20. package/app/assets/images/spotlight/default_browse_thumbnail.jpg +0 -0
  21. package/app/assets/images/spotlight/default_thumbnail.jpg +0 -0
  22. package/app/assets/images/spotlight/fallback/default.png +0 -0
  23. package/app/assets/javascripts/spotlight/admin/add_another.js +22 -0
  24. package/app/assets/javascripts/spotlight/admin/add_new_button.js +81 -0
  25. package/app/assets/javascripts/spotlight/admin/appearance.js +24 -0
  26. package/app/assets/javascripts/spotlight/admin/attachments.js +2 -0
  27. package/app/assets/javascripts/spotlight/admin/blacklight_configuration.js +63 -0
  28. package/app/assets/javascripts/spotlight/admin/block_mixins/autocompleteable.js +72 -0
  29. package/app/assets/javascripts/spotlight/admin/block_mixins/formable.js +78 -0
  30. package/app/assets/javascripts/spotlight/admin/block_mixins/plustextable.js +57 -0
  31. package/app/assets/javascripts/spotlight/admin/blocks/block.js +23 -0
  32. package/app/assets/javascripts/spotlight/admin/blocks/browse_block.js +87 -0
  33. package/app/assets/javascripts/spotlight/admin/blocks/browse_group_categories_block.js +88 -0
  34. package/app/assets/javascripts/spotlight/admin/blocks/iframe_block.js +34 -0
  35. package/app/assets/javascripts/spotlight/admin/blocks/link_to_search_block.js +16 -0
  36. package/app/assets/javascripts/spotlight/admin/blocks/oembed_block.js +40 -0
  37. package/app/assets/javascripts/spotlight/admin/blocks/pages_block.js +22 -0
  38. package/app/assets/javascripts/spotlight/admin/blocks/resources_block.js +145 -0
  39. package/app/assets/javascripts/spotlight/admin/blocks/rule_block.js +25 -0
  40. package/app/assets/javascripts/spotlight/admin/blocks/search_result_block.js +44 -0
  41. package/app/assets/javascripts/spotlight/admin/blocks/solr_documents_base_block.js +108 -0
  42. package/app/assets/javascripts/spotlight/admin/blocks/solr_documents_block.js +25 -0
  43. package/app/assets/javascripts/spotlight/admin/blocks/solr_documents_carousel_block.js +103 -0
  44. package/app/assets/javascripts/spotlight/admin/blocks/solr_documents_embed_block.js +17 -0
  45. package/app/assets/javascripts/spotlight/admin/blocks/solr_documents_features_block.js +41 -0
  46. package/app/assets/javascripts/spotlight/admin/blocks/solr_documents_grid_block.js +14 -0
  47. package/app/assets/javascripts/spotlight/admin/blocks/uploaded_items_block.js +145 -0
  48. package/app/assets/javascripts/spotlight/admin/catalog_edit.js +16 -0
  49. package/app/assets/javascripts/spotlight/admin/copy_email_addresses.js +9 -0
  50. package/app/assets/javascripts/spotlight/admin/crop.es6 +310 -0
  51. package/app/assets/javascripts/spotlight/admin/croppable.js +25 -0
  52. package/app/assets/javascripts/spotlight/admin/edit_in_place.js +54 -0
  53. package/app/assets/javascripts/spotlight/admin/exhibit_tag_autocomplete.js +37 -0
  54. package/app/assets/javascripts/spotlight/admin/exhibits.js +58 -0
  55. package/app/assets/javascripts/spotlight/admin/form_observer.js +86 -0
  56. package/app/assets/javascripts/spotlight/admin/iiif.es6 +54 -0
  57. package/app/assets/javascripts/spotlight/admin/index.js +16 -0
  58. package/app/assets/javascripts/spotlight/admin/locks.js +12 -0
  59. package/app/assets/javascripts/spotlight/admin/multi_image_selector.js +158 -0
  60. package/app/assets/javascripts/spotlight/admin/pages.js.erb +40 -0
  61. package/app/assets/javascripts/spotlight/admin/progress_monitor.js +148 -0
  62. package/app/assets/javascripts/spotlight/admin/readonly_checkbox.js +6 -0
  63. package/app/assets/javascripts/spotlight/admin/search_typeahead.js +108 -0
  64. package/app/assets/javascripts/spotlight/admin/select_related_input.js +34 -0
  65. package/app/assets/javascripts/spotlight/admin/sir-trevor/block_controls.js +120 -0
  66. package/app/assets/javascripts/spotlight/admin/sir-trevor/block_limits.js +37 -0
  67. package/app/assets/javascripts/spotlight/admin/sir-trevor/locales.js +120 -0
  68. package/app/assets/javascripts/spotlight/admin/spotlight_nestable.js +72 -0
  69. package/app/assets/javascripts/spotlight/admin/tabs.js +6 -0
  70. package/app/assets/javascripts/spotlight/admin/translation_progress.js +23 -0
  71. package/app/assets/javascripts/spotlight/admin/users.js +79 -0
  72. package/app/assets/javascripts/spotlight/application.js +14 -0
  73. package/app/assets/javascripts/spotlight/spotlight.js +23 -0
  74. package/app/assets/javascripts/spotlight/user/analytics.js +9 -0
  75. package/app/assets/javascripts/spotlight/user/browse_group_categories.js +59 -0
  76. package/app/assets/javascripts/spotlight/user/carousel.js +3 -0
  77. package/app/assets/javascripts/spotlight/user/clear_form_button.js +27 -0
  78. package/app/assets/javascripts/spotlight/user/index.js +8 -0
  79. package/app/assets/javascripts/spotlight/user/report_a_problem.js +39 -0
  80. package/app/assets/javascripts/spotlight/user/zpr_links.js.erb +45 -0
  81. package/app/assets/stylesheets/spotlight/_accessibility.scss +8 -0
  82. package/app/assets/stylesheets/spotlight/_attachments.css +4 -0
  83. package/app/assets/stylesheets/spotlight/_blacklight_configuration.scss +82 -0
  84. package/app/assets/stylesheets/spotlight/_blacklight_overrides.scss +21 -0
  85. package/app/assets/stylesheets/spotlight/_bootstrap_overrides.scss +105 -0
  86. package/app/assets/stylesheets/spotlight/_breadcrumbs.scss +6 -0
  87. package/app/assets/stylesheets/spotlight/_browse.scss +158 -0
  88. package/app/assets/stylesheets/spotlight/_catalog.scss +161 -0
  89. package/app/assets/stylesheets/spotlight/_collapse_toggle.scss +14 -0
  90. package/app/assets/stylesheets/spotlight/_croppable.scss +4 -0
  91. package/app/assets/stylesheets/spotlight/_curation.scss +224 -0
  92. package/app/assets/stylesheets/spotlight/_edit_in_place.scss +9 -0
  93. package/app/assets/stylesheets/spotlight/_exhibit_admin.scss +81 -0
  94. package/app/assets/stylesheets/spotlight/_exhibit_navbar.scss +10 -0
  95. package/app/assets/stylesheets/spotlight/_exhibits_index.scss +147 -0
  96. package/app/assets/stylesheets/spotlight/_featured_browse_categories_block.scss +269 -0
  97. package/app/assets/stylesheets/spotlight/_footer.scss +12 -0
  98. package/app/assets/stylesheets/spotlight/_header.scss +155 -0
  99. package/app/assets/stylesheets/spotlight/_item_text_block.scss +50 -0
  100. package/app/assets/stylesheets/spotlight/_mixins.scss +17 -0
  101. package/app/assets/stylesheets/spotlight/_modals.scss +3 -0
  102. package/app/assets/stylesheets/spotlight/_multi_image_selector.scss +22 -0
  103. package/app/assets/stylesheets/spotlight/_multi_up_item_grid.scss +63 -0
  104. package/app/assets/stylesheets/spotlight/_nestable.scss +124 -0
  105. package/app/assets/stylesheets/spotlight/_pages.scss +282 -0
  106. package/app/assets/stylesheets/spotlight/_report_a_problem.scss +14 -0
  107. package/app/assets/stylesheets/spotlight/_sir-trevor_overrides.scss +87 -0
  108. package/app/assets/stylesheets/spotlight/_slideshow_block.scss +87 -0
  109. package/app/assets/stylesheets/spotlight/_spotlight.scss +49 -0
  110. package/app/assets/stylesheets/spotlight/_translations.scss +86 -0
  111. package/app/assets/stylesheets/spotlight/_upload.scss +0 -0
  112. package/app/assets/stylesheets/spotlight/_uploaded_items_block.scss +7 -0
  113. package/app/assets/stylesheets/spotlight/_utilities.scss +7 -0
  114. package/app/assets/stylesheets/spotlight/_variables.scss +42 -0
  115. package/app/assets/stylesheets/spotlight/_variables_bootstrap.scss +7 -0
  116. package/app/assets/stylesheets/spotlight/_view_larger.scss +22 -0
  117. package/app/assets/stylesheets/spotlight/browse_group_categories_block.scss +92 -0
  118. package/app/assets/stylesheets/spotlight/typeahead.css +77 -0
  119. package/package.json +29 -0
  120. package/vendor/assets/images/sir-trevor-icons.svg +263 -0
  121. package/vendor/assets/javascripts/Leaflet.Editable.js +1917 -0
  122. package/vendor/assets/javascripts/MutationObserver.js +625 -0
  123. package/vendor/assets/javascripts/Path.Drag.js +137 -0
  124. package/vendor/assets/javascripts/bootstrap-tagsinput.js +530 -0
  125. package/vendor/assets/javascripts/eventable.js +205 -0
  126. package/vendor/assets/javascripts/jquery.serializejson.js +234 -0
  127. package/vendor/assets/javascripts/jquery.waitforimages.min.js +2 -0
  128. package/vendor/assets/javascripts/leaflet-iiif.js +323 -0
  129. package/vendor/assets/javascripts/nestable.js +645 -0
  130. package/vendor/assets/javascripts/parameterize.js +137 -0
  131. package/vendor/assets/javascripts/polyfill.min.js +4 -0
  132. package/vendor/assets/javascripts/sir-trevor.js +21639 -0
  133. package/vendor/assets/javascripts/tiny-slider.js +3218 -0
  134. package/vendor/assets/javascripts/typeahead.bundle.min.js +7 -0
  135. package/vendor/assets/stylesheets/bootstrap-tagsinput.css +46 -0
  136. package/vendor/assets/stylesheets/leaflet-areaselect.css +15 -0
  137. package/vendor/assets/stylesheets/sir-trevor/_icons.scss +6 -0
  138. package/vendor/assets/stylesheets/sir-trevor/_variables.scss +22 -0
  139. package/vendor/assets/stylesheets/sir-trevor/base.scss +17 -0
  140. package/vendor/assets/stylesheets/sir-trevor/block-addition-top.scss +95 -0
  141. package/vendor/assets/stylesheets/sir-trevor/block-addition.scss +72 -0
  142. package/vendor/assets/stylesheets/sir-trevor/block-controls.scss +34 -0
  143. package/vendor/assets/stylesheets/sir-trevor/block-positioner.scss +34 -0
  144. package/vendor/assets/stylesheets/sir-trevor/block-replacer.scss +43 -0
  145. package/vendor/assets/stylesheets/sir-trevor/block-ui.scss +120 -0
  146. package/vendor/assets/stylesheets/sir-trevor/block.scss +300 -0
  147. package/vendor/assets/stylesheets/sir-trevor/errors.scss +21 -0
  148. package/vendor/assets/stylesheets/sir-trevor/format-bar.scss +65 -0
  149. package/vendor/assets/stylesheets/sir-trevor/inputs.scss +45 -0
  150. package/vendor/assets/stylesheets/sir-trevor/main.scss +24 -0
  151. package/vendor/assets/stylesheets/sir-trevor/patterns/ui-popup.scss +38 -0
  152. package/vendor/assets/stylesheets/sir-trevor/utils.scss +10 -0
  153. package/vendor/assets/stylesheets/tiny-slider.css +1 -0
@@ -0,0 +1,145 @@
1
+ SirTrevor.Blocks.UploadedItems = (function(){
2
+ return Spotlight.Block.Resources.extend({
3
+ plustextable: true,
4
+ uploadable: true,
5
+ autocompleteable: false,
6
+
7
+ id_key: 'file',
8
+
9
+ type: 'uploaded_items',
10
+
11
+ icon_name: 'items',
12
+
13
+ blockGroup: 'undefined',
14
+
15
+ // Clear out the default Uploadable upload options
16
+ // since we will be using our own custom controls
17
+ upload_options: { html: '' },
18
+
19
+ fileInput: function() { return $(this.inner).find('input[type="file"]'); },
20
+
21
+ onBlockRender: function(){
22
+ SpotlightNestable.init($(this.inner).find('[data-behavior="nestable"]'));
23
+
24
+ this.fileInput().on('change', (function(ev) {
25
+ this.onDrop(ev.currentTarget);
26
+ }).bind(this));
27
+ },
28
+
29
+ onDrop: function(transferData){
30
+ var file = transferData.files[0],
31
+ urlAPI = (typeof URL !== "undefined") ? URL : (typeof webkitURL !== "undefined") ? webkitURL : null;
32
+
33
+ // Handle one upload at a time
34
+ if (/image/.test(file.type)) {
35
+ this.loading();
36
+
37
+ this.uploader(
38
+ file,
39
+ function(data) {
40
+ this.createItemPanel(data);
41
+ this.fileInput().val('');
42
+ this.ready();
43
+ },
44
+ function(error) {
45
+ this.addMessage(i18n.t('blocks:image:upload_error'));
46
+ this.ready();
47
+ }
48
+ );
49
+ }
50
+ },
51
+
52
+ title: function() { return i18n.t('blocks:uploaded_items:title'); },
53
+ description: function() { return i18n.t('blocks:uploaded_items:description'); },
54
+
55
+ globalIndex: 0,
56
+
57
+ _itemPanel: function(data) {
58
+ var index = "file_" + this.globalIndex++;
59
+ var checked = 'checked="checked"';
60
+
61
+ if (data.display == 'false') {
62
+ checked = '';
63
+ }
64
+
65
+ var dataId = data.id || data.uid;
66
+ var dataTitle = data.title || data.name;
67
+ var dataUrl = data.url || data.file.url;
68
+
69
+ var markup = [
70
+ '<li class="field form-inline dd-item dd3-item" data-id="' + index + '" id="' + this.formId("item_" + dataId) + '">',
71
+ '<input type="hidden" name="item[' + index + '][id]" value="' + dataId + '" />',
72
+ '<input type="hidden" name="item[' + index + '][title]" value="' + dataTitle + '" />',
73
+ '<input type="hidden" name="item[' + index + '][url]" data-item-grid-thumbnail="true" value="' + dataUrl + '"/>',
74
+ '<input data-property="weight" type="hidden" name="item[' + index + '][weight]" value="' + data.weight + '" />',
75
+ '<div class="card d-flex dd3-content">',
76
+ '<div class="dd-handle dd3-handle"><%= i18n.t("blocks:resources:panel:drag") %></div>',
77
+ '<div class="card-header d-flex item-grid">',
78
+ '<div class="checkbox">',
79
+ '<input name="item[' + index + '][display]" type="hidden" value="false" />',
80
+ '<input name="item[' + index + '][display]" id="'+ this.formId(this.display_checkbox + '_' + dataId) + '" type="checkbox" ' + checked + ' class="item-grid-checkbox" value="true" />',
81
+ '<label class="sr-only" for="'+ this.formId(this.display_checkbox + '_' + dataId) +'"><%= i18n.t("blocks:resources:panel:display") %></label>',
82
+ '</div>',
83
+ '<div class="pic">',
84
+ '<img class="img-thumbnail" src="' + dataUrl + '" />',
85
+ '</div>',
86
+ '<div class="main form-horizontal">',
87
+ '<div class="title card-title">' + dataTitle + '</div>',
88
+ '<div class="field row mr-3">',
89
+ '<label for="' + this.formId('caption_' + dataId) + '" class="col-form-label col-md-3"><%= i18n.t("blocks:uploaded_items:caption") %></label>',
90
+ '<input type="text" class="form-control col" id="' + this.formId('caption_' + dataId) + '" name="item[' + index + '][caption]" data-field="caption"/>',
91
+ '</div>',
92
+ '<div class="field row mr-3">',
93
+ '<label for="' + this.formId('link_' + dataId) + '" class="col-form-label col-md-3"><%= i18n.t("blocks:uploaded_items:link") %></label>',
94
+ '<input type="text" class="form-control col" id="' + this.formId('link_' + dataId) + '" name="item[' + index + '][link]" data-field="link"/>',
95
+ '</div>',
96
+ '</div>',
97
+ '<div class="remove float-right">',
98
+ '<a data-item-grid-panel-remove="true" href="#"><%= i18n.t("blocks:resources:panel:remove") %></a>',
99
+ '</div>',
100
+ '</div>',
101
+ '</li>'
102
+ ].join("\n");
103
+
104
+ var panel = $(_.template(markup)(this));
105
+ panel.find('[data-field="caption"]').val(data.caption);
106
+ panel.find('[data-field="link"]').val(data.link);
107
+ var context = this;
108
+
109
+ $('.remove a', panel).on('click', function(e) {
110
+ e.preventDefault();
111
+ $(this).closest('.field').remove();
112
+ context.afterPanelDelete();
113
+ });
114
+
115
+ this.afterPanelRender(data, panel);
116
+
117
+ return panel;
118
+ },
119
+
120
+ template: [
121
+ '<div class="form oembed-text-admin clearfix">',
122
+ '<div class="widget-header">',
123
+ '<%= description() %>',
124
+ '</div>',
125
+ '<div class="row">',
126
+ '<div class="form-group col-md-8">',
127
+ '<div class="panels dd nestable-item-grid" data-behavior="nestable" data-max-depth="1">',
128
+ '<ol class="dd-list">',
129
+ '</ol>',
130
+ '</div>',
131
+ '<input type="file" id="uploaded_item_url" name="file[file_0][file_data]" />',
132
+ '</div>',
133
+ '<div class="col-md-4">',
134
+ '<input name="<%= zpr_key %>" type="hidden" value="false" />',
135
+ '<input name="<%= zpr_key %>" id="<%= formId(zpr_key) %>" data-key="<%= zpr_key %>" type="checkbox" value="true" />',
136
+ '<label for="<%= formId(zpr_key) %>"><%= i18n.t("blocks:solr_documents:zpr:title") %></label>',
137
+ '</div>',
138
+ '</div>',
139
+ '<%= text_area() %>',
140
+ '</div>'
141
+ ].join("\n"),
142
+
143
+ zpr_key: 'zpr_link'
144
+ });
145
+ })();
@@ -0,0 +1,16 @@
1
+ Spotlight.onLoad(function() {
2
+ $(".visiblity_toggle").blCheckboxSubmit({
3
+ //css_class is added to elements added, plus used for id base
4
+ cssClass: "toggle_visibility",
5
+ //success is called at the end of the ajax success callback
6
+ success: function (isPublic){
7
+ // We store the selector of the label to toggle in a data attribute in the form
8
+ var docTarget = $($(this).data("label-toggle-target"));
9
+ if ( isPublic ) {
10
+ docTarget.removeClass("blacklight-private");
11
+ } else {
12
+ docTarget.addClass("blacklight-private");
13
+ }
14
+ }
15
+ });
16
+ });
@@ -0,0 +1,9 @@
1
+ (function($) {
2
+ $.fn.copyEmailAddresses = function( options ) {
3
+ var clip = new Clipboard('.copy-email-addresses');
4
+ };
5
+ })( jQuery );
6
+
7
+ Spotlight.onLoad(function() {
8
+ $('.copy-email-addresses').copyEmailAddresses();
9
+ });
@@ -0,0 +1,310 @@
1
+ export default class Crop {
2
+ constructor(cropArea) {
3
+ this.cropArea = cropArea;
4
+ this.cropArea.data('iiifCropper', this);
5
+ this.cropSelector = '[data-cropper="' + cropArea.data('cropperKey') + '"]';
6
+ this.cropTool = $(this.cropSelector);
7
+ this.formPrefix = this.cropTool.data('form-prefix');
8
+ this.iiifUrlField = $('#' + this.formPrefix + '_iiif_tilesource');
9
+ this.iiifRegionField = $('#' + this.formPrefix + '_iiif_region');
10
+ this.iiifManifestField = $('#' + this.formPrefix + '_iiif_manifest_url');
11
+ this.iiifCanvasField = $('#' + this.formPrefix + '_iiif_canvas_id');
12
+ this.iiifImageField = $('#' + this.formPrefix + '_iiif_image_id');
13
+
14
+ this.form = cropArea.closest('form');
15
+ this.tileSource = null;
16
+ }
17
+
18
+ // Render the cropper environment and add hooks into the autocomplete and upload forms
19
+ render() {
20
+ this.setupAutoCompletes();
21
+ this.setupAjaxFileUpload();
22
+ this.setupExistingIiifCropper();
23
+ }
24
+
25
+ // Setup the cropper on page load if the field
26
+ // that holds the IIIF url is populated
27
+ setupExistingIiifCropper() {
28
+ if(this.iiifUrlField.val() === '') {
29
+ return;
30
+ }
31
+
32
+ this.addImageSelectorToExistingCropTool();
33
+ this.setTileSource(this.iiifUrlField.val());
34
+ }
35
+
36
+ // Display the IIIF Cropper map with the current IIIF Layer (and cropbox, once the layer is available)
37
+ setupIiifCropper() {
38
+ this.loaded = false;
39
+
40
+ this.renderCropperMap();
41
+
42
+ if (this.imageLayer) {
43
+ // Force a broken layer's container to be an element before removing.
44
+ // Code in leaflet-iiif land calls delete on the image layer's container when removing,
45
+ // which errors if there is an issue fetching the info.json and stops further necessary steps to execute.
46
+ if(!this.imageLayer._container) {
47
+ this.imageLayer._container = $('<div></div>');
48
+ }
49
+ this.cropperMap.removeLayer(this.imageLayer);
50
+ }
51
+
52
+ this.imageLayer = L.tileLayer.iiif(this.tileSource).addTo(this.cropperMap);
53
+
54
+ var self = this;
55
+ this.imageLayer.on('load', function() {
56
+ if (!self.loaded) {
57
+ var region = self.getCropRegion();
58
+ self.positionIiifCropBox(region);
59
+ self.loaded = true;
60
+ }
61
+ });
62
+
63
+ this.cropArea.data('initiallyVisible', this.cropArea.is(':visible'));
64
+ }
65
+
66
+ // Get (or initialize) the current crop region from the form data
67
+ getCropRegion() {
68
+ var regionFieldValue = this.iiifRegionField.val();
69
+ if(!regionFieldValue || regionFieldValue === '') {
70
+ var region = this.defaultCropRegion();
71
+ this.iiifRegionField.val(region);
72
+ return region;
73
+ } else {
74
+ return regionFieldValue.split(',');
75
+ }
76
+ }
77
+
78
+ // Calculate a default crop region in the center of the image using the correct aspect ratio
79
+ defaultCropRegion() {
80
+ var imageWidth = this.imageLayer.x;
81
+ var imageHeight = this.imageLayer.y;
82
+
83
+ var boxWidth = Math.floor(imageWidth / 2);
84
+ var boxHeight = Math.floor(boxWidth / this.aspectRatio());
85
+
86
+ return [
87
+ Math.floor((imageWidth - boxWidth) / 2),
88
+ Math.floor((imageHeight - boxHeight) / 2),
89
+ boxWidth,
90
+ boxHeight
91
+ ];
92
+ }
93
+
94
+ // Calculate the required aspect ratio for the crop area
95
+ aspectRatio() {
96
+ var cropWidth = parseInt(this.cropArea.data('crop-width'));
97
+ var cropHeight = parseInt(this.cropArea.data('crop-height'));
98
+ return cropWidth / cropHeight;
99
+ }
100
+
101
+ // Position the IIIF Crop Box at the given IIIF region
102
+ positionIiifCropBox(region) {
103
+ var bounds = this.unprojectIIIFRegionToBounds(region);
104
+
105
+ if (!this.cropBox) {
106
+ this.renderCropBox(bounds);
107
+ }
108
+
109
+ this.cropBox.setBounds(bounds);
110
+ this.cropperMap.invalidateSize();
111
+ this.cropperMap.fitBounds(bounds);
112
+
113
+ this.cropBox.editor.editLayer.clearLayers();
114
+ this.cropBox.editor.refresh();
115
+ this.cropBox.editor.initVertexMarkers();
116
+ }
117
+
118
+ // Set all of the various input fields to
119
+ // the appropriate IIIF URL or identifier
120
+ setIiifFields(iiifObject) {
121
+ this.setTileSource(iiifObject.tilesource);
122
+ this.iiifManifestField.val(iiifObject.manifest);
123
+ this.iiifCanvasField.val(iiifObject.canvasId);
124
+ this.iiifImageField.val(iiifObject.imageId);
125
+ }
126
+
127
+ // Set the Crop tileSource and setup the cropper
128
+ setTileSource(source) {
129
+ if (source == this.tileSource) {
130
+ return;
131
+ }
132
+
133
+ if (source === null || source === undefined) {
134
+ console.error('No tilesource provided when setting up IIIF Cropper');
135
+ return;
136
+ }
137
+
138
+ if (this.cropBox) {
139
+ this.iiifRegionField.val("");
140
+ }
141
+
142
+ this.tileSource = source;
143
+ this.iiifUrlField.val(source);
144
+ this.setupIiifCropper();
145
+ }
146
+
147
+ // Render the Leaflet Map into the crop area
148
+ renderCropperMap() {
149
+ if (this.cropperMap) {
150
+ return;
151
+ }
152
+ this.cropperMap = L.map(this.cropArea.attr('id'), {
153
+ editable: true,
154
+ center: [0, 0],
155
+ crs: L.CRS.Simple,
156
+ zoom: 0,
157
+ editOptions: {
158
+ rectangleEditorClass: this.aspectRatioPreservingRectangleEditor(this.aspectRatio())
159
+ }
160
+ });
161
+ this.invalidateMapSizeOnTabToggle();
162
+ }
163
+
164
+ // Render the crop box (a Leaflet editable rectangle) onto the canvas
165
+ renderCropBox(initialBounds) {
166
+ this.cropBox = L.rectangle(initialBounds);
167
+ this.cropBox.addTo(this.cropperMap);
168
+ this.cropBox.enableEdit();
169
+ this.cropBox.on('dblclick', L.DomEvent.stop).on('dblclick', this.cropBox.toggleEdit);
170
+
171
+ var self = this;
172
+ this.cropperMap.on('editable:dragend editable:vertex:dragend', function(e) {
173
+ var bounds = e.layer.getBounds();
174
+ var region = self.projectBoundsToIIIFRegion(bounds);
175
+
176
+ self.iiifRegionField.val(region.join(','));
177
+ });
178
+ }
179
+
180
+ // Get the maximum zoom level for the IIIF Layer (always 1:1 image pixel to canvas?)
181
+ maxZoom() {
182
+ if(this.imageLayer) {
183
+ return this.imageLayer.maxZoom;
184
+ }
185
+ }
186
+
187
+ // Take a Leaflet LatLngBounds object and transform it into a IIIF [x, y, w, h] region
188
+ projectBoundsToIIIFRegion(bounds) {
189
+ var min = this.cropperMap.project(bounds.getNorthWest(), this.maxZoom());
190
+ var max = this.cropperMap.project(bounds.getSouthEast(), this.maxZoom());
191
+ return [
192
+ Math.max(Math.floor(min.x), 0),
193
+ Math.max(Math.floor(min.y), 0),
194
+ Math.floor(max.x - min.x),
195
+ Math.floor(max.y - min.y)
196
+ ];
197
+ }
198
+
199
+ // Take a IIIF [x, y, w, h] region and transform it into a Leaflet LatLngBounds
200
+ unprojectIIIFRegionToBounds(region) {
201
+ var minPoint = L.point(parseInt(region[0]), parseInt(region[1]));
202
+ var maxPoint = L.point(parseInt(region[0]) + parseInt(region[2]), parseInt(region[1]) + parseInt(region[3]));
203
+
204
+ var min = this.cropperMap.unproject(minPoint, this.maxZoom());
205
+ var max = this.cropperMap.unproject(maxPoint, this.maxZoom());
206
+ return L.latLngBounds(min, max);
207
+ }
208
+
209
+ // TODO: Add accessors to update hidden inputs with IIIF uri/ids?
210
+
211
+ // Setup autocomplete inputs to have the iiif_cropper context
212
+ setupAutoCompletes() {
213
+ var input = $('[data-behavior="autocomplete"]', this.cropTool);
214
+ input.data('iiifCropper', this);
215
+ }
216
+
217
+ setupAjaxFileUpload() {
218
+ this.fileInput = $('input[type="file"]', this.cropTool);
219
+ this.fileInput.change(() => this.uploadFile());
220
+ }
221
+
222
+ addImageSelectorToExistingCropTool() {
223
+ if(this.iiifManifestField.val() === '') {
224
+ return;
225
+ }
226
+
227
+ var input = $('[data-behavior="autocomplete"]', this.cropTool);
228
+ var panel = $(input.data('target-panel'));
229
+ // This is defined in search_typeahead.js
230
+ addImageSelector(input, panel, this.iiifManifestField.val(), !this.iiifImageField.val());
231
+ }
232
+
233
+ invalidateMapSizeOnTabToggle() {
234
+ var tabs = $('[role="tablist"]', this.form);
235
+ var self = this;
236
+ tabs.on('shown.bs.tab', function() {
237
+ if(self.cropArea.data('initiallyVisible') === false && self.cropArea.is(':visible')) {
238
+ self.cropperMap.invalidateSize();
239
+ // Because the map size is 0,0 when image is loading (not visible) we need to refit the bounds of the layer
240
+ self.imageLayer._fitBounds();
241
+ self.cropArea.data('initiallyVisible', null);
242
+ }
243
+ });
244
+ }
245
+
246
+ // Get all the form data with the exception of the _method field.
247
+ getData() {
248
+ var data = new FormData(this.form[0]);
249
+ data.append('_method', null);
250
+ return data;
251
+ }
252
+
253
+ uploadFile() {
254
+ // Set a ujs adapter to support both rails-ujs and jquery-ujs
255
+ var ujs = typeof Rails === 'undefined' ? $.rails : Rails;
256
+ var url = this.fileInput.data('endpoint')
257
+ // Every post creates a new image/masthead.
258
+ // Because they create IIIF urls which are heavily cached.
259
+ $.ajax({
260
+ url: url, //Server script to process data
261
+ type: 'POST',
262
+ success: (data, stat, xhr) => this.successHandler(data, stat, xhr),
263
+ // error: errorHandler,
264
+ // Form data
265
+ data: this.getData(),
266
+ headers: {
267
+ 'X-CSRF-Token': ujs.csrfToken() || ''
268
+ },
269
+ //Options to tell jQuery not to process data or worry about content-type.
270
+ cache: false,
271
+ contentType: false,
272
+ processData: false
273
+ });
274
+ }
275
+
276
+ successHandler(data, stat, xhr) {
277
+ this.setIiifFields({ tilesource: data.tilesource });
278
+ this.setUploadId(data.id);
279
+ }
280
+
281
+ setUploadId(id) {
282
+ $('#' + this.formPrefix + "_upload_id").val(id);
283
+ }
284
+
285
+ aspectRatioPreservingRectangleEditor(aspect) {
286
+ return L.Editable.RectangleEditor.extend({
287
+ extendBounds: function (e) {
288
+ var index = e.vertex.getIndex(),
289
+ next = e.vertex.getNext(),
290
+ previous = e.vertex.getPrevious(),
291
+ oppositeIndex = (index + 2) % 4,
292
+ opposite = e.vertex.latlngs[oppositeIndex];
293
+
294
+ if ((index % 2) == 1) {
295
+ // calculate horiz. displacement
296
+ e.latlng.update([opposite.lat + ((1 / aspect) * (opposite.lng - e.latlng.lng)), e.latlng.lng]);
297
+ } else {
298
+ // calculate vert. displacement
299
+ e.latlng.update([e.latlng.lat, (opposite.lng - (aspect * (opposite.lat - e.latlng.lat)))]);
300
+ }
301
+ var bounds = new L.LatLngBounds(e.latlng, opposite);
302
+ // Update latlngs by hand to preserve order.
303
+ previous.latlng.update([e.latlng.lat, opposite.lng]);
304
+ next.latlng.update([opposite.lat, e.latlng.lng]);
305
+ this.updateBounds(bounds);
306
+ this.refreshVertexMarkers();
307
+ }
308
+ });
309
+ }
310
+ }
@@ -0,0 +1,25 @@
1
+ Spotlight.onLoad(function() {
2
+ $('[data-behavior="iiif-cropper"]').croppable();
3
+ });
4
+
5
+
6
+ /*
7
+ IIIF image cropping plugin
8
+ Add iiif-crop data-attributes to file input (with data-behavior='iiif-cropper') to instantiate.
9
+ */
10
+
11
+ (function($) {
12
+ $.fn.croppable = function() {
13
+ var croppables = this;
14
+
15
+ var Crop = spotlightAdminCrop;
16
+ $(croppables).each(function() {
17
+ var cropElement = $(this);
18
+ var c = new Crop(cropElement);
19
+
20
+ c.render();
21
+ });
22
+
23
+ return this;
24
+ };
25
+ })(jQuery);
@@ -0,0 +1,54 @@
1
+ Spotlight.onLoad(function() {
2
+ $('[data-in-place-edit-target]').spotlightEditInPlace();
3
+ });
4
+ /*
5
+ Simple plugin add edit-in-place behavior
6
+ */
7
+ (function($) {
8
+ $.fn.spotlightEditInPlace = function() {
9
+ var clickElements = this;
10
+
11
+ $(clickElements).each(function() {
12
+ $(this).on('click.inplaceedit', function() {
13
+ var $label = $(this).find($(this).data('in-place-edit-target'));
14
+ var $input = $(this).find($(this).data('in-place-edit-field-target'));
15
+
16
+ // hide the edit-in-place affordance icon while in edit mode
17
+ $(this).addClass('hide-edit-icon');
18
+ $label.hide();
19
+ $input.val($label.text());
20
+ $input.attr('type', 'text');
21
+ $input.select();
22
+ $input.focus();
23
+
24
+ $input.on('keypress', function(e) {
25
+ if(e.which == 13) {
26
+ $input.trigger('blur.inplaceedit');
27
+ return false;
28
+ }
29
+ });
30
+
31
+ $input.on('blur.inplaceedit', function() {
32
+ var value = $input.val();
33
+
34
+ if ($.trim(value).length == 0) {
35
+ $input.val($label.text());
36
+ } else {
37
+ $label.text(value);
38
+ }
39
+
40
+ $label.show();
41
+ $input.attr('type', 'hidden');
42
+ // when leaving edit mode, should no longer hide edit-in-place affordance icon
43
+ $("[data-in-place-edit-target]").removeClass('hide-edit-icon');
44
+
45
+ return false;
46
+ });
47
+
48
+ return false;
49
+ });
50
+ });
51
+
52
+ return this;
53
+ };
54
+ })(jQuery);
@@ -0,0 +1,37 @@
1
+ Spotlight.onLoad(function() {
2
+ $('[data-autocomplete-tag="true"]').each(function(_i, el) {
3
+ var $el = $(el);
4
+ // By default tags input binds on page ready to [data-role=tagsinput],
5
+ // however, that doesn't work with Turbolinks. So we init manually:
6
+ $el.tagsinput();
7
+
8
+ var tags = new Bloodhound({
9
+ datumTokenizer: function(d) { return Bloodhound.tokenizers.whitespace(d.name); },
10
+ queryTokenizer: Bloodhound.tokenizers.whitespace,
11
+ limit: 100,
12
+ prefetch: {
13
+ url: $el.data('autocomplete-url'),
14
+ ttl: 1,
15
+ filter: function(list) {
16
+ // Let the dom know that the response has been returned
17
+ $el.attr('data-autocomplete-fetched', true);
18
+ return $.map(list, function(tag) { return { name: tag }; });
19
+ }
20
+ }
21
+ });
22
+
23
+ tags.initialize();
24
+
25
+ $el.tagsinput('input').typeahead({highlight: true, hint: false}, {
26
+ name: 'tags',
27
+ displayKey: 'name',
28
+ source: tags.ttAdapter()
29
+ }).bind('typeahead:selected', $.proxy(function (obj, datum) {
30
+ $el.tagsinput('add', datum.name);
31
+ $el.tagsinput('input').typeahead('val', '');
32
+ })).bind('blur', function() {
33
+ $el.tagsinput('add', $el.tagsinput('input').typeahead('val'));
34
+ $el.tagsinput('input').typeahead('val', '');
35
+ });
36
+ });
37
+ });
@@ -0,0 +1,58 @@
1
+ Spotlight.onLoad(function() {
2
+
3
+ // auto-fill the exhibit slug on the new exhibit form
4
+ $('#new_exhibit').each(function() {
5
+ $('#exhibit_title').on('change keyup', function() {
6
+ $('#exhibit_slug').attr('placeholder', URLify($(this).val(), $(this).val().length));
7
+ });
8
+
9
+ $('#exhibit_slug').on('focus', function() {
10
+ if ($(this).val() === '') {
11
+ $(this).val($(this).attr('placeholder'));
12
+ }
13
+ });
14
+ });
15
+
16
+ $("#another-email").on("click", function(e) {
17
+ e.preventDefault();
18
+
19
+ var container = $(this).closest('.form-group');
20
+ var contacts = container.find('.contact');
21
+ var inputContainer = contacts.first().clone();
22
+
23
+ // wipe out any values from the inputs
24
+ inputContainer.find('input').each(function() {
25
+ $(this).val('');
26
+ $(this).attr('id', $(this).attr('id').replace('0', contacts.length));
27
+ $(this).attr('name', $(this).attr('name').replace('0', contacts.length));
28
+ if ($(this).attr('aria-label')) {
29
+ $(this).attr('aria-label', $(this).attr('aria-label').replace('1', contacts.length + 1));
30
+ }
31
+ });
32
+
33
+ inputContainer.find('.contact-email-delete-wrapper').remove();
34
+ inputContainer.find('.confirmation-status').remove();
35
+
36
+ // bootstrap does not render input-groups with only one value in them correctly.
37
+ inputContainer.find('.input-group input:only-child').closest('.input-group').removeClass('input-group');
38
+
39
+ $(inputContainer).insertAfter(contacts.last());
40
+ });
41
+
42
+ $('.contact-email-delete').on('ajax:success', function() {
43
+ $(this).closest('.contact').fadeOut(250, function() { $(this).remove(); });
44
+ });
45
+
46
+ $('.contact-email-delete').on('ajax:error', function(event, _xhr, _status, error) {
47
+ var errSpan = $(this).closest('.contact').find('.contact-email-delete-error');
48
+ errSpan.show();
49
+ errSpan.find('.error-msg').first().text(error || event.detail[1]);
50
+ });
51
+
52
+ $('.btn-with-tooltip').tooltip();
53
+
54
+ // Put focus in saved search title input when Save this search modal is shown
55
+ $('#save-modal').on('shown.bs.modal', function () {
56
+ $('#search_title').focus();
57
+ });
58
+ });