@duffcloudservices/cms 0.3.13 → 0.3.15

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.
@@ -2,7 +2,7 @@
2
2
  * Composable for reading curated review selections from DCS content.
3
3
  * Reviews are stored in content.yaml by the visual editor's ReviewPickerSheet.
4
4
  */
5
- import { computed, type ComputedRef } from 'vue'
5
+ import { computed, onMounted, onUnmounted, shallowRef, type ComputedRef } from 'vue'
6
6
 
7
7
  export interface ReviewItem {
8
8
  id: string
@@ -14,9 +14,17 @@ export interface ReviewItem {
14
14
  date?: string
15
15
  replyText?: string
16
16
  locationName?: string
17
+ sourceLocationName?: string
17
18
  sourceUrl?: string
18
19
  }
19
20
 
21
+ function withoutAuthorPhotos(items: ReviewItem[]): ReviewItem[] {
22
+ return items.map(item => ({
23
+ ...item,
24
+ authorPhotoUrl: undefined,
25
+ }))
26
+ }
27
+
20
28
  export interface UseReviewContentConfig {
21
29
  /** The section key matching the data-dcs-reviews attribute value */
22
30
  sectionKey: string
@@ -35,18 +43,94 @@ export interface UseReviewContentReturn {
35
43
  count: ComputedRef<number>
36
44
  }
37
45
 
46
+ const previewReviewOverrides = shallowRef<Record<string, ReviewItem[]>>({})
47
+ let activePreviewReviewConsumers = 0
48
+
38
49
  // Declare the global content variable injected by dcsContentPlugin
39
50
  declare const __DCS_CONTENT__: {
40
51
  global?: Record<string, unknown>
41
52
  pages?: Record<string, Record<string, unknown>>
42
53
  } | undefined
43
54
 
55
+ function normalizeReviewItem(item: Record<string, unknown>): ReviewItem | null {
56
+ const id = String(item.id ?? '').trim()
57
+ if (!id) {
58
+ return null
59
+ }
60
+
61
+ const rawRating = Number(item.rating ?? 5)
62
+ const rating = Number.isFinite(rawRating)
63
+ ? Math.min(5, Math.max(1, Math.round(rawRating)))
64
+ : 5
65
+
66
+ return {
67
+ id,
68
+ platform: String(item.platform ?? 'google'),
69
+ rating,
70
+ authorName: String(item.authorName ?? 'Anonymous'),
71
+ authorPhotoUrl: undefined,
72
+ text: item.text ? String(item.text) : undefined,
73
+ date: item.date ? String(item.date) : undefined,
74
+ replyText: item.replyText ? String(item.replyText) : undefined,
75
+ locationName: item.locationName ? String(item.locationName) : undefined,
76
+ sourceLocationName: item.sourceLocationName ? String(item.sourceLocationName) : undefined,
77
+ sourceUrl: item.sourceUrl ? String(item.sourceUrl) : undefined,
78
+ }
79
+ }
80
+
81
+ function normalizeReviewList(value: unknown): ReviewItem[] {
82
+ if (!Array.isArray(value)) {
83
+ return []
84
+ }
85
+
86
+ return value
87
+ .filter((item): item is Record<string, unknown> => item != null && typeof item === 'object')
88
+ .map(normalizeReviewItem)
89
+ .filter((item): item is ReviewItem => item != null)
90
+ }
91
+
92
+ function handlePreviewReviewUpdate(event: Event) {
93
+ const detail = event instanceof CustomEvent && event.detail != null && typeof event.detail === 'object'
94
+ ? event.detail as { key?: unknown; reviews?: unknown }
95
+ : null
96
+ const key = typeof detail?.key === 'string' ? detail.key.trim() : ''
97
+ if (!key) {
98
+ return
99
+ }
100
+
101
+ const next = { ...previewReviewOverrides.value }
102
+ if (Array.isArray(detail?.reviews)) {
103
+ next[key] = normalizeReviewList(detail.reviews)
104
+ } else {
105
+ delete next[key]
106
+ }
107
+ previewReviewOverrides.value = next
108
+ }
109
+
44
110
  export function useReviewContent(config: UseReviewContentConfig): UseReviewContentReturn {
45
111
  const { sectionKey, pageSlug, defaults = [] } = config
46
112
 
113
+ onMounted(() => {
114
+ activePreviewReviewConsumers += 1
115
+ if (activePreviewReviewConsumers === 1) {
116
+ window.addEventListener('dcs:reviews-updated', handlePreviewReviewUpdate)
117
+ }
118
+ })
119
+
120
+ onUnmounted(() => {
121
+ activePreviewReviewConsumers = Math.max(0, activePreviewReviewConsumers - 1)
122
+ if (activePreviewReviewConsumers === 0) {
123
+ window.removeEventListener('dcs:reviews-updated', handlePreviewReviewUpdate)
124
+ }
125
+ })
126
+
47
127
  const reviews = computed<ReviewItem[]>(() => {
128
+ if (Object.prototype.hasOwnProperty.call(previewReviewOverrides.value, sectionKey)) {
129
+ return previewReviewOverrides.value[sectionKey] ?? []
130
+ }
131
+
48
132
  if (typeof __DCS_CONTENT__ === 'undefined' || __DCS_CONTENT__ == null) {
49
- return defaults
133
+ return withoutAuthorPhotos(defaults)
50
134
  }
51
135
 
52
136
  let reviewData: unknown = null
@@ -62,27 +146,10 @@ export function useReviewContent(config: UseReviewContentConfig): UseReviewConte
62
146
  }
63
147
 
64
148
  if (!reviewData || !Array.isArray(reviewData)) {
65
- return defaults
149
+ return withoutAuthorPhotos(defaults)
66
150
  }
67
151
 
68
- return reviewData
69
- .filter((item): item is Record<string, unknown> => (
70
- item != null &&
71
- typeof item === 'object' &&
72
- 'id' in item
73
- ))
74
- .map((item) => ({
75
- id: String(item.id ?? ''),
76
- platform: String(item.platform ?? 'google'),
77
- rating: Number(item.rating ?? 5),
78
- authorName: String(item.authorName ?? 'Anonymous'),
79
- authorPhotoUrl: item.authorPhotoUrl ? String(item.authorPhotoUrl) : undefined,
80
- text: item.text ? String(item.text) : undefined,
81
- date: item.date ? String(item.date) : undefined,
82
- replyText: item.replyText ? String(item.replyText) : undefined,
83
- locationName: item.locationName ? String(item.locationName) : undefined,
84
- sourceUrl: item.sourceUrl ? String(item.sourceUrl) : undefined,
85
- }))
152
+ return normalizeReviewList(reviewData)
86
153
  })
87
154
 
88
155
  const hasReviews = computed(() => reviews.value.length > 0)