@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.
- package/README.md +23 -0
- package/dist/editor/editorBridge.d.ts +4 -0
- package/dist/editor/editorBridge.js +48 -15
- package/dist/editor/editorBridge.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +68 -15
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/DcsReviewShowcase.vue +17 -22
- package/src/composables/useReviewContent.ts +88 -21
|
@@ -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)
|