@shopware/cms-base-layer 3.0.1 → 4.0.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/README.md +78 -15
- package/app/app.config.ts +3 -3
- package/app/assets/css/rich-text.css +80 -0
- package/app/components/SwContactForm.vue +2 -1
- package/app/components/SwFilterChips.vue +29 -7
- package/app/components/SwNewsletterForm.vue +2 -1
- package/app/components/SwProductCard.vue +19 -3
- package/app/components/SwProductCardImage.vue +5 -1
- package/app/components/SwProductListingFilter.vue +5 -0
- package/app/components/SwProductListingFilters.vue +76 -74
- package/app/components/SwProductListingFiltersHorizontal.vue +73 -72
- package/app/components/SwStockInfo.vue +5 -2
- package/app/components/SwVariantConfigurator.vue +8 -3
- package/app/components/listing-filters/SwFilterCategories.vue +158 -0
- package/app/components/public/cms/CmsGenericBlock.vue +1 -1
- package/app/components/public/cms/CmsGenericElement.vue +1 -1
- package/app/components/public/cms/CmsNoComponent.vue +1 -1
- package/app/components/public/cms/FrontendAccountCustomerGroupRegistrationPage.vue +1 -1
- package/app/components/public/cms/block/CmsBlockCenterText.vue +2 -1
- package/app/components/public/cms/block/CmsBlockImageBubbleRow.vue +5 -1
- package/app/components/public/cms/block/CmsBlockImageFourColumn.vue +6 -2
- package/app/components/public/cms/block/CmsBlockImageGalleryBig.vue +9 -5
- package/app/components/public/cms/block/CmsBlockImageHighlightRow.vue +7 -2
- package/app/components/public/cms/block/CmsBlockImageTextRow.vue +9 -3
- package/app/components/public/cms/block/CmsBlockImageThreeCover.vue +7 -2
- package/app/components/public/cms/block/CmsBlockImageTwoColumn.vue +7 -3
- package/app/components/public/cms/block/CmsBlockProductHeading.vue +2 -1
- package/app/components/public/cms/element/CmsElementBuyBox.vue +4 -1
- package/app/components/public/cms/element/CmsElementCrossSelling.vue +2 -1
- package/app/components/public/cms/element/CmsElementHtml.vue +1 -1
- package/app/components/public/cms/element/CmsElementImage.vue +41 -2
- package/app/components/public/cms/element/CmsElementImageGallery.vue +2 -1
- package/app/components/public/cms/element/CmsElementProductDescriptionReviews.vue +18 -9
- package/app/components/public/cms/element/CmsElementText.md +77 -0
- package/app/components/public/cms/element/CmsElementText.vue +5 -32
- package/app/helpers/html-to-vue/renderToHtml.test.ts +55 -0
- package/app/helpers/html-to-vue/renderToHtml.ts +5 -1
- package/app/helpers/html-to-vue/renderer.ts +10 -1
- package/app/utils/listingFilters.test.ts +39 -0
- package/app/utils/listingFilters.ts +19 -0
- package/app/utils/routeQuery.test.ts +65 -0
- package/app/utils/routeQuery.ts +26 -0
- package/app/utils/useSelectedListingFilters.test.ts +92 -0
- package/app/utils/useSelectedListingFilters.ts +111 -0
- package/component-dirs.json +20 -0
- package/index.d.ts +2 -2
- package/nuxt.config.ts +35 -29
- package/package.json +27 -31
- package/dist/index.d.mts +0 -5
- package/dist/index.d.ts +0 -5
- package/dist/index.mjs +0 -31
- package/index.cjs +0 -7
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[](https://github.com/shopware/frontends/issues?q=is%3Aopen+is%3Aissue+label%3Acms-base)
|
|
6
6
|
[](#)
|
|
7
7
|
|
|
8
|
-
Nuxt [layer](https://nuxt.com/docs/getting-started/layers) that provides an implementation of all CMS components in Shopware [based on utility-classes](https://
|
|
8
|
+
Nuxt [layer](https://nuxt.com/docs/getting-started/layers) that provides an implementation of all CMS components in Shopware [based on utility-classes](https://developer.shopware.com/frontends/concepts/styling.html).
|
|
9
9
|
|
|
10
10
|
It is useful for projects that want to use the CMS components while keeping CMS functionality separate from the styling system and design tokens.
|
|
11
11
|
|
|
@@ -90,7 +90,7 @@ Since all CMS components are registered in your Nuxt application, you can now st
|
|
|
90
90
|
|
|
91
91
|
> `@shopware/cms-base-layer` no longer owns the default UnoCSS theme. If you want the shared Shopware Frontends design tokens and UnoCSS defaults, extend `@shopware/unocss-design-tokens-layer` as shown above.
|
|
92
92
|
|
|
93
|
-
See a [short guide](https://
|
|
93
|
+
See a [short guide](https://developer.shopware.com/frontends/guides/cms/content-pages.html#use-the-cms-base-package) on how to use `cms-base-layer` in your Nuxt project.
|
|
94
94
|
|
|
95
95
|
## Styling and Design Tokens
|
|
96
96
|
|
|
@@ -192,10 +192,18 @@ Replace standard `<img>` tags with `<NuxtImg>` to enable automatic optimization:
|
|
|
192
192
|
:src="product.cover.media.url"
|
|
193
193
|
preset="productDetail"
|
|
194
194
|
:width="800"
|
|
195
|
-
:alt="product.cover.media
|
|
195
|
+
:alt="getTranslatedProperty(product.cover.media, 'alt')"
|
|
196
196
|
/>
|
|
197
197
|
```
|
|
198
198
|
|
|
199
|
+
The media `alt` text is a translatable field, so read it through `getTranslatedProperty` rather than
|
|
200
|
+
`media.alt` — the latter always holds the system language value, no matter which language the current
|
|
201
|
+
request asks for. The helper is not auto-imported, so add it to the component's script block:
|
|
202
|
+
|
|
203
|
+
```ts
|
|
204
|
+
import { getTranslatedProperty } from "@shopware/helpers";
|
|
205
|
+
```
|
|
206
|
+
|
|
199
207
|
### Supported Modifiers
|
|
200
208
|
|
|
201
209
|
Shopware supports the following URL parameters for image transformation:
|
|
@@ -350,7 +358,7 @@ url("https://cdn.shopware.store/.../image.jpg?width=1000&fit=crop,smart&format=w
|
|
|
350
358
|
|
|
351
359
|
## LCP Image Preload
|
|
352
360
|
|
|
353
|
-
This layer includes a `useLcpImagePreload` composable that
|
|
361
|
+
This layer includes a `useLcpImagePreload` composable that preloads the first image found in CMS page content when enabled via `lcpImagePreload: true` in your `app.config.ts` (disabled by default). This targets the [Largest Contentful Paint (LCP)](https://web.dev/lcp/) element, which is often a hero background image or the first visible image element.
|
|
354
362
|
|
|
355
363
|
### How it works
|
|
356
364
|
|
|
@@ -364,12 +372,10 @@ The first image found is injected as a `<link rel="preload" as="image" fetchprio
|
|
|
364
372
|
|
|
365
373
|
### Usage
|
|
366
374
|
|
|
367
|
-
The composable is already called in `CmsPage.vue`. If you override `CmsPage`, you can use it in your custom component:
|
|
375
|
+
The composable is already called in `CmsPage.vue`. If you override `CmsPage`, you can use it in your custom component. It is auto-imported once your app extends this layer, so it needs no import statement:
|
|
368
376
|
|
|
369
377
|
```vue
|
|
370
378
|
<script setup>
|
|
371
|
-
import { useLcpImagePreload } from "@shopware/cms-base-layer/composables/useLcpImagePreload";
|
|
372
|
-
|
|
373
379
|
const props = defineProps<{ content: Schemas["CmsPage"] }>();
|
|
374
380
|
|
|
375
381
|
useLcpImagePreload(props.content?.sections || []);
|
|
@@ -384,7 +390,7 @@ Images are optimized to prevent the browser from downloading images larger than
|
|
|
384
390
|
|
|
385
391
|
### Product Card Images (`SwProductCardImage`)
|
|
386
392
|
|
|
387
|
-
The `productCard` preset only defines URL modifiers (format/quality/fit). `width`/`height
|
|
393
|
+
The `productCard` preset only defines URL modifiers (format/quality/fit). `width`/`height` and `loading` stay on the component: in `@nuxt/image` 2.1.0 a preset carries `width`/`height` only as `modifiers`, which shape the URL rather than the rendered attributes, and `loading` is not a preset field at all. `densities` is kept alongside them for consistency, though a preset would propagate it:
|
|
388
394
|
|
|
389
395
|
```ts
|
|
390
396
|
// nuxt.config.ts
|
|
@@ -442,15 +448,15 @@ export default defineAppConfig({
|
|
|
442
448
|
|
|
443
449
|
## 📘 Available components
|
|
444
450
|
|
|
445
|
-
The list of available blocks and elements is [here](https://
|
|
451
|
+
The list of available blocks and elements is [here](https://developer.shopware.com/frontends/packages/cms-base-layer.html#available-components).
|
|
446
452
|
|
|
447
453
|
## 🔄 Overwriting components
|
|
448
454
|
|
|
449
455
|
The procedure is:
|
|
450
456
|
|
|
451
|
-
- find a component in component's [list](https://
|
|
457
|
+
- find a component in component's [list](https://developer.shopware.com/frontends/packages/cms-base-layer.html#available-components), using a [Vue devtools](https://devtools.vuejs.org/) or browsing the github [repository](https://github.com/shopware/frontends/tree/main/packages/cms-base-layer/app/components)
|
|
452
458
|
- take its name
|
|
453
|
-
- create a file with the same name and place it
|
|
459
|
+
- create a file with the same name and place it under a `components` directory that your `nuxt.config.ts` registers with `global: true` — CMS components are looked up with `resolveComponent`, so an override outside a global path is never found and this layer's version keeps rendering with no error. In the starter template that directory is `app/components/cms/`; because it is registered with `pathPrefix: false`, the name comes from the filename alone and subdirectory depth under it does not matter.
|
|
454
460
|
|
|
455
461
|
✅ Thanks to this, nuxt will take the component registered in your app instead of the one registered by this nuxt layer.
|
|
456
462
|
|
|
@@ -475,7 +481,7 @@ No additional packages needed to be installed.
|
|
|
475
481
|
|
|
476
482
|
## Links
|
|
477
483
|
|
|
478
|
-
- [📘 Documentation](https://
|
|
484
|
+
- [📘 Documentation](https://developer.shopware.com/frontends)
|
|
479
485
|
|
|
480
486
|
- [👥 Community](https://discord.com/channels/1308047705309708348/1405501315160739951) (`#composable-frontend`)
|
|
481
487
|
|
|
@@ -485,9 +491,66 @@ No additional packages needed to be installed.
|
|
|
485
491
|
|
|
486
492
|
Full changelog for stable version is available [here](https://github.com/shopware/frontends/blob/main/packages/cms-base-layer/CHANGELOG.md)
|
|
487
493
|
|
|
488
|
-
### Latest changes:
|
|
494
|
+
### Latest changes: 4.0.0
|
|
495
|
+
|
|
496
|
+
### Major Changes
|
|
497
|
+
|
|
498
|
+
- [#2609](https://github.com/shopware/frontends/pull/2609) [`2dc86da`](https://github.com/shopware/frontends/commit/2dc86da2532e986ebdadd06a922e68403414a0ff) Thanks [@mkucmus](https://github.com/mkucmus)! - Make the package a plain Nuxt layer.
|
|
499
|
+
|
|
500
|
+
**Breaking:** the package now declares an `exports` map, so only the layer entry point
|
|
501
|
+
is importable. Deep imports such as
|
|
502
|
+
`@shopware/cms-base-layer/app/components/SwProductCard.vue` no longer resolve. Use the
|
|
503
|
+
auto-registered global components instead, which is what extending the layer gives you.
|
|
504
|
+
|
|
505
|
+
**Breaking:** the leftover Nuxt module is gone, so `index.cjs` and the `dist` build are
|
|
506
|
+
no longer published. The module registered a component folder that moved to `app/` a
|
|
507
|
+
while ago, so it could not work any more. Consume this package with `extends`, which
|
|
508
|
+
resolves `nuxt.config.ts`:
|
|
509
|
+
|
|
510
|
+
```ts
|
|
511
|
+
export default defineNuxtConfig({
|
|
512
|
+
extends: ["@shopware/cms-base-layer"],
|
|
513
|
+
});
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
Other changes:
|
|
517
|
+
|
|
518
|
+
- Dropped the `build` and `dev` scripts and the `unbuild` dev dependency. There is
|
|
519
|
+
nothing left to bundle.
|
|
520
|
+
- Fixed `files`. It listed `helpers` and `app.config.ts`, which do not exist at the
|
|
521
|
+
package root, and shipped the dead `dist` and `index.cjs`.
|
|
522
|
+
- Deleted `components.d.ts`. It typed two components at paths that moved to
|
|
523
|
+
`app/components/public/cms/`, and no tsconfig referenced the file.
|
|
524
|
+
- Removed two vitest aliases that pointed at files which do not exist.
|
|
525
|
+
- Removed the `check-colors` script. It read `uno.config.ts` from this package, which
|
|
526
|
+
moved to `@shopware/unocss-design-tokens-layer`, so it always found zero colors. It
|
|
527
|
+
also relied on `tsx` without declaring it.
|
|
528
|
+
|
|
529
|
+
### Minor Changes
|
|
530
|
+
|
|
531
|
+
- [#2574](https://github.com/shopware/frontends/pull/2574) [`2ddf156`](https://github.com/shopware/frontends/commit/2ddf156805b2941fe2069e78453fb3c4eb6d44ac) Thanks [@mkucmus](https://github.com/mkucmus)! - `SwProductListingFilters` and `SwProductListingFiltersHorizontal` render a new `SwFilterCategories` checkbox filter when the listing response contains the category aggregations from `getCategoryFilterAggregations()`. The selection is kept in the `categories` URL param and applied as a criteria `post-filter`. Both are search listings only (`listing-type="productSearchListing"`); on any other listing the category filter is not rendered, because the selection would neither reach the URL nor the criteria. Listings without these aggregations are unaffected.
|
|
489
532
|
|
|
490
533
|
### Patch Changes
|
|
491
534
|
|
|
492
|
-
-
|
|
493
|
-
|
|
535
|
+
- [#2645](https://github.com/shopware/frontends/pull/2645) [`7a7ce3e`](https://github.com/shopware/frontends/commit/7a7ce3e5649e7ab84f7e547ce1ebcd9413117dde) Thanks [@mkucmus](https://github.com/mkucmus)! - Ship `component-dirs.json` in the published package. `nuxt.config.ts` imports it at load time, but it was missing from `files`, so the layer failed to load when installed from the registry.
|
|
536
|
+
|
|
537
|
+
- [#2660](https://github.com/shopware/frontends/pull/2660) [`8913956`](https://github.com/shopware/frontends/commit/89139563924163e57cafdd9770fe603f2dbd8cba) Thanks [@mdanilowicz](https://github.com/mdanilowicz)! - Read translatable entity fields through their `translated` object instead of the untranslated entity root, so they follow the language of the current request instead of rendering the system language. Affected components: `SwContactForm` and `SwNewsletterForm` (salutation `displayName`), `SwStockInfo` (delivery time `name`), `SwVariantConfigurator` (property group `name` — the option values below it already used `translated`), `CmsElementBuyBox` (unit `name`), `CmsElementCrossSelling` (cross selling `name`), `CmsElementProductDescriptionReviews` (category `name`), `SwProductCardImage` and `CmsElementImageGallery` (media `alt`).
|
|
538
|
+
|
|
539
|
+
All of them now go through `getTranslatedProperty()` from `@shopware/helpers`, which falls back to the root property when `translated` is missing — so the rendered value only ever changes on storefronts whose language differs from the system language.
|
|
540
|
+
|
|
541
|
+
- [#2606](https://github.com/shopware/frontends/pull/2606) [`1c9a604`](https://github.com/shopware/frontends/commit/1c9a604e05c0fa1b27708a5338cd5690a0ff5c67) Thanks [@mkucmus](https://github.com/mkucmus)! - Stop leaking internal types into the layer's public type surface.
|
|
542
|
+
|
|
543
|
+
- `index.d.ts` now only augments the app config. It no longer re-exports `@shopware/composables` and `./.nuxt/imports`.
|
|
544
|
+
- The `#imports` shim moved to a private `types/imports.d.ts`, which is not published.
|
|
545
|
+
- `app.config` exports a plain object with `satisfies AppConfigInput` instead of calling `defineAppConfig`. No runtime change.
|
|
546
|
+
|
|
547
|
+
This fixes `Cannot find name 'ref'` and similar errors in the shared and node contexts when a project uses the Nuxt 4 project-references `tsconfig.json`.
|
|
548
|
+
|
|
549
|
+
- [#2601](https://github.com/shopware/frontends/pull/2601) [`e64d2f9`](https://github.com/shopware/frontends/commit/e64d2f9c13a99054d7e56e4b7ec2f1f23f9768b7) Thanks [@mdanilowicz](https://github.com/mdanilowicz)! - Typography for CMS-authored HTML is now scoped to a `cms-element-text` class and ships from the layer as `app/assets/css/rich-text.css`, with overflow guards for long strings, media, `pre` and tables. Headings and lists in your own markup that had no explicit size or `list-*` class were relying on the removed global rules and will change.
|
|
550
|
+
|
|
551
|
+
- [#2663](https://github.com/shopware/frontends/pull/2663) [`7020545`](https://github.com/shopware/frontends/commit/70205458cb9357a068029d0aaef41898ab94b354) Thanks [@mkucmus](https://github.com/mkucmus)! - A linked CMS image always has an accessible name now. `CmsElementImage` names the link with the element's `ariaLabel`, then the media title, then a generic fallback translatable through `cms.image.linkWithoutLabel`. A decorative image skips the media title.
|
|
552
|
+
|
|
553
|
+
- Updated dependencies [[`2ddf156`](https://github.com/shopware/frontends/commit/2ddf156805b2941fe2069e78453fb3c4eb6d44ac), [`204c8f4`](https://github.com/shopware/frontends/commit/204c8f45f737e724db6d00b80c5faef8ddb77cb4), [`7020545`](https://github.com/shopware/frontends/commit/70205458cb9357a068029d0aaef41898ab94b354), [`183c183`](https://github.com/shopware/frontends/commit/183c183f905486c27fa770fd0f4cd9993e86c20e), [`458494e`](https://github.com/shopware/frontends/commit/458494e8bd2be88d4fbf161636a109c8f4efc443), [`183c183`](https://github.com/shopware/frontends/commit/183c183f905486c27fa770fd0f4cd9993e86c20e), [`183c183`](https://github.com/shopware/frontends/commit/183c183f905486c27fa770fd0f4cd9993e86c20e), [`8913956`](https://github.com/shopware/frontends/commit/89139563924163e57cafdd9770fe603f2dbd8cba), [`458494e`](https://github.com/shopware/frontends/commit/458494e8bd2be88d4fbf161636a109c8f4efc443)]:
|
|
554
|
+
- @shopware/helpers@1.8.0
|
|
555
|
+
- @shopware/composables@1.13.0
|
|
556
|
+
- @shopware/api-client@1.6.0
|
package/app/app.config.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { AppConfigInput } from "nuxt/schema";
|
|
2
2
|
|
|
3
|
-
export default
|
|
3
|
+
export default {
|
|
4
4
|
imagePlaceholder: {
|
|
5
5
|
color: "#543B95",
|
|
6
6
|
},
|
|
@@ -16,4 +16,4 @@ export default defineAppConfig({
|
|
|
16
16
|
default: "(max-width: 768px) 50vw, 25vw",
|
|
17
17
|
},
|
|
18
18
|
unocssRuntime: true,
|
|
19
|
-
}
|
|
19
|
+
} satisfies AppConfigInput;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
.cms-element-text {
|
|
2
|
+
word-break: break-word;
|
|
3
|
+
|
|
4
|
+
:where(h1, h2, h3, h4, h5, h6) {
|
|
5
|
+
margin-bottom: var(--rte-heading-mb, 10px);
|
|
6
|
+
font-weight: var(--rte-heading-weight, 600);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
:where(h1) {
|
|
10
|
+
font-size: var(--rte-h1-size, 2.25rem);
|
|
11
|
+
line-height: var(--rte-h1-line, 2.5rem);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
:where(h2) {
|
|
15
|
+
font-size: var(--rte-h2-size, 1.75rem);
|
|
16
|
+
line-height: var(--rte-h2-line, 2rem);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
:where(h3) {
|
|
20
|
+
font-size: var(--rte-h3-size, 1.25rem);
|
|
21
|
+
line-height: var(--rte-h3-line, 1.5rem);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
:where(h4, h5, h6) {
|
|
25
|
+
font-size: var(--rte-h4-size, 1.125rem);
|
|
26
|
+
line-height: var(--rte-h4-line, 1.5rem);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
:where(p, blockquote, pre) {
|
|
30
|
+
margin-bottom: var(--rte-flow, 1rem);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
:where(ol, ul, dl) {
|
|
34
|
+
list-style-type: disc;
|
|
35
|
+
padding-left: var(--rte-list-indent, 40px);
|
|
36
|
+
margin-top: 0;
|
|
37
|
+
margin-bottom: var(--rte-flow, 1rem);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
:where(ol) {
|
|
41
|
+
list-style-type: decimal;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
:where(img, iframe, video) {
|
|
45
|
+
max-width: 100%;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
:where(img) {
|
|
49
|
+
height: auto;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
:where(table) {
|
|
53
|
+
width: 100%;
|
|
54
|
+
margin-bottom: var(--rte-flow, 1rem);
|
|
55
|
+
border-collapse: collapse;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
:where(th, td) {
|
|
59
|
+
padding: var(--rte-cell-padding, 0.5rem 0.75rem);
|
|
60
|
+
border: 1px solid var(--rte-border-color, #e5e7eb);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
:where(th, thead td) {
|
|
64
|
+
text-align: left;
|
|
65
|
+
font-weight: var(--rte-heading-weight, 600);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
:where(hr) {
|
|
69
|
+
margin: var(--rte-flow, 1rem) 0;
|
|
70
|
+
border-top: 1px solid var(--rte-border-color, #e5e7eb);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
> :last-child {
|
|
74
|
+
margin-bottom: 0;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
:where(.sw-text-editor-table__col-selector) {
|
|
78
|
+
display: none;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -3,6 +3,7 @@ import { ApiClientError } from "@shopware/api-client";
|
|
|
3
3
|
import type { ApiError } from "@shopware/api-client";
|
|
4
4
|
import type { CmsElementForm } from "@shopware/composables";
|
|
5
5
|
import { useCmsTranslations } from "@shopware/composables";
|
|
6
|
+
import { getTranslatedProperty } from "@shopware/helpers";
|
|
6
7
|
import { useVuelidate } from "@vuelidate/core";
|
|
7
8
|
import { email, minLength, required } from "@vuelidate/validators";
|
|
8
9
|
import { defu } from "defu";
|
|
@@ -189,7 +190,7 @@ const invokeSubmit = async () => {
|
|
|
189
190
|
:key="salutation.id"
|
|
190
191
|
:value="salutation.id"
|
|
191
192
|
>
|
|
192
|
-
{{ salutation
|
|
193
|
+
{{ getTranslatedProperty(salutation, "displayName") }}
|
|
193
194
|
</option>
|
|
194
195
|
</select>
|
|
195
196
|
</div>
|
|
@@ -6,6 +6,9 @@ import type { Schemas } from "#shopware";
|
|
|
6
6
|
type FilterState = {
|
|
7
7
|
manufacturer: Set<string>;
|
|
8
8
|
properties: Set<string>;
|
|
9
|
+
// Optional: consumers building their own filter-state object predate this
|
|
10
|
+
// field, so a missing one must render no category chips, not throw.
|
|
11
|
+
categories?: Set<string>;
|
|
9
12
|
"min-price": number | undefined;
|
|
10
13
|
"max-price": number | undefined;
|
|
11
14
|
rating: number | undefined;
|
|
@@ -70,13 +73,14 @@ const activeChips = computed(() => {
|
|
|
70
73
|
}
|
|
71
74
|
|
|
72
75
|
// Add manufacturer filters
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
)
|
|
78
|
-
|
|
79
|
-
|
|
76
|
+
const manufacturerFilter = props.availableFilters.find(
|
|
77
|
+
(f) => f.code === "manufacturer",
|
|
78
|
+
);
|
|
79
|
+
if (manufacturerFilter?.entities) {
|
|
80
|
+
for (const manufacturerId of props.filters.manufacturer) {
|
|
81
|
+
const entity = manufacturerFilter.entities.find(
|
|
82
|
+
(e) => e.id === manufacturerId,
|
|
83
|
+
);
|
|
80
84
|
const name = getTranslatedName(entity);
|
|
81
85
|
if (name) {
|
|
82
86
|
chips.push({
|
|
@@ -88,6 +92,24 @@ const activeChips = computed(() => {
|
|
|
88
92
|
}
|
|
89
93
|
}
|
|
90
94
|
|
|
95
|
+
// Add category filters
|
|
96
|
+
const categoryFilter = props.availableFilters.find(
|
|
97
|
+
(f) => f.code === "categories",
|
|
98
|
+
);
|
|
99
|
+
if (categoryFilter?.entities) {
|
|
100
|
+
for (const categoryId of props.filters.categories ?? []) {
|
|
101
|
+
const entity = categoryFilter.entities.find((e) => e.id === categoryId);
|
|
102
|
+
const name = getTranslatedName(entity);
|
|
103
|
+
if (name) {
|
|
104
|
+
chips.push({
|
|
105
|
+
label: name,
|
|
106
|
+
code: "categories",
|
|
107
|
+
value: categoryId,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
91
113
|
// Add price filters
|
|
92
114
|
if (props.filters["min-price"] || props.filters["max-price"]) {
|
|
93
115
|
const min = props.filters["min-price"] || 0;
|
|
@@ -3,6 +3,7 @@ import { ApiClientError } from "@shopware/api-client";
|
|
|
3
3
|
import type { ApiError } from "@shopware/api-client";
|
|
4
4
|
import type { CmsElementForm } from "@shopware/composables";
|
|
5
5
|
import { useCmsTranslations } from "@shopware/composables";
|
|
6
|
+
import { getTranslatedProperty } from "@shopware/helpers";
|
|
6
7
|
import { useVuelidate } from "@vuelidate/core";
|
|
7
8
|
import type { ValidationRuleWithoutParams } from "@vuelidate/core";
|
|
8
9
|
import { email, required } from "@vuelidate/validators";
|
|
@@ -247,7 +248,7 @@ const invokeSubmit = async () => {
|
|
|
247
248
|
:key="salutation.id"
|
|
248
249
|
:value="salutation.id"
|
|
249
250
|
>
|
|
250
|
-
{{ salutation
|
|
251
|
+
{{ getTranslatedProperty(salutation, "displayName") }}
|
|
251
252
|
</option>
|
|
252
253
|
</select>
|
|
253
254
|
</div>
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
useAddToCart,
|
|
18
18
|
useCartErrorParamsResolver,
|
|
19
19
|
useCartNotification,
|
|
20
|
+
useNavigationContext,
|
|
20
21
|
useNotifications,
|
|
21
22
|
useProductWishlist,
|
|
22
23
|
useUrlResolver,
|
|
@@ -144,10 +145,25 @@ const productManufacturer = computed(() =>
|
|
|
144
145
|
getProductManufacturerName(product.value),
|
|
145
146
|
);
|
|
146
147
|
|
|
147
|
-
const {
|
|
148
|
-
const
|
|
149
|
-
|
|
148
|
+
const { foreignKey: currentNavigationId, routeName } = useNavigationContext();
|
|
149
|
+
const referrerCategoryId = computed(() =>
|
|
150
|
+
isProductListing && routeName.value === "frontend.navigation.page"
|
|
151
|
+
? currentNavigationId.value
|
|
152
|
+
: undefined,
|
|
150
153
|
);
|
|
154
|
+
const { getUrlPrefix } = useUrlResolver();
|
|
155
|
+
const productLink = computed(() => {
|
|
156
|
+
const productRoute = getProductRoute(product.value);
|
|
157
|
+
const productRouteWithReferrer = {
|
|
158
|
+
...productRoute,
|
|
159
|
+
state: {
|
|
160
|
+
...productRoute.state,
|
|
161
|
+
referrerCategoryId: referrerCategoryId.value,
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
return buildUrlPrefix(productRouteWithReferrer, getUrlPrefix());
|
|
166
|
+
});
|
|
151
167
|
</script>
|
|
152
168
|
|
|
153
169
|
<template>
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
type UrlRouteOutput,
|
|
4
4
|
getSmallestThumbnailUrl,
|
|
5
|
+
getTranslatedProperty,
|
|
5
6
|
isProductOnSale,
|
|
6
7
|
isProductTopSeller,
|
|
7
8
|
} from "@shopware/helpers";
|
|
@@ -37,7 +38,10 @@ const coverSrcPath = computed(() => {
|
|
|
37
38
|
});
|
|
38
39
|
|
|
39
40
|
const coverAlt = computed(() => {
|
|
40
|
-
return
|
|
41
|
+
return (
|
|
42
|
+
getTranslatedProperty(props.product?.cover?.media, "alt") ||
|
|
43
|
+
props.product?.translated?.name
|
|
44
|
+
);
|
|
41
45
|
});
|
|
42
46
|
|
|
43
47
|
const isOnSale = computed(() => isProductOnSale(props.product));
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { computed } from "vue";
|
|
3
3
|
import type { Component } from "vue";
|
|
4
4
|
|
|
5
|
+
import SwFilterCategoriesVue from "./listing-filters/SwFilterCategories.vue";
|
|
5
6
|
import SwFilterPriceVue from "./listing-filters/SwFilterPrice.vue";
|
|
6
7
|
import SwFilterPropertiesVue from "./listing-filters/SwFilterProperties.vue";
|
|
7
8
|
import SwFilterRatingVue from "./listing-filters/SwFilterRating.vue";
|
|
@@ -11,6 +12,7 @@ const {
|
|
|
11
12
|
filter,
|
|
12
13
|
selectedManufacturer,
|
|
13
14
|
selectedProperties,
|
|
15
|
+
selectedCategories = new Set<string>(),
|
|
14
16
|
selectedMinPrice,
|
|
15
17
|
selectedMaxPrice,
|
|
16
18
|
selectedRating,
|
|
@@ -20,6 +22,7 @@ const {
|
|
|
20
22
|
filter: ListingFilter;
|
|
21
23
|
selectedManufacturer: Set<string>;
|
|
22
24
|
selectedProperties: Set<string>;
|
|
25
|
+
selectedCategories?: Set<string>;
|
|
23
26
|
selectedMinPrice: number | undefined;
|
|
24
27
|
selectedMaxPrice: number | undefined;
|
|
25
28
|
selectedRating: number | undefined;
|
|
@@ -40,11 +43,13 @@ const transformedFilters = computed(() => ({
|
|
|
40
43
|
"shipping-free": selectedShippingFree,
|
|
41
44
|
manufacturer: [...selectedManufacturer],
|
|
42
45
|
properties: [...selectedProperties],
|
|
46
|
+
categories: [...selectedCategories],
|
|
43
47
|
}));
|
|
44
48
|
|
|
45
49
|
const filterComponent = computed<Component | undefined>(() => {
|
|
46
50
|
const componentMap: Record<string, Component> = {
|
|
47
51
|
manufacturer: SwFilterPropertiesVue,
|
|
52
|
+
categories: SwFilterCategoriesVue,
|
|
48
53
|
price: SwFilterPriceVue,
|
|
49
54
|
rating: SwFilterRatingVue,
|
|
50
55
|
"shipping-free": SwFilterShippingFreeVue,
|