henry-search 1.0.8 → 1.0.10

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.
@@ -1,15 +1,10 @@
1
1
  <script setup>
2
2
 
3
- import { ref, onMounted, onUnmounted, computed } from 'vue'
4
-
5
- import VueDatePicker from '@vuepic/vue-datepicker';
3
+ import { ref, onMounted } from 'vue'
6
4
  import '@vuepic/vue-datepicker/dist/main.css'
7
-
8
- import Typesense from 'typesense'
9
-
10
-
11
5
  import PTabs from './PTabs.vue'
12
6
  import SearchTab from "./SearchTab.vue"
7
+ import EventLinks from "./EventLinks.vue"
13
8
 
14
9
  import formatDate from '../composables/formatDate'
15
10
 
@@ -29,38 +24,38 @@
29
24
  })
30
25
 
31
26
  const displayDate = ref(null)
32
- const datepicker = ref(null)
33
- const rangeInput = ref(null)
34
- const venueOptions = ref([])
35
- const ensembleOptions = ref([])
36
27
 
37
28
  const today = Date.now()
38
29
  const date = ref([today, today])
39
30
 
40
- // Modal state management
41
- let isCreatingModal = false
42
- let modalCreated = false
31
+ onMounted(() => {
32
+ document.addEventListener("click", function(){
33
+ document.querySelectorAll(".audioLinkBox").forEach((el) => {
34
+ el.classList.remove("-open")
35
+ })
36
+ })
37
+ })
43
38
 
44
39
  const mainRefinements = [
45
40
  {attribute: 'works.composers', title: 'Composer', placeholder: 'Search Composers'},
46
41
  {attribute: 'works.title', title: 'Work', placeholder: 'Search Works'},
47
- {attribute: 'conductors', title: 'Conductor', placeholder: 'Search Conductors'},
48
- {attribute: 'ensembles', title: 'Orchestra/Ensemble', placeholder: 'Search Orchestras/Ensembles'},
42
+ {attribute: 'works.conductors', title: 'Conductor', placeholder: 'Search Conductors'},
43
+ {attribute: 'works.ensembles', title: 'Orchestra/Ensemble', placeholder: 'Search Orchestras/Ensembles'},
49
44
  {attribute: 'works.artists.name', title: 'Artist', placeholder: 'Search Artists'},
50
45
  ]
51
46
 
52
47
  const addlRefinements = [
53
- {attribute: 'works.artists.role', title: 'Instrument', placeholder: 'Search Instruments', type: 'list'},
48
+ {attribute: 'works.artists.role', title: 'Instrument', placeholder: 'Search Instruments', type: 'list', hideSearch: 'true'},
54
49
  // {attribute: 'works.additional_creators.creator_name', title: 'Additional Creator', placeholder: 'Search Creators', type: 'list'},
55
50
  // {attribute: 'works.additional_creators.creator_role', title: 'Additional Creator Role', placeholder: 'Search Creator Roles', type: 'list'},
56
51
  {attribute: 'season', title: 'Season', placeholder: 'Search Seasons', type: 'list'},
57
52
  {attribute: 'event_title', title: 'Event Title', placeholder: 'Search Event Titles', type: 'list'},
58
53
  {attribute: 'event_types', title: 'Series', placeholder: 'Search Event Types', type: 'list'},
59
- {attribute: 'venue', title: 'Venue', placeholder: 'Search Venues', type: 'list'},
54
+ {attribute: 'venue', title: 'Venue', placeholder: 'Search Venues', type: 'list', hideSearch: 'true'},
60
55
  {attribute: 'location', title: 'Location', type: 'location'},
61
- {attribute: 'media', title: 'Media', placeholder: 'Select Media', type: 'list', hideSearch: 'false'},
62
- {attribute: 'premiere', title: 'Premiere', placeholder: 'Select Premiere', type: 'list', hideSearch: 'false'},
63
- {attribute: 'works.commission', title: 'Commission', placeholder: 'Select Premiere', type: 'list', hideSearch: 'false'}
56
+ {attribute: 'media', title: 'Media', placeholder: 'Select Media', type: 'list', hideSearch: 'true'},
57
+ {attribute: 'works.premiere', title: 'Premiere', placeholder: 'Select Premiere', type: 'list', hideSearch: 'true'},
58
+ {attribute: 'works.commission', title: 'Commission', placeholder: 'Select Premiere', type: 'list', hideSearch: 'true'}
64
59
  ]
65
60
 
66
61
  const artistRefinements = [
@@ -79,108 +74,6 @@
79
74
  {attribute: 'commission', title: 'Commission', placeholder: 'Search Commissions'}
80
75
  ]
81
76
 
82
-
83
-
84
- // Check if we're on mobile
85
- const isMobile = ref(false)
86
-
87
- // Function to check mobile status
88
- function checkMobile() {
89
- if (typeof window !== 'undefined') {
90
- isMobile.value = window.innerWidth <= 767
91
- }
92
- }
93
-
94
- onMounted(() => {
95
-
96
- console.log("props.workIndex", props.workIndex)
97
-
98
- // Disable browser scroll restoration
99
- if ('scrollRestoration' in history) {
100
- history.scrollRestoration = 'manual'
101
- }
102
-
103
- // Listen for clicks on pagination and scroll after a delay
104
- setupPaginationScrollFix()
105
-
106
- // Check mobile status and add resize listener
107
- checkMobile()
108
- if (typeof window !== 'undefined') {
109
- window.addEventListener('resize', checkMobile)
110
- }
111
-
112
- // // Fallback: Watch for date picker menu to appear/disappear using MutationObserver
113
- // const observer = new MutationObserver((mutations) => {
114
- // mutations.forEach((mutation) => {
115
- // // Watch for added nodes (menu opening)
116
- // mutation.addedNodes.forEach((node) => {
117
- // if (node.nodeType === 1 && node.classList) {
118
- // // Only trigger for the main menu, not wrapper elements or our modal
119
- // if (node.classList.contains('dp__menu') &&
120
- // !node.classList.contains('dp__mobile_modal') &&
121
- // !modalCreated && !isCreatingModal) {
122
- // if (typeof window !== 'undefined' && window.innerWidth <= 767) {
123
- // onDatePickerOpened()
124
- // }
125
- // }
126
- // }
127
- // })
128
- // })
129
- // })
130
-
131
- // observer.observe(document.body, {
132
- // childList: true,
133
- // subtree: true
134
- // })
135
-
136
- })
137
-
138
-
139
- onUnmounted(() => {
140
- // Restore browser scroll restoration
141
- if ('scrollRestoration' in history) {
142
- history.scrollRestoration = 'auto'
143
- }
144
-
145
- // Remove resize listener
146
- if (typeof window !== 'undefined') {
147
- window.removeEventListener('resize', checkMobile)
148
- }
149
- })
150
-
151
-
152
- function setupPaginationScrollFix() {
153
- // Use event delegation to catch pagination clicks
154
- document.addEventListener('click', (event) => {
155
- const paginationLink = event.target.closest('.ais-Pagination-link')
156
-
157
- if (paginationLink) {
158
- // Don't prevent default - let InstantSearch handle navigation normally
159
- // Schedule a scroll after InstantSearch updates
160
- setTimeout(() => {
161
- scrollToResults()
162
- }, 200)
163
- }
164
- })
165
- }
166
-
167
- function scrollToResults() {
168
- const resultsSection = document.querySelector('.eventsSearch__results')
169
- if (resultsSection) {
170
- const rect = resultsSection.getBoundingClientRect()
171
- const scrollTop = window.pageYOffset + rect.top - 20 // 20px offset from top
172
-
173
- window.scrollTo({
174
- top: scrollTop,
175
- behavior: 'smooth'
176
- })
177
- }
178
- }
179
-
180
- const setDate = (value) => {
181
- date.value = value
182
- displayDate.value = value
183
- }
184
77
 
185
78
  function createURL(facets) {
186
79
  let returnUrl = "/?"
@@ -229,332 +122,10 @@
229
122
  returnLocation += location.country
230
123
  }
231
124
  }
232
- return returnLocation;
233
-
234
-
125
+ return returnLocation
235
126
  }
236
127
 
237
- // Generic mobile filter modal function
238
- function createMobileFilterModal(filterType, displayName, items, refineFunction) {
239
- const modalHTML = createMultiSelectModalHTML({
240
- modalId: `${filterType}__mobile_modal`,
241
- title: `Select ${displayName}`,
242
- description: `Choose your ${displayName.toLowerCase()}`,
243
- cancelText: 'Cancel',
244
- selectText: 'Apply',
245
- cancelButtonClass: `js-${filterType}-cancel`,
246
- selectButtonClass: `js-${filterType}-select`,
247
- contentContainerClass: `eventsModal__${filterType}Container`
248
- })
249
-
250
- // Insert modal into body
251
- document.body.insertAdjacentHTML('beforeend', modalHTML)
252
-
253
- const modal = document.querySelector(`.${filterType}__mobile_modal`)
254
- const container = modal.querySelector(`.eventsModal__${filterType}Container`)
255
-
256
- if (modal && container) {
257
- // Generate filter options HTML
258
- let optionsHTML = '<div class="filterModal__items">'
259
-
260
- items.forEach((item) => {
261
- const isChecked = item.isRefined ? 'checked' : ''
262
- const cleanLabel = item.value.replace(/<[^>]*>/g, '') // Remove HTML tags
263
-
264
- // For venue, split venue name from city
265
- let itemTextHTML = `<span class="filterModal__itemText">${cleanLabel}</span>`
266
- if (filterType === 'venue') {
267
- // Split on common patterns: ", City" or " | City" or similar
268
- const parts = cleanLabel.split(/,\s*(?=[A-Z])|(?:\s*\|\s*[^|]*,\s*)/);
269
- if (parts.length >= 2) {
270
- const venueName = parts[0].trim();
271
- const cityInfo = parts.slice(1).join(', ').trim();
272
- itemTextHTML = `
273
- <span class="filterModal__itemText">
274
- <span class="filterModal__venueName">${venueName}</span>
275
- <span class="filterModal__venueCity">${cityInfo}</span>
276
- </span>
277
- `
278
- }
279
- }
280
-
281
- optionsHTML += `
282
- <div class="filterModal__item">
283
- <label class="filterModal__itemLabel">
284
- <input type="checkbox"
285
- class="${filterType}-checkbox"
286
- value="${item.value.replace(/"/g, '&quot;')}"
287
- ${isChecked}
288
- data-count="${item.count}"
289
- style="display: block !important; width: 20px !important; height: 20px !important; margin: 0 !important; opacity: 1 !important; visibility: visible !important;">
290
- ${itemTextHTML}
291
- </label>
292
- </div>
293
- `
294
- })
295
-
296
- optionsHTML += '</div>'
297
- container.innerHTML = optionsHTML
298
-
299
- // Add body class for modal
300
- document.body.classList.add('menu-is-open')
301
-
302
- // Add close functionality
303
- const closeBtn = modal.querySelector('.js--modal-close')
304
- if (closeBtn) {
305
- closeBtn.addEventListener('click', (e) => {
306
- e.preventDefault()
307
- closeMobileFilterModal(filterType)
308
- })
309
- }
310
-
311
- // Add escape key handler
312
- const escapeHandler = (evt) => {
313
- if (evt.keyCode === 27) { // ESC key
314
- closeMobileFilterModal(filterType)
315
- }
316
- }
317
- document.addEventListener('keydown', escapeHandler)
318
-
319
- // Add click listeners to buttons
320
- const selectBtn = modal.querySelector(`.js-${filterType}-select`)
321
- const cancelBtn = modal.querySelector(`.js-${filterType}-cancel`)
322
-
323
- if (selectBtn) {
324
- selectBtn.addEventListener('click', () => {
325
- // Get all checkboxes
326
- const checkboxes = modal.querySelectorAll(`.${filterType}-checkbox`)
327
-
328
- // Get currently selected values from checkboxes
329
- const selectedValues = []
330
- checkboxes.forEach(checkbox => {
331
- if (checkbox.checked) {
332
- selectedValues.push(checkbox.value)
333
- }
334
- })
335
-
336
- // First, clear all existing refinements
337
- items.forEach(item => {
338
- if (item.isRefined) {
339
- refineFunction(item.value)
340
- }
341
- })
342
128
 
343
- // Then apply new selections
344
- selectedValues.forEach(value => {
345
- refineFunction(value)
346
- })
347
-
348
- closeMobileFilterModal(filterType)
349
- })
350
- }
351
-
352
- if (cancelBtn) {
353
- cancelBtn.addEventListener('click', () => {
354
- closeMobileFilterModal(filterType)
355
- })
356
-
357
- // Add hover effects for secondary button
358
- cancelBtn.addEventListener('mouseenter', () => {
359
- cancelBtn.style.background = '#e5e5e5'
360
- })
361
-
362
- cancelBtn.addEventListener('mouseleave', () => {
363
- cancelBtn.style.background = '#F5F5F5'
364
- })
365
- }
366
- }
367
- }
368
-
369
- function closeMobileFilterModal(filterType) {
370
- const modal = document.querySelector(`.${filterType}__mobile_modal`)
371
- if (modal) {
372
- modal.remove()
373
- document.body.classList.remove('menu-is-open')
374
- }
375
- }
376
-
377
- // Generic click handler for filter dropdowns
378
- function handleFilterDropdownClick(event, filterType, displayName, items, refine) {
379
- let windowWidth = 1024; // Default to desktop
380
-
381
- try {
382
- if (typeof window !== 'undefined' && window) {
383
- windowWidth = window.innerWidth || 1024;
384
- }
385
- } catch (e) {
386
- // Window not available, use default
387
- }
388
-
389
- // Show modal on mobile only
390
- const shouldShowModal = windowWidth <= 767;
391
-
392
- if (shouldShowModal) {
393
- event.preventDefault();
394
- event.stopPropagation();
395
- createMobileFilterModal(filterType, displayName, items, refine);
396
- return false;
397
- } else {
398
- // console.log('Desktop - allowing normal dropdown');
399
- }
400
- }
401
-
402
- function createMobileDatePickerModal() {
403
- const menu = document.querySelector('.dp__menu')
404
- if (!menu) {
405
- setTimeout(() => {
406
- const retryMenu = document.querySelector('.dp__menu')
407
- if (retryMenu) {
408
- createMobileDatePickerModal()
409
- }
410
- }, 50)
411
- return
412
- }
413
-
414
- // Create modal structure using reusable template function
415
- const modalHTML = createMultiSelectModalHTML({
416
- modalId: 'dp__mobile_modal',
417
- title: 'Select Date Range',
418
- description: 'Choose your date range',
419
- cancelText: 'Cancel',
420
- selectText: 'Select',
421
- cancelButtonClass: 'js-datepicker-cancel',
422
- selectButtonClass: 'js-datepicker-select',
423
- contentContainerClass: 'eventsModal__datePickerContainer'
424
- })
425
-
426
- // Insert modal into body
427
- document.body.insertAdjacentHTML('beforeend', modalHTML)
428
-
429
- // Move the date picker menu into the modal
430
- const modal = document.querySelector('.dp__mobile_modal')
431
- const datePickerContainer = modal.querySelector('.eventsModal__datePickerContainer')
432
-
433
- if (modal && datePickerContainer && menu) {
434
- datePickerContainer.appendChild(menu)
435
-
436
- // Hide the original VueDatePicker buttons since we have modal buttons
437
- const originalActionRow = menu.querySelector('.dp__action_row')
438
- if (originalActionRow) {
439
- originalActionRow.style.display = 'none'
440
- }
441
-
442
- // Add body class for modal
443
- document.body.classList.add('menu-is-open')
444
-
445
- // Track selected range manually
446
- let selectedRange = null
447
-
448
- // Add close functionality
449
- const closeBtn = modal.querySelector('.js--modal-close')
450
- if (closeBtn) {
451
- closeBtn.addEventListener('click', (e) => {
452
- e.preventDefault()
453
- closeMobileDatePickerModal()
454
- })
455
- }
456
-
457
- // Add escape key handler
458
- document.addEventListener('keydown', handleModalEscape)
459
-
460
- // Add click listeners to our custom buttons
461
- const selectBtn = modal.querySelector('.js-datepicker-select')
462
- const cancelBtn = modal.querySelector('.js-datepicker-cancel')
463
-
464
- if (selectBtn) {
465
- selectBtn.addEventListener('click', () => {
466
-
467
- // Get currently selected dates from the calendar
468
- const selectedDates = menu.querySelectorAll('.dp__calendar_item[aria-selected="true"]')
469
-
470
- if (selectedDates.length >= 2) {
471
- const dateRange = []
472
- selectedDates.forEach((dateEl) => {
473
- const dateId = dateEl.id // Format: "2025-11-01"
474
- if (dateId) {
475
- const dateObj = new Date(dateId + 'T00:00:00')
476
- dateRange.push(dateObj)
477
- }
478
- })
479
-
480
- // Sort dates to ensure proper range
481
- dateRange.sort((a, b) => a - b)
482
-
483
- // Set the date range
484
- setDate(dateRange)
485
- displayDate.value = [...dateRange]
486
-
487
- } else if (selectedDates.length === 1) {
488
- // Single date selected - create a single day range
489
- const dateId = selectedDates[0].id
490
- const singleDate = new Date(dateId + 'T00:00:00')
491
- const endDate = new Date(singleDate)
492
- endDate.setDate(endDate.getDate() + 1)
493
-
494
- const range = [singleDate, endDate]
495
- setDate(range)
496
- displayDate.value = [...range]
497
-
498
- } else {
499
- console.log('No dates selected')
500
- }
501
-
502
- // Close modal
503
- setTimeout(() => {
504
- closeMobileDatePickerModal()
505
- }, 100)
506
- })
507
- }
508
-
509
- if (cancelBtn) {
510
- cancelBtn.addEventListener('click', () => {
511
- closeMobileDatePickerModal()
512
- })
513
-
514
- // Add hover effects for secondary button
515
- cancelBtn.addEventListener('mouseenter', () => {
516
- cancelBtn.style.background = '#e5e5e5'
517
- })
518
-
519
- cancelBtn.addEventListener('mouseleave', () => {
520
- cancelBtn.style.background = '#F5F5F5'
521
- })
522
- }
523
-
524
- modalCreated = true
525
- } else {
526
- console.error('Failed to find modal elements after creation')
527
- }
528
- }
529
-
530
- function closeMobileDatePickerModal() {
531
-
532
- // Close the VueDatePicker directly
533
- if (datepicker.value && datepicker.value.closeMenu) {
534
- datepicker.value.closeMenu()
535
- }
536
-
537
- const modal = document.querySelector('.dp__mobile_modal')
538
- if (modal) {
539
- modal.remove()
540
- document.body.classList.remove('menu-is-open')
541
- document.removeEventListener('keydown', handleModalEscape)
542
- modalCreated = false // Reset the flag
543
- } else {
544
- console.log('No modal found to remove')
545
- }
546
- // Always reset the flag, even if no modal was found
547
- modalCreated = false
548
- }
549
-
550
- function handleModalEscape(evt) {
551
- if (evt.keyCode === 27) { // ESC key
552
- closeMobileDatePickerModal()
553
- if (datepicker.value && datepicker.value.closeMenu) {
554
- datepicker.value.closeMenu()
555
- }
556
- }
557
- }
558
129
  </script>
559
130
 
560
131
  <template>
@@ -567,16 +138,16 @@
567
138
  :addl-refinements="addlRefinements"
568
139
  :sort-field="'performance_date'"
569
140
  :query-by-fields="'works, works.composers, works.title, works.artists, season, venue, event_types, notes, event_title, ensembles, conductors'"
570
- :include-fields="'works, works.composers, works.title, works.artists, season, venue, event_types, notes, event_title, ensembles, conductors, id, performance_date'"
141
+ :include-fields="'works, works.composers, works.title, works.artists, season, venue, event_types, notes, event_title, ensembles, conductors, id, performance_date, program_book_link, media, bso_audio_id'"
571
142
  search-placeholder="Search by composer, works, conductor, orchestra, and more"
572
143
  results-title="Performances"
573
144
  >
574
- <template v-slot="{ items, showByWorks }">
145
+ <template v-slot="{ items }">
575
146
  <div class="eventsSearch__resultsGrid" v-if="items && items.length">
576
147
  <!-- Header Row -->
577
148
  <div class="eventsSearch__resultCell -header -first">Date/Season/Title</div>
578
149
  <div class="eventsSearch__resultCell -header">Venue</div>
579
- <div class="eventsSearch__resultCell -header">Orchestra</div>
150
+ <div class="eventsSearch__resultCell -header">Ensemble</div>
580
151
  <div class="eventsSearch__resultCell -header">Conductor</div>
581
152
  <div class="eventsSearch__resultCell -header">Composer/Work</div>
582
153
  <div class="eventsSearch__resultCell -header">Artist/Role</div>
@@ -624,31 +195,7 @@
624
195
  ${item.works.length > 1 ? '-details' : '' }
625
196
  ${index % 2 == 0 ? '-even' : '-odd'}
626
197
  ${(index + 1 == items.length && (i + 1 == item.works.length || i == 5)) ? '-last -lastMobile' : ''}`">
627
- <div class="eventsSearch__perfDetails">
628
- <svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
629
- <circle cx="9" cy="9" r="9" fill="#01ABE6"/>
630
- <path d="M5 9H13" stroke="white" stroke-width="1.5"/>
631
- <path d="M9 5L13 9L9 13" stroke="white" stroke-width="1.5"/>
632
- </svg>
633
- <a class="detailsLink" :href="`/details?performanceId=${item.id}`">Details</a>
634
- </div>
635
- <div v-if="item.program_link" class="eventsSearch__perfDetails">
636
- <svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
637
- <circle cx="9" cy="9" r="8.25" fill="white" stroke="#01ABE6" stroke-width="1.5"/>
638
- <path d="M6.22876 12.0713L11.8854 6.41469" stroke="#01ABE6" stroke-width="1.5"/>
639
- <path d="M6.22864 6.41382L11.8854 6.41395L11.8855 12.0707" stroke="#01ABE6" stroke-width="1.5"/>
640
- </svg>
641
- <a class="detailsLink" :href="item.program_link">Program</a>
642
- </div>
643
- <div v-if="item.media && item.media.includes('audio')" class="eventsSearch__perfDetails">
644
- <svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
645
- <circle cx="9" cy="9" r="8.25" fill="white" stroke="#01ABE6" stroke-width="1.5"/>
646
- <path d="M9.81086 5L6.92983 7.30483H4.625V10.7621H6.92983L9.81086 13.0669V5Z" fill="white" stroke="#01ABE6" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
647
- <path d="M12.4268 6.99219C12.9669 7.53246 13.2703 8.26513 13.2703 9.02908C13.2703 9.79302 12.9669 10.5257 12.4268 11.066" fill="white"/>
648
- <path d="M12.4268 6.99219C12.9669 7.53246 13.2703 8.26513 13.2703 9.02908C13.2703 9.79302 12.9669 10.5257 12.4268 11.066" stroke="#01ABE6" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
649
- </svg>
650
- <a class="detailsLink">Audio</a>
651
- </div>
198
+ <event-links :item="item" />
652
199
  </div>
653
200
  </template>
654
201
  <!-- Additional event rows -->
@@ -689,34 +236,10 @@
689
236
  </div>
690
237
  <div :class="`eventsSearch__resultCell -detailsMobile
691
238
  ${index % 2 == 0 ? '-even' : '-odd'}
692
- ${(index + 1 == items.length && (i + 1 == item.works.length || i == 5)) ? '-last -lastMobile' : ''}
239
+ ${(index + 1 == items.length && (i + 1 == item.works.length || i == 4)) ? '-last -lastMobile' : ''}
693
240
  ${ !(i + 1 == item.works.length || i == 5) ? '-empty' : ''}`">
694
241
  <template v-if="(i + 1 == item.works.length || i == 5)">
695
- <div class="eventsSearch__perfDetails -detailsMobile">
696
- <svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
697
- <circle cx="9" cy="9" r="9" fill="#01ABE6"/>
698
- <path d="M5 9H13" stroke="white" stroke-width="1.5"/>
699
- <path d="M9 5L13 9L9 13" stroke="white" stroke-width="1.5"/>
700
- </svg>
701
- <a class="detailsLink" :href="`/details?performanceId=${item.id}`">Details</a>
702
- </div>
703
- <div v-if="item.program_link" class="eventsSearch__perfDetails -detailsMobile">
704
- <svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
705
- <circle cx="9" cy="9" r="8.25" fill="white" stroke="#01ABE6" stroke-width="1.5"/>
706
- <path d="M6.22876 12.0713L11.8854 6.41469" stroke="#01ABE6" stroke-width="1.5"/>
707
- <path d="M6.22864 6.41382L11.8854 6.41395L11.8855 12.0707" stroke="#01ABE6" stroke-width="1.5"/>
708
- </svg>
709
- <a class="detailsLink" :href="item.program_link">Program</a>
710
- </div>
711
- <div v-if="item.media && item.media.includes('audio')" class="eventsSearch__perfDetails -detailsMobile">
712
- <svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
713
- <circle cx="9" cy="9" r="8.25" fill="white" stroke="#01ABE6" stroke-width="1.5"/>
714
- <path d="M9.81086 5L6.92983 7.30483H4.625V10.7621H6.92983L9.81086 13.0669V5Z" fill="white" stroke="#01ABE6" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
715
- <path d="M12.4268 6.99219C12.9669 7.53246 13.2703 8.26513 13.2703 9.02908C13.2703 9.79302 12.9669 10.5257 12.4268 11.066" fill="white"/>
716
- <path d="M12.4268 6.99219C12.9669 7.53246 13.2703 8.26513 13.2703 9.02908C13.2703 9.79302 12.9669 10.5257 12.4268 11.066" stroke="#01ABE6" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
717
- </svg>
718
- <a class="detailsLink">Audio</a>
719
- </div>
242
+ <event-links :item="item" extra-classes="-detailsMobile" />
720
243
  </template>
721
244
  </div>
722
245
  </template>
@@ -725,11 +248,20 @@
725
248
  <div :class="`eventsSearch__resultCell -empty ${index % 2 == 0 ? '-even' : '-odd'} ${(index + 1 == items.length && (i + 1 == item.works.length || i == 5)) ? '-last' : ''}`"></div>
726
249
  <div :class="`eventsSearch__resultCell -empty ${index % 2 == 0 ? '-even' : '-odd'} ${(index + 1 == items.length && (i + 1 == item.works.length || i == 5)) ? '-last' : ''}`"></div>
727
250
  <div :class="`eventsSearch__resultCell -empty ${index % 2 == 0 ? '-even' : '-odd'} ${(index + 1 == items.length && (i + 1 == item.works.length || i == 5)) ? '-last' : ''}`"></div>
251
+ <div :class="`eventsSearch__resultCell -work -right -emptyMobile ${index % 2 == 0 ? '-even' : '-odd'} ${(index + 1 == items.length && (i + 1 == item.works.length || i == 5)) ? '-last' : ''}`"></div>
728
252
  <div :class="`eventsSearch__resultCell ${index % 2 == 0 ? '-even' : '-odd'} ${(index + 1 == items.length && (i + 1 == item.works.length || i == 5)) ? '-last' : ''}`">
729
253
  <a :href="`/details?performanceId=${item.id}`">More...</a>
730
254
  </div>
255
+
731
256
  <div :class="`eventsSearch__resultCell -empty ${index % 2 == 0 ? '-even' : '-odd'} ${(index + 1 == items.length && (i + 1 == item.works.length || i == 5)) ? '-last' : ''}`"></div>
732
- <div :class="`eventsSearch__resultCell -empty ${index % 2 == 0 ? '-even' : '-odd'} ${(index + 1 == items.length && (i + 1 == item.works.length || i == 5)) ? '-last' : ''}`"></div>
257
+ <div :class="`eventsSearch__resultCell -detailsMobile
258
+ ${index % 2 == 0 ? '-even' : '-odd'}
259
+ ${(index + 1 == items.length && (i + 1 == item.works.length || i == 5)) ? '-last -lastMobile' : ''}
260
+ ${ !(i + 1 == item.works.length || i == 5) ? '-empty' : ''}`">
261
+ <template v-if="(i + 1 == item.works.length || i == 5)">
262
+ <event-links :item="item" extra-classes="-detailsMobile" />
263
+ </template>
264
+ </div>
733
265
  </template>
734
266
  </template>
735
267
  <template v-if="!item.works || item.works.length == 0">
@@ -754,31 +286,8 @@
754
286
  <div :class="`eventsSearch__resultCell ${index % 2 == 0 ? '-even' : '-odd'} ${(index + 1 == items.length) ? '-last' : ''}`"><span class="mobileHeader">Composer/Work</span></div>
755
287
  <div :class="`eventsSearch__resultCell -hideMobile ${index % 2 == 0 ? '-even' : '-odd'} ${(index + 1 == items.length) ? '-last' : ''}`"></div>
756
288
  <div :class="`eventsSearch__resultCell -detailsMobile ${index % 2 == 0 ? '-even' : '-odd'} ${(index + 1 == items.length) ? '-last' : ''}`">
757
- <div class="eventsSearch__perfDetails">
758
- <svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
759
- <circle cx="9" cy="9" r="9" fill="#01ABE6"/>
760
- <path d="M5 9H13" stroke="white" stroke-width="1.5"/>
761
- <path d="M9 5L13 9L9 13" stroke="white" stroke-width="1.5"/>
762
- </svg>
763
- <a class="detailsLink" :href="`/details?performanceId=${item.id}`">Details</a>
764
- </div>
765
- <div v-if="item.program_link" class="eventsSearch__perfDetails">
766
- <svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
767
- <circle cx="9" cy="9" r="8.25" fill="white" stroke="#01ABE6" stroke-width="1.5"/>
768
- <path d="M6.22876 12.0713L11.8854 6.41469" stroke="#01ABE6" stroke-width="1.5"/>
769
- <path d="M6.22864 6.41382L11.8854 6.41395L11.8855 12.0707" stroke="#01ABE6" stroke-width="1.5"/>
770
- </svg>
771
- <a class="detailsLink" :href="item.program_link">Program</a>
772
- </div>
773
- <div v-if="item.media && item.media.includes('audio')" class="eventsSearch__perfDetails">
774
- <svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
775
- <circle cx="9" cy="9" r="8.25" fill="white" stroke="#01ABE6" stroke-width="1.5"/>
776
- <path d="M9.81086 5L6.92983 7.30483H4.625V10.7621H6.92983L9.81086 13.0669V5Z" fill="white" stroke="#01ABE6" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
777
- <path d="M12.4268 6.99219C12.9669 7.53246 13.2703 8.26513 13.2703 9.02908C13.2703 9.79302 12.9669 10.5257 12.4268 11.066" fill="white"/>
778
- <path d="M12.4268 6.99219C12.9669 7.53246 13.2703 8.26513 13.2703 9.02908C13.2703 9.79302 12.9669 10.5257 12.4268 11.066" stroke="#01ABE6" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
779
- </svg>
780
- <a class="detailsLink">Audio</a>
781
- </div>
289
+ <event-links :item="item" />
290
+
782
291
  </div>
783
292
  </template>
784
293
  </template>
@@ -824,7 +333,7 @@
824
333
  {{ item.composer }} / {{ item.work_title }}
825
334
  </a>
826
335
  </div>
827
- <div :class="`eventsSearch__resultCell ${index % 2 == 0 ? '-even' : '-odd'} ${(index + 1 == items.length) ? '-last' : ''}`">
336
+ <div :class="`eventsSearch__resultCell -perfs ${index % 2 == 0 ? '-even' : '-odd'} ${(index + 1 == items.length) ? '-last' : ''}`">
828
337
  <span class="mobileHeader"># of Performances</span>
829
338
  <a :href="createURL([{ facet: 'works.artists.name', value: item.artist_name},
830
339
  { facet: 'works.artists.role', value: item.artist_role },
@@ -881,7 +390,7 @@
881
390
  </template>
882
391
  </a>
883
392
  </div>
884
- <div :class="`eventsSearch__resultCell ${index % 2 == 0 ? '-even' : '-odd'} ${(index + 1 == items.length) ? '-last' : ''}`">
393
+ <div :class="`eventsSearch__resultCell -perfs ${index % 2 == 0 ? '-even' : '-odd'} ${(index + 1 == items.length) ? '-last' : ''}`">
885
394
  <span class="mobileHeader"># of times Performed</span>
886
395
  <a :href="createURL([{ facet: 'works.composers', value: item.composers},
887
396
  { facet: 'works.creators', value: item.creators},