UNPKG

@shopgate/pwa-common

Version:

Common library for the Shopgate Connect PWA.

205 lines (188 loc) • 6.42 kB
import "core-js/modules/es.array.reduce.js"; import "core-js/modules/es.string.replace.js"; import event from '@shopgate/pwa-core/classes/Event/index'; /** * Parses a collection of DOM nodes for external script tags. * @param {Array} nodes A collection of DOM nodes. * @param {Function} callback Will be called when a single script is loaded. * @param {boolean} isRoot Whether this is the root level of the given DOM tree. * @return {Array} A collection of external script tags. */ export const getExternalScripts = (nodes, callback, isRoot = true) => { const nodesArray = [].slice.call(nodes); const externalScripts = nodesArray.reduce((result, node) => { // We only want external scripts. if (node.tagName !== 'SCRIPT' || !node.src) { if (node.childNodes && node.childNodes.length) { return result.concat(getExternalScripts(node.childNodes, callback, false)); } return result; } // Create a new script tag. const script = document.createElement('script'); script.type = node.type; script.src = node.src; script.async = node.async; script.onload = callback; script.onerror = callback; result.push(script); return result; }, []); if (!externalScripts.length && isRoot) { return callback(); } return externalScripts; }; /** * Parses a collection of DOM nodes for inline script tags. * @param {Array} nodes A collection of DOM nodes. * @return {Array} A collection of inline script tags. */ export const getInlineScripts = nodes => { const nodesArray = [].slice.call(nodes); return nodesArray.reduce((result, node) => { // We only want scripts. if (node.tagName !== 'SCRIPT' || node.src) { if (node.childNodes && node.childNodes.length) { return result.concat(getInlineScripts(node.childNodes)); } return result; } // Create a new script tag. const script = document.createElement('script'); script.type = node.type; script.textContent = node.innerText; result.push(script); return result; }, []); }; /** * Parses a collection of DOM nodes for non-script tags. * @param {Array} nodes A collection of DOM nodes. * @return {Object} A DOM node containing the HTML content. */ export const getHTMLContent = nodes => { const contents = document.createElement('div'); const nodesArray = [].slice.call(nodes); /** * Filters out unwanted nodes. * @param {Object} nodeList A node list. * @returns {Object} */ const filterBlacklistedNodes = nodeList => nodeList.map(node => { // We don't care about script tags. if (node.tagName === 'SCRIPT') { return null; } if (node.tagName === 'IMG') { // Images with a relative path won't work so we will remove them here. if (!node.getAttribute('src').startsWith('http')) { return null; } } if (node.childNodes.length > 0) { const filteredNodes = filterBlacklistedNodes(Array.from(node.childNodes)); /* eslint-disable no-param-reassign */ // Resets / Clears all children so it can be replaced with the filtered ones. node.innerHTML = ''; /* eslint-enable no-param-reassign */ filteredNodes.forEach(child => node.appendChild(child)); } return node; }).filter(node => node !== null); filterBlacklistedNodes(nodesArray).forEach(node => { contents.appendChild(node.cloneNode(true)); }); return contents; }; /** * Checks if a DOM container already exist and creates a new one if it doesn't exist. * @param {string} containerID The HTML id attribute of the container. * @return {Object} The container DOM node. */ export const getDOMContainer = containerID => { let container = document.getElementById(containerID); if (container) { container.innerHTML = ''; } else { container = document.createElement('div'); container.id = containerID; document.body.appendChild(container); } return container; }; /** * Stops a NodeList of youtube players. * @param {NodeList} players YouTube player iframes. */ const stopPlayer = players => { const cmdStop = '{"event":"command","func":"stopVideo","args":""}'; players.forEach((node, index) => { const yt = players[index]; if (yt.contentWindow && yt.contentWindow.postMessage) { yt.contentWindow.postMessage(cmdStop, '*'); } }); }; /** * Handles YouTube iframes so that we are able to controll when the video should be stopped. * It should not play in the background when a tab/page has changed. * @param {NodeList} container HTML widget container. * @deprecated Replaced by the EmbeddedVideos collection system. */ export const handleYouTube = container => { const youtubeIframes = container.querySelectorAll('iframe[src*="youtube.com"]'); youtubeIframes.forEach((node, index) => { let { src } = node; // Is it really needed? We just queried for iframes WITH src attribute. if (!src) { return; } // Enable the js api if (src.includes('enablejsapi=0')) { src = src.replace('enablejsapi=0', 'enablejsapi=1'); } if (!src.includes('enablejsapi')) { const queryChar = src.includes('?') ? '&' : '?'; src += `${queryChar}enablejsapi=1`; } // Set controls to avoid the iframe not being resumable because of controls=0 param on ios. if (!src.includes('controls')) { src += '&controls=1'; } else if (src.includes('controls=0')) { src = src.replace('controls=0', 'controls=1'); } youtubeIframes[index].src = src; }); /** * Stops the player when a native app event is triggered when a webview gets hidden or when the * user navigated to some other page. */ event.addCallback('routeDidChange', () => { stopPlayer(youtubeIframes); }); event.addCallback('viewDidDisappear', () => { stopPlayer(youtubeIframes); }); }; /** * Gets all styles from DOM. * Important: DOM parser might and probably will add <style> tags into <head> while parsing * the html string. * @param {HTMLDocument} dom DOM. * @returns {NodeList} */ export const getStyles = dom => dom.querySelectorAll('style'); /** * Load image and notify on load. * @param {string} src . * @returns {Promise} */ export const loadImage = src => new Promise((resolve, reject) => { const image = new window.Image(); image.onload = () => resolve(src); image.onerror = reject; image.src = src; });