@asd20/ui 3.6.1 → 3.8.0
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/package.json
CHANGED
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
<div class="asd20-site-search__viewport">
|
|
10
10
|
<div class="asd20-site-search__options">
|
|
11
11
|
<!-- Ask a Question -->
|
|
12
|
-
<div
|
|
12
|
+
<div
|
|
13
|
+
class="asd20-site-search__field asd20-site-search__field--question"
|
|
14
|
+
>
|
|
13
15
|
<!-- <label class="asd20-site-search__label">Ask a question</label> -->
|
|
14
16
|
<div class="asd20-site-search__question-row">
|
|
15
17
|
<asd20-search-field
|
|
@@ -17,19 +19,24 @@
|
|
|
17
19
|
ref="question"
|
|
18
20
|
v-model="questionText"
|
|
19
21
|
@keyup.enter.stop.prevent="onAskQuestion"
|
|
20
|
-
placeholder="Ask a question
|
|
22
|
+
placeholder="Ask a question (preview)"
|
|
21
23
|
/>
|
|
22
|
-
<button
|
|
23
|
-
type="button"
|
|
24
|
+
<asd20-button
|
|
24
25
|
class="asd20-site-search__ask-button"
|
|
25
|
-
|
|
26
|
+
label="Ask"
|
|
27
|
+
size="md"
|
|
28
|
+
bordered
|
|
29
|
+
@click.native="onAskQuestion"
|
|
26
30
|
:disabled="!questionText || searchingAi"
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
31
|
+
/>
|
|
32
|
+
<asd20-loader
|
|
33
|
+
v-if="searchingAi"
|
|
34
|
+
size="sm"
|
|
35
|
+
class="asd20-site-search__inline-loader"
|
|
36
|
+
/>
|
|
30
37
|
</div>
|
|
31
38
|
</div>
|
|
32
|
-
<hr/>
|
|
39
|
+
<hr />
|
|
33
40
|
|
|
34
41
|
<!-- Search by Keyword -->
|
|
35
42
|
<div class="asd20-site-search__field asd20-site-search__field--keyword">
|
|
@@ -38,7 +45,7 @@
|
|
|
38
45
|
idTag="-sitewide"
|
|
39
46
|
ref="search"
|
|
40
47
|
v-model="keywords"
|
|
41
|
-
placeholder="Search by keyword
|
|
48
|
+
placeholder="Search by keyword"
|
|
42
49
|
/>
|
|
43
50
|
</div>
|
|
44
51
|
|
|
@@ -56,39 +63,56 @@
|
|
|
56
63
|
|
|
57
64
|
<asd20-viewport class="asd20-site-search__results" scrollable>
|
|
58
65
|
<asd20-notification
|
|
59
|
-
v-if="
|
|
66
|
+
v-if="
|
|
67
|
+
!keywords &&
|
|
68
|
+
!questionText &&
|
|
69
|
+
!searchingFiles &&
|
|
70
|
+
!searchingPages &&
|
|
71
|
+
!searchingAi
|
|
72
|
+
"
|
|
60
73
|
title="Ask a Question, or Search by Keyword"
|
|
61
|
-
description="Use the top
|
|
74
|
+
description="Use the top input for plain-language questions, then press 'Ask'; or search by keyword in the lower input box."
|
|
62
75
|
/>
|
|
76
|
+
<div class="disclaimer">
|
|
77
|
+
<p>
|
|
78
|
+
AI generated answers can have errors. Please contact our
|
|
79
|
+
<a href="www.asd20.org/help-desk">Help Desk</a> if you need
|
|
80
|
+
additional information.
|
|
81
|
+
</p>
|
|
82
|
+
</div>
|
|
63
83
|
|
|
64
|
-
<!--
|
|
65
|
-
<div v-show="currentTab === '
|
|
66
|
-
<!-- AI Answer Section -->
|
|
84
|
+
<!-- Answers tab -->
|
|
85
|
+
<div v-show="currentTab === 'Answers'" scrollable>
|
|
67
86
|
<div v-if="aiAnswer" class="asd20-site-search__ai-result">
|
|
68
87
|
<h3>Answer</h3>
|
|
69
88
|
<div class="asd20-site-search__ai-answer" v-html="aiAnswer" />
|
|
70
89
|
|
|
71
|
-
<div
|
|
90
|
+
<div
|
|
91
|
+
v-if="aiUiSources.length"
|
|
92
|
+
class="asd20-site-search__ai-sources"
|
|
93
|
+
>
|
|
72
94
|
<h4>Sources</h4>
|
|
73
95
|
<ul>
|
|
74
96
|
<li v-for="src in aiUiSources" :key="src.url || src.id">
|
|
75
|
-
<strong>Content from: {{ src.hostLabel }}</strong
|
|
97
|
+
<strong>Content from: {{ src.hostLabel }}</strong
|
|
98
|
+
><br />
|
|
76
99
|
<a :href="src.url" target="_blank" rel="noreferrer">
|
|
77
100
|
{{ src.title }}
|
|
78
101
|
</a>
|
|
79
|
-
<p v-if="src.snippet" class="asd20-site-search__ai-snippet">
|
|
80
|
-
{{ src.snippet }}
|
|
81
|
-
</p>
|
|
82
102
|
</li>
|
|
83
103
|
</ul>
|
|
84
104
|
</div>
|
|
85
105
|
|
|
86
106
|
<asd20-loader v-if="searchingAi" size="lg" />
|
|
87
107
|
</div>
|
|
108
|
+
<asd20-loader v-if="searchingAi && !aiAnswer" size="lg" />
|
|
109
|
+
</div>
|
|
88
110
|
|
|
111
|
+
<!-- Pages tab -->
|
|
112
|
+
<div v-show="currentTab === 'Pages'" scrollable>
|
|
89
113
|
<!-- No results -->
|
|
90
114
|
<asd20-notification
|
|
91
|
-
v-if="keywords && _pages.length === 0 && !searchingPages
|
|
115
|
+
v-if="keywords && _pages.length === 0 && !searchingPages"
|
|
92
116
|
title="No Pages Found"
|
|
93
117
|
description="Try using different keywords."
|
|
94
118
|
/>
|
|
@@ -122,7 +146,9 @@
|
|
|
122
146
|
|
|
123
147
|
<div class="asd20-site-search__suggested" v-if="_groups.length > 0">
|
|
124
148
|
<h3>
|
|
125
|
-
{{
|
|
149
|
+
{{
|
|
150
|
+
_groups.length > 1 ? 'Suggested Contacts:' : 'Suggested Contact:'
|
|
151
|
+
}}
|
|
126
152
|
</h3>
|
|
127
153
|
<asd20-list-item
|
|
128
154
|
v-for="g in _groups"
|
|
@@ -164,6 +190,7 @@ import Asd20Loader from '../../molecules/Asd20Loader'
|
|
|
164
190
|
import Asd20Modal from '../../molecules/Asd20Modal'
|
|
165
191
|
import Asd20DepartmentContactCard from '../../molecules/Asd20DepartmentContactCard'
|
|
166
192
|
import Asd20Checkbox from '../../atoms/Asd20Checkbox'
|
|
193
|
+
import Asd20Button from '../../atoms/Asd20Button'
|
|
167
194
|
|
|
168
195
|
// Helpers
|
|
169
196
|
import debounce from 'lodash/debounce'
|
|
@@ -224,16 +251,20 @@ export default {
|
|
|
224
251
|
Asd20Modal,
|
|
225
252
|
Asd20DepartmentContactCard,
|
|
226
253
|
Asd20Checkbox,
|
|
254
|
+
Asd20Button,
|
|
227
255
|
},
|
|
228
256
|
|
|
229
257
|
props: {
|
|
230
258
|
active: { type: Boolean, default: false },
|
|
231
|
-
organization: {
|
|
259
|
+
organization: {
|
|
260
|
+
type: Object,
|
|
261
|
+
default: () => ({ title: 'Academy District 20' }),
|
|
262
|
+
},
|
|
232
263
|
organizationOptions: { type: Array, default: () => [] },
|
|
233
264
|
},
|
|
234
265
|
|
|
235
266
|
data: () => ({
|
|
236
|
-
currentTab: '
|
|
267
|
+
currentTab: 'Answers',
|
|
237
268
|
// keyword search (classic)
|
|
238
269
|
keywords: '',
|
|
239
270
|
// question input (AI)
|
|
@@ -279,6 +310,11 @@ export default {
|
|
|
279
310
|
},
|
|
280
311
|
tabs() {
|
|
281
312
|
return [
|
|
313
|
+
{
|
|
314
|
+
label: 'Answers',
|
|
315
|
+
badge: this.aiAnswer ? 1 : 0,
|
|
316
|
+
active: this.currentTab === 'Answers',
|
|
317
|
+
},
|
|
282
318
|
{
|
|
283
319
|
label: 'Pages',
|
|
284
320
|
badge: this._pages.length,
|
|
@@ -357,6 +393,9 @@ export default {
|
|
|
357
393
|
this.searchingPages = true
|
|
358
394
|
await this.$store.dispatch('search/queryPages', this.keywords)
|
|
359
395
|
this.searchingPages = false
|
|
396
|
+
if (this.keywords && this.keywords.trim()) {
|
|
397
|
+
this.currentTab = 'Pages'
|
|
398
|
+
}
|
|
360
399
|
}
|
|
361
400
|
},
|
|
362
401
|
|
|
@@ -436,6 +475,7 @@ export default {
|
|
|
436
475
|
if (this.searchingAi) return
|
|
437
476
|
|
|
438
477
|
this.searchingAi = true
|
|
478
|
+
this.currentTab = 'Answers'
|
|
439
479
|
try {
|
|
440
480
|
const organizationId =
|
|
441
481
|
this.organization && this.organization.id
|
|
@@ -456,9 +496,9 @@ export default {
|
|
|
456
496
|
}
|
|
457
497
|
)
|
|
458
498
|
|
|
459
|
-
this.aiAnswer = answer
|
|
499
|
+
this.aiAnswer = this.sanitizeAnswer(answer)
|
|
460
500
|
this.aiSources = sources || []
|
|
461
|
-
this.currentTab = '
|
|
501
|
+
this.currentTab = 'Answers'
|
|
462
502
|
} catch (e) {
|
|
463
503
|
console.error('AI search failed', e)
|
|
464
504
|
this.aiAnswer = null
|
|
@@ -467,6 +507,63 @@ export default {
|
|
|
467
507
|
this.searchingAi = false
|
|
468
508
|
}
|
|
469
509
|
},
|
|
510
|
+
|
|
511
|
+
sanitizeAnswer(rawHtml) {
|
|
512
|
+
if (!rawHtml) return ''
|
|
513
|
+
// If no DOM (e.g., SSR), return raw; client will re-run and sanitize.
|
|
514
|
+
if (typeof window === 'undefined' || !window.document) return rawHtml
|
|
515
|
+
|
|
516
|
+
const allowedTags = new Set(['P', 'UL', 'OL', 'LI', 'STRONG', 'EM', 'A'])
|
|
517
|
+
const allowedAttrs = {
|
|
518
|
+
A: ['href'],
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
const container = window.document.createElement('div')
|
|
522
|
+
container.innerHTML = rawHtml
|
|
523
|
+
|
|
524
|
+
const cleanNode = node => {
|
|
525
|
+
const children = Array.from(node.childNodes)
|
|
526
|
+
children.forEach(child => {
|
|
527
|
+
if (child.nodeType === 1) {
|
|
528
|
+
const tag = child.tagName.toUpperCase()
|
|
529
|
+
if (!allowedTags.has(tag)) {
|
|
530
|
+
while (child.firstChild) {
|
|
531
|
+
node.insertBefore(child.firstChild, child)
|
|
532
|
+
}
|
|
533
|
+
node.removeChild(child)
|
|
534
|
+
return
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
const allowed = allowedAttrs[tag] || []
|
|
538
|
+
Array.from(child.attributes).forEach(attr => {
|
|
539
|
+
const name = attr.name.toLowerCase()
|
|
540
|
+
if (!allowed.includes(attr.name)) {
|
|
541
|
+
child.removeAttribute(attr.name)
|
|
542
|
+
} else if (tag === 'A' && name === 'href') {
|
|
543
|
+
const href = attr.value || ''
|
|
544
|
+
const safe =
|
|
545
|
+
href.startsWith('#') ||
|
|
546
|
+
href.startsWith('mailto:') ||
|
|
547
|
+
/^https?:\/\//i.test(href)
|
|
548
|
+
if (!safe) {
|
|
549
|
+
child.removeAttribute('href')
|
|
550
|
+
} else {
|
|
551
|
+
child.setAttribute('rel', 'noreferrer noopener')
|
|
552
|
+
child.setAttribute('target', '_blank')
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
})
|
|
556
|
+
|
|
557
|
+
cleanNode(child)
|
|
558
|
+
} else if (child.nodeType === 8) {
|
|
559
|
+
node.removeChild(child)
|
|
560
|
+
}
|
|
561
|
+
})
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
cleanNode(container)
|
|
565
|
+
return container.innerHTML
|
|
566
|
+
},
|
|
470
567
|
},
|
|
471
568
|
}
|
|
472
569
|
</script>
|
|
@@ -502,7 +599,7 @@ export default {
|
|
|
502
599
|
}
|
|
503
600
|
|
|
504
601
|
&__options {
|
|
505
|
-
padding: space(0.
|
|
602
|
+
padding: space(0.125);
|
|
506
603
|
display: flex;
|
|
507
604
|
flex-direction: column;
|
|
508
605
|
}
|
|
@@ -532,24 +629,25 @@ export default {
|
|
|
532
629
|
|
|
533
630
|
&__ask-button {
|
|
534
631
|
flex: 0 0 auto;
|
|
535
|
-
|
|
536
|
-
border-radius: 999px;
|
|
537
|
-
border: 1px solid var(--color__primary, #22506a);
|
|
538
|
-
background: var(--color__primary, #22506a);
|
|
539
|
-
color: #fff;
|
|
540
|
-
font-weight: 600;
|
|
541
|
-
font-size: 0.9rem;
|
|
542
|
-
cursor: pointer;
|
|
543
|
-
white-space: nowrap;
|
|
632
|
+
}
|
|
544
633
|
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
cursor: default;
|
|
548
|
-
}
|
|
634
|
+
&__inline-loader {
|
|
635
|
+
align-self: center;
|
|
549
636
|
}
|
|
550
637
|
|
|
551
638
|
&__results {
|
|
552
639
|
flex-grow: 1;
|
|
640
|
+
.disclaimer {
|
|
641
|
+
padding: space(0.5);
|
|
642
|
+
font-size: 0.85rem;
|
|
643
|
+
background: var(--website-page__alternate-background-t25);
|
|
644
|
+
border-bottom: 1px solid var(--color__tertiary);
|
|
645
|
+
|
|
646
|
+
a {
|
|
647
|
+
color: var(--color__primary);
|
|
648
|
+
text-decoration: underline;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
553
651
|
}
|
|
554
652
|
|
|
555
653
|
&__ai-result {
|
|
@@ -608,6 +706,9 @@ export default {
|
|
|
608
706
|
flex-grow: 0;
|
|
609
707
|
}
|
|
610
708
|
}
|
|
709
|
+
hr {
|
|
710
|
+
padding: 0.2px;
|
|
711
|
+
}
|
|
611
712
|
}
|
|
612
713
|
|
|
613
714
|
@media (min-width: 768px) {
|