Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue #1226 Improve the SEO support #1514

Merged
merged 6 commits into from
Aug 3, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions config/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@
"products": {
"useShortCatalogUrls": false,
"useMagentoUrlKeys": false,
"setFirstVarianAsDefaultInURL": false,
"configurableChildrenStockPrefetchStatic": false,
"configurableChildrenStockPrefetchDynamic": false,
"configurableChildrenStockPrefetchStaticPrefetchCount": 8,
Expand Down
4 changes: 3 additions & 1 deletion core/filters/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ import { htmlDecode } from './html-decode'
import { date } from './date'
import { capitalize } from './capitalize'
import { formatProductMessages } from './product-messages'
import { stripHTML } from './strip-html'

export {
price,
htmlDecode,
date,
capitalize,
formatProductMessages
formatProductMessages,
stripHTML
}
8 changes: 8 additions & 0 deletions core/filters/strip-html/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/**
* Strip HTML tags
* @param {String} html
*/
export function stripHTML (html) {
if (!html) return ''
return html.replace(/<[^>]+>/g, '').trim()
}
18 changes: 12 additions & 6 deletions core/pages/Product.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import uniqBy from 'lodash-es/uniqBy'
import i18n from 'core/lib/i18n'
import config from 'config'
import EventBus from 'core/plugins/event-bus'
import { htmlDecode } from 'core/filters/html-decode'
import { htmlDecode, stripHTML } from 'core/filters'
import { currentStoreView } from '@vue-storefront/store/lib/multistore'

// Core mixins
import Composite from 'core/mixins/composite'
Expand Down Expand Up @@ -93,14 +94,17 @@ export default {
},
isOnCompare () {
return !!this.$store.state.compare.items.find(p => p.sku === this.product.sku)
},
currentStore () {
return currentStoreView()
}
},
asyncData ({ store, route }) { // this is for SSR purposes to prefetch data
EventBus.$emit('product-before-load', { store: store, route: route })
return store.dispatch('product/fetchAsync', { parentSku: route.params.parentSku, childSku: route && route.params && route.params.childSku ? route.params.childSku : null })
},
watch: {
'$route': 'validateRoute'
'$route.params.parentSku': 'validateRoute'
},
beforeDestroy () {
this.$bus.$off('product-after-removevariant')
Expand All @@ -127,8 +131,7 @@ export default {
this.loading = false
this.defaultOfflineImage = this.product.image
this.onStateCheck()
this.$bus.$on('filter-changed-product', this.onAfterFilterChanged)
}).catch(err => {
}).catch((err) => {
this.loading = false
console.error(err)
this.$bus.$emit('notification', {
Expand Down Expand Up @@ -208,7 +211,10 @@ export default {
configuration: this.configuration,
selectDefaultVariant: true,
fallbackToDefaultWhenNoAvailable: false
}).then(selectedVariant => {
}).then((selectedVariant) => {
if (config.products.setFirstVarianAsDefaultInURL) {
this.$router.push({params: { childSku: selectedVariant.sku }})
}
if (!selectedVariant) {
if (typeof prevOption !== 'undefined' && prevOption) {
this.configuration[filterOption.attribute_code] = prevOption
Expand All @@ -230,7 +236,7 @@ export default {
metaInfo () {
return {
title: htmlDecode(this.$route.meta.title || this.productName),
meta: this.$route.meta.description ? [{ vmid: 'description', description: htmlDecode(this.$route.meta.description) }] : []
meta: [{ vmid: 'description', description: this.product.short_description ? stripHTML(htmlDecode(this.product.short_description)) : htmlDecode(stripHTML(this.product.description)) }]
}
}
}
3 changes: 3 additions & 0 deletions core/store/modules/product/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,9 @@ export default {
if (!product.parentSku) {
product.parentSku = product.sku
}
if (config.products.setFirstVarianAsDefaultInURL && product.hasOwnProperty('configurable_children') && product.configurable_children.length > 0) {
product.sku = product.configurable_children[0].sku
}
if (configuration) {
let selectedVariant = configureProductAsync(context, { product: product, configuration: configuration, selectDefaultVariant: false })
Object.assign(product, selectedVariant)
Expand Down
2 changes: 2 additions & 0 deletions src/themes/default/components/core/ProductGallery.vue
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
class="mw-100 pointer"
ref="defaultImage"
alt=""
itemprop="image"
>
</transition>
</div>
Expand All @@ -66,6 +67,7 @@
@dblclick="toggleZoom"
alt=""
data-testid="productGalleryImage"
itemprop="image"
>
</div>
</slide>
Expand Down
4 changes: 2 additions & 2 deletions src/themes/default/pages/Home.vue
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export default {
query: newProductsQuery,
size: 8,
sort: 'created_at:desc',
includeFields: config.entities.optimize ? config.entities.productList.includeFields : []
includeFields: config.entities.optimize ? (config.products.setFirstVarianAsDefaultInURL ? config.entities.productListWithChildren.includeFields : config.entities.productList.includeFields) : []
}).then((res) => {
if (res) {
store.state.homepage.new_collection = res.items
Expand All @@ -105,7 +105,7 @@ export default {
query: coolBagsQuery,
size: 4,
sort: 'created_at:desc',
includeFields: config.entities.optimize ? config.entities.productList.includeFields : []
includeFields: config.entities.optimize ? (config.products.setFirstVarianAsDefaultInURL ? config.entities.productListWithChildren.includeFields : config.entities.productList.includeFields) : []
}).then((res) => {
if (res) {
store.state.homepage.coolbags_collection = res.items
Expand Down
175 changes: 90 additions & 85 deletions src/themes/default/pages/Product.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<div id="product">
<div id="product" itemscope itemtype="http://schema.org/Product">
<section class="bg-cl-secondary px20 product-top-section">
<div class="container">
<section class="row m0 between-xs">
Expand All @@ -16,104 +16,108 @@
:routes="breadcrumbs.routes"
:active-route="breadcrumbs.name"
/>
<h1 class="mb20 mt0 cl-mine-shaft product-name" data-testid="productName">
<h1 class="mb20 mt0 cl-mine-shaft product-name" data-testid="productName" itemprop="name">
{{ product.name | htmlDecode }}
</h1>
<div class="mb20 uppercase cl-secondary">
sku: {{ product.sku }}
</div>
<div
class="mb40 price serif"
v-if="product.type_id !== 'grouped'"
>
<div itemprop="offers" itemscope itemtype="http://schema.org/Offer">
<meta itemprop="priceCurrency" :content="currentStore.i18n.currencyCode">
<meta itemprop="price" :content="parseFloat(product.priceInclTax).toFixed(2)">
<div
class="h3 cl-secondary"
v-if="product.special_price && product.priceInclTax && product.originalPriceInclTax"
class="mb40 price serif"
v-if="product.type_id !== 'grouped'"
>
<span class="h2 cl-mine-shaft weight-700">
<div
class="h3 cl-secondary"
v-if="product.special_price && product.priceInclTax && product.originalPriceInclTax"
>
<span class="h2 cl-mine-shaft weight-700">
{{ product.priceInclTax | price }}
</span>&nbsp;
<span class="price-original h3">
{{ product.originalPriceInclTax | price }}
</span>
</div>
<div
class="h2 cl-mine-shaft weight-700"
v-if="!product.special_price && product.priceInclTax"
>
{{ product.priceInclTax | price }}
</span>&nbsp;
<span class="price-original h3">
{{ product.originalPriceInclTax | price }}
</span>
</div>
<div
class="h2 cl-mine-shaft weight-700"
v-if="!product.special_price && product.priceInclTax"
>
{{ product.priceInclTax | price }}
</div>
</div>
<div
class="cl-primary variants"
v-if="product.type_id =='configurable' && !loading"
>
<div class="error" v-if="product.errors && Object.keys(product.errors).length > 0">
{{ product.errors | formatProductMessages }}
</div>
</div>
<div
class="h5"
v-for="(option, index) in product.configurable_options"
v-if="!product.errors || Object.keys(product.errors).length === 0"
:key="index"
class="cl-primary variants"
v-if="product.type_id =='configurable' && !loading"
>
<div class="variants-label" data-testid="variantsLabel">
{{ option.label }}
<span class="weight-700">
{{ configuration[option.attribute_code ? option.attribute_code : option.label.toLowerCase()].label }}
</span>
<div class="error" v-if="product.errors && Object.keys(product.errors).length > 0">
{{ product.errors | formatProductMessages }}
</div>
<div class="row top-xs m0 pt15 pb40 variants-wrapper">
<div v-if="option.label == 'Color'">
<color-selector
v-for="(c, i) in options.color"
:key="i"
:id="c.id"
:label="c.label"
context="product"
code="color"
:class="{ active: c.id == configuration.color.id }"
/>
</div>
<div class="sizes" v-else-if="option.label == 'Size'">
<size-selector
v-for="(s, i) in options.size"
:key="i"
:id="s.id"
:label="s.label"
context="product"
code="size"
class="mr10 mb10"
:class="{ active: s.id == configuration.size.id }"
v-focus-clean
/>
<div
class="h5"
v-for="(option, index) in product.configurable_options"
v-if="!product.errors || Object.keys(product.errors).length === 0"
:key="index"
>
<div class="variants-label" data-testid="variantsLabel">
{{ option.label }}
<span class="weight-700">
{{ configuration[option.attribute_code ? option.attribute_code : option.label.toLowerCase()].label }}
</span>
</div>
<div :class="option.attribute_code" v-else>
<generic-selector
v-for="(s, i) in options[option.attribute_code]"
:key="i"
:id="s.id"
:label="s.label"
context="product"
:code="option.attribute_code"
class="mr10 mb10"
:class="{ active: s.id == configuration[option.attribute_code].id }"
v-focus-clean
/>
<div class="row top-xs m0 pt15 pb40 variants-wrapper">
<div v-if="option.label == 'Color'">
<color-selector
v-for="(c, i) in options.color"
:key="i"
:id="c.id"
:label="c.label"
context="product"
code="color"
:class="{ active: c.id == configuration.color.id }"
/>
</div>
<div class="sizes" v-else-if="option.label == 'Size'">
<size-selector
v-for="(s, i) in options.size"
:key="i"
:id="s.id"
:label="s.label"
context="product"
code="size"
class="mr10 mb10"
:class="{ active: s.id == configuration.size.id }"
v-focus-clean
/>
</div>
<div :class="option.attribute_code" v-else>
<generic-selector
v-for="(s, i) in options[option.attribute_code]"
:key="i"
:id="s.id"
:label="s.label"
context="product"
:code="option.attribute_code"
class="mr10 mb10"
:class="{ active: s.id == configuration[option.attribute_code].id }"
v-focus-clean
/>
</div>
<router-link
to="/size-guide"
v-if="option.label == 'Size'"
class="
p0 ml30 inline-flex middle-xs no-underline h5
action size-guide pointer cl-secondary
"
>
<i class="pr5 material-icons">accessibility</i>
<span>
{{ $t('Size guide') }}
</span>
</router-link>
</div>
<router-link
to="/size-guide"
v-if="option.label == 'Size'"
class="
p0 ml30 inline-flex middle-xs no-underline h5
action size-guide pointer cl-secondary
"
>
<i class="pr5 material-icons">accessibility</i>
<span>
{{ $t('Size guide') }}
</span>
</router-link>
</div>
</div>
</div>
Expand Down Expand Up @@ -191,6 +195,7 @@
<div class="col-xs-12 col-sm-6">
<div
class="lh30 h5"
itemprop="description"
v-html="product.description"
/>
</div>
Expand Down
1 change: 1 addition & 0 deletions src/themes/default/resource/head.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export default {
{ charset: 'utf-8' },
{ vmid: 'description', name: 'description', content: 'Vue Storefront is a standalone PWA storefront for your eCommerce, possible to connect with any eCommerce backend (eg. Magento, Prestashop or Shopware) through the API.' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1, minimal-ui' },
{ name: 'robots', content: 'index, follow' },
{ name: 'mobile-web-app-capable', content: 'yes' },
{ name: 'theme-color', content: '#ffffff' },
{ name: 'apple-mobile-web-app-status-bar-style', content: '#ffffff' }
Expand Down
1 change: 1 addition & 0 deletions src/themes/theme-starter/resource/head.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export default {
{ charset: 'utf-8' },
{ vmid: 'description', name: 'description', content: 'Vue Storefront is a standalone PWA storefront for your eCommerce, possible to connect with any eCommerce backend (eg. Magento, Prestashop or Shopware) through the API.' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1, minimal-ui' },
{ name: 'robots', content: 'index, follow' },
{ name: 'mobile-web-app-capable', content: 'yes' },
{ name: 'theme-color', content: '#f60' },
{ name: 'apple-mobile-web-app-status-bar-style', content: 'black-translucent' }
Expand Down