@asd20/ui-next 2.2.2 → 2.3.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.
@@ -167,6 +167,7 @@ import Asd20NotificationGroup from '../../../components/organisms/Asd20Notificat
167
167
 
168
168
  // Mixins
169
169
  import pageTemplateMixin from '../../../mixins/pageTemplateMixin'
170
+ import { bindAccessibilityModalTrigger } from '../../../helpers/injectedContentInteractions'
170
171
 
171
172
  export default {
172
173
  name: 'Asd20WayfindingAccessibilityTemplate',
@@ -209,17 +210,18 @@ export default {
209
210
  },
210
211
  },
211
212
  mounted() {
212
- this.$nextTick(() => {
213
- const btn = document.querySelector('[data-trigger-accessibility-modal]')
214
- if (btn) {
215
- btn.addEventListener('click', () => {
216
- this.modalVisible = true
217
- this.$nextTick(() => {
218
- this.modalOpen = true
219
- })
213
+ this.removeAccessibilityModalTrigger = bindAccessibilityModalTrigger(
214
+ this.$el,
215
+ () => {
216
+ this.modalVisible = true
217
+ this.$nextTick(() => {
218
+ this.modalOpen = true
220
219
  })
221
220
  }
222
- })
221
+ )
222
+ },
223
+ beforeUnmount() {
224
+ this.removeAccessibilityModalTrigger?.()
223
225
  },
224
226
  }
225
227
  </script>
@@ -1,3 +1,5 @@
1
+ import { normalizeBaseUrl, resolveRuntimeConfigValue } from './runtimeConfig'
2
+
1
3
  /**
2
4
  * Creates a Azure Search payload
3
5
  *
@@ -10,6 +12,11 @@ export default function buildFileSearchUrl({
10
12
  owners = [],
11
13
  tags = [],
12
14
  top = 5,
15
+ runtimeConfig = null,
16
+ searchEndpoint = '',
17
+ endpoint = '',
18
+ searchKey = '',
19
+ apiVersion = '',
13
20
  }) {
14
21
  let query = keywords || '*'
15
22
 
@@ -35,21 +42,42 @@ export default function buildFileSearchUrl({
35
42
  search: query,
36
43
  $filter: filters.join(' and '),
37
44
  $top: top,
38
- 'api-version':
39
- process.env.VUE_APP_AZURE_SEARCH_API_VERSION ||
40
- process.env.GRIDSOME_AZURE_SEARCH_API_VERSION,
41
- 'api-key':
42
- process.env.VUE_APP_AZURE_SEARCH_KEY ||
43
- process.env.GRIDSOME_AZURE_SEARCH_KEY,
45
+ 'api-version': resolveRuntimeConfigValue({
46
+ explicitValue: apiVersion,
47
+ runtimeConfig,
48
+ runtimeConfigKeys: ['azureSearchVersion'],
49
+ envKeys: [
50
+ 'VUE_APP_AZURE_SEARCH_API_VERSION',
51
+ 'GRIDSOME_AZURE_SEARCH_API_VERSION',
52
+ ],
53
+ }),
54
+ 'api-key': resolveRuntimeConfigValue({
55
+ explicitValue: searchKey,
56
+ runtimeConfig,
57
+ runtimeConfigKeys: ['azureSearchKey'],
58
+ envKeys: ['VUE_APP_AZURE_SEARCH_KEY', 'GRIDSOME_AZURE_SEARCH_KEY'],
59
+ }),
44
60
  }
45
61
 
46
62
  const params = Object.entries(payload)
47
63
  .map(([key, val]) => `${key}=${val}`)
48
64
  .join('&')
49
65
 
50
- let url = `${process.env.VUE_APP_AZURE_SEARCH_ENDPOINT ||
51
- process.env
52
- .GRIDSOME_AZURE_SEARCH_ENDPOINT}/indexes/files-index/docs?${params}`
66
+ const resolvedSearchEndpoint = resolveRuntimeConfigValue({
67
+ explicitValue: searchEndpoint || endpoint,
68
+ runtimeConfig,
69
+ runtimeConfigKeys: ['azureSearchEndpoint'],
70
+ envKeys: ['VUE_APP_AZURE_SEARCH_ENDPOINT', 'GRIDSOME_AZURE_SEARCH_ENDPOINT'],
71
+ })
72
+ if (!resolvedSearchEndpoint || !payload['api-version'] || !payload['api-key']) {
73
+ throw new Error(
74
+ 'Azure Search configuration is required for buildFileSearchUrl.'
75
+ )
76
+ }
77
+
78
+ let url = `${normalizeBaseUrl(
79
+ resolvedSearchEndpoint
80
+ )}/indexes/files-index/docs?${params}`
53
81
 
54
82
  return url
55
83
  }
@@ -0,0 +1,102 @@
1
+ function findClosestInRoot(event, selector, root) {
2
+ if (!(event.target instanceof Element)) {
3
+ return null
4
+ }
5
+
6
+ const match = event.target.closest(selector)
7
+
8
+ if (!match || !root.contains(match)) {
9
+ return null
10
+ }
11
+
12
+ return match
13
+ }
14
+
15
+ export function bindInjectedAccordionInteractions(root) {
16
+ if (!root?.addEventListener) {
17
+ return () => {}
18
+ }
19
+
20
+ const onClick = event => {
21
+ const button = findClosestInRoot(event, '.accordion-button', root)
22
+
23
+ if (!button) {
24
+ return
25
+ }
26
+
27
+ const contentId = button.getAttribute('aria-controls')
28
+ const content = contentId ? document.getElementById(contentId) : null
29
+
30
+ if (!content) {
31
+ return
32
+ }
33
+
34
+ const expanded = button.getAttribute('aria-expanded') === 'true'
35
+ const toggleIcon = button.querySelector('.toggle-icon')
36
+
37
+ button.setAttribute('aria-expanded', String(!expanded))
38
+
39
+ if (toggleIcon) {
40
+ toggleIcon.textContent = expanded ? '+' : '-'
41
+ }
42
+
43
+ if (!expanded) {
44
+ content.style.display = 'block'
45
+ content.style.maxHeight = '0'
46
+ content.style.padding = '0 var(--space-1, 1em)'
47
+
48
+ requestAnimationFrame(() => {
49
+ content.style.transition = 'max-height 0.4s ease, padding 0.4s ease'
50
+ content.style.maxHeight = `${content.scrollHeight}px`
51
+ content.style.padding = 'var(--space-1, 1em)'
52
+ })
53
+ return
54
+ }
55
+
56
+ content.style.maxHeight = `${content.scrollHeight}px`
57
+
58
+ requestAnimationFrame(() => {
59
+ content.style.transition = 'max-height 0.4s ease, padding 0.4s ease'
60
+ content.style.maxHeight = '0'
61
+ content.style.padding = '0 var(--space-1, 1em)'
62
+ })
63
+
64
+ setTimeout(() => {
65
+ if (content.style.maxHeight === '0px' || content.style.maxHeight === '0') {
66
+ content.style.display = 'none'
67
+ }
68
+ }, 400)
69
+ }
70
+
71
+ root.addEventListener('click', onClick)
72
+
73
+ return () => {
74
+ root.removeEventListener('click', onClick)
75
+ }
76
+ }
77
+
78
+ export function bindAccessibilityModalTrigger(root, onOpen) {
79
+ if (!root?.addEventListener) {
80
+ return () => {}
81
+ }
82
+
83
+ const onClick = event => {
84
+ const trigger = findClosestInRoot(
85
+ event,
86
+ '[data-trigger-accessibility-modal]',
87
+ root
88
+ )
89
+
90
+ if (!trigger) {
91
+ return
92
+ }
93
+
94
+ onOpen()
95
+ }
96
+
97
+ root.addEventListener('click', onClick)
98
+
99
+ return () => {
100
+ root.removeEventListener('click', onClick)
101
+ }
102
+ }
@@ -1,5 +1,7 @@
1
1
  import _get from 'lodash/get'
2
2
 
3
+ import { resolveRuntimeConfigValue } from './runtimeConfig'
4
+
3
5
  function getConfigurationValueOrDefault({
4
6
  configurations,
5
7
  category,
@@ -29,7 +31,10 @@ function getConfigurationValueOrDefault({
29
31
  * @param {*} queryResult s
30
32
  * @returns An object of common prop values for page templates
31
33
  */
32
- export default function mapPageQueryResultToPageTemplateProps(queryResult) {
34
+ export default function mapPageQueryResultToPageTemplateProps(
35
+ queryResult,
36
+ options = {}
37
+ ) {
33
38
  // Ensure query results is defined with required properties
34
39
  if (
35
40
  !queryResult.page ||
@@ -189,6 +194,14 @@ export default function mapPageQueryResultToPageTemplateProps(queryResult) {
189
194
  }
190
195
  }
191
196
 
197
+ const defaultOrganizationId =
198
+ resolveRuntimeConfigValue({
199
+ explicitValue: options.organizationId,
200
+ runtimeConfig: options.runtimeConfig,
201
+ runtimeConfigKeys: ['organizationId'],
202
+ envKeys: ['GRIDSOME_ORGANIZATION_ID', 'VUE_APP_ORGANIZATION_ID'],
203
+ }) || '26eaf390-d8ab-11e9-a3a8-5de5bba4f125'
204
+
192
205
  // Output page props
193
206
  let pageProps = {}
194
207
  try {
@@ -217,11 +230,8 @@ export default function mapPageQueryResultToPageTemplateProps(queryResult) {
217
230
  .map(l => l.link)
218
231
  .filter(l => l.type === 'Link'),
219
232
  organization: pageData.ownerOrganization || {
220
- id:
221
- process.env.GRIDSOME_ORGANIZATION_ID ||
222
- process.env.VUE_APP_ORGANIZATION_ID ||
223
- '26eaf390-d8ab-11e9-a3a8-5de5bba4f125', // District Org Id
224
- title: 'Academy District 20',
233
+ id: defaultOrganizationId, // District Org Id
234
+ title: options.organizationTitle || 'Academy District 20',
225
235
  },
226
236
 
227
237
  // TODO: add groups / department / committee table to Hasura
@@ -1,10 +1,9 @@
1
1
  import arraySearch from './arraySearch'
2
2
  import axios from 'axios'
3
+ import { normalizeBaseUrl, resolveRuntimeConfigValue } from './runtimeConfig'
3
4
  // import departments from '../data/departments.json'
4
5
 
5
- const API_ENDPOINT =
6
- process.env.VUE_APP_API_ENDPOINT || process.env.GRIDSOME_API_ENDPOINT
7
- let cachedDepartments = []
6
+ const cachedDepartmentsByEndpoint = new Map()
8
7
 
9
8
  /**
10
9
  * Retrieve departments from API
@@ -13,15 +12,20 @@ let cachedDepartments = []
13
12
  * @param {*} { commit }
14
13
  * @returns
15
14
  */
16
- async function getDepartments() {
17
- if (cachedDepartments.length === 0) {
18
- let { data: departments } = await axios.get(
19
- `${API_ENDPOINT}/api/departments`
15
+ async function getDepartments(apiEndpoint) {
16
+ const normalizedApiEndpoint = normalizeBaseUrl(apiEndpoint)
17
+ if (!normalizedApiEndpoint) {
18
+ throw new Error('apiEndpoint is required for queryDepartments.')
19
+ }
20
+
21
+ if (!cachedDepartmentsByEndpoint.has(normalizedApiEndpoint)) {
22
+ const { data: departments } = await axios.get(
23
+ `${normalizedApiEndpoint}/api/departments`
20
24
  )
21
- console.log(departments)
22
- cachedDepartments = departments
25
+ cachedDepartmentsByEndpoint.set(normalizedApiEndpoint, departments)
23
26
  }
24
- return cachedDepartments
27
+
28
+ return cachedDepartmentsByEndpoint.get(normalizedApiEndpoint) || []
25
29
  }
26
30
 
27
31
  /**
@@ -29,10 +33,22 @@ async function getDepartments() {
29
33
  *
30
34
  * @param {*} {keywords = '', top = 0}
31
35
  */
32
- export default async function queryDepartments({ keywords = '', top = 0 }) {
36
+ export default async function queryDepartments({
37
+ keywords = '',
38
+ top = 0,
39
+ apiEndpoint = '',
40
+ runtimeConfig = null,
41
+ }) {
33
42
  if (!keywords) return []
34
43
 
35
- let departments = await getDepartments()
44
+ const resolvedApiEndpoint = resolveRuntimeConfigValue({
45
+ explicitValue: apiEndpoint,
46
+ runtimeConfig,
47
+ runtimeConfigKeys: ['apiEndpoint'],
48
+ envKeys: ['VUE_APP_API_ENDPOINT', 'GRIDSOME_API_ENDPOINT'],
49
+ })
50
+
51
+ let departments = await getDepartments(resolvedApiEndpoint)
36
52
 
37
53
  let results = departments.filter(d => {
38
54
  let options = d.searchTerms
@@ -1,5 +1,8 @@
1
1
  import axios from 'axios'
2
2
 
3
+ import { hasSearchProxyRuntime, resolveSearchProxyUrl } from './searchProxyUrl'
4
+ import { normalizeBaseUrl, resolveRuntimeConfigValue } from './runtimeConfig'
5
+
3
6
  /**
4
7
  * Creates a Azure Search payload
5
8
  *
@@ -73,18 +76,41 @@ export default async function queryEvents({
73
76
  limit = 5,
74
77
  requireKeywords = false,
75
78
  indexName = 'events-index',
79
+ requestUrl = '',
80
+ runtimeConfig = null,
81
+ searchKey = '',
82
+ apiVersion = '',
83
+ searchEndpoint = '',
84
+ endpoint = '',
76
85
  }) {
77
- const env = {
78
- searchKey:
79
- process.env.VUE_APP_AZURE_SEARCH_KEY ||
80
- process.env.GRIDSOME_AZURE_SEARCH_KEY,
81
- apiVersion:
82
- process.env.VUE_APP_AZURE_SEARCH_API_VERSION ||
83
- process.env.GRIDSOME_AZURE_SEARCH_API_VERSION,
84
- endpoint:
85
- process.env.VUE_APP_AZURE_SEARCH_ENDPOINT ||
86
- process.env.GRIDSOME_AZURE_SEARCH_ENDPOINT,
87
- }
86
+ const resolvedSearchKey = resolveRuntimeConfigValue({
87
+ explicitValue: searchKey,
88
+ runtimeConfig,
89
+ runtimeConfigKeys: ['azureSearchKey'],
90
+ envKeys: ['VUE_APP_AZURE_SEARCH_KEY', 'GRIDSOME_AZURE_SEARCH_KEY'],
91
+ })
92
+ const resolvedApiVersion = resolveRuntimeConfigValue({
93
+ explicitValue: apiVersion,
94
+ runtimeConfig,
95
+ runtimeConfigKeys: ['azureSearchVersion'],
96
+ envKeys: [
97
+ 'VUE_APP_AZURE_SEARCH_API_VERSION',
98
+ 'GRIDSOME_AZURE_SEARCH_API_VERSION',
99
+ ],
100
+ })
101
+ const resolvedSearchEndpoint = resolveRuntimeConfigValue({
102
+ explicitValue: searchEndpoint || endpoint,
103
+ runtimeConfig,
104
+ runtimeConfigKeys: ['azureSearchEndpoint'],
105
+ envKeys: ['VUE_APP_AZURE_SEARCH_ENDPOINT', 'GRIDSOME_AZURE_SEARCH_ENDPOINT'],
106
+ })
107
+ const resolvedRequestUrl =
108
+ typeof requestUrl === 'string' && requestUrl.trim()
109
+ ? requestUrl.trim()
110
+ : !resolvedSearchEndpoint &&
111
+ hasSearchProxyRuntime(runtimeConfig)
112
+ ? resolveSearchProxyUrl('events', runtimeConfig)
113
+ : ''
88
114
 
89
115
  if (!keywords && requireKeywords)
90
116
  return {
@@ -92,27 +118,49 @@ export default async function queryEvents({
92
118
  facets: [],
93
119
  }
94
120
 
95
- let result = await axios({
96
- method: 'post',
97
- headers: {
98
- 'api-key': env.searchKey,
99
- },
100
- params: {
101
- 'api-version': env.apiVersion,
102
- },
103
- url: `${env.endpoint}/indexes/${indexName}/docs/search`,
104
- data: Object.assign(
105
- {},
106
- searchPayload({
107
- startDate,
108
- endDate,
109
- keywords,
110
- calendarIds,
111
- enableFuzzySearch: false,
112
- limit,
121
+ if (
122
+ !resolvedRequestUrl &&
123
+ (!resolvedSearchEndpoint || !resolvedSearchKey || !resolvedApiVersion)
124
+ ) {
125
+ throw new Error('Azure Search or proxy configuration is required for queryEvents.')
126
+ }
127
+
128
+ const payload = {
129
+ startDate,
130
+ endDate,
131
+ keywords,
132
+ calendarIds,
133
+ limit,
134
+ indexName,
135
+ }
136
+
137
+ const result = resolvedRequestUrl
138
+ ? await axios({
139
+ method: 'post',
140
+ url: resolvedRequestUrl,
141
+ data: payload,
142
+ })
143
+ : await axios({
144
+ method: 'post',
145
+ headers: {
146
+ 'api-key': resolvedSearchKey,
147
+ },
148
+ params: {
149
+ 'api-version': resolvedApiVersion,
150
+ },
151
+ url: `${normalizeBaseUrl(resolvedSearchEndpoint)}/indexes/${indexName}/docs/search`,
152
+ data: Object.assign(
153
+ {},
154
+ searchPayload({
155
+ startDate,
156
+ endDate,
157
+ keywords,
158
+ calendarIds,
159
+ enableFuzzySearch: false,
160
+ limit,
161
+ })
162
+ ),
113
163
  })
114
- ),
115
- })
116
164
 
117
165
  const { data } = result
118
166
 
@@ -1,5 +1,8 @@
1
1
  import axios from 'axios'
2
2
 
3
+ import { hasSearchProxyRuntime, resolveSearchProxyUrl } from './searchProxyUrl'
4
+ import { normalizeBaseUrl, resolveRuntimeConfigValue } from './runtimeConfig'
5
+
3
6
  /**
4
7
  * Creates a Azure Search payload
5
8
  *
@@ -64,8 +67,17 @@ export default async function queryFiles({
64
67
  keywords = '',
65
68
  categories = [],
66
69
  owners = [],
70
+ organizations = [],
67
71
  top = 5,
72
+ limit = 0,
68
73
  requireKeywords = false,
74
+ indexName = 'files-index',
75
+ requestUrl = '',
76
+ runtimeConfig = null,
77
+ searchKey = '',
78
+ apiVersion = '',
79
+ searchEndpoint = '',
80
+ endpoint = '',
69
81
  }) {
70
82
  if (!keywords && requireKeywords)
71
83
  return {
@@ -73,32 +85,81 @@ export default async function queryFiles({
73
85
  facets: [],
74
86
  }
75
87
 
76
- let { data } = await axios({
77
- method: 'post',
78
- headers: {
79
- 'api-key':
80
- process.env.VUE_APP_AZURE_SEARCH_KEY ||
81
- process.env.GRIDSOME_AZURE_SEARCH_KEY,
82
- },
83
- params: {
84
- 'api-version':
85
- process.env.VUE_APP_AZURE_SEARCH_API_VERSION ||
86
- process.env.GRIDSOME_AZURE_SEARCH_API_VERSION,
87
- },
88
- url: `${process.env.VUE_APP_AZURE_SEARCH_ENDPOINT ||
89
- process.env
90
- .GRIDSOME_AZURE_SEARCH_ENDPOINT}/indexes/files-index/docs/search`,
91
- data: Object.assign(
92
- {},
93
- searchPayload({
94
- keywords,
95
- categories,
96
- owners,
97
- enableFuzzySearch: true,
98
- }),
99
- { top }
100
- ),
88
+ const resolvedLimit = Number.isFinite(Number(limit)) && Number(limit) > 0
89
+ ? Number(limit)
90
+ : top
91
+ const resolvedSearchKey = resolveRuntimeConfigValue({
92
+ explicitValue: searchKey,
93
+ runtimeConfig,
94
+ runtimeConfigKeys: ['azureSearchKey'],
95
+ envKeys: ['VUE_APP_AZURE_SEARCH_KEY', 'GRIDSOME_AZURE_SEARCH_KEY'],
96
+ })
97
+ const resolvedApiVersion = resolveRuntimeConfigValue({
98
+ explicitValue: apiVersion,
99
+ runtimeConfig,
100
+ runtimeConfigKeys: ['azureSearchVersion'],
101
+ envKeys: [
102
+ 'VUE_APP_AZURE_SEARCH_API_VERSION',
103
+ 'GRIDSOME_AZURE_SEARCH_API_VERSION',
104
+ ],
101
105
  })
106
+ const resolvedSearchEndpoint = resolveRuntimeConfigValue({
107
+ explicitValue: searchEndpoint || endpoint,
108
+ runtimeConfig,
109
+ runtimeConfigKeys: ['azureSearchEndpoint'],
110
+ envKeys: ['VUE_APP_AZURE_SEARCH_ENDPOINT', 'GRIDSOME_AZURE_SEARCH_ENDPOINT'],
111
+ })
112
+ const resolvedRequestUrl =
113
+ typeof requestUrl === 'string' && requestUrl.trim()
114
+ ? requestUrl.trim()
115
+ : !resolvedSearchEndpoint &&
116
+ hasSearchProxyRuntime(runtimeConfig)
117
+ ? resolveSearchProxyUrl('files', runtimeConfig)
118
+ : ''
119
+
120
+ if (
121
+ !resolvedRequestUrl &&
122
+ (!resolvedSearchEndpoint || !resolvedSearchKey || !resolvedApiVersion)
123
+ ) {
124
+ throw new Error('Azure Search or proxy configuration is required for queryFiles.')
125
+ }
126
+
127
+ const payload = {
128
+ keywords,
129
+ categories,
130
+ owners,
131
+ organizations,
132
+ limit: resolvedLimit,
133
+ indexName,
134
+ }
135
+
136
+ const { data } = resolvedRequestUrl
137
+ ? await axios({
138
+ method: 'post',
139
+ url: resolvedRequestUrl,
140
+ data: payload,
141
+ })
142
+ : await axios({
143
+ method: 'post',
144
+ headers: {
145
+ 'api-key': resolvedSearchKey,
146
+ },
147
+ params: {
148
+ 'api-version': resolvedApiVersion,
149
+ },
150
+ url: `${normalizeBaseUrl(resolvedSearchEndpoint)}/indexes/${indexName}/docs/search`,
151
+ data: Object.assign(
152
+ {},
153
+ searchPayload({
154
+ keywords,
155
+ categories,
156
+ owners,
157
+ organizations,
158
+ enableFuzzySearch: true,
159
+ }),
160
+ { top: resolvedLimit }
161
+ ),
162
+ })
102
163
 
103
164
  // Return results
104
165
  return {