Merge branch 'master' into story-138524853-domain-whitelist

This commit is contained in:
gaba
2017-02-06 14:53:46 -08:00
22 changed files with 561 additions and 85 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
# Talk [![CircleCI](https://circleci.com/gh/coralproject/talk.svg?style=svg)](https://circleci.com/gh/coralproject/talk)
A commenting platform from [The Coral Project](https://coralproject.net).
A commenting platform from [The Coral Project](https://coralproject.net). Talk enters a closed beta in March 2017, but you can download the code for our alpha here. [Read more about Talk here.](https://coralproject.net/products/talk.html)
## Contributing to Talk
+1 -1
View File
@@ -35,7 +35,7 @@ test:
deployment:
release:
tag: /[0-9]+(\.[0-9]+)*/
tag: /v[0-9]+(\.[0-9]+)*/
commands:
- bash ./scripts/deploy.sh
@@ -25,8 +25,24 @@ class EmbedLink extends Component {
}
render () {
const embedText = `<div id='coralStreamEmbed'></div><script type='text/javascript' src='${window.location.protocol}//pym.nprapps.org/pym.v1.min.js'></script><script>var pymParent = new pym.Parent('coralStreamEmbed', '${window.location.protocol}//${window.location.host}/embed/stream', {title: 'Comments'});</script>`;
const location = window.location;
const talkBaseUrl = [
location.protocol,
'//',
location.hostname,
location.port ? (`:${ window.location.port}`) : ''
].join('');
const coralJsUrl = [talkBaseUrl, '/embed.js'].join('');
const nonce = String(Math.random()).slice(2);
const streamElementId = `coral_talk_${nonce}`;
const embedText = `
<div id="${streamElementId}"></div>
<script src="${coralJsUrl}" async onload="
Coral.Talk.render(document.getElementById('${streamElementId}'), {
talk: '${talkBaseUrl}'
});
"></script>
`.trim();
return (
<div>
<h3>{this.props.title}</h3>
+2
View File
@@ -20,6 +20,7 @@ import CommentBox from 'coral-plugin-commentbox/CommentBox';
import UserBox from 'coral-sign-in/components/UserBox';
import SignInContainer from 'coral-sign-in/containers/SignInContainer';
import SuspendedAccount from 'coral-framework/components/SuspendedAccount';
import ChangeDisplayNameContainer from '../../coral-sign-in/containers/ChangeDisplayNameContainer';
import SettingsContainer from 'coral-settings/containers/SettingsContainer';
import RestrictedContent from 'coral-framework/components/RestrictedContent';
import ConfigureStreamContainer from 'coral-configure/containers/ConfigureStreamContainer';
@@ -131,6 +132,7 @@ class Embed extends Component {
: <p>{asset.settings.closedMessage}</p>
}
{!loggedIn && <SignInContainer offset={signInOffset}/>}
{loggedIn && user && <ChangeDisplayNameContainer loggedIn={loggedIn} offset={signInOffset} user={user} />}
<Stream
refetch={refetch}
addNotification={this.props.addNotification}
File diff suppressed because one or more lines are too long
+36
View File
@@ -8,6 +8,28 @@ import coralApi, {base} from '../helpers/response';
export const showSignInDialog = (offset = 0) => ({type: actions.SHOW_SIGNIN_DIALOG, offset});
export const hideSignInDialog = () => ({type: actions.HIDE_SIGNIN_DIALOG});
export const createDisplayNameRequest = () => ({type: actions.CREATE_DISPLAYNAME_REQUEST});
export const showCreateDisplayNameDialog = () => ({type: actions.SHOW_CREATEDISPLAYNAME_DIALOG});
export const hideCreateDisplayNameDialog = () => ({type: actions.HIDE_CREATEDISPLAYNAME_DIALOG});
const createDisplayNameSuccess = () => ({type: actions.CREATEDISPLAYNAME_SUCCESS});
const createDisplayNameFailure = error => ({type: actions.CREATEDISPLAYNAME_FAILURE, error});
export const updateDisplayName = displayName => ({type: actions.UPDATE_DISPLAYNAME, displayName});
export const createDisplayName = (userId, formData) => dispatch => {
dispatch(createDisplayNameRequest());
coralApi(`/users/${userId}/displayname`, {method: 'POST', body: formData})
.then((user) => {
dispatch(createDisplayNameSuccess());
dispatch(hideCreateDisplayNameDialog());
dispatch(updateDisplayName(user));
})
.catch(error => {
dispatch(createDisplayNameFailure(lang.t(`error.${error.message}`)));
});
};
export const changeView = view => dispatch =>
dispatch({
type: actions.CHANGE_VIEW,
@@ -60,6 +82,19 @@ export const fetchSignInFacebook = () => dispatch => {
);
};
// Sign Up Facebook
const signUpFacebookRequest = () => ({type: actions.FETCH_SIGNUP_FACEBOOK_REQUEST});
export const fetchSignUpFacebook = () => dispatch => {
dispatch(signUpFacebookRequest());
window.open(
`${base}/auth/facebook`,
'Continue with Facebook',
'menubar=0,resizable=0,width=500,height=500,top=200,left=500'
);
};
export const facebookCallback = (err, data) => dispatch => {
if (err) {
signInFacebookFailure(err);
@@ -69,6 +104,7 @@ export const facebookCallback = (err, data) => dispatch => {
const user = JSON.parse(data);
dispatch(signInFacebookSuccess(user));
dispatch(hideSignInDialog());
dispatch(showCreateDisplayNameDialog());
} catch (err) {
dispatch(signInFacebookFailure(err));
return;
+9
View File
@@ -4,6 +4,13 @@ export const CLEAN_STATE = 'CLEAN_STATE';
export const SHOW_SIGNIN_DIALOG = 'SHOW_SIGNIN_DIALOG';
export const HIDE_SIGNIN_DIALOG = 'HIDE_SIGNIN_DIALOG';
export const CREATE_DISPLAYNAME_REQUEST = 'CREATE_DISPLAYNAME_REQUEST';
export const CREATEDISPLAYNAME_SUCCESS = 'CREATEDISPLAYNAME_SUCCESS';
export const CREATEDISPLAYNAME_FAILURE = 'CREATEDISPLAYNAME_FAILURE';
export const CREATE_DISPLAYNAME = 'CREATE_DISPLAYNAME';
export const SHOW_CREATEDISPLAYNAME_DIALOG = 'SHOW_CREATEDISPLAYNAME_DIALOG';
export const HIDE_CREATEDISPLAYNAME_DIALOG = 'HIDE_CREATEDISPLAYNAME_DIALOG';
export const FETCH_SIGNUP_REQUEST = 'FETCH_SIGNUP_REQUEST';
export const FETCH_SIGNUP_FAILURE = 'FETCH_SIGNUP_FAILURE';
export const FETCH_SIGNUP_SUCCESS = 'FETCH_SIGNUP_SUCCESS';
@@ -16,6 +23,7 @@ export const FETCH_SIGNIN_FACEBOOK_REQUEST = 'FETCH_SIGNIN_FACEBOOK_REQUEST';
export const FETCH_SIGNIN_FACEBOOK_FAILURE = 'FETCH_SIGNIN_FACEBOOK_FAILURE';
export const FETCH_SIGNIN_FACEBOOK_SUCCESS = 'FETCH_SIGNIN_FACEBOOK_SUCCESS';
export const FETCH_SIGNUP_FACEBOOK_REQUEST = 'FETCH_SIGNUP_FACEBOOK_REQUEST';
export const FETCH_FORGOT_PASSWORD_REQUEST = 'FETCH_FORGOT_PASSWORD_REQUEST';
export const FETCH_FORGOT_PASSWORD_SUCCESS = 'FETCH_FORGOT_PASSWORD_SUCCESS';
export const FETCH_FORGOT_PASSWORD_FAILURE = 'FETCH_FORGOT_PASSWORD_FAILURE';
@@ -33,6 +41,7 @@ export const CHECK_LOGIN_FAILURE = 'CHECK_LOGIN_FAILURE';
export const CHECK_CSRF_TOKEN = 'CHECK_CSRF_TOKEN';
export const UPDATE_DISPLAYNAME = 'UPDATE_DISPLAYNAME';
export const EMAIL_CONFIRM_ERROR = 'EMAIL_CONFIRM_ERROR';
export const CONFIRM_EMAIL_REQUEST = 'CONFIRM_EMAIL_REQUEST';
export const CONFIRM_EMAIL_SUCCESS = 'CONFIRM_EMAIL_SUCCESS';
+1
View File
@@ -5,3 +5,4 @@ export const COMMENTS_BY_USER_REQUEST = 'COMMENTS_BY_USER_REQUEST';
export const COMMENTS_BY_USER_SUCCESS = 'COMMENTS_BY_USER_SUCCESS';
export const COMMENTS_BY_USER_FAILURE = 'COMMENTS_BY_USER_FAILURE';
export const LOGOUT_SUCCESS = 'LOGOUT_SUCCESS';
export const UPDATE_DISPLAYNAME = 'UPDATE_DISPLAYNAME';
+27 -1
View File
@@ -7,6 +7,7 @@ const initialState = Map({
isAdmin: false,
user: null,
showSignInDialog: false,
showCreateDisplayNameDialog: false,
view: 'SIGNIN',
error: '',
passwordRequestSuccess: null,
@@ -14,7 +15,8 @@ const initialState = Map({
emailConfirmationFailure: false,
emailConfirmationLoading: false,
emailConfirmationSuccess: false,
successSignUp: false
successSignUp: false,
fromSignUp: false
});
const purge = user => {
@@ -41,6 +43,21 @@ export default function auth (state = initialState, action) {
emailConfirmationLoading: false,
successSignUp: false
}));
case actions.SHOW_CREATEDISPLAYNAME_DIALOG :
return state
.set('showCreateDisplayNameDialog', true);
case actions.HIDE_CREATEDISPLAYNAME_DIALOG :
return state.merge(Map({
showCreateDisplayNameDialog: false
}));
case actions.CREATEDISPLAYNAME_SUCCESS :
return state.merge(Map({
showCreateDisplayNameDialog: false,
error: ''
}));
case actions.CREATEDISPLAYNAME_FAILURE :
return state
.set('error', action.error);
case actions.CHANGE_VIEW :
return state
.set('error', '')
@@ -72,6 +89,12 @@ export default function auth (state = initialState, action) {
.set('isLoading', false)
.set('error', action.error)
.set('user', null);
case actions.FETCH_SIGNUP_FACEBOOK_REQUEST:
return state
.set('fromSignUp', true);
case actions.FETCH_SIGNIN_FACEBOOK_REQUEST:
return state
.set('fromSignUp', false);
case actions.FETCH_SIGNIN_FACEBOOK_SUCCESS:
return state
.set('user', purge(action.user))
@@ -107,6 +130,9 @@ export default function auth (state = initialState, action) {
return state
.set('passwordRequestFailure', 'There was an error sending your password reset email. Please try again soon!')
.set('passwordRequestSuccess', null);
case actions.UPDATE_DISPLAYNAME:
return state
.set('user', purge(action.displayName));
case actions.EMAIL_CONFIRM_ERROR:
return state
.set('emailConfirmationFailure', true)
@@ -0,0 +1,48 @@
import React from 'react';
import FormField from 'coral-ui/components/FormField';
import Alert from './Alert';
import Button from 'coral-ui/components/Button';
import {Dialog} from 'coral-ui';
import styles from './styles.css';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../translations';
const lang = new I18n(translations);
const CreateDisplayNameDialog = ({open, handleClose, offset, formData, handleSubmitDisplayName, handleChange, ...props}) => (
<Dialog
className={styles.dialog}
id="createDisplayNameDialog"
open={open}
style={{
position: 'relative',
top: offset !== 0 && offset
}}>
<span className={styles.close} onClick={handleClose}>×</span>
<div>
<div className={styles.header}>
<h1>
{lang.t('createdisplay.writeyourusername')}
</h1>
</div>
<div>
<label htmlFor="displayName">{lang.t('createdisplay.yourusername')}</label>
{ props.auth.error && <Alert>{props.auth.error}</Alert> }
<form id="saveDisplayName" onSubmit={handleSubmitDisplayName}>
<FormField
id="displayName"
type="string"
label={lang.t('createdisplay.displayName')}
value={formData.displayName}
onChange={handleChange}
/>
{ props.errors.displayName && <span className={styles.hint}> {lang.t('createdisplay.specialCharacters')} </span> }
<div className={styles.action}>
<Button id="save" type="submit" className={styles.saveButton}>{lang.t('createdisplay.save')}</Button>
</div>
</form>
</div>
</div>
</Dialog>
);
export default CreateDisplayNameDialog;
@@ -14,7 +14,7 @@ const SignUpContent = ({handleChange, formData, ...props}) => (
</h1>
</div>
<div className={styles.socialConnections}>
<Button cStyle="facebook" onClick={props.fetchSignInFacebook} full>
<Button cStyle="facebook" onClick={props.fetchSignUpFacebook} full>
{lang.t('signIn.facebookSignUp')}
</Button>
</div>
@@ -0,0 +1,135 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import validate from 'coral-framework/helpers/validate';
import errorMsj from 'coral-framework/helpers/error';
import CreateDisplayNameDialog from '../components/CreateDisplayNameDialog';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../translations';
const lang = new I18n(translations);
import {
showCreateDisplayNameDialog,
hideCreateDisplayNameDialog,
invalidForm,
validForm,
createDisplayName
} from '../../coral-framework/actions/auth';
class ChangeDisplayNameContainer extends Component {
initialState = {
formData: {
displayName: '',
},
errors: {},
showErrors: false
};
constructor(props) {
super(props);
this.state = this.initialState;
this.handleChange = this.handleChange.bind(this);
this.handleSubmitDisplayName = this.handleSubmitDisplayName.bind(this);
this.handleClose = this.handleClose.bind(this);
this.addError = this.addError.bind(this);
}
handleChange(e) {
const {name, value} = e.target;
this.setState(state => ({
...state,
formData: {
...state.formData,
[name]: value
}
}), () => {
this.validation(name, value);
});
}
addError(name, error) {
return this.setState(state => ({
errors: {
...state.errors,
[name]: error
}
}));
}
validation(name, value) {
const {addError} = this;
if (!value.length) {
addError(name, lang.t('createdisplay.requiredField'));
} else if (!validate[name](value)) {
addError(name, errorMsj[name]);
} else {
const { [name]: prop, ...errors } = this.state.errors; // eslint-disable-line
// Removes Error
this.setState(state => ({...state, errors}));
}
}
isCompleted() {
const {formData} = this.state;
return !Object.keys(formData).filter(prop => !formData[prop].length).length;
}
displayErrors(show = true) {
this.setState({showErrors: show});
}
handleSubmitDisplayName(e) {
e.preventDefault();
const {errors} = this.state;
const {validForm, invalidForm} = this.props;
this.displayErrors();
if (this.isCompleted() && !Object.keys(errors).length) {
this.props.createDisplayName(this.props.user.id, this.state.formData);
validForm();
} else {
invalidForm(lang.t('createdisplay.checkTheForm'));
}
}
handleClose() {
this.props.hideCreateDisplayNameDialog();
}
render() {
const {loggedIn, auth, offset} = this.props;
return (
<div>
<CreateDisplayNameDialog
open={auth.showCreateDisplayNameDialog && auth.fromSignUp}
offset={offset}
handleClose={this.handleClose}
loggedIn={loggedIn}
handleSubmitDisplayName={this.handleSubmitDisplayName}
{...this}
{...this.state}
{...this.props}
/>
</div>
);
}
}
const mapStateToProps = state => ({
auth: state.auth.toJS()
});
const mapDispatchToProps = dispatch => ({
createDisplayName: (userid, formData) => dispatch(createDisplayName(userid, formData)),
showCreateDisplayNameDialog: () => dispatch(showCreateDisplayNameDialog()),
hideCreateDisplayNameDialog: () => dispatch(hideCreateDisplayNameDialog()),
invalidForm: error => dispatch(invalidForm(error)),
validForm: () => dispatch(validForm())
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(ChangeDisplayNameContainer);
@@ -15,6 +15,7 @@ import {
showSignInDialog,
hideSignInDialog,
fetchSignInFacebook,
fetchSignUpFacebook,
fetchForgotPassword,
requestConfirmEmail,
facebookCallback,
@@ -177,6 +178,7 @@ const mapDispatchToProps = dispatch => ({
fetchSignUp: formData => dispatch(fetchSignUp(formData)),
fetchSignIn: formData => dispatch(fetchSignIn(formData)),
fetchSignInFacebook: () => dispatch(fetchSignInFacebook()),
fetchSignUpFacebook: () => dispatch(fetchSignUpFacebook()),
fetchForgotPassword: formData => dispatch(fetchForgotPassword(formData)),
requestConfirmEmail: email => dispatch(requestConfirmEmail(email)),
showSignInDialog: () => dispatch(showSignInDialog()),
+22 -2
View File
@@ -26,7 +26,17 @@ export default {
passwordsDontMatch: 'Passwords don\'t match.',
specialCharacters: 'Display names can contain letters, numbers and _ only',
checkTheForm: 'Invalid Form. Please, check the fields'
}
},
'createdisplay': {
writeyourusername: 'Write your username',
yourusername: 'Your username is publicly visible on all comments you post. A username is needed before you can post your first comment.',
displayName: 'Display Name',
save: 'Save',
requiredField: 'Required field',
errorCreate: 'Error when changing display name',
checkTheForm: 'Invalid Form. Please, check the fields',
specialCharacters: 'Display names can contain letters, numbers and _ only'
},
},
es: {
'signIn': {
@@ -55,6 +65,16 @@ export default {
passwordsDontMatch: 'Las contraseñas no coinciden',
specialCharacters: 'Los nombres pueden contener letras, números y _',
checkTheForm: 'Formulario Inválido. Por favor, completa los campos'
}
},
'createdisplay': {
writeyourusername: 'Escribe tu nombre',
yourusername: 'Tu nombre es visible publicamente en todos los comentarios que publiques. Es necesario tener un nombre de usuario antes de poder publicar tu primer comentario.',
displayName: 'Nombre a mostrar',
save: 'Guardar',
requiredField: 'Campo necesario',
errorCreate: 'Hubo un error al cambiar el nombre de usuario',
checkTheForm: 'Formulario Invalido. Por favor, verifica los campos',
specialCharacters: 'Sólo pueden contener letras, números y _'
},
}
};
+8
View File
@@ -60,6 +60,14 @@ router.post('/:user_id/status', authorization.needed('ADMIN'), (req, res, next)
.catch(next);
});
router.post('/:user_id/displayname', authorization.needed(), (req, res, next) => {
UsersService.setDisplayName(req.params.user_id, req.body.displayName)
.then((user) => {
res.status(201).json(user);
})
.catch(next);
});
router.post('/:user_id/email', authorization.needed('admin'), (req, res, next) => {
UsersService.findById(req.params.user_id)
.then(user => {
+2
View File
@@ -1,9 +1,11 @@
const express = require('express');
const path = require('path');
const router = express.Router();
router.use('/api/v1', require('./api'));
router.use('/admin', require('./admin'));
router.use('/embed', require('./embed'));
router.get('/embed.js', (req, res) => res.sendFile(path.join(__dirname, '../client/coral-embed/index.js')));
router.use('/assets', require('./assets'));
router.get('/', (req, res) => {
+4 -4
View File
@@ -8,11 +8,11 @@ docker login -e $DOCKER_EMAIL -u $DOCKER_USER -p $DOCKER_PASS
deploy_tag() {
# Find our individual versions from the tags
if [ -n "$(echo $CIRCLE_TAG | grep -E '.*\..*\..*')" ]
if [ -n "$(echo $CIRCLE_TAG | grep -E 'v.*\..*\..*')" ]
then
major=$(echo $CIRCLE_TAG | cut -d. -f1)
minor=$(echo $CIRCLE_TAG | cut -d. -f2)
patch=$(echo $CIRCLE_TAG | cut -d. -f3)
major=$(echo ${CIRCLE_TAG//v} | cut -d. -f1)
minor=$(echo ${CIRCLE_TAG//v} | cut -d. -f2)
patch=$(echo ${CIRCLE_TAG//v} | cut -d. -f3)
major_version_tag=$major
minor_version_tag=$major.$minor
+19
View File
@@ -353,6 +353,25 @@ module.exports = class UsersService {
return UserModel.update({id}, {$set: {status}});
}
/**
* Set the display name of a user.
* @param {String} id id of a user
* @param {String} displayName display name to set
* @param {Function} done callback after the operation is complete
*/
static setDisplayName(id, displayName) {
return UsersService.isValidDisplayName(displayName)
.then(() => { // displayName is valid
return UserModel.update(
{id},
{$set: {'displayName': displayName}})
.then(() => {
return UserModel.findOne({'id': id});
});
});
}
/**
* Finds a user with the id.
* @param {String} id user id (uuid)
+2 -2
View File
@@ -6,8 +6,8 @@ const embedStreamCommands = {
ready() {
return this
.waitForElementVisible('body', 4000)
.waitForElementVisible('iframe#coralStreamIframe')
.api.frame('coralStreamIframe');
.waitForElementVisible('#coralStreamEmbed > iframe')
.api.frame('coralStreamEmbed_iframe');
},
signUp(user) {
return this
+5 -5
View File
@@ -26,7 +26,7 @@ module.exports = {
// Load Page
client.resizeWindow(1200, 800)
.url(client.globals.baseUrl)
.frame('coralStreamIframe')
.frame('coralStreamEmbed_iframe')
// Register and Log In
.waitForElementVisible('#coralSignInButton', 2000)
@@ -65,7 +65,7 @@ module.exports = {
// Load Page
client.url(client.globals.baseUrl)
.frame('coralStreamIframe');
.frame('coralStreamEmbed_iframe');
// Post a comment
client.waitForElementVisible('.coral-plugin-commentbox-button', 2000)
@@ -91,7 +91,7 @@ module.exports = {
// Load Page
client.resizeWindow(1200, 800)
.url(client.globals.baseUrl)
.frame('coralStreamIframe');
.frame('coralStreamEmbed_iframe');
// Post a comment
client.waitForElementVisible('.coral-plugin-commentbox-button', 2000)
@@ -138,7 +138,7 @@ module.exports = {
// Load Page
client.resizeWindow(1200, 800)
.url(client.globals.baseUrl)
.frame('coralStreamIframe');
.frame('coralStreamEmbed_iframe');
// Post a reply
client.waitForElementVisible('.coral-plugin-replies-reply-button', 5000)
@@ -161,7 +161,7 @@ module.exports = {
'Total comment count premod on': client => {
client.perform((client, done) => {
client.url(client.globals.baseUrl)
.frame('coralStreamIframe');
.frame('coralStreamEmbed_iframe');
// Verify that comment count is correct
client.waitForElementVisible('.coral-plugin-comment-count-text', 2000)
+19
View File
@@ -178,6 +178,25 @@ describe('services.UsersService', () => {
});
});
describe('#setDisplayName', () => {
it('should set the display name to a new unique one', () => {
return UsersService
.setDisplayName(mockUsers[0].id, 'maria')
.then(() => UsersService.findById(mockUsers[0].id))
.then((user) => {
expect(user).to.have.property('displayName', 'maria');
});
});
it('should return an error when the displayName is not unique', () => {
return UsersService
.setDisplayName(mockUsers[0].id, 'marvel')
.catch((error) => {
expect(error).to.not.be.null;
});
});
});
describe('#ban', () => {
it('should set the status to banned', () => {
return UsersService
+6 -66
View File
@@ -23,72 +23,12 @@
<p><%= body %></p>
<p><a href="/admin">Admin</a> - <a href="/assets">All Assets</a></p>
<div id='coralStreamEmbed'></div>
<script src="/embed.js" async onload="
Coral.Talk.render(document.getElementById('coralStreamEmbed'), {
talk: '/',
asset: <%= asset_url ? asset_url : 'undefined' %>
})
"></script>
</main>
<script type='text/javascript' src='<%= basePath %>/pym.v1.min.js'></script>
<script>
var ready = false;
var notificationOffset = 200;
// default to using the window.location
var url = window.location.protocol + '//' + window.location.host + window.location.pathname;
// if a url is passed into the template prefer it to the current url
<%if (asset_url.length > 0) { %>
url = '<%= asset_url %>';
<%}%>
var pymParent = new pym.Parent('coralStreamEmbed', '/embed/stream?asset_url=' + encodeURIComponent(url), {title: 'Talk Comments', id:'coralStreamIframe', name: 'coralStreamIframe', asset_url: url});
pymParent.onMessage('height', function(height) {
document.querySelector('#coralStreamEmbed iframe').height = height + 'px';
})
pymParent.onMessage('getPosition', function(notification) {
var position = viewport().height + document.body.scrollTop;
if (position > notificationOffset) {
position = position - notificationOffset;
}
pymParent.sendMessage('position', position);
});
pymParent.onMessage('childReady', function () {
var interval = setInterval(function () {
if (ready) {
window.clearInterval(interval);
// default to using the window.location
var url = window.location.hash;
// if a url is passed into the template prefer it to the current url
<%if (asset_url.length > 0) { %>
url = '<%= asset_url %>';
<%}%>
pymParent.sendMessage('DOMContentLoaded', url);
}
}, 100);
});
pymParent.onMessage('navigate', function (url) {
window.open(url, '_blank').focus();
})
// wait till images and other iframes are loaded before scrolling the page.
// or do we want to be more aggressive and scroll when we hit DOM ready?
document.addEventListener('DOMContentLoaded', function () {
ready = true;
});
function viewport() {
var e = window, a = 'inner';
if ( !( 'innerWidth' in window ) ){
a = 'client';
e = document.documentElement || document.body;
}
return { width : e[ a+'Width' ] , height : e[ a+'Height' ] }
}
</script>
</body>
</html>