@asd20/ui 3.10.0 → 3.11.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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
\<template>
|
|
2
2
|
<div
|
|
3
3
|
ref="container"
|
|
4
4
|
class="asd20-site-search"
|
|
@@ -8,47 +8,27 @@
|
|
|
8
8
|
>
|
|
9
9
|
<div class="asd20-site-search__viewport">
|
|
10
10
|
<div class="asd20-site-search__options">
|
|
11
|
-
<!--
|
|
12
|
-
<div
|
|
13
|
-
class="asd20-site-search__field asd20-site-search__field--question"
|
|
14
|
-
>
|
|
15
|
-
<!-- <label class="asd20-site-search__label">Ask a question</label> -->
|
|
11
|
+
<!-- Unified Query Input -->
|
|
12
|
+
<div class="asd20-site-search__field asd20-site-search__field--question">
|
|
16
13
|
<div class="asd20-site-search__question-row">
|
|
17
14
|
<asd20-search-field
|
|
18
|
-
idTag="-
|
|
19
|
-
ref="
|
|
20
|
-
v-model="
|
|
21
|
-
@keyup.enter.native.stop.prevent="
|
|
22
|
-
placeholder="
|
|
15
|
+
idTag="-unified"
|
|
16
|
+
ref="query"
|
|
17
|
+
v-model="inputText"
|
|
18
|
+
@keyup.enter.native.stop.prevent="onSubmitInput"
|
|
19
|
+
placeholder="Question (preview) or keywords..."
|
|
23
20
|
/>
|
|
24
21
|
<asd20-button
|
|
25
22
|
class="asd20-site-search__ask-button"
|
|
26
|
-
label="
|
|
23
|
+
label="Go"
|
|
27
24
|
size="md"
|
|
28
25
|
bordered
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
<asd20-loader
|
|
33
|
-
v-if="searchingAi"
|
|
34
|
-
size="sm"
|
|
35
|
-
class="asd20-site-search__inline-loader"
|
|
26
|
+
reversed
|
|
27
|
+
@click.native="onSubmitInput"
|
|
28
|
+
:disabled="!inputText || searchingAi"
|
|
36
29
|
/>
|
|
37
30
|
</div>
|
|
38
31
|
</div>
|
|
39
|
-
<hr />
|
|
40
|
-
|
|
41
|
-
<!-- Search by Keyword -->
|
|
42
|
-
<div class="asd20-site-search__field asd20-site-search__field--keyword">
|
|
43
|
-
<!-- <label class="asd20-site-search__label">Search by keyword</label> -->
|
|
44
|
-
<asd20-search-field
|
|
45
|
-
idTag="-sitewide"
|
|
46
|
-
ref="search"
|
|
47
|
-
v-model="keywords"
|
|
48
|
-
placeholder="Search by keyword"
|
|
49
|
-
/>
|
|
50
|
-
</div>
|
|
51
|
-
|
|
52
32
|
<!-- Only show school results -->
|
|
53
33
|
<asd20-checkbox
|
|
54
34
|
v-show="hasParentOrg"
|
|
@@ -61,17 +41,22 @@
|
|
|
61
41
|
|
|
62
42
|
<asd20-tab-bar :tabs="tabs" @tabClick="onTabClick" />
|
|
63
43
|
|
|
64
|
-
<asd20-viewport
|
|
44
|
+
<asd20-viewport
|
|
45
|
+
ref="results"
|
|
46
|
+
class="asd20-site-search__results"
|
|
47
|
+
scrollable
|
|
48
|
+
>
|
|
65
49
|
<asd20-notification
|
|
66
50
|
v-if="
|
|
67
51
|
!keywords &&
|
|
68
|
-
!
|
|
52
|
+
!inputText &&
|
|
69
53
|
!searchingFiles &&
|
|
70
54
|
!searchingPages &&
|
|
71
55
|
!searchingAi
|
|
72
56
|
"
|
|
73
|
-
title="Ask a Question
|
|
74
|
-
description="
|
|
57
|
+
title="Ask a Question or Search by Keyword"
|
|
58
|
+
description="Asking a question will generate an AI answer. Search by for pages and files by entering keyword(s).
|
|
59
|
+
<hr/>Our AI functionality is currently in preview and may not always provide accurate answers."
|
|
75
60
|
/>
|
|
76
61
|
|
|
77
62
|
<!-- Answers tab -->
|
|
@@ -94,14 +79,11 @@
|
|
|
94
79
|
{{ aiKeywordsDisplay }}
|
|
95
80
|
</div>
|
|
96
81
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
<ul class="asd20-site-search__ai-source-groups">
|
|
103
|
-
<li v-for="group in aiGroupedSources" :key="group.hostLabel">
|
|
104
|
-
<strong>Content from: {{ group.hostLabel }}</strong>
|
|
82
|
+
<!--
|
|
83
|
+
<div v-if="aiGroupedSources.length" class="asd20-site-search__ai-sources">
|
|
84
|
+
<h3>Sources</h3>
|
|
85
|
+
<div class="asd20-site-search__ai-source-groups">
|
|
86
|
+
<div v-for="group in aiGroupedSources" :key="group.hostLabel">
|
|
105
87
|
<ul class="asd20-site-search__ai-source-list">
|
|
106
88
|
<li v-for="src in group.sources" :key="src.url || src.id">
|
|
107
89
|
<a :href="src.url" target="_blank" rel="noreferrer">
|
|
@@ -109,9 +91,10 @@
|
|
|
109
91
|
</a>
|
|
110
92
|
</li>
|
|
111
93
|
</ul>
|
|
112
|
-
</
|
|
113
|
-
</
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
114
96
|
</div>
|
|
97
|
+
-->
|
|
115
98
|
|
|
116
99
|
<asd20-loader v-if="searchingAi" size="lg" />
|
|
117
100
|
</div>
|
|
@@ -140,6 +123,13 @@
|
|
|
140
123
|
/>
|
|
141
124
|
</asd20-list>
|
|
142
125
|
<asd20-loader v-if="searchingPages" size="lg" />
|
|
126
|
+
|
|
127
|
+
<div
|
|
128
|
+
v-if="aiKeywordsDisplay"
|
|
129
|
+
class="asd20-site-search__ai-keywords"
|
|
130
|
+
>
|
|
131
|
+
{{ aiKeywordsDisplay }}
|
|
132
|
+
</div>
|
|
143
133
|
</div>
|
|
144
134
|
|
|
145
135
|
<!-- Files tab -->
|
|
@@ -210,6 +200,14 @@ import mapFilesToListItems from '../../../helpers/mapFilesToListItems'
|
|
|
210
200
|
// Mixins
|
|
211
201
|
import globalPropMixinFactory from '../../../mixins/globalPropMixinFactory.js'
|
|
212
202
|
|
|
203
|
+
const SUBDOMAIN_LABELS = {
|
|
204
|
+
rampart: 'Rampart High School',
|
|
205
|
+
pinecreek: 'Pine Creek High School',
|
|
206
|
+
dcchigh: 'Discovery Canyon Campus High School',
|
|
207
|
+
liberty: 'Liberty High School',
|
|
208
|
+
d20online: 'Academy Online High School',
|
|
209
|
+
}
|
|
210
|
+
|
|
213
211
|
export default {
|
|
214
212
|
name: 'Asd20SiteSearch',
|
|
215
213
|
|
|
@@ -275,10 +273,9 @@ export default {
|
|
|
275
273
|
|
|
276
274
|
data: () => ({
|
|
277
275
|
currentTab: 'Answers',
|
|
278
|
-
//
|
|
276
|
+
// unified input
|
|
277
|
+
inputText: '',
|
|
279
278
|
keywords: '',
|
|
280
|
-
// question input (AI)
|
|
281
|
-
questionText: '',
|
|
282
279
|
aiAnswer: null,
|
|
283
280
|
searchingAi: false,
|
|
284
281
|
aiSources: [],
|
|
@@ -287,6 +284,7 @@ export default {
|
|
|
287
284
|
searchingFiles: false,
|
|
288
285
|
keywordsFromAi: false,
|
|
289
286
|
aiKeywordsDisplay: '',
|
|
287
|
+
skipKeywordWatcher: false,
|
|
290
288
|
}),
|
|
291
289
|
|
|
292
290
|
computed: {
|
|
@@ -357,6 +355,7 @@ export default {
|
|
|
357
355
|
aiUiSources() {
|
|
358
356
|
return (this.aiSources || []).map(src => ({
|
|
359
357
|
...src,
|
|
358
|
+
title: this.decodeHtml(src.title),
|
|
360
359
|
hostLabel:
|
|
361
360
|
this.resolveOrgTitleFromUrl(src.url) || this.labelFromUrl(src.url),
|
|
362
361
|
}))
|
|
@@ -399,35 +398,40 @@ export default {
|
|
|
399
398
|
active(newVal) {
|
|
400
399
|
if (newVal) {
|
|
401
400
|
// Focus keyword search by default for power users
|
|
402
|
-
if (this.$refs.
|
|
403
|
-
const input = this.$refs.
|
|
401
|
+
if (this.$refs.query && this.$refs.query.$el) {
|
|
402
|
+
const input = this.$refs.query.$el.querySelector('input')
|
|
404
403
|
if (input) input.focus()
|
|
405
404
|
}
|
|
406
|
-
} else if (this.$refs.
|
|
407
|
-
const input = this.$refs.
|
|
405
|
+
} else if (this.$refs.query && this.$refs.query.$el) {
|
|
406
|
+
const input = this.$refs.query.$el.querySelector('input')
|
|
408
407
|
if (input) input.blur()
|
|
409
408
|
}
|
|
410
409
|
},
|
|
411
410
|
|
|
412
|
-
//
|
|
411
|
+
// keyword updates trigger searches unless explicitly skipped
|
|
413
412
|
keywords: debounce(function (newVal, oldVal) {
|
|
413
|
+
if (this.skipKeywordWatcher) {
|
|
414
|
+
this.skipKeywordWatcher = false
|
|
415
|
+
return
|
|
416
|
+
}
|
|
414
417
|
if (newVal !== oldVal) {
|
|
415
418
|
this.searchPages()
|
|
416
419
|
this.searchFiles()
|
|
417
420
|
this.searchGroups()
|
|
418
421
|
}
|
|
419
|
-
},
|
|
422
|
+
}, 400),
|
|
420
423
|
|
|
421
424
|
_includeDistrictResults() {
|
|
422
425
|
this.searchPages()
|
|
423
426
|
this.searchFiles()
|
|
424
427
|
},
|
|
425
428
|
|
|
426
|
-
// Clear AI answer if user clears the
|
|
427
|
-
|
|
429
|
+
// Clear AI answer if user clears the input
|
|
430
|
+
inputText(newVal) {
|
|
428
431
|
if (!newVal) {
|
|
429
432
|
this.aiAnswer = null
|
|
430
433
|
this.aiSources = []
|
|
434
|
+
this.aiKeywordsDisplay = ''
|
|
431
435
|
}
|
|
432
436
|
},
|
|
433
437
|
},
|
|
@@ -441,8 +445,14 @@ export default {
|
|
|
441
445
|
this.searchingPages = false
|
|
442
446
|
if (this.keywords && this.keywords.trim() && !keepAnswersTab) {
|
|
443
447
|
this.currentTab = 'Pages'
|
|
448
|
+
this.scrollResultsToTop()
|
|
444
449
|
}
|
|
445
450
|
this.keywordsFromAi = false
|
|
451
|
+
if (!keepAnswersTab) {
|
|
452
|
+
this.aiAnswer = null
|
|
453
|
+
this.aiSources = []
|
|
454
|
+
this.aiKeywordsDisplay = ''
|
|
455
|
+
}
|
|
446
456
|
}
|
|
447
457
|
},
|
|
448
458
|
|
|
@@ -462,6 +472,9 @@ export default {
|
|
|
462
472
|
|
|
463
473
|
onTabClick(tab) {
|
|
464
474
|
this.currentTab = tab.label
|
|
475
|
+
if (tab.label === 'Pages') {
|
|
476
|
+
this.scrollResultsToTop()
|
|
477
|
+
}
|
|
465
478
|
},
|
|
466
479
|
|
|
467
480
|
onTab(event) {
|
|
@@ -469,6 +482,16 @@ export default {
|
|
|
469
482
|
this.dismiss()
|
|
470
483
|
},
|
|
471
484
|
|
|
485
|
+
scrollResultsToTop() {
|
|
486
|
+
this.$nextTick(() => {
|
|
487
|
+
const ref = this.$refs.results
|
|
488
|
+
const el = ref ? ref.$el || ref : null
|
|
489
|
+
if (el && typeof el.scrollTop === 'number') {
|
|
490
|
+
el.scrollTop = 0
|
|
491
|
+
}
|
|
492
|
+
})
|
|
493
|
+
},
|
|
494
|
+
|
|
472
495
|
onBackgroundClick(e) {
|
|
473
496
|
if (e.target === this.$refs.container) this.dismiss()
|
|
474
497
|
},
|
|
@@ -483,27 +506,16 @@ export default {
|
|
|
483
506
|
labelFromUrl(url) {
|
|
484
507
|
if (!url) return 'Academy District 20'
|
|
485
508
|
try {
|
|
486
|
-
const host = new URL(url).hostname
|
|
509
|
+
const host = new URL(url).hostname.toLowerCase()
|
|
487
510
|
|
|
488
511
|
if (host === 'www.asd20.org') return 'Academy District 20'
|
|
489
512
|
|
|
490
513
|
if (host.endsWith('.asd20.org')) {
|
|
491
514
|
const subdomain = host.replace('.asd20.org', '')
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
return 'Pine Creek High School'
|
|
497
|
-
case 'dcchigh':
|
|
498
|
-
return 'Discovery Canyon Campus High School'
|
|
499
|
-
case 'liberty':
|
|
500
|
-
return 'Liberty High School'
|
|
501
|
-
case 'd20online':
|
|
502
|
-
return 'Academy Online High School'
|
|
503
|
-
// add more mappings as needed
|
|
504
|
-
default:
|
|
505
|
-
return subdomain.charAt(0).toUpperCase() + subdomain.slice(1)
|
|
506
|
-
}
|
|
515
|
+
return (
|
|
516
|
+
SUBDOMAIN_LABELS[subdomain] ||
|
|
517
|
+
subdomain.replace(/(^|[-_.])([a-z])/g, (_, __, c) => c.toUpperCase())
|
|
518
|
+
)
|
|
507
519
|
}
|
|
508
520
|
|
|
509
521
|
return host
|
|
@@ -559,15 +571,28 @@ export default {
|
|
|
559
571
|
return false
|
|
560
572
|
},
|
|
561
573
|
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
if (!q) {
|
|
574
|
+
onSubmitInput() {
|
|
575
|
+
const text = (this.inputText || '').trim()
|
|
576
|
+
if (!text) {
|
|
566
577
|
this.aiAnswer = null
|
|
567
578
|
this.aiSources = []
|
|
579
|
+
this.aiKeywordsDisplay = ''
|
|
568
580
|
return
|
|
569
581
|
}
|
|
570
|
-
|
|
582
|
+
|
|
583
|
+
const mode = this.decideMode(text)
|
|
584
|
+
|
|
585
|
+
if (mode === 'question') {
|
|
586
|
+
this.searchAi(text)
|
|
587
|
+
} else {
|
|
588
|
+
this.keywordsFromAi = false
|
|
589
|
+
this.skipKeywordWatcher = true
|
|
590
|
+
this.keywords = text
|
|
591
|
+
this.searchPages({ keepAnswersTab: false })
|
|
592
|
+
this.searchFiles()
|
|
593
|
+
this.searchGroups()
|
|
594
|
+
this.currentTab = 'Pages'
|
|
595
|
+
}
|
|
571
596
|
},
|
|
572
597
|
|
|
573
598
|
async searchAi(question) {
|
|
@@ -605,7 +630,7 @@ export default {
|
|
|
605
630
|
const safeKeywords = this.sanitizeKeywords(aiKeywords)
|
|
606
631
|
const fallbackKeywords = safeKeywords
|
|
607
632
|
? ''
|
|
608
|
-
: this.deriveFallbackKeywords(this.
|
|
633
|
+
: this.deriveFallbackKeywords(this.inputText)
|
|
609
634
|
const keywordsToUse = safeKeywords || fallbackKeywords
|
|
610
635
|
|
|
611
636
|
// Show the AI-provided keywords (or fallback) in brackets for visibility.
|
|
@@ -616,8 +641,11 @@ export default {
|
|
|
616
641
|
|
|
617
642
|
if (keywordsToUse) {
|
|
618
643
|
this.keywordsFromAi = true
|
|
644
|
+
this.skipKeywordWatcher = true
|
|
619
645
|
this.keywords = keywordsToUse
|
|
620
|
-
|
|
646
|
+
this.searchPages({ keepAnswersTab: true })
|
|
647
|
+
this.searchFiles()
|
|
648
|
+
this.searchGroups()
|
|
621
649
|
}
|
|
622
650
|
|
|
623
651
|
this.currentTab = 'Answers'
|
|
@@ -747,6 +775,46 @@ export default {
|
|
|
747
775
|
return result
|
|
748
776
|
},
|
|
749
777
|
|
|
778
|
+
decodeHtml(value) {
|
|
779
|
+
if (!value || typeof value !== 'string') return value || ''
|
|
780
|
+
if (typeof window === 'undefined' || !window.document) return value
|
|
781
|
+
const parser = new DOMParser()
|
|
782
|
+
const decoded = parser.parseFromString(value, 'text/html').documentElement
|
|
783
|
+
.textContent
|
|
784
|
+
return decoded || value
|
|
785
|
+
},
|
|
786
|
+
|
|
787
|
+
decideMode(text) {
|
|
788
|
+
const lower = (text || '').toLowerCase()
|
|
789
|
+
if (!lower) return 'keyword'
|
|
790
|
+
if (lower.includes('?')) return 'question'
|
|
791
|
+
|
|
792
|
+
const questionStarters = [
|
|
793
|
+
'who',
|
|
794
|
+
'what',
|
|
795
|
+
'when',
|
|
796
|
+
'where',
|
|
797
|
+
'why',
|
|
798
|
+
'how',
|
|
799
|
+
'should',
|
|
800
|
+
'can',
|
|
801
|
+
'could',
|
|
802
|
+
'would',
|
|
803
|
+
'is',
|
|
804
|
+
'are',
|
|
805
|
+
'do',
|
|
806
|
+
'does',
|
|
807
|
+
'will',
|
|
808
|
+
'may',
|
|
809
|
+
]
|
|
810
|
+
const words = lower.split(/\s+/).filter(Boolean)
|
|
811
|
+
if (words.length >= 7) return 'question'
|
|
812
|
+
if (words.length > 0 && questionStarters.includes(words[0])) {
|
|
813
|
+
return 'question'
|
|
814
|
+
}
|
|
815
|
+
return 'keyword'
|
|
816
|
+
},
|
|
817
|
+
|
|
750
818
|
deriveFallbackKeywords(questionText) {
|
|
751
819
|
if (!questionText) return ''
|
|
752
820
|
const STOP = new Set([
|
|
@@ -900,8 +968,8 @@ export default {
|
|
|
900
968
|
::v-deep ol,
|
|
901
969
|
::v-deep ul {
|
|
902
970
|
display: block !important;
|
|
903
|
-
list-style-position:
|
|
904
|
-
padding-left:
|
|
971
|
+
list-style-position: outside;
|
|
972
|
+
padding-left: 1.5rem;
|
|
905
973
|
margin-left: 0;
|
|
906
974
|
flex: 0 0 auto;
|
|
907
975
|
flex-wrap: nowrap;
|
|
@@ -924,16 +992,8 @@ export default {
|
|
|
924
992
|
}
|
|
925
993
|
|
|
926
994
|
ul {
|
|
927
|
-
list-style: disc;
|
|
928
|
-
list-style-position: inside;
|
|
929
|
-
padding-left: 0;
|
|
930
|
-
margin: 0;
|
|
931
|
-
}
|
|
932
|
-
|
|
933
|
-
li {
|
|
934
995
|
list-style: none;
|
|
935
|
-
|
|
936
|
-
margin-bottom: space(0.25);
|
|
996
|
+
margin: 0;
|
|
937
997
|
}
|
|
938
998
|
}
|
|
939
999
|
|
|
@@ -941,21 +1001,22 @@ export default {
|
|
|
941
1001
|
list-style: none;
|
|
942
1002
|
padding-left: 0;
|
|
943
1003
|
margin: 0;
|
|
944
|
-
&__ai-source-list {
|
|
945
|
-
list-style: disc;
|
|
946
|
-
list-style-position: inside;
|
|
947
|
-
padding-left: space(0.75);
|
|
948
|
-
margin: space(0.25) 0 0.35rem space(0.5);
|
|
949
|
-
|
|
950
|
-
li {
|
|
951
|
-
display: list-item;
|
|
952
|
-
margin: 0;
|
|
953
|
-
padding: 0;
|
|
954
|
-
}
|
|
955
|
-
}
|
|
956
1004
|
}
|
|
957
1005
|
|
|
1006
|
+
&__ai-source-list {
|
|
1007
|
+
display: block;
|
|
1008
|
+
list-style: disc;
|
|
1009
|
+
list-style-position: outside;
|
|
1010
|
+
padding-left: space(0.5);
|
|
1011
|
+
margin: 0 0 0.35rem space(0.5);
|
|
958
1012
|
|
|
1013
|
+
li {
|
|
1014
|
+
list-style: disc;
|
|
1015
|
+
list-style-position: outside;
|
|
1016
|
+
display: list-item;
|
|
1017
|
+
margin: 0;
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
959
1020
|
|
|
960
1021
|
&__ai-snippet {
|
|
961
1022
|
margin: 0;
|