@shopware/cms-base-layer 1.5.1 → 2.1.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.
Files changed (198) hide show
  1. package/README.md +398 -12
  2. package/app/app.config.ts +18 -0
  3. package/app/assets/icons/check-circle.svg +3 -0
  4. package/app/assets/icons/checkmark.svg +3 -0
  5. package/app/assets/icons/chevron.svg +3 -0
  6. package/app/assets/icons/exclamation-circle.svg +3 -0
  7. package/app/assets/icons/star-empty.svg +3 -0
  8. package/app/assets/icons/star-filled.svg +3 -0
  9. package/app/assets/icons/user.svg +1 -0
  10. package/app/components/SwCategoryNavigation.vue +83 -0
  11. package/app/components/SwCategoryNavigationLink.vue +128 -0
  12. package/{components → app/components}/SwContactForm.vue +27 -27
  13. package/app/components/SwFilterChips.vue +144 -0
  14. package/app/components/SwFilterDropdown.vue +54 -0
  15. package/app/components/SwListingProductPrice.vue +89 -0
  16. package/{components → app/components}/SwMedia3D.vue +4 -2
  17. package/{components → app/components}/SwNewsletterForm.vue +45 -34
  18. package/{components → app/components}/SwPagination.vue +3 -5
  19. package/{components → app/components}/SwProductAddToCart.vue +22 -27
  20. package/app/components/SwProductCard.vue +169 -0
  21. package/app/components/SwProductCardDetails.vue +74 -0
  22. package/app/components/SwProductCardImage.vue +90 -0
  23. package/app/components/SwProductCardSkeleton.vue +33 -0
  24. package/app/components/SwProductGallery.vue +43 -0
  25. package/app/components/SwProductListingFilter.vue +75 -0
  26. package/app/components/SwProductListingFilters.vue +304 -0
  27. package/app/components/SwProductListingFiltersHorizontal.vue +306 -0
  28. package/{components → app/components}/SwProductPrice.vue +3 -3
  29. package/app/components/SwProductRating.vue +40 -0
  30. package/{components → app/components}/SwProductReviews.vue +25 -23
  31. package/app/components/SwProductReviewsForm.vue +292 -0
  32. package/{components → app/components}/SwProductUnits.vue +10 -15
  33. package/app/components/SwQuantitySelect.vue +103 -0
  34. package/{components → app/components}/SwSlider.vue +154 -55
  35. package/app/components/SwSortDropdown.vue +87 -0
  36. package/app/components/SwStockInfo.vue +44 -0
  37. package/{components → app/components}/SwVariantConfigurator.vue +13 -12
  38. package/app/components/listing-filters/SwFilterPrice.vue +219 -0
  39. package/app/components/listing-filters/SwFilterProperties.vue +120 -0
  40. package/app/components/listing-filters/SwFilterRating.vue +99 -0
  41. package/app/components/listing-filters/SwFilterShippingFree.vue +114 -0
  42. package/app/components/public/cms/CmsBlockSpatialViewer.vue +94 -0
  43. package/app/components/public/cms/CmsGenericBlock.md +42 -0
  44. package/{components → app/components}/public/cms/CmsGenericBlock.vue +15 -1
  45. package/{components → app/components}/public/cms/CmsPage.md +19 -2
  46. package/{components → app/components}/public/cms/CmsPage.vue +30 -5
  47. package/{components → app/components}/public/cms/block/CmsBlockCenterText.vue +1 -1
  48. package/{components → app/components}/public/cms/block/CmsBlockGalleryBuybox.vue +5 -5
  49. package/{components → app/components}/public/cms/block/CmsBlockImageBubbleRow.vue +5 -5
  50. package/app/components/public/cms/block/CmsBlockImageFourColumn.vue +41 -0
  51. package/app/components/public/cms/block/CmsBlockImageGalleryBig.vue +42 -0
  52. package/app/components/public/cms/block/CmsBlockImageHighlightRow.vue +37 -0
  53. package/{components → app/components}/public/cms/block/CmsBlockImageSimpleGrid.vue +11 -5
  54. package/{components → app/components}/public/cms/block/CmsBlockImageText.vue +7 -3
  55. package/{components → app/components}/public/cms/block/CmsBlockImageTextBubble.vue +13 -16
  56. package/{components → app/components}/public/cms/block/CmsBlockImageTextCover.vue +7 -9
  57. package/app/components/public/cms/block/CmsBlockImageTextGallery.vue +88 -0
  58. package/app/components/public/cms/block/CmsBlockImageTextRow.vue +53 -0
  59. package/{components → app/components}/public/cms/block/CmsBlockImageThreeColumn.vue +10 -4
  60. package/app/components/public/cms/block/CmsBlockImageThreeCover.vue +37 -0
  61. package/app/components/public/cms/block/CmsBlockImageTwoColumn.vue +37 -0
  62. package/{components → app/components}/public/cms/block/CmsBlockProductHeading.vue +1 -1
  63. package/{components → app/components}/public/cms/block/CmsBlockProductThreeColumn.vue +10 -4
  64. package/{components → app/components}/public/cms/block/CmsBlockSidebarFilter.vue +3 -1
  65. package/{components → app/components}/public/cms/block/CmsBlockTextOnImage.vue +8 -5
  66. package/app/components/public/cms/element/CmsElementBuyBox.vue +145 -0
  67. package/app/components/public/cms/element/CmsElementCategoryNavigation.vue +53 -0
  68. package/{components → app/components}/public/cms/element/CmsElementCrossSelling.vue +22 -6
  69. package/{components → app/components}/public/cms/element/CmsElementImage.vue +58 -21
  70. package/app/components/public/cms/element/CmsElementImageGallery.vue +225 -0
  71. package/{components → app/components}/public/cms/element/CmsElementImageSlider.vue +2 -2
  72. package/{components → app/components}/public/cms/element/CmsElementProductBox.vue +8 -1
  73. package/app/components/public/cms/element/CmsElementProductDescriptionReviews.vue +217 -0
  74. package/{components → app/components}/public/cms/element/CmsElementProductListing.vue +31 -95
  75. package/app/components/public/cms/element/CmsElementProductName.vue +16 -0
  76. package/app/components/public/cms/element/CmsElementProductSlider.vue +101 -0
  77. package/app/components/public/cms/element/CmsElementSidebarFilter.vue +20 -0
  78. package/{components → app/components}/public/cms/element/CmsElementText.vue +17 -12
  79. package/app/components/public/cms/element/SwProductListingPagination.vue +70 -0
  80. package/{components → app/components}/public/cms/section/CmsSectionDefault.vue +2 -2
  81. package/app/components/public/cms/section/CmsSectionSidebar.vue +39 -0
  82. package/app/components/public/cms/skeleton/ProductCardSkeleton.vue +28 -0
  83. package/app/components/ui/BaseButton.vue +102 -0
  84. package/app/components/ui/BaseIcon.vue +15 -0
  85. package/app/components/ui/Checkbox.vue +49 -0
  86. package/app/components/ui/CheckmarkIcon.vue +23 -0
  87. package/app/components/ui/ChevronIcon.vue +34 -0
  88. package/app/components/ui/ExclamationIcon.vue +11 -0
  89. package/app/components/ui/IconButton.vue +32 -0
  90. package/app/components/ui/RadioButton.vue +26 -0
  91. package/app/components/ui/StarIcon.vue +18 -0
  92. package/app/components/ui/SwitchButton.vue +100 -0
  93. package/app/components/ui/UserIcon.vue +11 -0
  94. package/app/components/ui/WishlistIcon.vue +15 -0
  95. package/app/composables/useImagePlaceholder.ts +27 -0
  96. package/app/composables/useLcpImagePreload.test.ts +229 -0
  97. package/app/composables/useLcpImagePreload.ts +39 -0
  98. package/{helpers → app/helpers}/clientOnly.ts +5 -0
  99. package/app/helpers/cms/findFirstCmsImageUrl.ts +86 -0
  100. package/app/helpers/cms/getImageSizes.test.ts +50 -0
  101. package/app/helpers/cms/getImageSizes.ts +36 -0
  102. package/app/helpers/html-to-vue/ast.ts +106 -0
  103. package/{helpers → app/helpers}/html-to-vue/getOptionsFromNode.ts +1 -1
  104. package/{helpers → app/helpers}/html-to-vue/renderToHtml.ts +7 -11
  105. package/app/helpers/html-to-vue/renderer.ts +116 -0
  106. package/app/plugins/unocss-runtime.client.ts +23 -0
  107. package/app/providers/shopware.test.ts +213 -0
  108. package/app/providers/shopware.ts +107 -0
  109. package/dist/index.d.mts +3 -3
  110. package/dist/index.d.ts +3 -3
  111. package/dist/index.mjs +2 -2
  112. package/index.d.ts +36 -0
  113. package/nuxt.config.ts +100 -6
  114. package/package.json +33 -23
  115. package/uno.config.ts +94 -0
  116. package/components/SwCategoryNavigation.vue +0 -44
  117. package/components/SwCategoryNavigationLink.vue +0 -57
  118. package/components/SwListingProductPrice.vue +0 -89
  119. package/components/SwProductCard.vue +0 -286
  120. package/components/SwProductGallery.vue +0 -39
  121. package/components/SwProductListingFilter.vue +0 -42
  122. package/components/SwProductListingFilters.vue +0 -292
  123. package/components/listing-filters/SwFilterPrice.vue +0 -160
  124. package/components/listing-filters/SwFilterProperties.vue +0 -123
  125. package/components/listing-filters/SwFilterRating.vue +0 -101
  126. package/components/listing-filters/SwFilterShippingFree.vue +0 -104
  127. package/components/public/cms/CmsGenericBlock.md +0 -27
  128. package/components/public/cms/block/CmsBlockImageFourColumn.vue +0 -29
  129. package/components/public/cms/block/CmsBlockImageHighlightRow.vue +0 -27
  130. package/components/public/cms/block/CmsBlockImageTextGallery.vue +0 -85
  131. package/components/public/cms/block/CmsBlockImageTextRow.vue +0 -43
  132. package/components/public/cms/block/CmsBlockImageThreeCover.vue +0 -27
  133. package/components/public/cms/block/CmsBlockImageTwoColumn.vue +0 -25
  134. package/components/public/cms/element/CmsElementBuyBox.vue +0 -190
  135. package/components/public/cms/element/CmsElementCategoryNavigation.vue +0 -167
  136. package/components/public/cms/element/CmsElementImageGallery.vue +0 -249
  137. package/components/public/cms/element/CmsElementProductDescriptionReviews.vue +0 -123
  138. package/components/public/cms/element/CmsElementProductName.vue +0 -10
  139. package/components/public/cms/element/CmsElementProductSlider.vue +0 -80
  140. package/components/public/cms/element/CmsElementSidebarFilter.vue +0 -12
  141. package/components/public/cms/section/CmsSectionSidebar.vue +0 -41
  142. package/components/public/cms/skeleton/ProductCardSkeleton.vue +0 -44
  143. package/helpers/html-to-vue/ast.ts +0 -72
  144. package/helpers/html-to-vue/renderer.ts +0 -56
  145. /package/{components → app/components}/SwSharedPrice.vue +0 -0
  146. /package/{components → app/components}/public/cms/CmsGenericElement.md +0 -0
  147. /package/{components → app/components}/public/cms/CmsGenericElement.vue +0 -0
  148. /package/{components → app/components}/public/cms/CmsNoComponent.vue +0 -0
  149. /package/{components → app/components}/public/cms/block/CmsBlockCategoryNavigation.vue +0 -0
  150. /package/{components → app/components}/public/cms/block/CmsBlockCrossSelling.vue +0 -0
  151. /package/{components → app/components}/public/cms/block/CmsBlockCustomForm.vue +0 -0
  152. /package/{components → app/components}/public/cms/block/CmsBlockDefault.vue +0 -0
  153. /package/{components → app/components}/public/cms/block/CmsBlockForm.vue +0 -0
  154. /package/{components → app/components}/public/cms/block/CmsBlockHtml.vue +0 -0
  155. /package/{components → app/components}/public/cms/block/CmsBlockImage.vue +0 -0
  156. /package/{components → app/components}/public/cms/block/CmsBlockImageCover.vue +0 -0
  157. /package/{components → app/components}/public/cms/block/CmsBlockImageGallery.vue +0 -0
  158. /package/{components → app/components}/public/cms/block/CmsBlockImageSlider.vue +0 -0
  159. /package/{components → app/components}/public/cms/block/CmsBlockProductDescriptionReviews.vue +0 -0
  160. /package/{components → app/components}/public/cms/block/CmsBlockProductListing.vue +0 -0
  161. /package/{components → app/components}/public/cms/block/CmsBlockProductSlider.vue +0 -0
  162. /package/{components → app/components}/public/cms/block/CmsBlockText.vue +0 -0
  163. /package/{components → app/components}/public/cms/block/CmsBlockTextHero.vue +0 -0
  164. /package/{components → app/components}/public/cms/block/CmsBlockTextTeaser.vue +0 -0
  165. /package/{components → app/components}/public/cms/block/CmsBlockTextTeaserSection.vue +0 -0
  166. /package/{components → app/components}/public/cms/block/CmsBlockTextThreeColumn.vue +0 -0
  167. /package/{components → app/components}/public/cms/block/CmsBlockTextTwoColumn.vue +0 -0
  168. /package/{components → app/components}/public/cms/block/CmsBlockVimeoVideo.vue +0 -0
  169. /package/{components → app/components}/public/cms/block/CmsBlockYoutubeVideo.vue +0 -0
  170. /package/{components → app/components}/public/cms/element/CmsElementBuyBox.md +0 -0
  171. /package/{components → app/components}/public/cms/element/CmsElementCategoryNavigation.md +0 -0
  172. /package/{components → app/components}/public/cms/element/CmsElementCrossSelling.md +0 -0
  173. /package/{components → app/components}/public/cms/element/CmsElementCustomForm.md +0 -0
  174. /package/{components → app/components}/public/cms/element/CmsElementCustomForm.vue +0 -0
  175. /package/{components → app/components}/public/cms/element/CmsElementForm.md +0 -0
  176. /package/{components → app/components}/public/cms/element/CmsElementForm.vue +0 -0
  177. /package/{components → app/components}/public/cms/element/CmsElementHtml.vue +0 -0
  178. /package/{components → app/components}/public/cms/element/CmsElementImage.md +0 -0
  179. /package/{components → app/components}/public/cms/element/CmsElementImageGallery.md +0 -0
  180. /package/{components → app/components}/public/cms/element/CmsElementImageGallery3dPlaceholder.vue +0 -0
  181. /package/{components → app/components}/public/cms/element/CmsElementImageSlider.md +0 -0
  182. /package/{components → app/components}/public/cms/element/CmsElementManufacturerLogo.md +0 -0
  183. /package/{components → app/components}/public/cms/element/CmsElementManufacturerLogo.vue +0 -0
  184. /package/{components → app/components}/public/cms/element/CmsElementProductBox.md +0 -0
  185. /package/{components → app/components}/public/cms/element/CmsElementProductDescriptionReviews.md +0 -0
  186. /package/{components → app/components}/public/cms/element/CmsElementProductListing.md +0 -0
  187. /package/{components → app/components}/public/cms/element/CmsElementProductName.md +0 -0
  188. /package/{components → app/components}/public/cms/element/CmsElementProductSlider.md +0 -0
  189. /package/{components → app/components}/public/cms/element/CmsElementSidebarFilter.md +0 -0
  190. /package/{components → app/components}/public/cms/element/CmsElementText.md +0 -0
  191. /package/{components → app/components}/public/cms/element/CmsElementVimeoVideo.md +0 -0
  192. /package/{components → app/components}/public/cms/element/CmsElementVimeoVideo.vue +0 -0
  193. /package/{components → app/components}/public/cms/element/CmsElementYoutubeVideo.md +0 -0
  194. /package/{components → app/components}/public/cms/element/CmsElementYoutubeVideo.vue +0 -0
  195. /package/{components → app/components}/public/cms/section/CmsSectionDefault.md +0 -0
  196. /package/{components → app/components}/public/cms/section/CmsSectionSidebar.md +0 -0
  197. /package/{helpers → app/helpers}/html-to-vue/getOptionsFromNode.test.ts +0 -0
  198. /package/{helpers → app/helpers}/media/isSpatial.ts +0 -0
@@ -1,249 +0,0 @@
1
- <script setup lang="ts">
2
- import type { CmsElementImageGallery } from "@shopware/composables";
3
- import { computed, onMounted, ref, useTemplateRef } from "vue";
4
- import { useCmsElementConfig } from "#imports";
5
- import { isSpatial } from "../../../../helpers/media/isSpatial";
6
-
7
- const props = withDefaults(
8
- defineProps<{
9
- content: CmsElementImageGallery;
10
- slidesToShow?: number;
11
- slidesToScroll?: number;
12
- }>(),
13
- {
14
- slidesToShow: 5,
15
- slidesToScroll: 4,
16
- },
17
- );
18
-
19
- const { getConfigValue } = useCmsElementConfig(props.content);
20
-
21
- const speed = ref<number>(300);
22
- const currentIndex = ref(0);
23
- const currentThumb = ref(0);
24
- const imageSlider = useTemplateRef("imageSlider");
25
- const imageThumbsTrack = useTemplateRef("imageThumbsTrack");
26
- const isLoading = ref(true);
27
- const imageThumbsTrackStyle = ref({});
28
- const imageThumbs = useTemplateRef("imageThumbs");
29
- const imageThumbsStyle = ref({});
30
- const mediaGallery = computed(() => props.content.data?.sliderItems ?? []);
31
- const galleryPosition = computed<string>(
32
- () => getConfigValue("galleryPosition") ?? "left",
33
- );
34
- const scrollPx = ref(0);
35
-
36
- onMounted(() => {
37
- initThumbs();
38
- });
39
-
40
- function initThumbs() {
41
- if (imageThumbsTrack.value) {
42
- setTimeout(() => {
43
- if (galleryPosition.value === "left") {
44
- const clientHeight = imageThumbsTrack.value?.clientHeight ?? 0;
45
- scrollPx.value = clientHeight / mediaGallery.value.length;
46
- imageThumbsStyle.value = {
47
- height: `${scrollPx.value * +props.slidesToShow}px`,
48
- };
49
- } else {
50
- const clientWidth = imageThumbs.value?.clientWidth ?? 0;
51
- scrollPx.value = clientWidth / props.slidesToShow;
52
- imageThumbsTrackStyle.value = {
53
- width: `${scrollPx.value * mediaGallery.value.length}px`,
54
- };
55
- }
56
- isLoading.value = false;
57
- }, 100);
58
- }
59
- }
60
-
61
- function changeCover(i: number) {
62
- if (i === currentIndex.value) return;
63
- imageSlider.value?.goToSlide(i);
64
- }
65
-
66
- function handleChangeSlide(e: number) {
67
- currentIndex.value = e;
68
- if (currentIndex.value > currentThumb.value + props.slidesToShow - 1) {
69
- move("next", currentIndex.value);
70
- return;
71
- }
72
- if (currentIndex.value < currentThumb.value) {
73
- move("previous", currentIndex.value);
74
- return;
75
- }
76
- }
77
-
78
- function move(type: "next" | "previous", specificIndex?: number | string) {
79
- let step: number;
80
- const index =
81
- typeof specificIndex !== "number"
82
- ? Number.parseInt(specificIndex as string)
83
- : specificIndex;
84
- if (index >= 0) {
85
- if (type === "next") {
86
- step =
87
- index + props.slidesToScroll < mediaGallery.value.length
88
- ? index
89
- : mediaGallery.value.length - props.slidesToShow;
90
- } else {
91
- step =
92
- index - props.slidesToScroll > 0 ? index - props.slidesToScroll : 0;
93
- }
94
- } else {
95
- if (type === "next") {
96
- step =
97
- currentThumb.value + props.slidesToShow - 1 + props.slidesToScroll <
98
- mediaGallery.value.length
99
- ? currentThumb.value + props.slidesToScroll
100
- : mediaGallery.value.length - props.slidesToShow;
101
- } else {
102
- step =
103
- currentThumb.value - props.slidesToScroll > 0
104
- ? currentThumb.value - props.slidesToScroll
105
- : 0;
106
- }
107
- }
108
- currentThumb.value = step;
109
- let xAxis = 0;
110
- let yAxis = 0;
111
- if (galleryPosition.value === "left") {
112
- yAxis = scrollPx.value * currentThumb.value;
113
- } else {
114
- xAxis = scrollPx.value * currentThumb.value;
115
- }
116
- imageThumbsTrackStyle.value = {
117
- ...imageThumbsTrackStyle.value,
118
- transform: `translate3d(-${xAxis}px, -${yAxis}px, 0px)`,
119
- transition: `transform ${speed.value}ms ease 0s`,
120
- };
121
- }
122
-
123
- function previous() {
124
- if (currentThumb.value <= 0) {
125
- return;
126
- }
127
- move("previous");
128
- }
129
-
130
- function next() {
131
- if (currentThumb.value + props.slidesToShow >= mediaGallery.value.length) {
132
- return;
133
- }
134
- move("next");
135
- }
136
- </script>
137
-
138
- <template>
139
- <div
140
- :class="{
141
- 'opacity-0': isLoading,
142
- 'flex gap-10': true,
143
- 'flex-col-reverse': galleryPosition === 'underneath',
144
- }"
145
- >
146
- <div
147
- :class="{
148
- 'hidden lg:flex basis-20 relative flex-col items-center':
149
- galleryPosition === 'left',
150
- 'flex relative w-full': galleryPosition === 'underneath',
151
- }"
152
- >
153
- <button
154
- v-if="mediaGallery.length > slidesToShow"
155
- class="disabled:opacity-10 p-1"
156
- aria-label="Previous image"
157
- @click="previous"
158
- >
159
- <div
160
- class="h-7 w-7"
161
- :class="{
162
- 'i-carbon-chevron-up': galleryPosition === 'left',
163
- 'i-carbon-chevron-left': galleryPosition !== 'left',
164
- }"
165
- />
166
- </button>
167
- <span class="sr-only">Previous image</span>
168
- <div
169
- ref="imageThumbs"
170
- class="overflow-hidden -my-2.5"
171
- :style="imageThumbsStyle"
172
- >
173
- <div
174
- ref="imageThumbsTrack"
175
- :class="{
176
- flex: true,
177
- 'flex-col': galleryPosition === 'left',
178
- }"
179
- :style="imageThumbsTrackStyle"
180
- >
181
- <div
182
- v-for="(image, i) in mediaGallery"
183
- :key="image.media.url"
184
- :class="{
185
- 'py-2.5': galleryPosition === 'left',
186
- 'flex-1 px-2.5': galleryPosition === 'underneath',
187
- }"
188
- >
189
- <div
190
- class="w-20 h-20 overflow-hidden cursor-pointer p-1 border-secondary-200 rounded transition duration-150 ease-in-out"
191
- :class="{
192
- border: i !== currentIndex,
193
- 'border-indigo-500 border-3': i === currentIndex,
194
- }"
195
- @click="() => changeCover(i)"
196
- >
197
- <div v-if="isSpatial(image.media)" class="h-full relative">
198
- <CmsElementImageGallery3dPlaceholder
199
- class="w-full h-full object-center"
200
- />
201
- <span
202
- class="absolute bottom-0 text-sm bg-gray rounded px-1 text-white"
203
- >
204
- 3D</span
205
- >
206
- </div>
207
- <img
208
- v-else
209
- loading="lazy"
210
- :src="image.media.url"
211
- class="w-full h-full object-center object-cover"
212
- alt="Product image"
213
- />
214
- </div>
215
- </div>
216
- </div>
217
- </div>
218
- <button
219
- v-if="mediaGallery.length > slidesToShow"
220
- class="disabled:opacity-10 p-1"
221
- aria-label="Next image"
222
- @click="next"
223
- >
224
- <span class="sr-only">Next image</span>
225
- <div
226
- class="h-7 w-7"
227
- :class="{
228
- 'i-carbon-chevron-down': galleryPosition === 'left',
229
- 'i-carbon-chevron-right': galleryPosition !== 'left',
230
- }"
231
- />
232
- </button>
233
- </div>
234
- <div class="flex-1 overflow-hidden">
235
- <SwSlider
236
- ref="imageSlider"
237
- :config="props.content.config"
238
- @change-slide="handleChangeSlide"
239
- >
240
- <CmsElementImage
241
- v-for="image of mediaGallery"
242
- :key="image.media.url"
243
- :image-gallery="true"
244
- :content="{ data: image, config: props.content.config } as any"
245
- />
246
- </SwSlider>
247
- </div>
248
- </div>
249
- </template>
@@ -1,123 +0,0 @@
1
- <script setup lang="ts">
2
- import type { CmsElementProductDescriptionReviews } from "@shopware/composables";
3
- import { useCmsTranslations } from "@shopware/composables";
4
- import { getProductName, getTranslatedProperty } from "@shopware/helpers";
5
- import { defu } from "defu";
6
- import { type Ref, computed, onMounted, ref } from "vue";
7
- import xss from "xss";
8
- import { useProduct } from "#imports";
9
- import type { Schemas } from "#shopware";
10
-
11
- const props = defineProps<{
12
- content: CmsElementProductDescriptionReviews;
13
- }>();
14
-
15
- type Translations = {
16
- product: {
17
- description: string;
18
- reviews: string;
19
- messages: {
20
- reviewAdded: string;
21
- };
22
- };
23
- };
24
-
25
- let translations: Translations = {
26
- product: {
27
- description: "Description",
28
- reviews: "Reviews",
29
- messages: {
30
- reviewAdded: "Thank you for submitting your review",
31
- },
32
- },
33
- };
34
- translations = defu(useCmsTranslations(), translations) as Translations;
35
-
36
- const currentTab = ref<number>(1);
37
- const { product } = useProduct(props.content.data?.product);
38
-
39
- const description = computed(() =>
40
- xss(getTranslatedProperty(product.value, "description")),
41
- );
42
-
43
- const toggleTabs = (tabNumber: number) => {
44
- currentTab.value = tabNumber;
45
- };
46
-
47
- const reviews: Ref<Schemas["ProductReview"][]> = ref([]);
48
-
49
- onMounted(async () => {
50
- if (props.content.data?.reviews?.elements) {
51
- reviews.value = props.content.data.reviews.elements;
52
- }
53
- });
54
- </script>
55
-
56
- <template>
57
- <div
58
- v-if="product"
59
- class="cms-block-product-description-reviews flex flex-wrap"
60
- >
61
- <div class="w-full">
62
- <ul
63
- class="flex flex-wrap text-sm font-medium list-none text-center text-secondary-500 border-b border-secondary-200 dark:border-secondary-500 dark:text-secondary-400"
64
- >
65
- <li class="mr-2 text-center">
66
- <a
67
- class="font-bold uppercase px-5 py-3 block leading-normal cursor-pointer"
68
- :class="[
69
- currentTab !== 1
70
- ? 'text-secondary-500 bg-white'
71
- : 'text-white bg-secondary-500',
72
- ]"
73
- @click="() => toggleTabs(1)"
74
- >
75
- <i class="fas fa-space-shuttle text-base mr-1" />
76
- {{ translations.product.description }}
77
- </a>
78
- </li>
79
- <li class="mr-2 text-center">
80
- <a
81
- class="font-bold uppercase px-5 py-3 block leading-normal cursor-pointer"
82
- :class="[
83
- currentTab !== 2
84
- ? 'text-secondary-500 bg-white'
85
- : 'text-white bg-secondary-500',
86
- ]"
87
- data-testid="product-reviews-tab"
88
- @click="() => toggleTabs(2)"
89
- >
90
- <i class="fas fa-cog text-base mr-1" />
91
- {{ translations.product.reviews }} ({{ reviews.length }})
92
- </a>
93
- </li>
94
- </ul>
95
- <div class="relative flex flex-col min-w-0 break-words w-full mb-6">
96
- <div class="px-4 py-5 flex-auto">
97
- <div class="tab-content tab-space">
98
- <div
99
- :class="[
100
- 'cms-block-product-description-reviews__description',
101
- currentTab !== 1 ? 'hidden' : 'block',
102
- ]"
103
- >
104
- <p class="text-xl font-bold mt-3">
105
- {{ getProductName({ product }) }}
106
- </p>
107
- <!-- eslint-disable-next-line vue/no-v-html -->
108
- <div class="mt-2" v-html="description"></div>
109
- </div>
110
- <div
111
- :class="[
112
- 'cms-block-product-description-reviews__reviews',
113
- currentTab !== 2 ? 'hidden' : 'block',
114
- ]"
115
- >
116
- <SwProductReviews :product="product" :reviews="reviews" />
117
- </div>
118
- </div>
119
- </div>
120
- </div>
121
- </div>
122
- </div>
123
- </template>
@@ -1,10 +0,0 @@
1
- <script setup lang="ts">
2
- import type { CmsElementProductName } from "@shopware/composables";
3
-
4
- defineProps<{
5
- content: CmsElementProductName;
6
- }>();
7
- </script>
8
- <template>
9
- <CmsElementText :content="content as any" />
10
- </template>
@@ -1,80 +0,0 @@
1
- <script setup lang="ts">
2
- import type {
3
- CmsElementProductSlider,
4
- SliderElementConfig,
5
- } from "@shopware/composables";
6
- import { computed, onMounted, ref, useTemplateRef } from "vue";
7
- import type { ComputedRef } from "vue";
8
- import { useCmsElementConfig } from "#imports";
9
-
10
- const props = defineProps<{
11
- content: CmsElementProductSlider;
12
- }>();
13
- const { getConfigValue } = useCmsElementConfig(props.content);
14
-
15
- const productSlider = useTemplateRef("productSlider");
16
- const slidesToShow = ref<number>();
17
- const products = computed(() => props.content?.data?.products ?? []);
18
- const config: ComputedRef<SliderElementConfig> = computed(() => ({
19
- minHeight: {
20
- value: "300px",
21
- source: "static",
22
- },
23
- verticalAlign: {
24
- source: "static",
25
- value: getConfigValue("verticalAlign") || "",
26
- },
27
- displayMode: {
28
- value: "contain",
29
- source: "static",
30
- },
31
- navigationDots: {
32
- value: "",
33
- source: "static",
34
- },
35
- navigationArrows: {
36
- value: getConfigValue("navigation") ? "outside" : "",
37
- source: "static",
38
- },
39
- }));
40
-
41
- onMounted(() => {
42
- setTimeout(() => {
43
- let temp = 1;
44
- const minWidth = +getConfigValue("elMinWidth").replace(/\D+/g, "");
45
- if (productSlider.value?.clientWidth) {
46
- temp = Math.ceil(productSlider.value?.clientWidth / (minWidth * 1.2));
47
- }
48
- slidesToShow.value = temp;
49
- }, 100);
50
- });
51
-
52
- const autoplay = computed(() => getConfigValue("rotate"));
53
- const title = computed(() => getConfigValue("title"));
54
- const border = computed(() => getConfigValue("border"));
55
- </script>
56
- <template>
57
- <div ref="productSlider" class="cms-element-product-slider">
58
- <h3 v-if="title" class="mb-5 text-lg font-bold text-secondary-700">
59
- {{ title }}
60
- </h3>
61
- <div :class="{ 'py-5 border border-secondary-300': border }">
62
- <SwSlider
63
- :config="config"
64
- gap="1.25rem"
65
- :slides-to-show="slidesToShow"
66
- :slides-to-scroll="1"
67
- :autoplay="autoplay"
68
- >
69
- <SwProductCard
70
- v-for="product of products"
71
- :key="product.id"
72
- class="h-full"
73
- :product="product"
74
- :layout-type="getConfigValue('boxLayout')"
75
- :display-mode="getConfigValue('displayMode')"
76
- />
77
- </SwSlider>
78
- </div>
79
- </div>
80
- </template>
@@ -1,12 +0,0 @@
1
- <script setup lang="ts">
2
- import type { CmsElementSidebarFilter } from "@shopware/composables";
3
-
4
- defineProps<{
5
- content: CmsElementSidebarFilter;
6
- }>();
7
- </script>
8
- <template>
9
- <div class="max-w-screen-xl mx-auto">
10
- <SwProductListingFilters :content="content" />
11
- </div>
12
- </template>
@@ -1,41 +0,0 @@
1
- <script setup lang="ts">
2
- import { useCmsSection } from "@shopware/composables";
3
- import type { CmsSectionSidebar } from "@shopware/composables";
4
- import { computed } from "vue";
5
-
6
- const props = defineProps<{
7
- content: CmsSectionSidebar;
8
- }>();
9
- const { getPositionContent } = useCmsSection(props.content);
10
-
11
- const sidebarBlocks = getPositionContent("sidebar");
12
- const mainBlocks = getPositionContent("main");
13
- const mobileBehavior = computed(() => props.content.mobileBehavior);
14
- </script>
15
-
16
- <template>
17
- <div class="cms-section-sidebar grid grid-cols-12 md:grid">
18
- <div class="col-span-12 md:col-span-7 lg:col-span-9 order-1 md:order-2">
19
- <CmsGenericBlock
20
- v-for="cmsBlock in mainBlocks"
21
- :key="cmsBlock.id"
22
- class="overflow-auto"
23
- :content="cmsBlock"
24
- />
25
- </div>
26
- <div
27
- :class="{
28
- 'align-top col-span-12 md:col-span-5 lg:col-span-3 order-2 md:order-1':
29
- mobileBehavior !== 'hidden',
30
- 'hidden md:block': mobileBehavior === 'hidden',
31
- }"
32
- >
33
- <CmsGenericBlock
34
- v-for="cmsBlock in sidebarBlocks"
35
- :key="cmsBlock.id"
36
- class="overflow-auto"
37
- :content="cmsBlock"
38
- />
39
- </div>
40
- </div>
41
- </template>
@@ -1,44 +0,0 @@
1
- <template>
2
- <div
3
- role="status"
4
- class="max-w-sm p-4 border border-gray-200 rounded shadow animate-pulse md:p-6 dark:border-gray-700"
5
- >
6
- <div
7
- class="flex items-center justify-center h-48 mb-4 bg-gray-300 rounded dark:bg-gray-700"
8
- >
9
- <svg
10
- class="w-10 h-10 text-gray-200 dark:text-gray-600"
11
- aria-hidden="true"
12
- xmlns="http://www.w3.org/2000/svg"
13
- fill="currentColor"
14
- viewBox="0 0 16 20"
15
- >
16
- <path
17
- d="M14.066 0H7v5a2 2 0 0 1-2 2H0v11a1.97 1.97 0 0 0 1.934 2h12.132A1.97 1.97 0 0 0 16 18V2a1.97 1.97 0 0 0-1.934-2ZM10.5 6a1.5 1.5 0 1 1 0 2.999A1.5 1.5 0 0 1 10.5 6Zm2.221 10.515a1 1 0 0 1-.858.485h-8a1 1 0 0 1-.9-1.43L5.6 10.039a.978.978 0 0 1 .936-.57 1 1 0 0 1 .9.632l1.181 2.981.541-1a.945.945 0 0 1 .883-.522 1 1 0 0 1 .879.529l1.832 3.438a1 1 0 0 1-.031.988Z"
18
- />
19
- <path
20
- d="M5 5V.13a2.96 2.96 0 0 0-1.293.749L.879 3.707A2.98 2.98 0 0 0 .13 5H5Z"
21
- />
22
- </svg>
23
- </div>
24
- <div
25
- class="h-2.5 bg-gray-200 rounded-full dark:bg-gray-700 w-48 mb-4"
26
- ></div>
27
- <div class="h-2 bg-gray-200 rounded-full dark:bg-gray-700 mb-2.5"></div>
28
- <div class="h-2.5 bg-gray-200 rounded-full dark:bg-gray-700 mb-2.5"></div>
29
- <div class="mt-4 text-right">
30
- <div
31
- class="mt-8 h-2 bg-gray-200 rounded-full dark:bg-gray-700 w-24"
32
- ></div>
33
- <button
34
- disabled
35
- tabindex="-1"
36
- class="select-none font-sans font-bold text-center uppercase transition-all disabled:opacity-50 disabled:shadow-none disabled:pointer-events-none text-xs py-3 px-6 rounded-lg text-white shadow-gray-900/10 hover:shadow-gray-900/20 focus:opacity-[0.85] focus:shadow-none active:opacity-[0.85] active:shadow-none h-8 w-20 bg-gray-300 shadow-none hover:shadow-none"
37
- type="button"
38
- >
39
- &nbsp;
40
- </button>
41
- </div>
42
- <span class="sr-only">Loading...</span>
43
- </div>
44
- </template>
@@ -1,72 +0,0 @@
1
- //@ts-nocheck
2
- /**
3
- * Based on the https://github.com/HCESrl/html-to-vue
4
- */
5
- import { parse } from "html-to-ast";
6
- import type { NodeObject } from "./getOptionsFromNode";
7
-
8
- /**
9
- * Visit each node in the AST - with callback (adapted from https://lihautan.com/manipulating-ast-with-javascript/)
10
- * @param {*} ast html-parse-stringify AST
11
- * @param {*} callback
12
- */
13
- function _visitAST(ast, callback) {
14
- function _visit(node, parent, key, index) {
15
- callback(node, parent, key, index);
16
- if (Array.isArray(node)) {
17
- // node is an array
18
- node.forEach((value, index) => {
19
- _visit.call(this, value, node, null, index);
20
- });
21
- } else if (isNode(node)) {
22
- const keys = Object.keys(node);
23
- for (let i = 0; i < keys.length; i++) {
24
- const child = node[keys[i]];
25
- if (Array.isArray(child)) {
26
- for (let j = 0; j < child.length; j++) {
27
- _visit.call(this, child[j], node, key, j);
28
- }
29
- } else if (isNode(child)) {
30
- _visit.call(this, child, node, key, undefined);
31
- }
32
- }
33
- }
34
- }
35
- _visit.call(this, ast, null, undefined, undefined);
36
- }
37
-
38
- /**
39
- *
40
- * @param node html-parse-stringify AST node
41
- * @returns {boolean|boolean}
42
- */
43
- export function isNode(node: NodeObject) {
44
- return typeof node === "object" && typeof node.type !== "undefined";
45
- }
46
-
47
- export function generateAST(html) {
48
- return parse(html);
49
- }
50
-
51
- /**
52
- * Converts ast html nodes in vue components
53
- * @param ast
54
- * @param config
55
- * @returns {*}
56
- */
57
- export function rectifyAST(ast, config) {
58
- const _ast = JSON.parse(JSON.stringify(ast));
59
- const keys = config.extraComponentsMap
60
- ? Object.keys(config.extraComponentsMap)
61
- : [];
62
- _visitAST(_ast, (node) => {
63
- // checking whether the AST has some components that has to become Vue Components
64
- for (let i = 0; i < keys.length; i++) {
65
- const currentKey = keys[i];
66
- if (config.extraComponentsMap[currentKey].conditions(node)) {
67
- node.name = currentKey;
68
- }
69
- }
70
- });
71
- return _ast;
72
- }
@@ -1,56 +0,0 @@
1
- //@ts-nocheck
2
- /**
3
- * Based on the https://github.com/HCESrl/html-to-vue
4
- */
5
-
6
- import { isNode } from "./ast";
7
- import { getOptionsFromNode } from "./getOptionsFromNode";
8
-
9
- /**
10
- * rendering the ast into vue render functions
11
- * @param {*} ast AST generated by html-parse-stringify
12
- * @param {*} config our configuration
13
- * @param {*} createElement vue's createElement
14
- * @param {*} context vue functional component context
15
- */
16
- export function renderer(ast, config, createElement, context, resolveUrl) {
17
- function _render(h, node) {
18
- if (Array.isArray(node)) {
19
- const nodes = [];
20
- // node is an array
21
- node.forEach((subnode, index) => {
22
- nodes.push(_render.call(this, h, subnode, node, null, index, h));
23
- });
24
- return nodes;
25
- }
26
-
27
- if (isNode(node)) {
28
- // node is either a node with children or a node or a text node
29
- if (node.type === "text") {
30
- return config.textTransformer(node.content); // return text
31
- }
32
- if (node.type === "tag") {
33
- const transformedNode = getOptionsFromNode(node, resolveUrl);
34
- const children = [];
35
- node.children.forEach((child, index) => {
36
- children.push(_render.call(this, h, child, transformedNode, index));
37
- });
38
- // if it's an extra component use custom renderer
39
- if (typeof config.extraComponentsMap[node.name] !== "undefined") {
40
- return config.extraComponentsMap[node.name].renderer.call(
41
- this,
42
- transformedNode,
43
- children,
44
- h,
45
- context,
46
- );
47
- }
48
- // else, create normal html element
49
- return h(node.name, transformedNode, [...children]);
50
- }
51
- }
52
- }
53
- return createElement(config.container.type, context.data, [
54
- ..._render.call(this, createElement, ast),
55
- ]);
56
- }