@duffcloudservices/cms 0.3.12 → 0.3.14
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 +332 -309
- package/dist/editor/editorBridge.js +127 -50
- package/dist/editor/editorBridge.js.map +1 -1
- package/dist/index.js +59 -13
- package/dist/index.js.map +1 -1
- package/dist/plugins/index.js.map +1 -1
- package/package.json +90 -90
- package/src/components/DcsReviewShowcase.vue +321 -326
- package/src/components/PreviewRibbon.vue +612 -612
- package/src/components/ResponsiveImage.vue +55 -55
- package/src/composables/index.ts +10 -10
- package/src/composables/useMediaCarousel.ts +158 -158
- package/src/composables/useReleaseNotes.ts +153 -153
- package/src/composables/useResponsiveImage.ts +85 -85
- package/src/composables/useReviewContent.ts +150 -92
- package/src/composables/useSEO.ts +387 -387
- package/src/composables/useSiteVersion.ts +123 -123
- package/src/composables/useTextContent.ts +297 -297
|
@@ -1,92 +1,150 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Composable for reading curated review selections from DCS content.
|
|
3
|
-
* Reviews are stored in content.yaml by the visual editor's ReviewPickerSheet.
|
|
4
|
-
*/
|
|
5
|
-
import { computed, type ComputedRef } from 'vue'
|
|
6
|
-
|
|
7
|
-
export interface ReviewItem {
|
|
8
|
-
id: string
|
|
9
|
-
platform: 'google' | 'meta' | string
|
|
10
|
-
rating: number
|
|
11
|
-
authorName: string
|
|
12
|
-
authorPhotoUrl?: string
|
|
13
|
-
text?: string
|
|
14
|
-
date?: string
|
|
15
|
-
replyText?: string
|
|
16
|
-
locationName?: string
|
|
17
|
-
sourceUrl?: string
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export interface UseReviewContentConfig {
|
|
21
|
-
/** The section key matching the data-dcs-reviews attribute value */
|
|
22
|
-
sectionKey: string
|
|
23
|
-
/** Page slug for page-specific content lookup (defaults to current page) */
|
|
24
|
-
pageSlug?: string
|
|
25
|
-
/** Fallback reviews when no content is available */
|
|
26
|
-
defaults?: ReviewItem[]
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export interface UseReviewContentReturn {
|
|
30
|
-
/** The curated review items from content */
|
|
31
|
-
reviews: ComputedRef<ReviewItem[]>
|
|
32
|
-
/** Whether any reviews are available */
|
|
33
|
-
hasReviews: ComputedRef<boolean>
|
|
34
|
-
/** Number of reviews */
|
|
35
|
-
count: ComputedRef<number>
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Composable for reading curated review selections from DCS content.
|
|
3
|
+
* Reviews are stored in content.yaml by the visual editor's ReviewPickerSheet.
|
|
4
|
+
*/
|
|
5
|
+
import { computed, onMounted, onUnmounted, shallowRef, type ComputedRef } from 'vue'
|
|
6
|
+
|
|
7
|
+
export interface ReviewItem {
|
|
8
|
+
id: string
|
|
9
|
+
platform: 'google' | 'meta' | string
|
|
10
|
+
rating: number
|
|
11
|
+
authorName: string
|
|
12
|
+
authorPhotoUrl?: string
|
|
13
|
+
text?: string
|
|
14
|
+
date?: string
|
|
15
|
+
replyText?: string
|
|
16
|
+
locationName?: string
|
|
17
|
+
sourceUrl?: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface UseReviewContentConfig {
|
|
21
|
+
/** The section key matching the data-dcs-reviews attribute value */
|
|
22
|
+
sectionKey: string
|
|
23
|
+
/** Page slug for page-specific content lookup (defaults to current page) */
|
|
24
|
+
pageSlug?: string
|
|
25
|
+
/** Fallback reviews when no content is available */
|
|
26
|
+
defaults?: ReviewItem[]
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface UseReviewContentReturn {
|
|
30
|
+
/** The curated review items from content */
|
|
31
|
+
reviews: ComputedRef<ReviewItem[]>
|
|
32
|
+
/** Whether any reviews are available */
|
|
33
|
+
hasReviews: ComputedRef<boolean>
|
|
34
|
+
/** Number of reviews */
|
|
35
|
+
count: ComputedRef<number>
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const previewReviewOverrides = shallowRef<Record<string, ReviewItem[]>>({})
|
|
39
|
+
let activePreviewReviewConsumers = 0
|
|
40
|
+
|
|
41
|
+
// Declare the global content variable injected by dcsContentPlugin
|
|
42
|
+
declare const __DCS_CONTENT__: {
|
|
43
|
+
global?: Record<string, unknown>
|
|
44
|
+
pages?: Record<string, Record<string, unknown>>
|
|
45
|
+
} | undefined
|
|
46
|
+
|
|
47
|
+
function normalizeReviewItem(item: Record<string, unknown>): ReviewItem | null {
|
|
48
|
+
const id = String(item.id ?? '').trim()
|
|
49
|
+
if (!id) {
|
|
50
|
+
return null
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const rawRating = Number(item.rating ?? 5)
|
|
54
|
+
const rating = Number.isFinite(rawRating)
|
|
55
|
+
? Math.min(5, Math.max(1, Math.round(rawRating)))
|
|
56
|
+
: 5
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
id,
|
|
60
|
+
platform: String(item.platform ?? 'google'),
|
|
61
|
+
rating,
|
|
62
|
+
authorName: String(item.authorName ?? 'Anonymous'),
|
|
63
|
+
authorPhotoUrl: item.authorPhotoUrl ? String(item.authorPhotoUrl) : undefined,
|
|
64
|
+
text: item.text ? String(item.text) : undefined,
|
|
65
|
+
date: item.date ? String(item.date) : undefined,
|
|
66
|
+
replyText: item.replyText ? String(item.replyText) : undefined,
|
|
67
|
+
locationName: item.locationName ? String(item.locationName) : undefined,
|
|
68
|
+
sourceUrl: item.sourceUrl ? String(item.sourceUrl) : undefined,
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function normalizeReviewList(value: unknown): ReviewItem[] {
|
|
73
|
+
if (!Array.isArray(value)) {
|
|
74
|
+
return []
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return value
|
|
78
|
+
.filter((item): item is Record<string, unknown> => item != null && typeof item === 'object')
|
|
79
|
+
.map(normalizeReviewItem)
|
|
80
|
+
.filter((item): item is ReviewItem => item != null)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function handlePreviewReviewUpdate(event: Event) {
|
|
84
|
+
const detail = event instanceof CustomEvent && event.detail != null && typeof event.detail === 'object'
|
|
85
|
+
? event.detail as { key?: unknown; reviews?: unknown }
|
|
86
|
+
: null
|
|
87
|
+
const key = typeof detail?.key === 'string' ? detail.key.trim() : ''
|
|
88
|
+
if (!key) {
|
|
89
|
+
return
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const next = { ...previewReviewOverrides.value }
|
|
93
|
+
if (Array.isArray(detail?.reviews)) {
|
|
94
|
+
next[key] = normalizeReviewList(detail.reviews)
|
|
95
|
+
} else {
|
|
96
|
+
delete next[key]
|
|
97
|
+
}
|
|
98
|
+
previewReviewOverrides.value = next
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function useReviewContent(config: UseReviewContentConfig): UseReviewContentReturn {
|
|
102
|
+
const { sectionKey, pageSlug, defaults = [] } = config
|
|
103
|
+
|
|
104
|
+
onMounted(() => {
|
|
105
|
+
activePreviewReviewConsumers += 1
|
|
106
|
+
if (activePreviewReviewConsumers === 1) {
|
|
107
|
+
window.addEventListener('dcs:reviews-updated', handlePreviewReviewUpdate)
|
|
108
|
+
}
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
onUnmounted(() => {
|
|
112
|
+
activePreviewReviewConsumers = Math.max(0, activePreviewReviewConsumers - 1)
|
|
113
|
+
if (activePreviewReviewConsumers === 0) {
|
|
114
|
+
window.removeEventListener('dcs:reviews-updated', handlePreviewReviewUpdate)
|
|
115
|
+
}
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
const reviews = computed<ReviewItem[]>(() => {
|
|
119
|
+
if (Object.prototype.hasOwnProperty.call(previewReviewOverrides.value, sectionKey)) {
|
|
120
|
+
return previewReviewOverrides.value[sectionKey] ?? []
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (typeof __DCS_CONTENT__ === 'undefined' || __DCS_CONTENT__ == null) {
|
|
124
|
+
return defaults
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
let reviewData: unknown = null
|
|
128
|
+
|
|
129
|
+
if (pageSlug && __DCS_CONTENT__.pages?.[pageSlug]) {
|
|
130
|
+
reviewData = __DCS_CONTENT__.pages[pageSlug][`reviews.${sectionKey}.items`]
|
|
131
|
+
?? __DCS_CONTENT__.pages[pageSlug][`reviews.${sectionKey}`]
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (!reviewData && __DCS_CONTENT__.global) {
|
|
135
|
+
reviewData = __DCS_CONTENT__.global[`reviews.${sectionKey}.items`]
|
|
136
|
+
?? __DCS_CONTENT__.global[`reviews.${sectionKey}`]
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (!reviewData || !Array.isArray(reviewData)) {
|
|
140
|
+
return defaults
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return normalizeReviewList(reviewData)
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
const hasReviews = computed(() => reviews.value.length > 0)
|
|
147
|
+
const count = computed(() => reviews.value.length)
|
|
148
|
+
|
|
149
|
+
return { reviews, hasReviews, count }
|
|
150
|
+
}
|