[
  {
    "path": ".gitignore",
    "content": "# OS generated files #\n######################\n*.DS_Store*\n.DS_Store?\n._*\n.Spotlight-V100\n.Trashes\nehthumbs.db\nThumbs.db\n\n# Helper files #\n######################\n_*\n\n# Build tools #\n######################\nnode_modules/\ndist/\n.env"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2021 Daniel Lerman\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Shopify Wishlist\n\nA set of files used to implement a simple customer wishlist on a Shopify store.\n\n_Version: 2.3.0_ - Compatible with Online Store 2.0\n\nPrevious Versions:\n- [v2.1.0 (Online Store 1.0)](https://github.com/dlerm/shopify-wishlist/tree/version/2.1.0)\n- [v2.0.0](https://github.com/dlerm/shopify-wishlist/tree/version/2.0.0)\n- [v1.0.0](https://github.com/dlerm/shopify-wishlist/tree/version/1.0.0)\n\n## Installation\n\nTo begin using **Shopify Wishlist**, you must copy some of the files in this repo into your Shopify theme code.\n\n_Note_: This setup assumes that you have a snippet for displaying a product card.\n\n**Files to copy:**\n|Repo File|Shopify Theme Location|\n|:--:|:--:|\n|[`button-wishlist.liquid`](https://github.com/dlerm/shopify-wishlist/blob/master/snippets/button-wishlist.liquid)|`snippets/`|\n|[`icon-heart.liquid`](https://github.com/dlerm/shopify-wishlist/blob/master/snippets/icon-heart.liquid)| `snippets/`|\n|[`wishlist-template.liquid`](https://github.com/dlerm/shopify-wishlist/blob/master/sections/wishlist-template.liquid)| `sections/`|\n|[`product-card-template.liquid`](https://github.com/dlerm/shopify-wishlist/blob/master/sections/product-card-template.liquid)| `sections/`|\n|[`page.wishlist.json`](https://github.com/dlerm/shopify-wishlist/blob/master/templates/page.wishlist.json)|`templates/`|\n|[`product.card.json`](https://github.com/dlerm/shopify-wishlist/blob/master/templates/product.card.json)|`templates/`|\n|[`Wishlist.js`](https://github.com/dlerm/shopify-wishlist/blob/master/assets/Wishlist.js)|`assets/`|\n\n1. Place the `button-wishlist.liquid` snippet inside your existing product card snippet, or on the `product.liquid` template\n   - `{%- render 'button-wishlist', product: product -%}`\n   - This will allow customer's to add/remove items to/from their wishlist\n2. Replace the snippet in the `product-card-template.liquid` section with your existing product card snippet\n   - Same snippet from step 1\n   - Your product card snippet may use a different product variable name, double check it :+1:\n3. Add your product card element classname to the `selectors` object in `Wishlist.js`\n   - Example: If your product card classname is `.product-item`, the `selectors` variable would look like this:\n      ```\n      const selectors = {\n        button: '[button-wishlist]',\n        grid: '[grid-wishlist]',\n        productCard: '.product-item', // your classname here\n      };\n      ```\n4. Create a new page in the Shopify admin:\n   - Admin > Online Store > Pages > Add Page\n   - Set the new page's `template` to `page.wishlist`\n   - This page will display a customer's saved wishlist items\n5. Place the script in `theme.liquid` before the closing `</head>` tag\n   - `<script src=\"{{ 'Wishlist.js' | asset_url }}\" defer=\"defer\"></script>`\n\nThat's it! When viewing your Shopify store, you should see the wishlist buttons inside your product cards (likely on collections pages) or on the product template. A click on the wishlist button will add/remove the item from the customer's wishlist and trigger active styling on the button. After adding wishlist items, you can view your wishlist by navigating to the page created in step 3.\n\n[Demo Shopify Store](https://lerman-labs.myshopify.com/collections/all)\n\n## Notes\n\n- This wishlist uses Javascript and localStorage to save the customer's wishlist on their browser. The localStorage will not persist if the user clears browser storage or browses in incognito mode.\n- As customers browser products and adds them to their wishlist, the script will automatically set any wishlist buttons to active state if the corresponding product is already included in the wishlist.\n- These files come with no styling or structure so that you can customize as needed. This is intended to bring you the base functionality of a wishlist with no frills.\n- If you are working in an unpublished theme, you will need to create the new templates on the published theme as well. The Shopify admin will only allow you to assign a page to a template if the template exists on the published theme.\n- If you are upgrading to the Online Store 2.0 version, you will be required to delete the older `.liquid` wishlist and product card templates.\n"
  },
  {
    "path": "assets/Wishlist.js",
    "content": "const LOCAL_STORAGE_WISHLIST_KEY = 'shopify-wishlist';\nconst LOCAL_STORAGE_DELIMITER = ',';\nconst BUTTON_ACTIVE_CLASS = 'active';\nconst GRID_LOADED_CLASS = 'loaded';\n\nconst selectors = {\n  button: '[button-wishlist]',\n  grid: '[grid-wishlist]',\n  productCard: '.product-card',\n};\n\ndocument.addEventListener('DOMContentLoaded', () => {\n  initButtons();\n  initGrid();\n});\n\ndocument.addEventListener('shopify-wishlist:updated', (event) => {\n  console.log('[Shopify Wishlist] Wishlist Updated ✅', event.detail.wishlist);\n  initGrid();\n});\n\ndocument.addEventListener('shopify-wishlist:init-product-grid', (event) => {\n  console.log('[Shopify Wishlist] Wishlist Product List Loaded ✅', event.detail.wishlist);\n});\n\ndocument.addEventListener('shopify-wishlist:init-buttons', (event) => {\n  console.log('[Shopify Wishlist] Wishlist Buttons Loaded ✅', event.detail.wishlist);\n});\n\nconst fetchProductCardHTML = (handle) => {\n  const productTileTemplateUrl = `/products/${handle}?view=card`;\n  return fetch(productTileTemplateUrl)\n  .then((res) => res.text())\n  .then((res) => {\n    const text = res;\n    const parser = new DOMParser();\n    const htmlDocument = parser.parseFromString(text, 'text/html');\n    const productCard = htmlDocument.documentElement.querySelector(selectors.productCard);\n    return productCard.outerHTML;\n  })\n  .catch((err) => console.error(`[Shopify Wishlist] Failed to load content for handle: ${handle}`, err));\n};\n\nconst setupGrid = async (grid) => {\n  const wishlist = getWishlist();\n  const requests = wishlist.map(fetchProductCardHTML);\n  const responses = await Promise.all(requests);\n  const wishlistProductCards = responses.join('');\n  grid.innerHTML = wishlistProductCards;\n  grid.classList.add(GRID_LOADED_CLASS);\n  initButtons();\n\n  const event = new CustomEvent('shopify-wishlist:init-product-grid', {\n    detail: { wishlist: wishlist }\n  });\n  document.dispatchEvent(event);\n};\n\nconst setupButtons = (buttons) => {\n  buttons.forEach((button) => {\n    const productHandle = button.dataset.productHandle || false;\n    if (!productHandle) return console.error('[Shopify Wishlist] Missing `data-product-handle` attribute. Failed to update the wishlist.');\n    if (wishlistContains(productHandle)) button.classList.add(BUTTON_ACTIVE_CLASS);\n    button.addEventListener('click', () => {\n      updateWishlist(productHandle);\n      button.classList.toggle(BUTTON_ACTIVE_CLASS);\n    });\n  });\n};\n\nconst initGrid = () => {\n  const grid = document.querySelector(selectors.grid) || false;\n  if (grid) setupGrid(grid);\n};\n\nconst initButtons = () => {\n  const buttons = document.querySelectorAll(selectors.button) || [];\n  if (buttons.length) setupButtons(buttons);\n  else return;\n  const event = new CustomEvent('shopify-wishlist:init-buttons', {\n    detail: { wishlist: getWishlist() }\n  });\n  document.dispatchEvent(event);\n};\n\nconst getWishlist = () => {\n  const wishlist = localStorage.getItem(LOCAL_STORAGE_WISHLIST_KEY) || false;\n  if (wishlist) return wishlist.split(LOCAL_STORAGE_DELIMITER);\n  return [];\n};\n\nconst setWishlist = (array) => {\n  const wishlist = array.join(LOCAL_STORAGE_DELIMITER);\n  if (array.length) localStorage.setItem(LOCAL_STORAGE_WISHLIST_KEY, wishlist);\n  else localStorage.removeItem(LOCAL_STORAGE_WISHLIST_KEY);\n\n  const event = new CustomEvent('shopify-wishlist:updated', {\n    detail: { wishlist: array }\n  });\n  document.dispatchEvent(event);\n\n  return wishlist;\n};\n\nconst updateWishlist = (handle) => {\n  const wishlist = getWishlist();\n  const indexInWishlist = wishlist.indexOf(handle);\n  if (indexInWishlist === -1) wishlist.push(handle);\n  else wishlist.splice(indexInWishlist, 1);\n  return setWishlist(wishlist);\n};\n\nconst wishlistContains = (handle) => {\n  const wishlist = getWishlist();\n  return wishlist.includes(handle);\n};\n\nconst resetWishlist = () => {\n  return setWishlist([]);\n};\n"
  },
  {
    "path": "config.yml",
    "content": "development:\n  password: ${PASSWORD}\n  theme_id: ${THEME_ID}\n  store: ${URL}\n"
  },
  {
    "path": "gulpfile.js",
    "content": "const gulp = require('gulp');\n\nrequire('require-dir')('./tasks');\n\n// build\ngulp.task(\n  'build',\n  gulp.series('clean:dist', 'copy')\n);\n\n// watch\ngulp.task(\n  'watch',\n  gulp.series(\n    gulp.parallel(\n      'copy:watch',\n    ),\n    gulp.series('upload:watch')\n  )\n);\n\n// dev\ngulp.task('dev', gulp.series('build', 'watch'));\n\n// default\ngulp.task('default', gulp.series('dev'));\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"shopify-wishlist\",\n  \"version\": \"2.3.0\",\n  \"author\": \"Daniel Lerman\",\n  \"description\": \"💙 A set of files used to implement a simple customer wishlist on a Shopify store\",\n  \"keywords\": [\n    \"shopify\",\n    \"theme\",\n    \"wishlist\"\n  ],\n  \"bugs\": \"https://github.com/dlerm/shopify-wishlist/issues\",\n  \"license\": \"MIT\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/dlerm/shopify-wishlist\"\n  },\n  \"scripts\": {\n    \"start\": \"gulp\",\n    \"build\": \"gulp build\"\n  },\n  \"dependencies\": {},\n  \"devDependencies\": {\n    \"@shopify/themekit\": \"^1.1.9\",\n    \"dotenv\": \"^9.0.0\",\n    \"gulp\": \"^4.0.2\",\n    \"gulp-changed\": \"^4.0.2\",\n    \"gulp-clean\": \"^0.4.0\",\n    \"gulp-flatten\": \"^0.4.0\",\n    \"gulp-if\": \"^3.0.0\",\n    \"gulp-rename\": \"^2.0.0\",\n    \"gulp-watch\": \"^5.0.1\",\n    \"require-dir\": \"^1.2.0\"\n  }\n}\n"
  },
  {
    "path": "sections/product-card-template.liquid",
    "content": "{% comment %}\n  Shopify Wishlist\n  Usage:\n    - Replace the snippet name ('product-card') with your existing product card snippet\n    - Pass the product object as a snippet variable named 'product'\n{% endcomment %}\n{%- render 'product-card', product: product -%}\n\n{% schema %}\n  {\n    \"templates\": [\"product\"]\n  }\n{% endschema %}"
  },
  {
    "path": "sections/wishlist-template.liquid",
    "content": "{% comment %}\n  Shopify Wishlist\n  Usage:\n    - Create a new page in the Shopify admin\n      - Admin > Online store > Pages > Add page\n    - Set the new page's template to: 'page.wishlist'\n    - Do NOT remove the `grid-wishlist` attribute\n  \n  Notes:\n  - The grid will be populated with product cards using Javascript\n  - Any content inside of the `[grid-wishlist]` element will be completely replaced by the product cards\n  \n  Tip:\n  - Place a loading element inside the `[grid-wishlist]` element and it will automatically be removed once the product cards have loaded\n  - Add any liquid code before/after the grid element\n{% endcomment %}\n<div class=\"wishlist__grid flex container\" grid-wishlist>\n  {% comment %} Sample loading element {% endcomment %}\n  <p class=\"wishlist__loader full-width text-center\">Loading...</p>\n</div>\n\n{% schema %}\n  {\n    \"templates\": [\"page\"]\n  }\n{% endschema %}"
  },
  {
    "path": "snippets/button-wishlist.liquid",
    "content": "{% comment %}\n  Shopify Wishlist\n  Usage:\n    - Markup: {%- render 'button-wishlist', product: product -%}\n    - Place this snippet inside your existing product card snippet\n\n  Parameters:\n    - product: <Shopify product> (required)\n{% endcomment %}\n{%- if product.handle != blank -%}\n  <button type=\"button\" aria-label=\"Add to wishlist\" class=\"\" button-wishlist data-product-handle=\"{{ product.handle }}\">\n    <style scoped>\n      .icon {\n        fill: transparent;\n        stroke: #000000;\n        transition: fill 0.3s ease;\n      }\n\n      .active .icon {\n        fill: red;\n        stroke: #000000;\n      }\n    </style>\n    {%- render 'icon-heart' -%}\n  </button>\n{%- endif -%}\n"
  },
  {
    "path": "snippets/icon-heart.liquid",
    "content": "{% comment %}\n  Shopify Wishlist\n  Usage:\n    - Markup: {%- render 'icon-heart', class: product -%}\n    - Place this snippet inside your existing product card snippet\n\n  Parameters:\n    - class: Add a css class for custom styling (optional)\n{% endcomment %}\n<svg class=\"icon icon-heart {{ class -}}\" viewBox=\"0 0 290 256\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M258.844192 127.790368L145 241.63456 31.1558082 127.790368c-26.9461761-26.946176-26.9461761-70.6345598 0-97.5807359 26.9461762-26.94617613 70.6345598-26.94617613 97.5807358 0L145 46.4730881l16.263456-16.263456c26.946176-26.94617613 70.63456-26.94617613 97.580736 0 26.946176 26.9461761 26.946176 70.6345599 0 97.5807359z\" stroke=\"#000\" stroke-width=\"20\" fill=\"red\" fill-rule=\"evenodd\"/></svg>"
  },
  {
    "path": "tasks/clean.js",
    "content": "const gulp = require('gulp');\nconst clean = require('gulp-clean');\nconst fs = require('fs');\n\ngulp.task('clean:dist', function (done) {\n  if (fs.existsSync('dist')) return gulp.src(['dist'], { read: false }).pipe(clean());\n  else done();\n});\n"
  },
  {
    "path": "tasks/copy.js",
    "content": "const gulp = require('gulp');\nconst changed = require('gulp-changed');\nconst watch = require('gulp-watch');\nconst rename = require('gulp-rename');\n\nconst liquidPaths = [\n  '*assets/**/*',\n  '*snippets/**/*',\n  '*sections/**/*',\n  '*templates/**/*',\n  '*layout/**/*',\n];\n\nfunction renameHiddenFiles (path) {\n  if (path.basename[0] === '_') path.basename = path.basename.substring(1);\n};\n\ngulp.task('copy', function () {\n  return gulp\n    .src(liquidPaths)\n    .pipe(rename(renameHiddenFiles))\n    .pipe(changed('dist/', { hasChanged: changed.compareContents }))\n    .pipe(gulp.dest('dist/'));\n});\n\ngulp.task('copy:watch', function (done) {\n  watch(liquidPaths)\n    .pipe(rename(renameHiddenFiles))\n    .pipe(gulp.dest('dist/'));\n  done();\n});\n"
  },
  {
    "path": "tasks/upload.js",
    "content": "const gulp = require('gulp');\nconst path = require('path');\nconst themeKit = require('@shopify/themekit');\n\nconst options = {\n  vars: path.resolve(__dirname, '../.env'),\n  dir: path.resolve(__dirname, '../dist'),\n};\n\ngulp.task(\"upload:deploy\", function () {\n  themeKit.command('deploy', options);\n});\n\ngulp.task(\"upload:watch\", function () {\n  themeKit.command('watch', options);\n});\n"
  },
  {
    "path": "templates/page.wishlist.json",
    "content": "{\n  \"name\": \"Wishlist template\",\n  \"wrapper\": \"section#wishlist.wishlist\",\n  \"sections\": {\n    \"main\": {\n      \"type\": \"wishlist-template\"\n    }\n  },\n  \"order\": [\n    \"main\"\n  ]\n}"
  },
  {
    "path": "templates/product.card.json",
    "content": "{\n  \"name\": \"Product card template\",\n  \"layout\": false,\n  \"sections\": {\n    \"main\": {\n      \"type\": \"product-card-template\"\n    }\n  },\n  \"order\": [\n    \"main\"\n  ]\n}"
  }
]