diff --git a/shopify-dev-utils/convertToGlobalDataStructure.js b/shopify-dev-utils/convertToGlobalDataStructure.js index 2c69370..02070b0 100644 --- a/shopify-dev-utils/convertToGlobalDataStructure.js +++ b/shopify-dev-utils/convertToGlobalDataStructure.js @@ -1,5 +1,78 @@ module.exports.convertToGlobalDataStructure = function convertToGlobalDataStructure(gqlData) { // return gqlData; + const firstProduct = gqlData.products.edges[0].node; + const product = { + id: firstProduct.id, + title: firstProduct.title, + handle: firstProduct.handle, + description: firstProduct.description, + content: firstProduct.description, + published_at: firstProduct.publishedAt, + created_at: firstProduct.createdAt, + vendor: firstProduct.vendor, + type: firstProduct.productType, + tags: firstProduct.tags, + available: firstProduct.availableForSale, + price: firstProduct.priceRange.maxVariantPrice, // preserve the entire obj for money-* filters + price_min: firstProduct.priceRange.minVariantPrice, + price_max: firstProduct.priceRange.maxVariantPrice, + price_varies: + +firstProduct.priceRange.maxVariantPrice.amount !== + +firstProduct.priceRange.minVariantPrice, + compare_at_price: firstProduct.compareAtPriceRange.maxVariantPrice, // preserve the entire obj for money-* filters, + compare_at_price_min: firstProduct.compareAtPriceRange.minVariantPrice, + compare_at_price_max: firstProduct.compareAtPriceRange.maxVariantPrice, + compare_at_price_varies: + +firstProduct.compareAtPriceRange.maxVariantPrice.amount !== + +firstProduct.compareAtPriceRange.minVariantPrice, + images: firstProduct.images.edges.map(({ node }) => node.originalSrc), + featured_image: + firstProduct.images.edges.length > 0 ? firstProduct.images.edges[0].node : '', + media: firstProduct.images.edges.map(({ node }, index) => { + const image = { + aspect_ratio: node.height / node.width, + height: node.height, + width: node.width, + src: node.originalSrc, + }; + return { + id: node.id, + alt: node.altText, + position: index + 1, + media_type: 'image', + preview_image: image, + ...image, + }; + }), + options: firstProduct.options.map(({ name }) => name), + options_by_name: firstProduct.options.reduce((result, option) => { + result[option.name] = option; + return result; + }, {}), + variants: firstProduct.variants.edges.map(({ node }) => { + return { + id: node.id, + title: node.title, + public_title: null, + options: node.selectedOptions.map((option) => option.value), + option1: (node.selectedOptions[0] && node.selectedOptions[0].value) || null, + option2: (node.selectedOptions[1] && node.selectedOptions[1].value) || null, + option3: (node.selectedOptions[2] && node.selectedOptions[2].value) || null, + sku: node.sku, + requires_shipping: node.requiresShipping, + taxable: true, + featured_image: node.image, + available: node.availableForSale, + name: firstProduct.title, + weight: node.weight, + price: node.priceV2, + compare_at_price: node.compareAtPriceV2, + inventory_management: 'shopify', + barcode: '', + }; + }), + }; + return { shop: { name: gqlData.shop.name, @@ -34,5 +107,9 @@ module.exports.convertToGlobalDataStructure = function convertToGlobalDataStruct : null, })), })), + product: { + ...product, + selected_or_first_available_variant: product.variants[0] || null, + }, }; }; diff --git a/shopify-dev-utils/filters/img_url.js b/shopify-dev-utils/filters/img_url.js new file mode 100644 index 0000000..923a8d8 --- /dev/null +++ b/shopify-dev-utils/filters/img_url.js @@ -0,0 +1,14 @@ +const path = require('path'); + +module.exports.imgUrl = function imgUrl(image, size) { + if (!image || !image.originalSrc) { + return ''; + } + + const [imgPath, query] = image.originalSrc.split('?'); + const imageParts = path.parse(imgPath); + + return `${imageParts.dir}/${imageParts.name}${size !== 'master' ? '_' + size : ''}${ + imageParts.ext + }?${query}`; +}; diff --git a/shopify-dev-utils/liquidDev.entry.js b/shopify-dev-utils/liquidDev.entry.js index bbbf9ab..55bbaa3 100644 --- a/shopify-dev-utils/liquidDev.entry.js +++ b/shopify-dev-utils/liquidDev.entry.js @@ -1,7 +1,7 @@ const context = require.context( '../src', true, - /(collection|footer|featured-product|featured-collection|header|message)\.liquid$/ + /(global-css|product|collection|footer|featured-product|featured-collection|header|message)\.liquid$/ ); const cache = {}; @@ -29,7 +29,7 @@ if (module.hot) { const newContext = require.context( '../src', true, - /(collection|footer|featured-product|featured-collection|header|message)\.liquid$/ + /(global-css|product|collection|footer|featured-product|featured-collection|header|message)\.liquid$/ ); const changes = []; newContext.keys().forEach(function (key) { diff --git a/shopify-dev-utils/liquidDev.loader.js b/shopify-dev-utils/liquidDev.loader.js index 427441d..f52185f 100644 --- a/shopify-dev-utils/liquidDev.loader.js +++ b/shopify-dev-utils/liquidDev.loader.js @@ -9,9 +9,11 @@ const { defaultPagination } = require('./filters/default_pagination'); const { scriptTag } = require('./filters/script_tag'); const { stylesheetTag } = require('./filters/stylesheet_tag'); const { assetUrl } = require('./filters/asset_url'); +const { imgUrl } = require('./filters/img_url'); const { getStoreGlobalData } = require('./storeData'); const { liquidSectionTags } = require('./section-tags/index'); const { Paginate } = require('./tags/paginate'); +const { Style } = require('./tags/style'); let engine; let loadPromise; @@ -44,8 +46,10 @@ function initEngine() { engine.registerFilter('within', within); engine.registerFilter('money_with_currency', moneyWithCurrency); engine.registerFilter('money_without_trailing_zeros', moneyWithoutTrailingZeros); + engine.registerFilter('img_url', imgUrl); engine.registerTag('paginate', Paginate); + engine.registerTag('style', Style); engine.plugin(liquidSectionTags()); resolve(); diff --git a/shopify-dev-utils/section-tags/index.js b/shopify-dev-utils/section-tags/index.js index d0f109e..a957ea4 100644 --- a/shopify-dev-utils/section-tags/index.js +++ b/shopify-dev-utils/section-tags/index.js @@ -1,10 +1,11 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); exports.liquidSectionTags = void 0; -const javascript_1 = require("./javascript"); -const schema_1 = require("./schema"); -const section_1 = require("./section"); -const stylesheet_1 = require("./stylesheet"); +const javascript_1 = require('./javascript'); +const schema_1 = require('./schema'); +const section_1 = require('./section'); +const stylesheet_1 = require('./stylesheet'); + function liquidSectionTags() { return function () { this.registerTag('section', section_1.Section); diff --git a/shopify-dev-utils/storeData.js b/shopify-dev-utils/storeData.js index 1c9d8a4..3db5e52 100644 --- a/shopify-dev-utils/storeData.js +++ b/shopify-dev-utils/storeData.js @@ -62,6 +62,7 @@ async function getStoreGlobalData() { }, }, collection: data.collections[0], + product: data.product, }; } diff --git a/shopify-dev-utils/storefrontApi.js b/shopify-dev-utils/storefrontApi.js index 43febaf..2f5594f 100644 --- a/shopify-dev-utils/storefrontApi.js +++ b/shopify-dev-utils/storefrontApi.js @@ -2,9 +2,8 @@ const Axios = require('axios'); class StorefrontApi { constructor({ baseURL, token }) { - console.log(`https://${baseURL}/api/2020-10/graphql`); this.axios = Axios.create({ - baseURL: `https://${baseURL}/api/2020-10/graphql`, + baseURL: `https://${baseURL}/api/2021-01/graphql.json`, headers: { Accept: 'application/json', 'Content-Type': 'application/graphql', @@ -12,7 +11,7 @@ class StorefrontApi { }, }); } - + // GQL query can be tested https://shopify.dev/graphiql/storefront-graphiql async getStoreData() { return this.axios .post( @@ -69,6 +68,93 @@ class StorefrontApi { } } } + products(first: 10) { + edges { + node { + id + handle + createdAt + publishedAt + vendor + tags + description + descriptionHtml + title + tags + priceRange { + maxVariantPrice { + amount + currencyCode + } + minVariantPrice { + amount + currencyCode + } + } + compareAtPriceRange { + maxVariantPrice { + amount + currencyCode + } + minVariantPrice { + amount + currencyCode + } + } + options { + id + name + values + } + variants(first: 10) { + edges { + node { + id + title + sku + priceV2 { + amount + currencyCode + } + compareAtPriceV2 { + amount + currencyCode + } + selectedOptions { + name + value + } + availableForSale + quantityAvailable + requiresShipping + weight + image { + id + height + altText + width + originalSrc + } + } + } + } + productType + images(first: 10) { + edges { + node { + id + altText + height + width + originalSrc + + } + } + } + availableForSale + } + } + } } ` ) diff --git a/shopify-dev-utils/tags/style.d.ts b/shopify-dev-utils/tags/style.d.ts new file mode 100644 index 0000000..dbec700 --- /dev/null +++ b/shopify-dev-utils/tags/style.d.ts @@ -0,0 +1,2 @@ +import { TagImplOptions } from 'liquidjs/dist/template/tag/tag-impl-options'; +export declare const Style: TagImplOptions; diff --git a/shopify-dev-utils/tags/style.js b/shopify-dev-utils/tags/style.js new file mode 100644 index 0000000..0412277 --- /dev/null +++ b/shopify-dev-utils/tags/style.js @@ -0,0 +1,24 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.Style = void 0; +exports.Style = { + parse: function (tagToken, remainTokens) { + this.tokens = []; + const stream = this.liquid.parser.parseStream(remainTokens); + stream + .on('token', (token) => { + if (token.name === 'endstyle') + stream.stop(); + else + this.tokens.push(token); + }) + .on('end', () => { + throw new Error(`tag ${tagToken.getText()} not closed`); + }); + stream.start(); + }, + render: function () { + const text = this.tokens.map((token) => token.getText()).join(''); + return ``; + } +}; diff --git a/src/components/sections/product/product.liquid b/src/components/sections/product/product.liquid index 4320664..658d47d 100644 --- a/src/components/sections/product/product.liquid +++ b/src/components/sections/product/product.liquid @@ -12,33 +12,15 @@
{{ product.content }}