diff --git a/CHANGELOG.md b/CHANGELOG.md index a4c71f30fe..6e8b661069 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add unit tests for `core/modules/order` - @dz3n (#3466) - Add unit tests for `core/modules/user` - @dz3n (#3470) - Add to cart from Wishlist and Product listing for simple products - @Dnd-Dboy, @dz3n (#2637) +- Add global Category and Breadcrumb filters, defined in local.json - @grimasod (#3691) ### Fixed - Fixed problem with cutting image height in category page on 1024px+ screen res - @AdKamil (#3781) @@ -22,6 +23,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed sorting on category page and product tile sizing - @andrzejewsky (#3817) - Redirect from simple product using url_path - @benjick (#3804) - Mount app in 'beforeResolve' if it's not dispatched in 'onReady' - @gibkigonzo (#3669) +- Fixed AMP pages - @andrzejewsky (#3799) +- Fixed Product page breadcrumbs problem when products are in multiple categories in different branches of the category tree - @grimasod (#3691) - Change translation from jp-JP to ja-JP - @gibkigonzo (#3824) ### Changed / Improved @@ -34,6 +37,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Add defense for incomplete config in preferchCachedAttributes helper + ### Fixed - Fixed deprecated getter in cmsBlock store - @resubaka (#3683) - Fixed problem around dynamic urls when default storeView is set with appendStoreCode false and url set to / . @resubaka (#3685) @@ -57,7 +61,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed missing parameter in the compare list - @andrzejewsky (#3757) - Fixed product link on mobile - @andrzejewsky (#3772) - Custom module `ConfigProvider` aren't called anymore - @cewald (#3797) -- Fixed AMP pages - @andrzejewsky (#3799) ### Added - Added Estonian translations - @alphpkeemik diff --git a/config/default.json b/config/default.json index b04fe152e8..1a5ba573aa 100644 --- a/config/default.json +++ b/config/default.json @@ -177,6 +177,8 @@ "category": { "includeFields": [ "id", "*.children_data.id", "*.id", "children_count", "sku", "name", "is_active", "parent_id", "level", "url_key", "url_path", "product_count", "path", "position"], "excludeFields": [ "sgn" ], + "filterFields": {}, + "breadcrumbFilterFields": {}, "categoriesRootCategorylId": 2, "categoriesDynamicPrefetchLevel": 2, "categoriesDynamicPrefetch": true, diff --git a/core/data-resolver/CategoryService.ts b/core/data-resolver/CategoryService.ts index d9c04eb9a7..8eed001af3 100644 --- a/core/data-resolver/CategoryService.ts +++ b/core/data-resolver/CategoryService.ts @@ -25,10 +25,14 @@ const getCategories = async ({ } for (var [key, value] of Object.entries(filters)) { - if (Array.isArray(value)) { - searchQuery = searchQuery.applyFilter({key: key, value: {'in': value}}) - } else { - searchQuery = searchQuery.applyFilter({key: key, value: {'eq': value}}) + if (value !== null) { + if (Array.isArray(value)) { + searchQuery = searchQuery.applyFilter({key: key, value: {'in': value}}) + } else if (typeof value === 'object') { + searchQuery = searchQuery.applyFilter({key: key, value: value}) + } else { + searchQuery = searchQuery.applyFilter({key: key, value: {'eq': value}}) + } } } diff --git a/core/lib/multistore.ts b/core/lib/multistore.ts index 249b45ee51..57e0e727c7 100644 --- a/core/lib/multistore.ts +++ b/core/lib/multistore.ts @@ -148,7 +148,7 @@ export function localizedDispatcherRouteName (routeName: string, storeCode: stri return routeName } -export function localizedRoute (routeObj: LocalizedRoute | string | RouteConfig | RawLocation, storeCode: string): any { +export function localizedRoute (routeObj: LocalizedRoute | string | RouteConfig | RawLocation, storeCode: string = null): any { if (!storeCode) { storeCode = currentStoreView().storeCode } diff --git a/core/modules/breadcrumbs/components/Breadcrumbs.ts b/core/modules/breadcrumbs/components/Breadcrumbs.ts index f1e96e11c0..b0c097acbf 100644 --- a/core/modules/breadcrumbs/components/Breadcrumbs.ts +++ b/core/modules/breadcrumbs/components/Breadcrumbs.ts @@ -1,10 +1,42 @@ +import { localizedRoute, currentStoreView } from '@vue-storefront/core/lib/multistore' +import i18n from '@vue-storefront/i18n' +import { mapGetters } from 'vuex' + export const Breadcrumbs = { computed: { - routes () { - return this.$store.state.breadcrumbs.routes + ...mapGetters({ + getBreadcrumbsRoutes: 'breadcrumbs/getBreadcrumbsRoutes', + getBreadcrumbsCurrent: 'breadcrumbs/getBreadcrumbsCurrent' + }), + paths () { + const routes = this.routes ? this.routes : this.getBreadcrumbsRoutes + + if (this.withHomepage) { + return [ + { name: i18n.t('Homepage'), route_link: localizedRoute('/', currentStoreView().storeCode) }, + ...routes + ] + } + + return routes }, current () { - return this.$store.state.breadcrumbs.current + return this.activeRoute || this.getBreadcrumbsCurrent + } + }, + props: { + routes: { + type: Array, + required: false, + default: null + }, + withHomepage: { + type: Boolean, + default: false + }, + activeRoute: { + type: String, + default: '' } } } diff --git a/core/modules/breadcrumbs/store/index.ts b/core/modules/breadcrumbs/store/index.ts index b18501a717..02f195275d 100644 --- a/core/modules/breadcrumbs/store/index.ts +++ b/core/modules/breadcrumbs/store/index.ts @@ -15,5 +15,9 @@ export const breadcrumbsStore = { set ({ commit }, payload) { commit('set', payload) } + }, + getters: { + getBreadcrumbsRoutes: (state) => state.routes, + getBreadcrumbsCurrent: (state) => state.current } } diff --git a/core/modules/catalog-next/store/category/actions.ts b/core/modules/catalog-next/store/category/actions.ts index cf1ba8c136..53ab1fa6e2 100644 --- a/core/modules/catalog-next/store/category/actions.ts +++ b/core/modules/catalog-next/store/category/actions.ts @@ -21,6 +21,7 @@ import chunk from 'lodash-es/chunk' import Product from 'core/modules/catalog/types/Product'; import omit from 'lodash-es/omit' import config from 'config' +import { parseCategoryPath } from '@vue-storefront/core/modules/breadcrumbs/helpers' const actions: ActionTree = { async loadCategoryProducts ({ commit, getters, dispatch, rootState }, { route, category, pageSize = 50 } = {}) { @@ -127,20 +128,27 @@ const actions: ActionTree = { return CategoryService.getCategories(categorySearchOptions) }, async loadCategories ({ commit, getters }, categorySearchOptions: DataResolver.CategorySearchOptions): Promise { - const searchingByIds = categorySearchOptions && categorySearchOptions.filters && categorySearchOptions.filters.id + const searchingByIds = !(!categorySearchOptions || !categorySearchOptions.filters || !categorySearchOptions.filters.id) const searchedIds: string[] = searchingByIds ? (categorySearchOptions.filters.id as string[]) : [] - if (searchingByIds) { // removing from search query already loaded categories + const loadedCategories: Category[] = [] + if (searchingByIds) { // removing from search query already loaded categories, they are added to returned results + for (const [categoryId, category] of Object.entries(getters.getCategoriesMap)) { + if (searchedIds.includes(categoryId)) { + loadedCategories.push(category as Category) + } + } categorySearchOptions.filters.id = searchedIds.filter(categoryId => !getters.getCategoriesMap[categoryId] && !getters.getNotFoundCategoryIds.includes(categoryId)) } if (!searchingByIds || categorySearchOptions.filters.id.length) { + categorySearchOptions.filters = Object.assign(config.entities.category.filterFields, categorySearchOptions.filters) const categories = await CategoryService.getCategories(categorySearchOptions) const notFoundCategories = searchedIds.filter(categoryId => !categories.some(cat => cat.id === parseInt(categoryId))) commit(types.CATEGORY_ADD_CATEGORIES, categories) commit(types.CATEGORY_ADD_NOT_FOUND_CATEGORY_IDS, notFoundCategories) - return categories + return [...loadedCategories, ...categories] } - return [] + return loadedCategories }, async loadCategory ({ commit }, categorySearchOptions: DataResolver.CategorySearchOptions): Promise { const categories: Category[] = await CategoryService.getCategories(categorySearchOptions) @@ -185,11 +193,20 @@ const actions: ActionTree = { async changeRouterFilterParameters (context, query) { router.push({[products.routerFiltersSource]: query}) }, - async loadCategoryBreadcrumbs ({ dispatch, getters }, category: Category) { + async loadCategoryBreadcrumbs ({ dispatch, getters }, { category, currentRouteName, omitCurrent = false }) { if (!category) return const categoryHierarchyIds = _prepareCategoryPathIds(category) // getters.getCategoriesHierarchyMap.find(categoryMapping => categoryMapping.includes(category.id)) const categoryFilters = { 'id': categoryHierarchyIds } - await dispatch('loadCategories', {filters: categoryFilters}) + const categories = await dispatch('loadCategories', {filters: categoryFilters}) + const sorted = [] + for (const id of categoryHierarchyIds) { + const index = categories.findIndex(cat => cat.id.toString() === id) + if (index >= 0 && (!omitCurrent || categories[index].id !== category.id)) { + sorted.push(categories[index]) + } + } + await dispatch('breadcrumbs/set', { current: currentRouteName, routes: parseCategoryPath(sorted) }, { root: true }) + return sorted } } diff --git a/core/modules/catalog/store/product/actions.ts b/core/modules/catalog/store/product/actions.ts index 49fd45011d..671f8b84c3 100644 --- a/core/modules/catalog/store/product/actions.ts +++ b/core/modules/catalog/store/product/actions.ts @@ -680,12 +680,15 @@ const actions: ActionTree = { context.commit(types.PRODUCT_SET_GALLERY, productGallery) } }, - async loadProductBreadcrumbs ({ dispatch }, { product } = {}) { + async loadProductBreadcrumbs ({ dispatch, rootGetters }, { product } = {}) { if (product && product.category_ids) { - const categoryFilters = { 'id': product.category_ids } - const categories = await dispatch('category-next/loadCategories', {filters: categoryFilters}, { root: true }) - const deepestCategory = categories.sort((a, b) => (a.level > b.level) ? -1 : 1)[0] // sort starting by deepest level - await dispatch('category-next/loadCategoryBreadcrumbs', deepestCategory, { root: true }) + let currentCategory = rootGetters['category-next/getCurrentCategory'] // use current category, if set + if (!currentCategory || !currentCategory.id || !product.category_ids.includes(currentCategory.id.toString())) { + const categoryFilters = Object.assign({ 'id': product.category_ids }, config.entities.category.breadcrumbFilterFields) + const categories = await dispatch('category-next/loadCategories', {filters: categoryFilters}, { root: true }) + currentCategory = categories.sort((a, b) => (a.level > b.level) ? -1 : 1)[0] // sort starting by deepest level + } + await dispatch('category-next/loadCategoryBreadcrumbs', { category: currentCategory, currentRouteName: product.name }, { root: true }) } } } diff --git a/core/modules/catalog/store/product/getters.ts b/core/modules/catalog/store/product/getters.ts index 3b8bac8fed..cc4431a7ca 100644 --- a/core/modules/catalog/store/product/getters.ts +++ b/core/modules/catalog/store/product/getters.ts @@ -4,12 +4,6 @@ import ProductState from '../../types/ProductState' const getters: GetterTree = { getCurrentProduct: state => state.current, - getCurrentProductCategoryId: (state, getters, rootState, rootGetters) => { - if (getters.getCurrentProduct && getters.getCurrentProduct.category_ids) { - return rootGetters['category-next/getCategoriesMap'][getters.getCurrentProduct.category_ids[0]] - } - return null - }, getCurrentProductConfiguration: state => state.current_configuration, getCurrentProductOptions: state => state.current_options, getOriginalProduct: state => state.original, @@ -17,8 +11,7 @@ const getters: GetterTree = { getProductsSearchResult: state => state.list, getProducts: (state, getters) => getters.getProductsSearchResult.items, getProductGallery: state => state.productGallery, - getProductRelated: state => state.related, - getProductBreadcrumbs: (state, getters, rootState, rootGetters) => rootGetters['category-next/getBreadcrumbsFor'](getters.getCurrentProductCategoryId) + getProductRelated: state => state.related } export default getters diff --git a/docs/guide/upgrade-notes/README.md b/docs/guide/upgrade-notes/README.md index ec7861fbbe..93ae851902 100644 --- a/docs/guide/upgrade-notes/README.md +++ b/docs/guide/upgrade-notes/README.md @@ -87,6 +87,8 @@ If by some reasons you wan't to have the `localStorage` back on for `Products by - Optionally give theme routes priority, to ensure they override module routes if there are any conflicts. For example `setupMultistoreRoutes(config, router, routes, 10)`. - See `/src/themes/default/index.js` for a complete example. - In `storeView` config there is no more `disabled` flag for specific language config. Links for other languages will be displayed if specific `storeView` config exist. +- Categories can be filtered globally, to never be loaded, by setting `entities.category.filterFields` in local.json, e.g. `"filterFields": { "is_active": true }`. +- Categories can be filtered in the Breadcrumbs, by setting `entities.category.breadcrumbFilterFields` in local.json, e.g. `"breadcrumbFilterFields": { "include_in_menu": true }`. ## 1.9 -> 1.10 - Event `application-after-init` is now emitted by event bus instead of root Vue instance (app), so you need to listen to `Vue.prototype.$bus` (`EventBus.$on()`) now diff --git a/src/themes/default-amp/pages/Category.vue b/src/themes/default-amp/pages/Category.vue index 53e84621ce..d9143413d4 100755 --- a/src/themes/default-amp/pages/Category.vue +++ b/src/themes/default-amp/pages/Category.vue @@ -2,7 +2,7 @@
- +

{{ category.name }} diff --git a/src/themes/default-amp/pages/Product.vue b/src/themes/default-amp/pages/Product.vue index 1f6d672347..58b2de643d 100755 --- a/src/themes/default-amp/pages/Product.vue +++ b/src/themes/default-amp/pages/Product.vue @@ -25,8 +25,6 @@