@asd20/ui-next 2.28.1 → 2.29.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.
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.29.1](https://github.com/academydistrict20/asd20-ui-next/compare/ui-next-v2.29.0...ui-next-v2.29.1) (2026-10-05)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* carry ai search verified supporting pages into related pages and pages tab ([46253e0](https://github.com/academydistrict20/asd20-ui-next/commit/46253e05ef064c99dbe3151cd093f6c938e2e9f6))
|
|
9
|
+
|
|
10
|
+
# [2.29.0](https://github.com/academydistrict20/asd20-ui-next/compare/ui-next-v2.28.1...ui-next-v2.29.0) (2026-10-02)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
* **handbook:** add expandable H2 subtopics to index ([ac65b77](https://github.com/academydistrict20/asd20-ui-next/commit/ac65b778538ea118855cb876d712909ae038a679))
|
|
16
|
+
|
|
3
17
|
## [2.28.1](https://github.com/academydistrict20/asd20-ui-next/compare/ui-next-v2.28.0...ui-next-v2.28.1) (2026-10-02)
|
|
4
18
|
|
|
5
19
|
|
package/package.json
CHANGED
|
@@ -2701,6 +2701,12 @@ export default {
|
|
|
2701
2701
|
question: this.searchQuestion || this.inputText,
|
|
2702
2702
|
}
|
|
2703
2703
|
if (options.purpose) searchInput.purpose = options.purpose
|
|
2704
|
+
if (
|
|
2705
|
+
options.purpose === 'ai-related-pages' &&
|
|
2706
|
+
options.sourcePages?.length
|
|
2707
|
+
) {
|
|
2708
|
+
searchInput.sourcePages = options.sourcePages
|
|
2709
|
+
}
|
|
2704
2710
|
|
|
2705
2711
|
this.searchingPages = true
|
|
2706
2712
|
try {
|
|
@@ -3105,6 +3111,16 @@ export default {
|
|
|
3105
3111
|
? `[Keywords: ${keywordsToUse.replace(/<[^>]*>/g, '')}]`
|
|
3106
3112
|
: ''
|
|
3107
3113
|
|
|
3114
|
+
const sourcePages = Array.isArray(sources)
|
|
3115
|
+
? sources
|
|
3116
|
+
.filter(
|
|
3117
|
+
source =>
|
|
3118
|
+
typeof source?.url === 'string' &&
|
|
3119
|
+
typeof source?.chunkId === 'string'
|
|
3120
|
+
)
|
|
3121
|
+
.slice(0, 8)
|
|
3122
|
+
.map(({ url, chunkId }) => ({ url, chunkId }))
|
|
3123
|
+
: []
|
|
3108
3124
|
const assistantTurnKeywords = safeKeywords || keywordsToUse || ''
|
|
3109
3125
|
const assistantTurn = {
|
|
3110
3126
|
id: `a-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
@@ -3115,7 +3131,8 @@ export default {
|
|
|
3115
3131
|
feedbackValue: null,
|
|
3116
3132
|
feedbackSubmitting: false,
|
|
3117
3133
|
relatedPages: [],
|
|
3118
|
-
relatedPagesStatus:
|
|
3134
|
+
relatedPagesStatus:
|
|
3135
|
+
keywordsToUse || sourcePages.length ? 'pending' : 'complete',
|
|
3119
3136
|
}
|
|
3120
3137
|
assistantTurnId = assistantTurn.id
|
|
3121
3138
|
this.aiTranscript = [...this.aiTranscript, assistantTurn]
|
|
@@ -3124,13 +3141,14 @@ export default {
|
|
|
3124
3141
|
this.scrollTurnToTop(this.anchoredUserTurnId, { defer: true })
|
|
3125
3142
|
}
|
|
3126
3143
|
|
|
3127
|
-
if (keywordsToUse) {
|
|
3144
|
+
if (keywordsToUse || sourcePages.length) {
|
|
3128
3145
|
this.keywordsFromAi = true
|
|
3129
3146
|
this.skipKeywordWatcher = true
|
|
3130
3147
|
this.keywords = keywordsToUse
|
|
3131
3148
|
await this.searchPages({
|
|
3132
3149
|
keepAnswersTab: true,
|
|
3133
3150
|
purpose: 'ai-related-pages',
|
|
3151
|
+
sourcePages,
|
|
3134
3152
|
runId,
|
|
3135
3153
|
})
|
|
3136
3154
|
if (!this.isCurrentSearchRun(runId)) return
|
|
@@ -78,13 +78,50 @@
|
|
|
78
78
|
v-for="message in topicMessages"
|
|
79
79
|
:key="message.id"
|
|
80
80
|
>
|
|
81
|
-
<
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
81
|
+
<div class="asd20-template-handbook__topic-row">
|
|
82
|
+
<button
|
|
83
|
+
class="asd20-template-handbook__topic-button"
|
|
84
|
+
type="button"
|
|
85
|
+
:aria-current="activeMessage && activeMessage.id === message.id ? 'true' : null"
|
|
86
|
+
@click="selectTopic(message.id)"
|
|
87
|
+
>
|
|
88
|
+
{{ message.heading || message.title }}
|
|
89
|
+
</button>
|
|
90
|
+
<button
|
|
91
|
+
v-if="subtopicsFor(message.id).length"
|
|
92
|
+
class="asd20-template-handbook__expand-button"
|
|
93
|
+
type="button"
|
|
94
|
+
:aria-expanded="expandedTopicId === message.id"
|
|
95
|
+
:aria-controls="`handbook-subtopics-${message.id}`"
|
|
96
|
+
:aria-label="`${expandedTopicId === message.id ? 'Hide' : 'Show'} sections in ${message.heading || message.title}`"
|
|
97
|
+
@click="toggleTopic(message.id)"
|
|
98
|
+
>
|
|
99
|
+
<asd20-icon
|
|
100
|
+
name="chevron"
|
|
101
|
+
size="sm"
|
|
102
|
+
focusable="false"
|
|
103
|
+
/>
|
|
104
|
+
</button>
|
|
105
|
+
</div>
|
|
106
|
+
<ol
|
|
107
|
+
v-if="subtopicsFor(message.id).length"
|
|
108
|
+
v-show="expandedTopicId === message.id"
|
|
109
|
+
:id="`handbook-subtopics-${message.id}`"
|
|
110
|
+
class="asd20-template-handbook__subtopics"
|
|
85
111
|
>
|
|
86
|
-
|
|
87
|
-
|
|
112
|
+
<li
|
|
113
|
+
v-for="subtopic in subtopicsFor(message.id)"
|
|
114
|
+
:key="subtopic.index"
|
|
115
|
+
>
|
|
116
|
+
<button
|
|
117
|
+
class="asd20-template-handbook__subtopic-button"
|
|
118
|
+
type="button"
|
|
119
|
+
@click="selectSubtopic(message.id, subtopic.index)"
|
|
120
|
+
>
|
|
121
|
+
{{ subtopic.label }}
|
|
122
|
+
</button>
|
|
123
|
+
</li>
|
|
124
|
+
</ol>
|
|
88
125
|
</li>
|
|
89
126
|
</ol>
|
|
90
127
|
</nav>
|
|
@@ -92,6 +129,7 @@
|
|
|
92
129
|
<article
|
|
93
130
|
v-for="message in topicMessages"
|
|
94
131
|
:key="message.id"
|
|
132
|
+
:data-message-id="message.id"
|
|
95
133
|
class="asd20-template-handbook__topic"
|
|
96
134
|
:class="{ 'is-active': activeMessage && activeMessage.id === message.id }"
|
|
97
135
|
>
|
|
@@ -133,6 +171,7 @@
|
|
|
133
171
|
<script>
|
|
134
172
|
import Asd20SkipTo from '../../../components/atoms/Asd20SkipTo'
|
|
135
173
|
import Asd20Messaging from '../../../components/atoms/Asd20Messaging'
|
|
174
|
+
import Asd20Icon from '../../../components/atoms/Asd20Icon'
|
|
136
175
|
import Asd20PageHeader from '../../../components/organisms/Asd20PageHeader'
|
|
137
176
|
import Asd20SiteNavigation from '../../../components/organisms/Asd20SiteNavigation'
|
|
138
177
|
import Asd20NotificationGroup from '../../../components/organisms/Asd20NotificationGroup'
|
|
@@ -145,6 +184,7 @@ export default {
|
|
|
145
184
|
components: {
|
|
146
185
|
Asd20SkipTo,
|
|
147
186
|
Asd20Messaging,
|
|
187
|
+
Asd20Icon,
|
|
148
188
|
Asd20PageHeader,
|
|
149
189
|
Asd20SiteNavigation,
|
|
150
190
|
Asd20NotificationGroup,
|
|
@@ -154,6 +194,8 @@ export default {
|
|
|
154
194
|
mixins: [pageTemplateMixin],
|
|
155
195
|
data: () => ({
|
|
156
196
|
selectedMessageId: null,
|
|
197
|
+
expandedTopicId: null,
|
|
198
|
+
subtopicsByMessageId: {},
|
|
157
199
|
topicsOpen: false,
|
|
158
200
|
}),
|
|
159
201
|
computed: {
|
|
@@ -169,13 +211,61 @@ export default {
|
|
|
169
211
|
|| null
|
|
170
212
|
},
|
|
171
213
|
},
|
|
214
|
+
watch: {
|
|
215
|
+
messages: {
|
|
216
|
+
handler() {
|
|
217
|
+
this.$nextTick(() => this.refreshSubtopics())
|
|
218
|
+
},
|
|
219
|
+
deep: true,
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
mounted() {
|
|
223
|
+
this.$nextTick(() => this.refreshSubtopics())
|
|
224
|
+
},
|
|
172
225
|
methods: {
|
|
173
226
|
topicSummary(message) {
|
|
174
227
|
return message.shortDescription || message.summary || ''
|
|
175
228
|
},
|
|
176
|
-
|
|
229
|
+
topicArticle(messageId) {
|
|
230
|
+
return Array.from(this.$el.querySelectorAll('.asd20-template-handbook__topic'))
|
|
231
|
+
.find(article => article.dataset.messageId === String(messageId))
|
|
232
|
+
},
|
|
233
|
+
subtopicsFor(messageId) {
|
|
234
|
+
return this.subtopicsByMessageId[messageId] || []
|
|
235
|
+
},
|
|
236
|
+
refreshSubtopics() {
|
|
237
|
+
const nextSubtopics = {}
|
|
238
|
+
this.topicMessages.forEach(message => {
|
|
239
|
+
const article = this.topicArticle(message.id)
|
|
240
|
+
if (!article) return
|
|
241
|
+
const headings = Array.from(article.querySelectorAll('.asd20-rich-body-content h2'))
|
|
242
|
+
const subtopics = headings
|
|
243
|
+
.map((heading, index) => ({ index, label: heading.textContent.trim() }))
|
|
244
|
+
.filter(subtopic => subtopic.label)
|
|
245
|
+
if (subtopics.length) nextSubtopics[message.id] = subtopics
|
|
246
|
+
})
|
|
247
|
+
if (JSON.stringify(nextSubtopics) !== JSON.stringify(this.subtopicsByMessageId)) {
|
|
248
|
+
this.subtopicsByMessageId = nextSubtopics
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
toggleTopic(messageId) {
|
|
252
|
+
this.expandedTopicId = this.expandedTopicId === messageId ? null : messageId
|
|
253
|
+
},
|
|
254
|
+
async selectTopic(messageId) {
|
|
177
255
|
this.selectedMessageId = messageId
|
|
256
|
+
this.expandedTopicId = this.subtopicsFor(messageId).length ? messageId : null
|
|
178
257
|
this.topicsOpen = false
|
|
258
|
+
await this.$nextTick()
|
|
259
|
+
this.topicArticle(messageId)?.scrollIntoView({ block: 'start' })
|
|
260
|
+
},
|
|
261
|
+
async selectSubtopic(messageId, headingIndex) {
|
|
262
|
+
this.selectedMessageId = messageId
|
|
263
|
+
this.expandedTopicId = messageId
|
|
264
|
+
this.topicsOpen = false
|
|
265
|
+
await this.$nextTick()
|
|
266
|
+
const article = this.topicArticle(messageId)
|
|
267
|
+
const headings = article?.querySelectorAll('.asd20-rich-body-content h2')
|
|
268
|
+
headings?.[headingIndex]?.scrollIntoView({ block: 'start' })
|
|
179
269
|
},
|
|
180
270
|
},
|
|
181
271
|
}
|
|
@@ -227,8 +317,49 @@ export default {
|
|
|
227
317
|
padding: 0;
|
|
228
318
|
}
|
|
229
319
|
|
|
230
|
-
&__index ol.is-open
|
|
320
|
+
&__index ol.is-open,
|
|
321
|
+
&__index ol.is-open ol.asd20-template-handbook__subtopics {
|
|
322
|
+
display: block;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
&__subtopics {
|
|
326
|
+
padding-left: space(0.75) !important;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
&__topic-row {
|
|
330
|
+
display: flex;
|
|
331
|
+
align-items: stretch;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
&__topic-button {
|
|
335
|
+
flex: 1 1 auto;
|
|
336
|
+
min-width: 0;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
&__expand-button {
|
|
340
|
+
display: grid;
|
|
341
|
+
place-items: center;
|
|
342
|
+
flex: 0 0 44px;
|
|
343
|
+
min-height: 44px;
|
|
344
|
+
padding: 0;
|
|
345
|
+
border: 0;
|
|
346
|
+
background: transparent;
|
|
347
|
+
color: inherit;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
&__expand-button .asd20-icon {
|
|
231
351
|
display: block;
|
|
352
|
+
color: inherit;
|
|
353
|
+
transform: rotate(90deg);
|
|
354
|
+
transition: transform 180ms ease-in-out;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
&__expand-button[aria-expanded='true'] .asd20-icon {
|
|
358
|
+
transform: rotate(180deg);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
&__subtopic-button {
|
|
362
|
+
font-size: 0.9em !important;
|
|
232
363
|
}
|
|
233
364
|
|
|
234
365
|
&__index-heading {
|
|
@@ -241,7 +372,8 @@ export default {
|
|
|
241
372
|
cursor: pointer;
|
|
242
373
|
}
|
|
243
374
|
|
|
244
|
-
&
|
|
375
|
+
&__topic-button,
|
|
376
|
+
&__subtopic-button {
|
|
245
377
|
display: block;
|
|
246
378
|
width: 100%;
|
|
247
379
|
min-height: 44px;
|
|
@@ -276,6 +408,11 @@ export default {
|
|
|
276
408
|
|
|
277
409
|
&__topic {
|
|
278
410
|
min-width: 0;
|
|
411
|
+
scroll-margin-top: space(2);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
&__topic .asd20-rich-body-content h2 {
|
|
415
|
+
scroll-margin-top: space(2);
|
|
279
416
|
}
|
|
280
417
|
|
|
281
418
|
&__topic:not(.is-active) {
|
|
@@ -287,8 +424,8 @@ export default {
|
|
|
287
424
|
.asd20-template-handbook {
|
|
288
425
|
&__layout {
|
|
289
426
|
grid-template-columns: minmax(12rem, 16rem) minmax(0, 1fr);
|
|
290
|
-
gap: space(
|
|
291
|
-
padding: space(
|
|
427
|
+
gap: space(2);
|
|
428
|
+
padding: space(2);
|
|
292
429
|
}
|
|
293
430
|
|
|
294
431
|
&__index {
|
|
@@ -314,6 +451,17 @@ export default {
|
|
|
314
451
|
.asd20-template-handbook {
|
|
315
452
|
margin-left: space(3);
|
|
316
453
|
margin-top: 0;
|
|
454
|
+
&__layout {
|
|
455
|
+
grid-template-columns: minmax(12rem, 22rem) minmax(0, 1fr);
|
|
456
|
+
gap: space(3);
|
|
457
|
+
padding: space(3);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
@media (prefers-reduced-motion: reduce) {
|
|
463
|
+
.asd20-template-handbook__expand-button .asd20-icon {
|
|
464
|
+
transition: none;
|
|
317
465
|
}
|
|
318
466
|
}
|
|
319
467
|
|
|
@@ -3,6 +3,7 @@ import axios from 'axios'
|
|
|
3
3
|
import { hasSearchProxyRuntime, resolveSearchProxyUrl } from './searchProxyUrl'
|
|
4
4
|
import { resolveRuntimeConfigValue } from './runtimeConfig'
|
|
5
5
|
import { buildTypoTolerantSearchQuery } from './searchQuery'
|
|
6
|
+
import { mergeSupportingPages } from './supportingPages'
|
|
6
7
|
|
|
7
8
|
export const NO_PAGE_RESULTS_FALLBACKS = [
|
|
8
9
|
{
|
|
@@ -100,6 +101,8 @@ function searchPayload(state) {
|
|
|
100
101
|
*/
|
|
101
102
|
export default async function queryPages({
|
|
102
103
|
keywords = '',
|
|
104
|
+
purpose = '',
|
|
105
|
+
sourcePages = [],
|
|
103
106
|
top = 25,
|
|
104
107
|
requireKeywords = false,
|
|
105
108
|
organizations = [],
|
|
@@ -111,7 +114,11 @@ export default async function queryPages({
|
|
|
111
114
|
apiVersion = '',
|
|
112
115
|
searchVersion = '',
|
|
113
116
|
}) {
|
|
114
|
-
|
|
117
|
+
const supportingSources =
|
|
118
|
+
purpose === 'ai-related-pages' && Array.isArray(sourcePages)
|
|
119
|
+
? sourcePages.slice(0, 8)
|
|
120
|
+
: []
|
|
121
|
+
if (!keywords && requireKeywords && !supportingSources.length)
|
|
115
122
|
return {
|
|
116
123
|
pages: [],
|
|
117
124
|
facets: [],
|
|
@@ -150,16 +157,17 @@ export default async function queryPages({
|
|
|
150
157
|
const resolvedRequestUrl =
|
|
151
158
|
typeof requestUrl === 'string' && requestUrl.trim()
|
|
152
159
|
? requestUrl.trim()
|
|
153
|
-
: !resolvedSearchEndpoint &&
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
: ''
|
|
160
|
+
: !resolvedSearchEndpoint && hasSearchProxyRuntime(runtimeConfig)
|
|
161
|
+
? resolveSearchProxyUrl('pages', runtimeConfig)
|
|
162
|
+
: ''
|
|
157
163
|
|
|
158
164
|
if (
|
|
159
165
|
!resolvedRequestUrl &&
|
|
160
166
|
(!resolvedSearchEndpoint || !resolvedSearchKey || !resolvedApiVersion)
|
|
161
167
|
) {
|
|
162
|
-
throw new Error(
|
|
168
|
+
throw new Error(
|
|
169
|
+
'Azure Search or proxy configuration is required for queryPages.'
|
|
170
|
+
)
|
|
163
171
|
}
|
|
164
172
|
|
|
165
173
|
const { data } = resolvedRequestUrl
|
|
@@ -170,6 +178,9 @@ export default async function queryPages({
|
|
|
170
178
|
keywords,
|
|
171
179
|
organizations,
|
|
172
180
|
top,
|
|
181
|
+
...(supportingSources.length
|
|
182
|
+
? { sourcePages: supportingSources }
|
|
183
|
+
: {}),
|
|
173
184
|
},
|
|
174
185
|
})
|
|
175
186
|
: await axios({
|
|
@@ -194,10 +205,15 @@ export default async function queryPages({
|
|
|
194
205
|
),
|
|
195
206
|
})
|
|
196
207
|
|
|
197
|
-
const pages =
|
|
208
|
+
const pages = mergeSupportingPages(
|
|
209
|
+
Array.isArray(data.value) ? data.value : [],
|
|
210
|
+
Array.isArray(data.supportingPages) ? data.supportingPages : [],
|
|
211
|
+
supportingSources,
|
|
212
|
+
organizations
|
|
213
|
+
)
|
|
198
214
|
const resolvedPages = pages.length
|
|
199
215
|
? pages
|
|
200
|
-
: NO_PAGE_RESULTS_FALLBACKS.map(
|
|
216
|
+
: NO_PAGE_RESULTS_FALLBACKS.map(page => ({
|
|
201
217
|
...page,
|
|
202
218
|
categories: Array.isArray(page.categories) ? [...page.categories] : [],
|
|
203
219
|
}))
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export function normalizeSupportingPageUrl(value) {
|
|
2
|
+
if (typeof value !== 'string' || value.length > 2048) return ''
|
|
3
|
+
try {
|
|
4
|
+
const url = new URL(value.trim())
|
|
5
|
+
if (
|
|
6
|
+
!['http:', 'https:'].includes(url.protocol) ||
|
|
7
|
+
url.username ||
|
|
8
|
+
url.password
|
|
9
|
+
)
|
|
10
|
+
return ''
|
|
11
|
+
url.hash = ''
|
|
12
|
+
url.pathname = url.pathname.replace(/\/+$/, '') || '/'
|
|
13
|
+
return url.toString().replace(/\/$/, '')
|
|
14
|
+
} catch (_error) {
|
|
15
|
+
return ''
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function mergeSupportingPages(
|
|
20
|
+
pages = [],
|
|
21
|
+
supportingPages = [],
|
|
22
|
+
sourcePages = [],
|
|
23
|
+
organizations = []
|
|
24
|
+
) {
|
|
25
|
+
const sourceUrls = sourcePages
|
|
26
|
+
.map(source => normalizeSupportingPageUrl(source?.url))
|
|
27
|
+
.filter(Boolean)
|
|
28
|
+
const supported = supportingPages
|
|
29
|
+
.filter(
|
|
30
|
+
page =>
|
|
31
|
+
page.type === 'page' &&
|
|
32
|
+
sourceUrls.includes(normalizeSupportingPageUrl(page.url)) &&
|
|
33
|
+
(!organizations.length ||
|
|
34
|
+
organizations.includes(page.ownerOrganizationId))
|
|
35
|
+
)
|
|
36
|
+
.sort(
|
|
37
|
+
(a, b) =>
|
|
38
|
+
sourceUrls.indexOf(normalizeSupportingPageUrl(a.url)) -
|
|
39
|
+
sourceUrls.indexOf(normalizeSupportingPageUrl(b.url))
|
|
40
|
+
)
|
|
41
|
+
if (!supported.length) return pages
|
|
42
|
+
const seen = new Set()
|
|
43
|
+
return [
|
|
44
|
+
...supported.map(page => ({ ...page, isAnswerSupportingPage: true })),
|
|
45
|
+
...pages,
|
|
46
|
+
].filter(page => {
|
|
47
|
+
const key = normalizeSupportingPageUrl(page.url) || page.id
|
|
48
|
+
if (!key) return true
|
|
49
|
+
if (seen.has(key)) return false
|
|
50
|
+
seen.add(key)
|
|
51
|
+
return true
|
|
52
|
+
})
|
|
53
|
+
}
|