Files
talk/client/coral-embed/src/index.js
T
2017-05-09 15:53:57 -03:00

205 lines
5.6 KiB
JavaScript

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;