@asd20/ui 3.7.0 → 3.9.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,6 +1,6 @@
1
1
  {
2
2
  "name": "@asd20/ui",
3
- "version": "3.7.0",
3
+ "version": "3.9.0",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "sideEffects": [
@@ -9,15 +9,17 @@
9
9
  <div class="asd20-site-search__viewport">
10
10
  <div class="asd20-site-search__options">
11
11
  <!-- Ask a Question -->
12
- <div class="asd20-site-search__field asd20-site-search__field--question">
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
16
18
  idTag="-question"
17
19
  ref="question"
18
20
  v-model="questionText"
19
- @keyup.enter.stop.prevent="onAskQuestion"
20
- placeholder="Ask a question"
21
+ @keyup.enter.native.stop.prevent="onAskQuestion"
22
+ placeholder="Ask a question (preview)"
21
23
  />
22
24
  <asd20-button
23
25
  class="asd20-site-search__ask-button"
@@ -34,7 +36,7 @@
34
36
  />
35
37
  </div>
36
38
  </div>
37
- <hr/>
39
+ <hr />
38
40
 
39
41
  <!-- Search by Keyword -->
40
42
  <div class="asd20-site-search__field asd20-site-search__field--keyword">
@@ -74,18 +76,33 @@
74
76
 
75
77
  <!-- Answers tab -->
76
78
  <div v-show="currentTab === 'Answers'" scrollable>
79
+ <div v-if="aiAnswer" class="disclaimer">
80
+ <p>
81
+ AI generated answers can have errors. Please contact our
82
+ <a href="www.asd20.org/help-desk">Help Desk</a> if you need
83
+ additional information.
84
+ </p>
85
+ </div>
86
+
77
87
  <div v-if="aiAnswer" class="asd20-site-search__ai-result">
78
88
  <h3>Answer</h3>
79
89
  <div class="asd20-site-search__ai-answer" v-html="aiAnswer" />
80
90
 
81
- <div v-if="aiUiSources.length" class="asd20-site-search__ai-sources">
91
+ <div
92
+ v-if="aiGroupedSources.length"
93
+ class="asd20-site-search__ai-sources"
94
+ >
82
95
  <h4>Sources</h4>
83
96
  <ul>
84
- <li v-for="src in aiUiSources" :key="src.url || src.id">
85
- <strong>Content from: {{ src.hostLabel }}</strong><br />
86
- <a :href="src.url" target="_blank" rel="noreferrer">
87
- {{ src.title }}
88
- </a>
97
+ <li v-for="group in aiGroupedSources" :key="group.hostLabel">
98
+ <strong>Content from: {{ group.hostLabel }}</strong>
99
+ <ul class="asd20-site-search__ai-source-list">
100
+ <li v-for="src in group.sources" :key="src.url || src.id">
101
+ <a :href="src.url" target="_blank" rel="noreferrer">
102
+ {{ src.title }}
103
+ </a>
104
+ </li>
105
+ </ul>
89
106
  </li>
90
107
  </ul>
91
108
  </div>
@@ -97,7 +114,6 @@
97
114
 
98
115
  <!-- Pages tab -->
99
116
  <div v-show="currentTab === 'Pages'" scrollable>
100
-
101
117
  <!-- No results -->
102
118
  <asd20-notification
103
119
  v-if="keywords && _pages.length === 0 && !searchingPages"
@@ -134,7 +150,9 @@
134
150
 
135
151
  <div class="asd20-site-search__suggested" v-if="_groups.length > 0">
136
152
  <h3>
137
- {{ _groups.length > 1 ? 'Suggested Contacts:' : 'Suggested Contact:' }}
153
+ {{
154
+ _groups.length > 1 ? 'Suggested Contacts:' : 'Suggested Contact:'
155
+ }}
138
156
  </h3>
139
157
  <asd20-list-item
140
158
  v-for="g in _groups"
@@ -242,7 +260,10 @@ export default {
242
260
 
243
261
  props: {
244
262
  active: { type: Boolean, default: false },
245
- organization: { type: Object, default: () => ({ title: 'Academy District 20' }) },
263
+ organization: {
264
+ type: Object,
265
+ default: () => ({ title: 'Academy District 20' }),
266
+ },
246
267
  organizationOptions: { type: Array, default: () => [] },
247
268
  },
248
269
 
@@ -328,9 +349,39 @@ export default {
328
349
  aiUiSources() {
329
350
  return (this.aiSources || []).map(src => ({
330
351
  ...src,
331
- hostLabel: this.labelFromUrl(src.url),
352
+ hostLabel:
353
+ this.resolveOrgTitleFromUrl(src.url) || this.labelFromUrl(src.url),
332
354
  }))
333
355
  },
356
+ aiGroupedSources() {
357
+ const groups = new Map()
358
+ this.aiUiSources.forEach(src => {
359
+ const key = src.hostLabel || 'Academy District 20'
360
+ if (!groups.has(key)) {
361
+ groups.set(key, { hostLabel: key, sources: [] })
362
+ }
363
+ const group = groups.get(key)
364
+ const exists = group.sources.find(
365
+ s => (s.url && src.url && s.url === src.url) || (s.id && src.id && s.id === src.id)
366
+ )
367
+ if (!exists) {
368
+ group.sources.push(src)
369
+ }
370
+ })
371
+ const currentOrgTitle =
372
+ (this.organization && this.organization.title) || 'Academy District 20'
373
+ const currentLower = currentOrgTitle.toLowerCase()
374
+
375
+ return Array.from(groups.values()).sort((a, b) => {
376
+ const aIsCurrent =
377
+ (a.hostLabel || '').toLowerCase() === currentLower
378
+ const bIsCurrent =
379
+ (b.hostLabel || '').toLowerCase() === currentLower
380
+ if (aIsCurrent && !bIsCurrent) return -1
381
+ if (bIsCurrent && !aIsCurrent) return 1
382
+ return (a.hostLabel || '').localeCompare(b.hostLabel || '')
383
+ })
384
+ },
334
385
  },
335
386
 
336
387
  watch: {
@@ -430,6 +481,12 @@ export default {
430
481
  return 'Rampart High School'
431
482
  case 'pinecreek':
432
483
  return 'Pine Creek High School'
484
+ case 'dcchigh':
485
+ return 'Discovery Canyon Campus High School'
486
+ case 'liberty':
487
+ return 'Liberty High School'
488
+ case 'd20online':
489
+ return 'Academy Online High School'
433
490
  // add more mappings as needed
434
491
  default:
435
492
  return subdomain.charAt(0).toUpperCase() + subdomain.slice(1)
@@ -442,6 +499,26 @@ export default {
442
499
  }
443
500
  },
444
501
 
502
+ // Try to resolve the full organization title from organizationOptions by URL
503
+ resolveOrgTitleFromUrl(url) {
504
+ if (!url || !Array.isArray(this.organizationOptions)) return null
505
+ try {
506
+ const host = new URL(url).hostname.toLowerCase()
507
+ const match = this.organizationOptions.find((org) => {
508
+ if (!org || !org.website) return false
509
+ try {
510
+ const orgHost = new URL(org.website).hostname.toLowerCase()
511
+ return host === orgHost || host.endsWith(orgHost)
512
+ } catch {
513
+ return false
514
+ }
515
+ })
516
+ return match && match.title ? match.title : null
517
+ } catch {
518
+ return null
519
+ }
520
+ },
521
+
445
522
  // Called when user clicks Ask or presses Enter in question field
446
523
  onAskQuestion() {
447
524
  const q = (this.questionText || '').trim()
@@ -496,15 +573,7 @@ export default {
496
573
  // If no DOM (e.g., SSR), return raw; client will re-run and sanitize.
497
574
  if (typeof window === 'undefined' || !window.document) return rawHtml
498
575
 
499
- const allowedTags = new Set([
500
- 'P',
501
- 'UL',
502
- 'OL',
503
- 'LI',
504
- 'STRONG',
505
- 'EM',
506
- 'A',
507
- ])
576
+ const allowedTags = new Set(['P', 'UL', 'OL', 'LI', 'STRONG', 'EM', 'A'])
508
577
  const allowedAttrs = {
509
578
  A: ['href'],
510
579
  }
@@ -512,9 +581,9 @@ export default {
512
581
  const container = window.document.createElement('div')
513
582
  container.innerHTML = rawHtml
514
583
 
515
- const cleanNode = (node) => {
584
+ const cleanNode = node => {
516
585
  const children = Array.from(node.childNodes)
517
- children.forEach((child) => {
586
+ children.forEach(child => {
518
587
  if (child.nodeType === 1) {
519
588
  const tag = child.tagName.toUpperCase()
520
589
  if (!allowedTags.has(tag)) {
@@ -526,7 +595,7 @@ export default {
526
595
  }
527
596
 
528
597
  const allowed = allowedAttrs[tag] || []
529
- Array.from(child.attributes).forEach((attr) => {
598
+ Array.from(child.attributes).forEach(attr => {
530
599
  const name = attr.name.toLowerCase()
531
600
  if (!allowed.includes(attr.name)) {
532
601
  child.removeAttribute(attr.name)
@@ -628,6 +697,17 @@ export default {
628
697
 
629
698
  &__results {
630
699
  flex-grow: 1;
700
+ .disclaimer {
701
+ padding: space(0.5);
702
+ font-size: 0.85rem;
703
+ background: var(--website-page__alternate-background-t25);
704
+ border-bottom: 1px solid var(--color__tertiary);
705
+
706
+ a {
707
+ color: var(--color__primary);
708
+ text-decoration: underline;
709
+ }
710
+ }
631
711
  }
632
712
 
633
713
  &__ai-result {
@@ -638,6 +718,22 @@ export default {
638
718
 
639
719
  &__ai-answer {
640
720
  margin-bottom: space(0.5);
721
+
722
+ ::v-deep ol,
723
+ ::v-deep ul {
724
+ display: block !important;
725
+ list-style-position: inside;
726
+ padding-left: 0;
727
+ margin-left: 0;
728
+ flex: 0 0 auto;
729
+ flex-wrap: nowrap;
730
+ }
731
+
732
+ ::v-deep li {
733
+ display: list-item;
734
+ flex: 0 0 auto;
735
+ width: auto;
736
+ }
641
737
  }
642
738
 
643
739
  &__ai-sources {
@@ -661,6 +757,19 @@ export default {
661
757
  }
662
758
  }
663
759
 
760
+ &__ai-source-list {
761
+ list-style: disc;
762
+ list-style-position: inside;
763
+ padding-left: 0;
764
+ margin: space(0.25) 0 0.35rem 0;
765
+
766
+ li {
767
+ display: list-item;
768
+ margin: 0;
769
+ padding: 0;
770
+ }
771
+ }
772
+
664
773
  &__ai-snippet {
665
774
  margin: 0;
666
775
  opacity: 0.85;
@@ -94,7 +94,7 @@
94
94
  ul,
95
95
  ol {
96
96
  padding: 0;
97
- list-style-position: outside;
97
+ list-style-position: inside;
98
98
  margin: space(0.25) 0 space(1) 0;
99
99
  display: flex;
100
100
  flex-wrap: wrap;