@asd20/ui 3.6.0 → 3.7.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
|
@@ -17,16 +17,21 @@
|
|
|
17
17
|
ref="question"
|
|
18
18
|
v-model="questionText"
|
|
19
19
|
@keyup.enter.stop.prevent="onAskQuestion"
|
|
20
|
-
placeholder="Ask a question
|
|
20
|
+
placeholder="Ask a question"
|
|
21
21
|
/>
|
|
22
|
-
<button
|
|
23
|
-
type="button"
|
|
22
|
+
<asd20-button
|
|
24
23
|
class="asd20-site-search__ask-button"
|
|
25
|
-
|
|
24
|
+
label="Ask"
|
|
25
|
+
size="md"
|
|
26
|
+
bordered
|
|
27
|
+
@click.native="onAskQuestion"
|
|
26
28
|
:disabled="!questionText || searchingAi"
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
/>
|
|
30
|
+
<asd20-loader
|
|
31
|
+
v-if="searchingAi"
|
|
32
|
+
size="sm"
|
|
33
|
+
class="asd20-site-search__inline-loader"
|
|
34
|
+
/>
|
|
30
35
|
</div>
|
|
31
36
|
</div>
|
|
32
37
|
<hr/>
|
|
@@ -38,7 +43,7 @@
|
|
|
38
43
|
idTag="-sitewide"
|
|
39
44
|
ref="search"
|
|
40
45
|
v-model="keywords"
|
|
41
|
-
placeholder="Search by keyword
|
|
46
|
+
placeholder="Search by keyword"
|
|
42
47
|
/>
|
|
43
48
|
</div>
|
|
44
49
|
|
|
@@ -56,14 +61,19 @@
|
|
|
56
61
|
|
|
57
62
|
<asd20-viewport class="asd20-site-search__results" scrollable>
|
|
58
63
|
<asd20-notification
|
|
59
|
-
v-if="
|
|
64
|
+
v-if="
|
|
65
|
+
!keywords &&
|
|
66
|
+
!questionText &&
|
|
67
|
+
!searchingFiles &&
|
|
68
|
+
!searchingPages &&
|
|
69
|
+
!searchingAi
|
|
70
|
+
"
|
|
60
71
|
title="Ask a Question, or Search by Keyword"
|
|
61
|
-
description="Use the top
|
|
72
|
+
description="Use the top input for plain-language questions, then press 'Ask'; or search by keyword in the lower input box."
|
|
62
73
|
/>
|
|
63
74
|
|
|
64
|
-
<!--
|
|
65
|
-
<div v-show="currentTab === '
|
|
66
|
-
<!-- AI Answer Section -->
|
|
75
|
+
<!-- Answers tab -->
|
|
76
|
+
<div v-show="currentTab === 'Answers'" scrollable>
|
|
67
77
|
<div v-if="aiAnswer" class="asd20-site-search__ai-result">
|
|
68
78
|
<h3>Answer</h3>
|
|
69
79
|
<div class="asd20-site-search__ai-answer" v-html="aiAnswer" />
|
|
@@ -76,19 +86,21 @@
|
|
|
76
86
|
<a :href="src.url" target="_blank" rel="noreferrer">
|
|
77
87
|
{{ src.title }}
|
|
78
88
|
</a>
|
|
79
|
-
<p v-if="src.snippet" class="asd20-site-search__ai-snippet">
|
|
80
|
-
{{ src.snippet }}
|
|
81
|
-
</p>
|
|
82
89
|
</li>
|
|
83
90
|
</ul>
|
|
84
91
|
</div>
|
|
85
92
|
|
|
86
93
|
<asd20-loader v-if="searchingAi" size="lg" />
|
|
87
94
|
</div>
|
|
95
|
+
<asd20-loader v-if="searchingAi && !aiAnswer" size="lg" />
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
<!-- Pages tab -->
|
|
99
|
+
<div v-show="currentTab === 'Pages'" scrollable>
|
|
88
100
|
|
|
89
101
|
<!-- No results -->
|
|
90
102
|
<asd20-notification
|
|
91
|
-
v-if="keywords && _pages.length === 0 && !searchingPages
|
|
103
|
+
v-if="keywords && _pages.length === 0 && !searchingPages"
|
|
92
104
|
title="No Pages Found"
|
|
93
105
|
description="Try using different keywords."
|
|
94
106
|
/>
|
|
@@ -164,6 +176,7 @@ import Asd20Loader from '../../molecules/Asd20Loader'
|
|
|
164
176
|
import Asd20Modal from '../../molecules/Asd20Modal'
|
|
165
177
|
import Asd20DepartmentContactCard from '../../molecules/Asd20DepartmentContactCard'
|
|
166
178
|
import Asd20Checkbox from '../../atoms/Asd20Checkbox'
|
|
179
|
+
import Asd20Button from '../../atoms/Asd20Button'
|
|
167
180
|
|
|
168
181
|
// Helpers
|
|
169
182
|
import debounce from 'lodash/debounce'
|
|
@@ -224,6 +237,7 @@ export default {
|
|
|
224
237
|
Asd20Modal,
|
|
225
238
|
Asd20DepartmentContactCard,
|
|
226
239
|
Asd20Checkbox,
|
|
240
|
+
Asd20Button,
|
|
227
241
|
},
|
|
228
242
|
|
|
229
243
|
props: {
|
|
@@ -233,7 +247,7 @@ export default {
|
|
|
233
247
|
},
|
|
234
248
|
|
|
235
249
|
data: () => ({
|
|
236
|
-
currentTab: '
|
|
250
|
+
currentTab: 'Answers',
|
|
237
251
|
// keyword search (classic)
|
|
238
252
|
keywords: '',
|
|
239
253
|
// question input (AI)
|
|
@@ -279,6 +293,11 @@ export default {
|
|
|
279
293
|
},
|
|
280
294
|
tabs() {
|
|
281
295
|
return [
|
|
296
|
+
{
|
|
297
|
+
label: 'Answers',
|
|
298
|
+
badge: this.aiAnswer ? 1 : 0,
|
|
299
|
+
active: this.currentTab === 'Answers',
|
|
300
|
+
},
|
|
282
301
|
{
|
|
283
302
|
label: 'Pages',
|
|
284
303
|
badge: this._pages.length,
|
|
@@ -357,6 +376,9 @@ export default {
|
|
|
357
376
|
this.searchingPages = true
|
|
358
377
|
await this.$store.dispatch('search/queryPages', this.keywords)
|
|
359
378
|
this.searchingPages = false
|
|
379
|
+
if (this.keywords && this.keywords.trim()) {
|
|
380
|
+
this.currentTab = 'Pages'
|
|
381
|
+
}
|
|
360
382
|
}
|
|
361
383
|
},
|
|
362
384
|
|
|
@@ -436,6 +458,7 @@ export default {
|
|
|
436
458
|
if (this.searchingAi) return
|
|
437
459
|
|
|
438
460
|
this.searchingAi = true
|
|
461
|
+
this.currentTab = 'Answers'
|
|
439
462
|
try {
|
|
440
463
|
const organizationId =
|
|
441
464
|
this.organization && this.organization.id
|
|
@@ -456,9 +479,9 @@ export default {
|
|
|
456
479
|
}
|
|
457
480
|
)
|
|
458
481
|
|
|
459
|
-
this.aiAnswer = answer
|
|
482
|
+
this.aiAnswer = this.sanitizeAnswer(answer)
|
|
460
483
|
this.aiSources = sources || []
|
|
461
|
-
this.currentTab = '
|
|
484
|
+
this.currentTab = 'Answers'
|
|
462
485
|
} catch (e) {
|
|
463
486
|
console.error('AI search failed', e)
|
|
464
487
|
this.aiAnswer = null
|
|
@@ -467,6 +490,71 @@ export default {
|
|
|
467
490
|
this.searchingAi = false
|
|
468
491
|
}
|
|
469
492
|
},
|
|
493
|
+
|
|
494
|
+
sanitizeAnswer(rawHtml) {
|
|
495
|
+
if (!rawHtml) return ''
|
|
496
|
+
// If no DOM (e.g., SSR), return raw; client will re-run and sanitize.
|
|
497
|
+
if (typeof window === 'undefined' || !window.document) return rawHtml
|
|
498
|
+
|
|
499
|
+
const allowedTags = new Set([
|
|
500
|
+
'P',
|
|
501
|
+
'UL',
|
|
502
|
+
'OL',
|
|
503
|
+
'LI',
|
|
504
|
+
'STRONG',
|
|
505
|
+
'EM',
|
|
506
|
+
'A',
|
|
507
|
+
])
|
|
508
|
+
const allowedAttrs = {
|
|
509
|
+
A: ['href'],
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
const container = window.document.createElement('div')
|
|
513
|
+
container.innerHTML = rawHtml
|
|
514
|
+
|
|
515
|
+
const cleanNode = (node) => {
|
|
516
|
+
const children = Array.from(node.childNodes)
|
|
517
|
+
children.forEach((child) => {
|
|
518
|
+
if (child.nodeType === 1) {
|
|
519
|
+
const tag = child.tagName.toUpperCase()
|
|
520
|
+
if (!allowedTags.has(tag)) {
|
|
521
|
+
while (child.firstChild) {
|
|
522
|
+
node.insertBefore(child.firstChild, child)
|
|
523
|
+
}
|
|
524
|
+
node.removeChild(child)
|
|
525
|
+
return
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
const allowed = allowedAttrs[tag] || []
|
|
529
|
+
Array.from(child.attributes).forEach((attr) => {
|
|
530
|
+
const name = attr.name.toLowerCase()
|
|
531
|
+
if (!allowed.includes(attr.name)) {
|
|
532
|
+
child.removeAttribute(attr.name)
|
|
533
|
+
} else if (tag === 'A' && name === 'href') {
|
|
534
|
+
const href = attr.value || ''
|
|
535
|
+
const safe =
|
|
536
|
+
href.startsWith('#') ||
|
|
537
|
+
href.startsWith('mailto:') ||
|
|
538
|
+
/^https?:\/\//i.test(href)
|
|
539
|
+
if (!safe) {
|
|
540
|
+
child.removeAttribute('href')
|
|
541
|
+
} else {
|
|
542
|
+
child.setAttribute('rel', 'noreferrer noopener')
|
|
543
|
+
child.setAttribute('target', '_blank')
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
})
|
|
547
|
+
|
|
548
|
+
cleanNode(child)
|
|
549
|
+
} else if (child.nodeType === 8) {
|
|
550
|
+
node.removeChild(child)
|
|
551
|
+
}
|
|
552
|
+
})
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
cleanNode(container)
|
|
556
|
+
return container.innerHTML
|
|
557
|
+
},
|
|
470
558
|
},
|
|
471
559
|
}
|
|
472
560
|
</script>
|
|
@@ -502,7 +590,7 @@ export default {
|
|
|
502
590
|
}
|
|
503
591
|
|
|
504
592
|
&__options {
|
|
505
|
-
padding: space(0.
|
|
593
|
+
padding: space(0.125);
|
|
506
594
|
display: flex;
|
|
507
595
|
flex-direction: column;
|
|
508
596
|
}
|
|
@@ -532,20 +620,10 @@ export default {
|
|
|
532
620
|
|
|
533
621
|
&__ask-button {
|
|
534
622
|
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;
|
|
623
|
+
}
|
|
544
624
|
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
cursor: default;
|
|
548
|
-
}
|
|
625
|
+
&__inline-loader {
|
|
626
|
+
align-self: center;
|
|
549
627
|
}
|
|
550
628
|
|
|
551
629
|
&__results {
|
|
@@ -608,6 +686,9 @@ export default {
|
|
|
608
686
|
flex-grow: 0;
|
|
609
687
|
}
|
|
610
688
|
}
|
|
689
|
+
hr {
|
|
690
|
+
padding: 0.2px;
|
|
691
|
+
}
|
|
611
692
|
}
|
|
612
693
|
|
|
613
694
|
@media (min-width: 768px) {
|
|
@@ -21,7 +21,7 @@ export default async function queryAiSite({
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
const { data } = await axios.post(
|
|
24
|
-
process.env.
|
|
24
|
+
process.env.FUNCTIONS_ENDPOINT + '/ai-site-search',
|
|
25
25
|
{
|
|
26
26
|
question: q,
|
|
27
27
|
organizationId,
|