import pym from 'pym.js'; import {stringify} from 'querystring'; // TODO: Styles should live in a separate file const snackbarStyles = { position: 'fixed', cursor: 'default', userSelect: 'none', backgroundColor: '#323232', zIndex: 3, willChange: 'transform, opacity', transition: 'transform .35s cubic-bezier(.55,0,.1,1), opacity .35s', pointerEvents: 'none', padding: '12px 18px', color: '#fff', borderRadius: '3px 3px 0 0', textAlign: 'center', maxWidth: '400px', left: '50%', opacity: 0, transform: 'translate(-50%, 20px)', bottom: 0, boxSizing: 'border-box', fontFamily: 'Helvetica, "Helvetica Neue", Verdana, sans-serif' }; // This function should return value of window.Coral const Coral = {}; const Talk = (Coral.Talk = {}); // build the URL to load in the pym iframe function buildStreamIframeUrl(talkBaseUrl, query) { let url = [ talkBaseUrl, talkBaseUrl.match(/\/$/) ? '' : '/', // make sure no double-'/' if opts.talk already ends with '/' 'embed/stream?' ].join(''); url += stringify(query); return url; } // Set up postMessage listeners/handlers on the pymParent // e.g. to resize the iframe, and navigate the host page function configurePymParent(pymParent, opts) { let notificationOffset = 200; let cachedHeight; const snackbar = document.createElement('div'); // Sends config to pymChild function sendConfig(config) { pymParent.sendMessage('config', JSON.stringify(config)); } // Sends config to the child pymParent.onMessage('getConfig', function() { sendConfig(opts || {}); }); snackbar.id = 'coral-notif'; for (let key in snackbarStyles) { snackbar.style[key] = snackbarStyles[key]; } window.document.body.appendChild(snackbar); // Workaround: IOS Safari ignores `width` but respects `min-width` value. pymParent.el.firstChild.style.width = '1px'; pymParent.el.firstChild.style.minWidth = '100%'; // Resize parent iframe height when child height changes pymParent.onMessage('height', function(height) { if (height !== cachedHeight) { pymParent.el.firstChild.style.height = `${height}px`; cachedHeight = height; } }); pymParent.onMessage('coral-clear-notification', function() { snackbar.style.opacity = 0; }); // remove the permalink comment id from the hash pymParent.onMessage('coral-view-all-comments', function() { window.history.replaceState( {}, document.title, location.origin + location.pathname + location.search ); }); pymParent.onMessage('coral-alert', function(message) { const [type, text] = message.split('|'); snackbar.style.transform = 'translate(-50%, 20px)'; snackbar.style.opacity = 0; snackbar.className = `coral-notif-${type}`; snackbar.textContent = text; setTimeout(() => { snackbar.style.transform = 'translate(-50%, 0)'; snackbar.style.opacity = 1; }, 0); setTimeout(() => { snackbar.style.opacity = 0; }, 5000); }); // Helps child show notifications at the right scrollTop pymParent.onMessage('getPosition', function() { let position = viewport().height + document.body.scrollTop; if (position > notificationOffset) { position = position - notificationOffset; } pymParent.sendMessage('position', position); }); // When end-user clicks link in iframe, open it in parent context pymParent.onMessage('navigate', function(url) { window.open(url, '_blank').focus(); }); // get dimensions of viewport const viewport = () => { let e = window, a = 'inner'; if (!('innerWidth' in window)) { a = 'client'; e = document.documentElement || document.body; } return { width: e[`${a}Width`], height: e[`${a}Height`] }; }; } /** * Render a Talk stream * @param {HTMLElement} el - Element to render the stream in * @param {Object} opts - Configuration options for talk * @param {String} opts.talk - Talk base URL * @param {String} [opts.title] - Title of Stream (rendered in iframe) * @param {String} [opts.asset_url] - Asset URL * @param {String} [opts.asset_id] - Asset ID */ Talk.render = function(el, opts) { if (!el) { throw new Error( 'Please provide Coral.Talk.render() the HTMLElement you want to render Talk in.' ); } if (typeof el !== 'object') { throw new Error( `Coral.Talk.render() expected HTMLElement but got ${el} (${typeof el})` ); } opts = opts || {}; // TODO: infer this URL without explicit user input (if possible, may have to be added at build/render time of this script) if (!opts.talk) { throw new Error( 'Coral.Talk.render() expects opts.talk as the Talk Base URL' ); } // Ensure el has an id, as pym can't directly accept the HTMLElement. if (!el.id) { el.id = `_${Math.random()}`; } // Compose the query to send down to the Talk API so it knows what to load. let query = {}; query.comment_id = window.location.hash.slice(1); query.asset_id = opts.asset_id; query.asset_url = opts.asset_url; if (!query.asset_url) { try { query.asset_url = document.querySelector('link[rel="canonical"]').href; } catch (e) { console.warn( 'This page does not include a canonical link tag. Talk has inferred this asset_url from the window object. Query params have been stripped, which may cause a single thread to be present across multiple pages.' ); query.asset_url = window.location.origin + window.location.pathname; } } configurePymParent( new pym.Parent(el.id, buildStreamIframeUrl(opts.talk, query), { title: opts.title, id: `${el.id}_iframe`, name: `${el.id}_iframe` }), opts ); }; export default Coral;