Merge pull request #1789 from LLK/hotfix/es6-upgrade

[Master] Upgrade to ES6, Take II
This commit is contained in:
Ray Schamp 2018-01-30 16:29:51 -05:00 committed by GitHub
commit 51533bbf39
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
188 changed files with 13697 additions and 11511 deletions

3
.babelrc Normal file
View file

@ -0,0 +1,3 @@
{
"presets": ["es2015", "react"],
}

View file

@ -1,29 +0,0 @@
{
"parser": "babel-eslint",
"rules": {
"curly": [2, "multi-line"],
"eol-last": [2],
"indent": [2, 4],
"linebreak-style": [2, "unix"],
"max-len": [2, 120, 4, {"ignoreUrls": true}],
"no-trailing-spaces": [2, { "skipBlankLines": true }],
"no-unused-vars": [2, {"args": "after-used", "varsIgnorePattern": "^_"}],
"quotes": [2, "single"],
"semi": [2, "always"],
"space-before-function-paren": [2, "always"],
"strict": [2, "never"]
},
"env": {
"browser": true,
"es6": true,
"node": true
},
"globals": {
"formatMessage": true
},
"plugins": [
"react",
"json"
],
"extends": "eslint:recommended"
}

4
.eslintrc.js Normal file
View file

@ -0,0 +1,4 @@
module.exports = {
extends: ['scratch', 'scratch/node'],
plugins: ['json']
};

View file

@ -133,3 +133,9 @@ file_filter = localizations/conference-index/<lang>.json
source_file = src/views/conference/2018/index/l10n.json
source_lang = en
type = KEYVALUEJSON
[scratch-website.preview-faq-l10njson]
file_filter = localizations/preview-faq/<lang>.json
source_file = src/views/preview-faq/l10n.json
source_lang = en
type = KEYVALUEJSON

View file

@ -3,7 +3,7 @@ var defaults = require('lodash.defaults');
var fastlyConfig = require('./lib/fastly-config-methods');
const languages = require('../languages.json');
var route_json = require('../src/routes.json');
var routeJson = require('../src/routes.json');
const FASTLY_SERVICE_ID = process.env.FASTLY_SERVICE_ID || '';
const S3_BUCKET_NAME = process.env.S3_BUCKET_NAME || '';
@ -15,10 +15,10 @@ var extraAppRoutes = [
// TODO: Should this be added for every route?
'/\\?',
// View html
'/[^\/]*\.html$'
'/[^/]*.html$'
];
var routes = route_json.map(function (route) {
var routes = routeJson.map(function (route) {
return defaults({}, {pattern: fastlyConfig.expressPatternToRegex(route.pattern)}, route);
});
@ -28,9 +28,9 @@ async.auto({
if (err) return cb(err);
// Validate latest version before continuing
if (response.active || response.locked) {
fastly.cloneVersion(response.number, function (err, response) {
if (err) return cb('Failed to clone latest version: ' + err);
cb(null, response.number);
fastly.cloneVersion(response.number, function (e, resp) {
if (e) return cb('Failed to clone latest version: ' + e);
cb(null, resp.number);
});
} else {
cb(null, response.number);
@ -46,11 +46,11 @@ async.auto({
var recvCondition = '' +
'if (' + notPassStatement + ') {\n' +
' set req.backend = F_s3;\n' +
' set req.http.host = \"' + S3_BUCKET_NAME + '\";\n' +
' set req.http.host = "' + S3_BUCKET_NAME + '";\n' +
'} else {\n' +
' if (!req.http.Fastly-FF) {\n' +
' if (req.http.X-Forwarded-For) {\n' +
' set req.http.Fastly-Temp-XFF = req.http.X-Forwarded-For \", \" client.ip;\n' +
' set req.http.Fastly-Temp-XFF = req.http.X-Forwarded-For ", " client.ip;\n' +
' } else {\n' +
' set req.http.Fastly-Temp-XFF = client.ip;\n' +
' }\n' +
@ -171,20 +171,19 @@ async.auto({
if (err) return cb(err);
cb(null, headers);
});
}]},
function (err, results) {
}]
}, function (err, results) {
if (err) throw new Error(err);
if (process.env.FASTLY_ACTIVATE_CHANGES) {
fastly.activateVersion(results.version, function (err, response) {
if (err) throw new Error(err);
process.stdout.write('Successfully configured and activated version ' + response.number + '\n');
fastly.activateVersion(results.version, function (e, resp) {
if (err) throw new Error(e);
process.stdout.write('Successfully configured and activated version ' + resp.number + '\n');
if (process.env.FASTLY_PURGE_ALL) {
fastly.purgeAll(FASTLY_SERVICE_ID, function (err) {
if (err) throw new Error(err);
fastly.purgeAll(FASTLY_SERVICE_ID, function (error) {
if (error) throw new Error(error);
process.stdout.write('Purged all.\n');
});
}
});
}
}
);
});

View file

@ -24,9 +24,9 @@ var FastlyConfigMethods = {
*/
getViewPaths: function (routes) {
return routes.reduce(function (paths, route) {
var path = route.routeAlias || route.pattern;
if (paths.indexOf(path) === -1) {
paths.push(path);
var p = route.routeAlias || route.pattern;
if (paths.indexOf(p) === -1) {
paths.push(p);
}
return paths;
}, []);
@ -39,7 +39,7 @@ var FastlyConfigMethods = {
* 2. /path/:arg([regex]) :arg is removed, leaving just /path/([regex])
*/
expressPatternToRegex: function (pattern) {
pattern = pattern.replace(/(:\w+)(\([^\)]+\))/gi, '$2');
pattern = pattern.replace(/(:\w+)(\([^)]+\))/gi, '$2');
return pattern.replace(/(:\w+)/gi, '.+?');
},
@ -84,7 +84,7 @@ var FastlyConfigMethods = {
return 'redirects/' + route.pattern;
},
/**
/*
* Returns custom vcl configuration as a string that sets the varnish
* Time to Live (TTL) for responses that come from s3.
*

View file

@ -19,8 +19,8 @@ module.exports = function (apiKey, serviceId) {
*
* @return {string}
*/
fastly.getFastlyAPIPrefix = function (serviceId, version) {
return '/service/' + encodeURIComponent(serviceId) + '/version/' + version;
fastly.getFastlyAPIPrefix = function (servId, version) {
return '/service/' + encodeURIComponent(servId) + '/version/' + version;
};
/*
@ -32,15 +32,15 @@ module.exports = function (apiKey, serviceId) {
if (!this.serviceId) {
return cb('Failed to get latest version. No serviceId configured');
}
var url = '/service/'+ encodeURIComponent(this.serviceId) +'/version';
var url = '/service/' + encodeURIComponent(this.serviceId) + '/version';
this.request('GET', url, function (err, versions) {
if (err) {
return cb('Failed to fetch versions: ' + err);
}
var latestVersion = versions.reduce(function (latestVersion, version) {
if (!latestVersion) return version;
if (version.number > latestVersion.number) return version;
return latestVersion;
var latestVersion = versions.reduce(function (lateVersion, version) {
if (!lateVersion) return version;
if (version.number > lateVersion.number) return version;
return lateVersion;
});
return cb(null, latestVersion);
});
@ -63,16 +63,16 @@ module.exports = function (apiKey, serviceId) {
var postUrl = this.getFastlyAPIPrefix(this.serviceId, version) + '/condition';
return this.request('PUT', putUrl, condition, function (err, response) {
if (err && err.statusCode === 404) {
this.request('POST', postUrl, condition, function (err, response) {
if (err) {
return cb('Failed while inserting condition \"' + condition.statement + '\": ' + err);
this.request('POST', postUrl, condition, function (e, resp) {
if (e) {
return cb('Failed while inserting condition "' + condition.statement + '": ' + e);
}
return cb(null, response);
return cb(null, resp);
});
return;
}
if (err) {
return cb('Failed to update condition \"' + condition.statement + '\": ' + err);
return cb('Failed to update condition "' + condition.statement + '": ' + err);
}
return cb(null, response);
}.bind(this));
@ -95,11 +95,11 @@ module.exports = function (apiKey, serviceId) {
var postUrl = this.getFastlyAPIPrefix(this.serviceId, version) + '/header';
return this.request('PUT', putUrl, header, function (err, response) {
if (err && err.statusCode === 404) {
this.request('POST', postUrl, header, function (err, response) {
if (err) {
return cb('Failed to insert header: ' + err);
this.request('POST', postUrl, header, function (e, resp) {
if (e) {
return cb('Failed to insert header: ' + e);
}
return cb(null, response);
return cb(null, resp);
});
return;
}
@ -127,11 +127,11 @@ module.exports = function (apiKey, serviceId) {
var postUrl = this.getFastlyAPIPrefix(this.serviceId, version) + '/response_object';
return this.request('PUT', putUrl, responseObj, function (err, response) {
if (err && err.statusCode === 404) {
this.request('POST', postUrl, responseObj, function (err, response) {
if (err) {
return cb('Failed to insert response object: ' + err);
this.request('POST', postUrl, responseObj, function (e, resp) {
if (e) {
return cb('Failed to insert response object: ' + e);
}
return cb(null, response);
return cb(null, resp);
});
return;
}
@ -166,7 +166,7 @@ module.exports = function (apiKey, serviceId) {
this.request('PUT', url, cb);
};
/**
/*
* Upsert a custom vcl file. Attempts a PUT, and falls back
* to POST if not there already.
*
@ -186,16 +186,16 @@ module.exports = function (apiKey, serviceId) {
return this.request('PUT', url, content, function (err, response) {
if (err && err.statusCode === 404) {
content.name = name;
this.request('POST', postUrl, content, function (err, response) {
if (err) {
return cb('Failed while adding custom vcl \"' + name + '\": ' + err);
this.request('POST', postUrl, content, function (e, resp) {
if (e) {
return cb('Failed while adding custom vcl "' + name + '": ' + e);
}
return cb(null, response);
return cb(null, resp);
});
return;
}
if (err) {
return cb('Failed to update custom vcl \"' + name + '\": ' + err);
return cb('Failed to update custom vcl "' + name + '": ' + err);
}
return cb(null, response);
}.bind(this));

View file

@ -1,10 +1,12 @@
/**
/*
* Constructor
*/
function Handler (route) {
const Handler = function (route) {
// Handle redirects
if (route.redirect) {
return (req, res) => { res.redirect(route.redirect); };
return (req, res) => {
res.redirect(route.redirect);
};
}
var url = '/' + route.name + '.html';
@ -12,9 +14,9 @@ function Handler (route) {
req.url = url;
next();
};
}
};
/**
/*
* Export a new instance
*/
module.exports = function (route) {

View file

@ -1,4 +1,5 @@
{
"ab": "Аҧсшәа",
"ar": "العربية",
"an": "Aragonés",
"ast": "Asturianu",

View file

@ -33,24 +33,27 @@
"devDependencies": {
"async": "1.5.2",
"autoprefixer": "6.3.6",
"babel-core": "6.10.4",
"babel-eslint": "5.0.4",
"babel-loader": "6.2.4",
"babel-preset-es2015": "6.9.0",
"babel-preset-react": "6.11.1",
"babel-cli": "6.26.0",
"babel-core": "6.23.1",
"babel-eslint": "8.0.2",
"babel-loader": "7.1.0",
"babel-preset-es2015": "6.22.0",
"babel-preset-react": "6.22.0",
"cheerio": "1.0.0-rc.2",
"classnames": "2.1.3",
"classnames": "2.2.5",
"cookie": "0.2.2",
"copy-webpack-plugin": "0.2.0",
"create-react-class": "15.6.2",
"css-loader": "0.23.1",
"eslint": "1.3.1",
"eslint": "4.7.1",
"eslint-config-scratch": "5.0.0",
"eslint-plugin-json": "1.2.0",
"eslint-plugin-react": "3.3.1",
"eslint-plugin-react": "7.4.0",
"exenv": "1.2.0",
"fastly": "1.2.1",
"file-loader": "0.8.4",
"formsy-react": "0.18.0",
"formsy-react-components": "0.7.1",
"formsy-react": "0.19.5",
"formsy-react-components": "0.11.1",
"git-bundle-sha": "0.0.2",
"glob": "5.0.15",
"google-libphonenumber": "1.0.21",
@ -59,6 +62,7 @@
"json-loader": "0.5.2",
"json2po-stream": "1.0.3",
"keymirror": "0.1.1",
"lodash.bindall": "4.4.0",
"lodash.clone": "3.0.3",
"lodash.defaultsdeep": "3.10.0",
"lodash.isarray": "3.0.4",
@ -70,21 +74,22 @@
"node-sass": "4.6.1",
"pako": "0.2.8",
"po2icu": "0.0.2",
"postcss-loader": "0.8.2",
"postcss-loader": "2.0.10",
"prop-types": "15.6.0",
"raven-js": "3.0.4",
"react": "15.1.0",
"react-dom": "15.0.1",
"react": "15.5.4",
"react-dom": "15.5.4",
"react-intl": "2.1.2",
"react-modal": "1.5.2",
"react-onclickoutside": "4.1.1",
"react-modal": "3.1.11",
"react-onclickoutside": "6.7.1",
"react-redux": "4.4.5",
"react-responsive": "1.1.4",
"react-responsive": "3.0.0",
"react-slick": "0.12.2",
"react-telephone-input": "3.4.5",
"react-telephone-input": "3.8.6",
"redux": "3.5.2",
"redux-thunk": "2.0.1",
"sass-lint": "1.5.1",
"sass-loader": "2.0.1",
"sass-loader": "6.0.6",
"scratchr2_translations": "git://github.com/LLK/scratchr2_translations.git#master",
"slick-carousel": "1.5.8",
"source-map-support": "0.3.2",
@ -92,8 +97,8 @@
"tap": "7.1.2",
"url-loader": "0.5.6",
"watch": "0.16.0",
"webpack": "1.12.14",
"webpack-dev-middleware": "1.2.0",
"webpack": "2.7.0",
"webpack-dev-middleware": "2.0.4",
"xhr": "2.2.0"
},
"nyc": {

11
src/.eslintrc.js Normal file
View file

@ -0,0 +1,11 @@
module.exports = {
root: true,
extends: ['scratch', 'scratch/es6', 'scratch/react'],
env: {
browser: true
},
globals: {
process: true
},
plugins: ['json']
};

View file

@ -1,33 +1,35 @@
var classNames = require('classnames');
var React = require('react');
const bindAll = require('lodash.bindall');
const classNames = require('classnames');
const PropTypes = require('prop-types');
const React = require('react');
require('./accordion.scss');
var Accordion = React.createClass({
type: 'Accordion',
getDefaultProps: function () {
return {
titleAs: 'div',
contentAs: 'div'
};
},
getInitialState: function () {
return {
class Accordion extends React.Component {
constructor (props) {
super(props);
bindAll(this, [
'handleClick'
]);
this.state = {
isOpen: false
};
},
toggleContent: function () {
}
handleClick (e) {
e.preventDefault();
this.setState({isOpen: !this.state.isOpen});
},
render: function () {
var classes = classNames({
'content': true,
'open': this.state.isOpen
}
render () {
const classes = classNames({
content: true,
open: this.state.isOpen
});
return (
<div className="accordion">
<this.props.titleAs className="title"
onClick={this.toggleContent}>
<this.props.titleAs
className="title"
onClick={this.handleClick}
>
{this.props.title}
</this.props.titleAs>
<this.props.contentAs className={classes}>
@ -36,6 +38,16 @@ var Accordion = React.createClass({
</div>
);
}
});
}
Accordion.propTypes = {
content: PropTypes.node,
title: PropTypes.string
};
Accordion.defaultProps = {
contentAs: 'div',
titleAs: 'div'
};
module.exports = Accordion;

View file

@ -1,37 +1,39 @@
var React = require('react');
var connect = require('react-redux').connect;
const bindAll = require('lodash.bindall');
const connect = require('react-redux').connect;
const PropTypes = require('prop-types');
const React = require('react');
var Button = require('../forms/button.jsx');
const Button = require('../forms/button.jsx');
require('./adminpanel.scss');
var AdminPanel = React.createClass({
type: 'AdminPanel',
getInitialState: function () {
return {
class AdminPanel extends React.Component {
constructor (props) {
super(props);
bindAll(this, [
'handleToggleVisibility'
]);
this.state = {
showPanel: false
};
},
handleToggleVisibility: function (e) {
}
handleToggleVisibility (e) {
e.preventDefault();
this.setState({showPanel: !this.state.showPanel});
},
render: function () {
// make sure user is present before checking if they're an admin. Don't show anything if user not an admin.
var showAdmin = false;
if (this.props.session.session.user) {
showAdmin = this.props.session.session.permissions.admin;
}
if (!showAdmin) return false;
render () {
if (!this.props.isAdmin) return false;
if (this.state.showPanel) {
return (
<div id="admin-panel" className="visible">
<div
className="visible"
id="admin-panel"
>
<span
className="toggle"
onClick={this.handleToggleVisibility}>
onClick={this.handleToggleVisibility}
>
x
</span>
<div className="admin-header">
@ -44,8 +46,15 @@ var AdminPanel = React.createClass({
<dd>
<ul className="cache-list">
<li>
<form method="post" action="/scratch_admin/page/clear-anon-cache/">
<input type="hidden" name="path" value="/" />
<form
action="/scratch_admin/page/clear-anon-cache/"
method="post"
>
<input
name="path"
type="hidden"
value="/"
/>
<div className="button-row">
<span>For anonymous users:</span>
<Button type="submit">
@ -60,27 +69,32 @@ var AdminPanel = React.createClass({
</div>
</div>
);
} else {
}
return (
<div id="admin-panel" className="hidden">
<div
className="hidden"
id="admin-panel"
>
<span
className="toggle"
onClick={this.handleToggleVisibility}>
onClick={this.handleToggleVisibility}
>
&gt;
</span>
</div>
);
}
}
});
}
var mapStateToProps = function (state) {
return {
session: state.session
};
AdminPanel.propTypes = {
children: PropTypes.node,
isAdmin: PropTypes.bool
};
var ConnectedAdminPanel = connect(mapStateToProps)(AdminPanel);
const mapStateToProps = state => ({
isAdmin: state.permissions.admin
});
const ConnectedAdminPanel = connect(mapStateToProps)(AdminPanel);
module.exports = ConnectedAdminPanel;

View file

@ -1,23 +1,22 @@
var React = require('react');
var classNames = require('classnames');
const classNames = require('classnames');
const omit = require('lodash.omit');
const PropTypes = require('prop-types');
const React = require('react');
var Avatar = React.createClass({
type: 'Avatar',
propTypes: {
src: React.PropTypes.string
},
getDefaultProps: function () {
return {
const Avatar = props => (
<img
className={classNames('avatar', props.className)}
{...omit(props, ['className'])}
/>
);
Avatar.propTypes = {
className: PropTypes.string,
src: PropTypes.string
};
Avatar.defaultProps = {
src: '//cdn2.scratch.mit.edu/get_image/user/2584924_24x24.png?v=1438702210.96'
};
},
render: function () {
var classes = classNames(
'avatar',
this.props.className
);
return <img {... this.props} className={classes} />;
}
});
};
module.exports = Avatar;

View file

@ -1,40 +1,38 @@
var classNames = require('classnames');
var React = require('react');
const classNames = require('classnames');
const PropTypes = require('prop-types');
const React = require('react');
require('./box.scss');
var Box = React.createClass({
type: 'Box',
propTypes: {
title: React.PropTypes.string.isRequired,
subtitle: React.PropTypes.string,
moreTitle: React.PropTypes.string,
moreHref: React.PropTypes.string,
moreProps: React.PropTypes.object
},
render: function () {
var classes = classNames(
'box',
this.props.className
);
return (
<div className={classes}>
const Box = props => (
<div className={classNames('box', props.className)}>
<div className="box-header">
<h4>{this.props.title}</h4>
<h5>{this.props.subtitle}</h5>
<h4>{props.title}</h4>
<h5>{props.subtitle}</h5>
<p>
<a href={this.props.moreHref} {...this.props.moreProps}>
{this.props.moreTitle}
<a
href={props.moreHref}
{...props.moreProps}
>
{props.moreTitle}
</a>
</p>
</div>
<div className="box-content">
{this.props.children}
{props.children}
</div>
</div>
);
}
});
);
Box.propTypes = {
children: PropTypes.node,
className: PropTypes.string,
moreHref: PropTypes.string,
moreProps: PropTypes.object, // eslint-disable-line react/forbid-prop-types
moreTitle: PropTypes.string,
subtitle: PropTypes.string,
title: PropTypes.string.isRequired
};
module.exports = Box;

View file

@ -1,17 +1,18 @@
var classNames = require('classnames');
var React = require('react');
const classNames = require('classnames');
const PropTypes = require('prop-types');
const React = require('react');
require('./card.scss');
var Card = React.createClass({
displayName: 'Card',
render: function () {
return (
<div className={classNames(['card', this.props.className])}>
{this.props.children}
const Card = props => (
<div className={classNames(['card', props.className])}>
{props.children}
</div>
);
}
});
);
Card.propTypes = {
children: PropTypes.node,
className: PropTypes.string
};
module.exports = Card;

View file

@ -1,35 +1,19 @@
var classNames = require('classnames');
var defaults = require('lodash.defaults');
var React = require('react');
var Slider = require('react-slick');
const classNames = require('classnames');
const defaults = require('lodash.defaults');
const PropTypes = require('prop-types');
const React = require('react');
const Slider = require('react-slick');
var Thumbnail = require('../thumbnail/thumbnail.jsx');
const Thumbnail = require('../thumbnail/thumbnail.jsx');
var frameless = require('../../lib/frameless.js');
const frameless = require('../../lib/frameless.js');
require('slick-carousel/slick/slick.scss');
require('slick-carousel/slick/slick-theme.scss');
require('./carousel.scss');
/**
* Displays content in horizontal scrolling box. Example usage: splash page rows.
*/
var Carousel = React.createClass({
type: 'Carousel',
propTypes: {
items: React.PropTypes.array
},
getDefaultProps: function () {
return {
items: require('./carousel.json'),
showRemixes: false,
showLoves: false,
type: 'project'
};
},
render: function () {
var settings = this.props.settings || {};
defaults(settings, {
const Carousel = props => {
defaults(props.settings, {
centerMode: false,
dots: false,
infinite: false,
@ -38,58 +22,94 @@ var Carousel = React.createClass({
slidesToScroll: 5,
variableWidth: true,
responsive: [
{breakpoint: frameless.mobile, settings: {
{
breakpoint: frameless.mobile,
settings: {
arrows: true,
slidesToScroll: 1,
slidesToShow: 1,
centerMode: true
}},
{breakpoint: frameless.tablet, settings: {
}
},
{
breakpoint: frameless.tablet,
settings: {
slidesToScroll: 2,
slidesToShow: 2
}},
{breakpoint: frameless.desktop, settings: {
}
},
{
breakpoint: frameless.desktop,
settings: {
slidesToScroll: 4,
slidesToShow: 4
}}
}
}
]
});
var arrows = this.props.items.length > settings.slidesToShow;
var classes = classNames(
'carousel',
this.props.className
);
const arrows = props.items.length > props.settings.slidesToShow;
return (
<Slider className={classes} arrows={arrows} {... settings}>
{this.props.items.map(function (item) {
var href = '';
switch (this.props.type) {
<Slider
arrows={arrows}
className={classNames('carousel', props.className)}
{... props.settings}
>
{props.items.map(item => {
let href = '';
switch (props.type) {
case 'gallery':
href = '/studios/' + item.id + '/';
href = `/studios/${item.id}/`;
break;
case 'project':
href = '/projects/' + item.id + '/';
href = `/projects/${item.id}/`;
break;
default:
href = '/' + item.type + '/' + item.id + '/';
href = `/${item.type}/${item.id}/`;
}
return (
<Thumbnail key={[this.key, item.id].join('.')}
showLoves={this.props.showLoves}
showRemixes={this.props.showRemixes}
type={this.props.type}
href={href}
title={item.title}
src={item.image}
<Thumbnail
creator={item.author.username}
href={href}
key={[props.type, item.id].join('.')}
loves={item.stats.loves}
remixes={item.stats.remixes}
loves={item.stats.loves} />
showLoves={props.showLoves}
showRemixes={props.showRemixes}
src={item.image}
title={item.title}
type={props.type}
/>
);
}.bind(this))}
})}
</Slider>
);
}
});
};
Carousel.propTypes = {
className: PropTypes.string,
items: PropTypes.arrayOf(PropTypes.any),
settings: PropTypes.shape({
centerMode: PropTypes.bool,
dots: PropTypes.bool,
infinite: PropTypes.bool,
lazyLoad: PropTypes.bool,
slidesToShow: PropTypes.number,
slidesToScroll: PropTypes.number,
variableWidth: PropTypes.bool,
responsive: PropTypes.array
}),
showLoves: PropTypes.bool,
showRemixes: PropTypes.bool,
type: PropTypes.string
};
Carousel.defaultProps = {
items: require('./carousel.json'),
settings: {},
showRemixes: false,
showLoves: false,
type: 'project'
};
module.exports = Carousel;

View file

@ -1,37 +1,22 @@
// This component handles json returned via proxy from a django server,
// or directly from a django server, and the model structure that system
// has.
var classNames = require('classnames');
var defaults = require('lodash.defaults');
var React = require('react');
var Slider = require('react-slick');
const classNames = require('classnames');
const defaults = require('lodash.defaults');
const PropTypes = require('prop-types');
const React = require('react');
const Slider = require('react-slick');
var Thumbnail = require('../thumbnail/thumbnail.jsx');
const Thumbnail = require('../thumbnail/thumbnail.jsx');
var frameless = require('../../lib/frameless.js');
const frameless = require('../../lib/frameless.js');
require('slick-carousel/slick/slick.scss');
require('slick-carousel/slick/slick-theme.scss');
require('./carousel.scss');
/**
* Displays content in horizontal scrolling box. Example usage: splash page rows.
*/
var LegacyCarousel = React.createClass({
type: 'LegacyCarousel',
propTypes: {
items: React.PropTypes.array
},
getDefaultProps: function () {
return {
items: require('./carousel.json'),
showRemixes: false,
showLoves: false
};
},
render: function () {
var settings = this.props.settings || {};
defaults(settings, {
const Carousel = props => {
defaults(props.settings, {
centerMode: false,
dots: false,
infinite: false,
@ -40,58 +25,94 @@ var LegacyCarousel = React.createClass({
slidesToScroll: 5,
variableWidth: true,
responsive: [
{breakpoint: frameless.mobile, settings: {
{
breakpoint: frameless.mobile,
settings: {
arrows: true,
slidesToScroll: 1,
slidesToShow: 1,
centerMode: true
}},
{breakpoint: frameless.tablet, settings: {
}
},
{
breakpoint: frameless.tablet,
settings: {
slidesToScroll: 2,
slidesToShow: 2
}},
{breakpoint: frameless.desktop, settings: {
}
},
{
breakpoint: frameless.desktop,
settings: {
slidesToScroll: 4,
slidesToShow: 4
}}
}
}
]
});
var arrows = this.props.items.length > settings.slidesToShow;
var classes = classNames(
'carousel',
this.props.className
);
const arrows = props.items.length > props.settings.slidesToShow;
return (
<Slider className={classes} arrows={arrows} {... settings}>
{this.props.items.map(function (item) {
var href = '';
<Slider
arrows={arrows}
className={classNames('carousel', props.className)}
{... props.settings}
>
{props.items.map(item => {
let href = '';
switch (item.type) {
case 'gallery':
href = '/studios/' + item.id + '/';
href = `/studios/${item.id}/`;
break;
case 'project':
href = '/projects/' + item.id + '/';
href = `/projects/${item.id}/`;
break;
default:
href = '/' + item.type + '/' + item.id + '/';
href = `/${item.type}/${item.id}/`;
}
return (
<Thumbnail key={[this.key, item.id].join('.')}
showLoves={this.props.showLoves}
showRemixes={this.props.showRemixes}
type={item.type}
href={href}
title={item.title}
src={item.thumbnail_url}
<Thumbnail
creator={item.creator}
href={href}
key={[props.type, item.id].join('.')}
loves={item.love_count}
remixes={item.remixers_count}
loves={item.love_count} />
showLoves={props.showLoves}
showRemixes={props.showRemixes}
src={item.thumbnail_url}
title={item.title}
type={item.type}
/>
);
}.bind(this))}
})}
</Slider>
);
}
});
};
module.exports = LegacyCarousel;
Carousel.propTypes = {
className: PropTypes.string,
items: PropTypes.arrayOf(PropTypes.any),
settings: PropTypes.shape({
centerMode: PropTypes.bool,
dots: PropTypes.bool,
infinite: PropTypes.bool,
lazyLoad: PropTypes.bool,
slidesToShow: PropTypes.number,
slidesToScroll: PropTypes.number,
variableWidth: PropTypes.bool,
responsive: PropTypes.array
}),
showLoves: PropTypes.bool,
showRemixes: PropTypes.bool,
type: PropTypes.string
};
Carousel.defaultProps = {
items: require('./carousel.json'),
settings: {},
showRemixes: false,
showLoves: false,
type: 'project'
};
module.exports = Carousel;

View file

@ -1,34 +1,33 @@
var classNames = require('classnames');
var FormattedRelative = require('react-intl').FormattedRelative;
var React = require('react');
const classNames = require('classnames');
const FormattedRelative = require('react-intl').FormattedRelative;
const PropTypes = require('prop-types');
const React = require('react');
var EmojiText = require('../emoji-text/emoji-text.jsx');
const EmojiText = require('../emoji-text/emoji-text.jsx');
require('./comment.scss');
var CommentText = React.createClass({
type: 'CommentText',
propTypes: {
comment: React.PropTypes.string.isRequired,
datetimeCreated: React.PropTypes.string,
className: React.PropTypes.string
},
render: function () {
var classes = classNames(
'comment-text',
this.props.class
);
return (
<div className={classes}>
<EmojiText className="mod-comment" text={this.props.comment} />
{typeof this.props.datetimeCreated !== 'undefined' ? [
<p className="comment-text-timestamp">
<FormattedRelative value={new Date(this.props.datetimeCreated)} />
const CommentText = props => (
<div className={classNames('comment-text', props.className)}>
<EmojiText
className="mod-comment"
text={props.comment}
/>
{typeof props.datetimeCreated === 'undefined' ? [] : [
<p
className="comment-text-timestamp"
key="comment-text-timestamp"
>
<FormattedRelative value={new Date(props.datetimeCreated)} />
</p>
] : []}
]}
</div>
);
}
});
);
CommentText.propTypes = {
className: PropTypes.string,
comment: PropTypes.string.isRequired,
datetimeCreated: PropTypes.string
};
module.exports = CommentText;

View file

@ -1,22 +1,29 @@
var classNames = require('classnames');
var React = require('react');
const classNames = require('classnames');
const PropTypes = require('prop-types');
const React = require('react');
require('./deck.scss');
var Deck = React.createClass({
displayName: 'Deck',
render: function () {
return (
<div className={classNames(['deck', this.props.className])}>
const Deck = props => (
<div className={classNames(['deck', props.className])}>
<div className="inner">
<a href="/" aria-label="Scratch">
<img className="logo" src="/images/logo_sm.png" />
<a
aria-label="Scratch"
href="/"
>
<img
className="logo"
src="/images/logo_sm.png"
/>
</a>
{this.props.children}
{props.children}
</div>
</div>
);
}
});
);
Deck.propTypes = {
children: PropTypes.node,
className: PropTypes.string
};
module.exports = Deck;

View file

@ -1,33 +1,29 @@
var classNames = require('classnames');
var React = require('react');
const classNames = require('classnames');
const PropTypes = require('prop-types');
const React = require('react');
require('./banner.scss');
/**
* Container for messages displayed below the nav bar that can be dismissed
* (See: email not confirmed banner)
*/
var Banner = React.createClass({
type: 'Banner',
propTypes: {
onRequestDismiss: React.PropTypes.func
},
render: function () {
var classes = classNames(
'banner',
this.props.className
);
return (
<div className={classes}>
const Banner = props => (
<div className={classNames('banner', props.className)}>
<div className="inner">
{this.props.children}
{this.props.onRequestDismiss ? [
<a className="close" key="close" href="#" onClick={this.props.onRequestDismiss}>x</a>
{props.children}
{props.onRequestDismiss ? [
<a
className="close"
href="#"
key="close"
onClick={props.onRequestDismiss}
>x</a>
] : []}
</div>
</div>
);
}
});
);
Banner.propTypes = {
children: PropTypes.node,
className: PropTypes.string,
onRequestDismiss: PropTypes.func
};
module.exports = Banner;

View file

@ -1,40 +1,46 @@
var React = require('react');
var classNames = require('classnames');
const bindAll = require('lodash.bindall');
const classNames = require('classnames');
const onClickOutside = require('react-onclickoutside').default;
const PropTypes = require('prop-types');
const React = require('react');
require('./dropdown.scss');
var Dropdown = React.createClass({
type: 'Dropdown',
mixins: [
require('react-onclickoutside')
],
propTypes: {
onRequestClose: React.PropTypes.func,
isOpen: React.PropTypes.bool
},
getDefaultProps: function () {
return {
as: 'div',
isOpen: false
};
},
handleClickOutside: function () {
class Dropdown extends React.Component {
constructor (props) {
super(props);
bindAll(this, [
'handleClickOutside'
]);
}
handleClickOutside () {
if (this.props.isOpen) {
this.props.onRequestClose();
}
},
render: function () {
var classes = classNames(
'dropdown',
this.props.className,
{open: this.props.isOpen}
);
}
render () {
return (
<this.props.as className={classes}>
<this.props.as
className={classNames('dropdown', this.props.className, {
open: this.props.isOpen
})}
>
{this.props.children}
</this.props.as>
);
}
});
}
module.exports = Dropdown;
Dropdown.propTypes = {
children: PropTypes.node,
className: PropTypes.string,
isOpen: PropTypes.bool,
onRequestClose: PropTypes.func.isRequired
};
Dropdown.defaultProps = {
as: 'div',
isOpen: false
};
module.exports = onClickOutside(Dropdown);

View file

@ -24,9 +24,13 @@
}
a {
&:link,
&:visited,
&:active {
background-color: transparent;
color: $type-white;
}
}
input {
// 100% minus border and padding

View file

@ -1,33 +1,25 @@
var classNames = require('classnames');
var React = require('react');
const classNames = require('classnames');
const PropTypes = require('prop-types');
const React = require('react');
require('./emoji-text.scss');
var EmojiText = React.createClass({
type: 'EmojiText',
propTyes: {
text: React.PropTypes.string.isRequired,
className: React.PropTypes.string
},
getDefaultProps: function () {
return {
as: 'p'
};
},
render: function () {
var classes = classNames(
'emoji-text',
this.props.className
);
return (
<this.props.as
className={classes}
dangerouslySetInnerHTML={{
__html: this.props.text
const EmojiText = props => (
<props.as
className={classNames('emoji-text', props.className)}
dangerouslySetInnerHTML={{ // eslint-disable-line react/no-danger
__html: props.text
}}
/>
);
}
});
);
EmojiText.propTypes = {
className: PropTypes.string,
text: PropTypes.string.isRequired
};
EmojiText.defaultProps = {
as: 'p'
};
module.exports = EmojiText;

View file

@ -1,26 +1,22 @@
var classNames = require('classnames');
var React = require('react');
const classNames = require('classnames');
const PropTypes = require('prop-types');
const React = require('react');
require('./flex-row.scss');
var FlexRow = React.createClass({
type: 'FlexRow',
getDefaultProps: function () {
return {
const FlexRow = props => (
<props.as className={classNames('flex-row', props.className)}>
{props.children}
</props.as>
);
FlexRow.propTypes = {
children: PropTypes.node,
className: PropTypes.string
};
FlexRow.defaultProps = {
as: 'div'
};
},
render: function () {
var classes = classNames(
'flex-row',
this.props.className
);
return (
<this.props.as className={classes}>
{this.props.children}
</this.props.as>
);
}
});
};
module.exports = FlexRow;

View file

@ -1,58 +1,69 @@
var React = require('react');
const React = require('react');
var FlexRow = require('../../../flex-row/flex-row.jsx');
var FooterBox = require('../../container/footer.jsx');
const FlexRow = require('../../../flex-row/flex-row.jsx');
const FooterBox = require('../../container/footer.jsx');
require('../footer.scss');
var ConferenceFooter = React.createClass({
type: 'ConferenceFooter',
render: function () {
return (
const ConferenceFooter = () => (
<FooterBox>
<div className="collaborators">
<h4>Sponsors</h4>
<FlexRow as="ul">
<li className="odl">
<a href="https://odl.mit.edu/">
<img src="/images/conference/footer/mit-odl.png"
alt="MIT Office of Digital Learning" />
<img
alt="MIT Office of Digital Learning"
src="/images/conference/footer/mit-odl.png"
/>
</a>
</li>
<li className="intel">
<a href="http://www.intel.com/content/www/us/en/homepage.html">
<img src="/images/conference/footer/intel.png"
alt="Intel" />
<img
alt="Intel"
src="/images/conference/footer/intel.png"
/>
</a>
</li>
<li className="lego">
<a href="http://www.legofoundation.com/">
<img src="/images/conference/footer/lego-foundation.png"
alt="LEGO Foundation" />
<img
alt="LEGO Foundation"
src="/images/conference/footer/lego-foundation.png"
/>
</a>
</li>
<li className="google">
<a href="http://www.google.com/">
<img src="/images/conference/footer/google.png"
alt="Google" />
<img
alt="Google"
src="/images/conference/footer/google.png"
/>
</a>
</li>
<li className="siegel">
<a href="http://www.siegelendowment.org/">
<img src="/images/conference/footer/siegel-endowment.png"
alt="Siegel Family Endowment" />
<img
alt="Siegel Family Endowment"
src="/images/conference/footer/siegel-endowment.png"
/>
</a>
</li>
<li className="nostarch">
<a href="https://www.nostarch.com/">
<img src="/images/conference/footer/no-starch.png"
alt="No Starch Press" />
<img
alt="No Starch Press"
src="/images/conference/footer/no-starch.png"
/>
</a>
</li>
<li className="scratchfoundation">
<a href="http://www.scratchfoundation.org/">
<img src="/images/conference/footer/scratch-foundation.png"
alt="Scratch Foundation" />
<img
alt="Scratch Foundation"
src="/images/conference/footer/scratch-foundation.png"
/>
</a>
</li>
</FlexRow>
@ -61,25 +72,34 @@ var ConferenceFooter = React.createClass({
<div className="family">
<h4>Scratch Family</h4>
<FlexRow>
<FlexRow as="ul" className="column">
<FlexRow
as="ul"
className="column"
>
<li>
<a href="https://scratch.mit.edu" target="_blank">Scratch</a>
<a href="https://scratch.mit.edu">Scratch</a>
</li>
<li>
<a href="http://www.scratchjr.org/" target="_blank">ScratchJr</a>
<a href="http://www.scratchjr.org/">ScratchJr</a>
</li>
</FlexRow>
<FlexRow as="ul" className="column">
<FlexRow
as="ul"
className="column"
>
<li>
<a href="http://www.scratchfoundation.org/" target="_blank">Scratch Foundation</a>
<a href="http://www.scratchfoundation.org/">Scratch Foundation</a>
</li>
<li>
<a href="http://scratched.gse.harvard.edu/" target="_blank">ScratchEd</a>
<a href="http://scratched.gse.harvard.edu/">ScratchEd</a>
</li>
</FlexRow>
<FlexRow as="ul" className="column">
<FlexRow
as="ul"
className="column"
>
<li>
<a href="http://day.scratch.mit.edu" target="_blank">Scratch Day</a>
<a href="http://day.scratch.mit.edu">Scratch Day</a>
</li>
</FlexRow>
</FlexRow>
@ -91,7 +111,7 @@ var ConferenceFooter = React.createClass({
<div className="contact-us">
<h4>Contact</h4>
<p>
<a href="mailto:help@scratch.mit.edu" target="_blank">
<a href="mailto:help@scratch.mit.edu">
Email Us
</a>
</p>
@ -99,18 +119,27 @@ var ConferenceFooter = React.createClass({
<div className="social">
<FlexRow as="ul">
<li>
<a href="//www.twitter.com/scratch" target="_blank">
<img src="/images/conference/footer/twitter.png" alt="scratch twitter" />
<a href="//www.twitter.com/scratch">
<img
alt="scratch twitter"
src="/images/conference/footer/twitter.png"
/>
</a>
</li>
<li>
<a href="//www.facebook.com/scratchteam" target="_blank">
<img src="/images/conference/footer/facebook.png" alt="scratch facebook" />
<a href="//www.facebook.com/scratchteam">
<img
alt="scratch facebook"
src="/images/conference/footer/facebook.png"
/>
</a>
</li>
<li>
<a href="http://medium.com/scratchfoundation-blog" target="_blank">
<img src="/images/conference/footer/medium.png" alt="scratch foundation blog" />
<a href="http://medium.com/scratchfoundation-blog">
<img
alt="scratch foundation blog"
src="/images/conference/footer/medium.png"
/>
</a>
</li>
</FlexRow>
@ -118,8 +147,6 @@ var ConferenceFooter = React.createClass({
</div>
</FlexRow>
</FooterBox>
);
}
});
);
module.exports = ConferenceFooter;

View file

@ -1,55 +1,60 @@
var React = require('react');
var ReactIntl = require('react-intl');
const injectIntl = require('react-intl').injectIntl;
const intlShape = require('react-intl').intlShape;
const FormattedMessage = require('react-intl').FormattedMessage;
const React = require('react');
var injectIntl = ReactIntl.injectIntl;
var FormattedMessage = ReactIntl.FormattedMessage;
var FlexRow = require('../../../flex-row/flex-row.jsx');
var FooterBox = require('../../container/footer.jsx');
var LanguageChooser = require('../../../languagechooser/languagechooser.jsx');
const FlexRow = require('../../../flex-row/flex-row.jsx');
const FooterBox = require('../../container/footer.jsx');
const LanguageChooser = require('../../../languagechooser/languagechooser.jsx');
require('../footer.scss');
var ConferenceFooter = React.createClass({
type: 'ConferenceFooter',
render: function () {
return (
const ConferenceFooter = props => (
<FooterBox>
<FlexRow className="scratch-links">
<div className="family">
<h4><FormattedMessage id='footer.scratchFamily' /></h4>
<h4><FormattedMessage id="footer.scratchFamily" /></h4>
<FlexRow>
<FlexRow as="ul" className="column">
<FlexRow
as="ul"
className="column"
>
<li>
<a href="https://scratch.mit.edu" target="_blank">Scratch</a>
<a href="https://scratch.mit.edu">Scratch</a>
</li>
<li>
<a href="http://www.scratchjr.org/" target="_blank">ScratchJr</a>
<a href="http://www.scratchjr.org/">ScratchJr</a>
</li>
</FlexRow>
<FlexRow as="ul" className="column">
<FlexRow
as="ul"
className="column"
>
<li>
<a href="http://www.scratchfoundation.org/" target="_blank">Scratch Foundation</a>
<a href="http://www.scratchfoundation.org/">Scratch Foundation</a>
</li>
<li>
<a href="http://scratched.gse.harvard.edu/" target="_blank">ScratchEd</a>
<a href="http://scratched.gse.harvard.edu/">ScratchEd</a>
</li>
</FlexRow>
<FlexRow as="ul" className="column">
<FlexRow
as="ul"
className="column"
>
<li>
<a href="http://day.scratch.mit.edu" target="_blank">Scratch Day</a>
<a href="http://day.scratch.mit.edu">Scratch Day</a>
</li>
</FlexRow>
</FlexRow>
<p className="legal">
<FormattedMessage id='general.copyright' />
<FormattedMessage id="general.copyright" />
</p>
</div>
<div className="media">
<div className="contact-us">
<h4>Contact</h4>
<p>
<a href="mailto:help@scratch.mit.edu" target="_blank">
<a href="mailto:help@scratch.mit.edu">
Email Us
</a>
</p>
@ -57,28 +62,39 @@ var ConferenceFooter = React.createClass({
<div className="social">
<FlexRow as="ul">
<li>
<a href="//www.twitter.com/scratch" target="_blank">
<img src="/images/conference/footer/twitter.png" alt="scratch twitter" />
<a href="//www.twitter.com/scratch">
<img
alt="scratch twitter"
src="/images/conference/footer/twitter.png"
/>
</a>
</li>
<li>
<a href="//www.facebook.com/scratchteam" target="_blank">
<img src="/images/conference/footer/facebook.png" alt="scratch facebook" />
<a href="//www.facebook.com/scratchteam">
<img
alt="scratch facebook"
src="/images/conference/footer/facebook.png"
/>
</a>
</li>
<li>
<a href="http://medium.com/scratchfoundation-blog" target="_blank">
<img src="/images/conference/footer/medium.png" alt="scratch foundation blog" />
<a href="http://medium.com/scratchfoundation-blog">
<img
alt="scratch foundation blog"
src="/images/conference/footer/medium.png"
/>
</a>
</li>
</FlexRow>
</div>
</div>
</FlexRow>
<LanguageChooser locale={this.props.intl.locale} />
<LanguageChooser locale={props.intl.locale} />
</FooterBox>
);
}
});
);
ConferenceFooter.propTypes = {
intl: intlShape
};
module.exports = injectIntl(ConferenceFooter);

View file

@ -1,55 +1,94 @@
var React = require('react');
var ReactIntl = require('react-intl');
const FormattedMessage = require('react-intl').FormattedMessage;
const injectIntl = require('react-intl').injectIntl;
const intlShape = require('react-intl').intlShape;
const React = require('react');
var injectIntl = ReactIntl.injectIntl;
var FormattedMessage = ReactIntl.FormattedMessage;
var FlexRow = require('../../../flex-row/flex-row.jsx');
var FooterBox = require('../../container/footer.jsx');
var LanguageChooser = require('../../../languagechooser/languagechooser.jsx');
const FlexRow = require('../../../flex-row/flex-row.jsx');
const FooterBox = require('../../container/footer.jsx');
const LanguageChooser = require('../../../languagechooser/languagechooser.jsx');
require('../footer.scss');
var ConferenceFooter = React.createClass({
type: 'ConferenceFooter',
render: function () {
return (
const ConferenceFooter = props => (
<FooterBox>
<FlexRow className="scratch-links">
<div className="family">
<h4><FormattedMessage id='footer.scratchFamily' /></h4>
<h4><FormattedMessage id="footer.scratchFamily" /></h4>
<FlexRow>
<FlexRow as="ul" className="column">
<FlexRow
as="ul"
className="column"
>
<li>
<a href="https://scratch.mit.edu" target="_blank">Scratch</a>
<a
href="https://scratch.mit.edu"
rel="noopener noreferrer"
target="_blank"
>
Scratch
</a>
</li>
<li>
<a href="http://www.scratchjr.org/" target="_blank">ScratchJr</a>
<a
href="http://www.scratchjr.org/"
rel="noopener noreferrer"
target="_blank"
>
ScratchJr
</a>
</li>
</FlexRow>
<FlexRow as="ul" className="column">
<FlexRow
as="ul"
className="column"
>
<li>
<a href="http://www.scratchfoundation.org/" target="_blank">Scratch Foundation</a>
<a
href="http://www.scratchfoundation.org/"
rel="noopener noreferrer"
target="_blank"
>
Scratch Foundation
</a>
</li>
<li>
<a href="http://scratched.gse.harvard.edu/" target="_blank">ScratchEd</a>
<a
href="http://scratched.gse.harvard.edu/"
rel="noopener noreferrer"
target="_blank"
>
ScratchEd
</a>
</li>
</FlexRow>
<FlexRow as="ul" className="column">
<FlexRow
as="ul"
className="column"
>
<li>
<a href="http://day.scratch.mit.edu" target="_blank">Scratch Day</a>
<a
href="http://day.scratch.mit.edu"
rel="noopener noreferrer"
target="_blank"
>
Scratch Day
</a>
</li>
</FlexRow>
</FlexRow>
<p className="legal">
<FormattedMessage id='general.copyright' />
<FormattedMessage id="general.copyright" />
</p>
</div>
<div className="media">
<div className="contact-us">
<h4>Contact</h4>
<p>
<a href="mailto:conference@scratch.mit.edu" target="_blank">
<a
href="mailto:conference@scratch.mit.edu"
rel="noopener noreferrer"
target="_blank"
>
Email Us
</a>
</p>
@ -57,28 +96,51 @@ var ConferenceFooter = React.createClass({
<div className="social">
<FlexRow as="ul">
<li>
<a href="//www.twitter.com/scratch" target="_blank">
<img src="/images/conference/footer/twitter.png" alt="scratch twitter" />
<a
href="//www.twitter.com/scratch"
rel="noopener noreferrer"
target="_blank"
>
<img
alt="scratch twitter"
src="/images/conference/footer/twitter.png"
/>
</a>
</li>
<li>
<a href="//www.facebook.com/scratchteam" target="_blank">
<img src="/images/conference/footer/facebook.png" alt="scratch facebook" />
<a
href="//www.facebook.com/scratchteam"
rel="noopener noreferrer"
target="_blank"
>
<img
alt="scratch facebook"
src="/images/conference/footer/facebook.png"
/>
</a>
</li>
<li>
<a href="http://medium.com/scratchfoundation-blog" target="_blank">
<img src="/images/conference/footer/medium.png" alt="scratch foundation blog" />
<a
href="http://medium.com/scratchfoundation-blog"
rel="noopener noreferrer"
target="_blank"
>
<img
alt="scratch foundation blog"
src="/images/conference/footer/medium.png"
/>
</a>
</li>
</FlexRow>
</div>
</div>
</FlexRow>
<LanguageChooser locale={this.props.intl.locale} />
<LanguageChooser locale={props.intl.locale} />
</FooterBox>
);
}
});
);
ConferenceFooter.propTypes = {
intl: intlShape
};
module.exports = injectIntl(ConferenceFooter);

View file

@ -1,16 +1,16 @@
var React = require('react');
const PropTypes = require('prop-types');
const React = require('react');
require('./footer.scss');
var FooterBox = React.createClass({
type: 'FooterBox',
render: function () {
return (
const FooterBox = props => (
<div className="inner">
{this.props.children}
{props.children}
</div>
);
}
});
);
FooterBox.propTypes = {
children: PropTypes.node
};
module.exports = FooterBox;

View file

@ -1,54 +1,51 @@
var React = require('react');
var ReactIntl = require('react-intl');
var FormattedMessage = ReactIntl.FormattedMessage;
var injectIntl = ReactIntl.injectIntl;
const FormattedMessage = require('react-intl').FormattedMessage;
const injectIntl = require('react-intl').injectIntl;
const intlShape = require('react-intl').intlShape;
const MediaQuery = require('react-responsive').default;
const React = require('react');
var FooterBox = require('../container/footer.jsx');
var LanguageChooser = require('../../languagechooser/languagechooser.jsx');
const FooterBox = require('../container/footer.jsx');
const LanguageChooser = require('../../languagechooser/languagechooser.jsx');
var MediaQuery = require('react-responsive');
var frameless = require('../../../lib/frameless');
const frameless = require('../../../lib/frameless');
require('./footer.scss');
var Footer = React.createClass({
type: 'Footer',
render: function () {
return (
const Footer = props => (
<FooterBox>
<MediaQuery maxWidth={frameless.tablet - 1}>
<div className="lists">
<dl>
<dd>
<a href="/about">
<FormattedMessage id='general.aboutScratch' />
<FormattedMessage id="general.aboutScratch" />
</a>
</dd>
<dd>
<a href="/jobs">
<FormattedMessage id='general.jobs' />
<FormattedMessage id="general.jobs" />
</a>
</dd>
<dd>
<a href="/contact-us/">
<FormattedMessage id='general.contactUs' />
<FormattedMessage id="general.contactUs" />
</a>
</dd>
</dl>
<dl>
<dd>
<a href="/terms_of_use">
<FormattedMessage id='general.termsOfUse' />
<FormattedMessage id="general.termsOfUse" />
</a>
</dd>
<dd>
<a href="/privacy_policy">
<FormattedMessage id='general.privacyPolicy' />
<FormattedMessage id="general.privacyPolicy" />
</a>
</dd>
<dd>
<a href="/community_guidelines">
<FormattedMessage id='general.guidelines' />
<FormattedMessage id="general.guidelines" />
</a>
</dd>
</dl>
@ -58,139 +55,139 @@ var Footer = React.createClass({
<div className="lists">
<dl>
<dt>
<FormattedMessage id='general.about' />
<FormattedMessage id="general.about" />
</dt>
<dd>
<a href="/about">
<FormattedMessage id='general.aboutScratch' />
<FormattedMessage id="general.aboutScratch" />
</a>
</dd>
<dd>
<a href="/parents/">
<FormattedMessage id='general.forParents' />
<FormattedMessage id="general.forParents" />
</a>
</dd>
<dd>
<a href="/educators">
<FormattedMessage id='general.forEducators' />
<FormattedMessage id="general.forEducators" />
</a>
</dd>
<dd>
<a href="/developers">
<FormattedMessage id='general.forDevelopers' />
<FormattedMessage id="general.forDevelopers" />
</a>
</dd>
<dd>
<a href="/info/credits">
<FormattedMessage id='general.credits' />
<FormattedMessage id="general.credits" />
</a>
</dd>
<dd>
<a href="/jobs">
<FormattedMessage id='general.jobs' />
<FormattedMessage id="general.jobs" />
</a>
</dd>
<dd>
<a href="http://wiki.scratch.mit.edu/wiki/Scratch_Press">
<FormattedMessage id='general.press' />
<FormattedMessage id="general.press" />
</a>
</dd>
</dl>
<dl>
<dt>
<FormattedMessage id='general.community' />
<FormattedMessage id="general.community" />
</dt>
<dd>
<a href="/community_guidelines">
<FormattedMessage id='general.guidelines' />
<FormattedMessage id="general.guidelines" />
</a>
</dd>
<dd>
<a href="/discuss/">
<FormattedMessage id='footer.discuss' />
<FormattedMessage id="footer.discuss" />
</a>
</dd>
<dd>
<a href="https://wiki.scratch.mit.edu/">
<FormattedMessage id='general.wiki' />
<FormattedMessage id="general.wiki" />
</a>
</dd>
<dd>
<a href="/statistics/">
<FormattedMessage id='general.statistics' />
<FormattedMessage id="general.statistics" />
</a>
</dd>
</dl>
<dl>
<dt>
<FormattedMessage id='general.support' />
<FormattedMessage id="general.support" />
</dt>
<dd>
<a href="/tips">
<FormattedMessage id='general.tips' />
<FormattedMessage id="general.tips" />
</a>
</dd>
<dd>
<a href="/info/faq">
<FormattedMessage id='general.faq' />
<FormattedMessage id="general.faq" />
</a>
</dd>
<dd>
<a href="/download">
<FormattedMessage id='general.offlineEditor' />
<FormattedMessage id="general.offlineEditor" />
</a>
</dd>
<dd>
<a href="/contact-us/">
<FormattedMessage id='general.contactUs' />
<FormattedMessage id="general.contactUs" />
</a>
</dd>
<dd>
<a href="/store">
<FormattedMessage id='general.scratchStore' />
<FormattedMessage id="general.scratchStore" />
</a>
</dd>
<dd>
<a href="https://secure.donationpay.org/scratchfoundation/">
<FormattedMessage id='general.donate'/>
<FormattedMessage id="general.donate" />
</a>
</dd>
</dl>
<dl>
<dt>
<FormattedMessage id='general.legal'/>
<FormattedMessage id="general.legal" />
</dt>
<dd>
<a href="/terms_of_use">
<FormattedMessage id='general.termsOfUse' />
<FormattedMessage id="general.termsOfUse" />
</a>
</dd>
<dd>
<a href="/privacy_policy">
<FormattedMessage id='general.privacyPolicy' />
<FormattedMessage id="general.privacyPolicy" />
</a>
</dd>
<dd>
<a href="/DMCA">
<FormattedMessage id='general.dmca' />
<FormattedMessage id="general.dmca" />
</a>
</dd>
</dl>
<dl>
<dt>
<FormattedMessage id='footer.scratchFamily' />
<FormattedMessage id="footer.scratchFamily" />
</dt>
<dd>
<a href="http://scratched.gse.harvard.edu/">
<FormattedMessage id='general.scratchEd' />
<FormattedMessage id="general.scratchEd" />
</a>
</dd>
<dd>
<a href="http://www.scratchjr.org/">
<FormattedMessage id='general.scratchJr' />
<FormattedMessage id="general.scratchJr" />
</a>
</dd>
<dd>
@ -200,27 +197,29 @@ var Footer = React.createClass({
</dd>
<dd>
<a href="/conference">
<FormattedMessage id='general.scratchConference' />
<FormattedMessage id="general.scratchConference" />
</a>
</dd>
<dd>
<a href="http://www.scratchfoundation.org/">
<FormattedMessage id='general.scratchFoundation' />
<FormattedMessage id="general.scratchFoundation" />
</a>
</dd>
</dl>
</div>
</MediaQuery>
<LanguageChooser locale={this.props.intl.locale} />
<LanguageChooser locale={props.intl.locale} />
<div className="copyright">
<p>
<FormattedMessage id='general.copyright' />
<FormattedMessage id="general.copyright" />
</p>
</div>
</FooterBox>
);
}
});
);
Footer.propTypes = {
intl: intlShape.isRequired
};
module.exports = injectIntl(Footer);

View file

@ -1,22 +1,26 @@
var React = require('react');
var classNames = require('classnames');
const classNames = require('classnames');
const omit = require('lodash.omit');
const PropTypes = require('prop-types');
const React = require('react');
require('./button.scss');
var Button = React.createClass({
type: 'Button',
propTypes: {
const Button = props => {
const classes = classNames('button', props.className);
},
render: function () {
var classes = classNames(
'button',
this.props.className
);
return (
<button {... this.props} className={classes} >{this.props.children}</button>
<button
className={classes}
{...omit(props, ['className', 'children'])}
>
{props.children}
</button>
);
}
});
};
Button.propTypes = {
children: PropTypes.node,
className: PropTypes.string
};
module.exports = Button;

View file

@ -1,28 +1,28 @@
var classNames = require('classnames');
var React = require('react');
const classNames = require('classnames');
const PropTypes = require('prop-types');
const React = require('react');
require('./charcount.scss');
var CharCount = React.createClass({
type: 'CharCount',
getDefaultProps: function () {
return {
maxCharacters: 0,
currentCharacters: 0
};
},
render: function () {
var classes = classNames(
'char-count',
this.props.className,
{overmax: (this.props.currentCharacters > this.props.maxCharacters)}
);
return (
<p className={classes}>
{this.props.currentCharacters}/{this.props.maxCharacters}
const CharCount = props => (
<p
className={classNames('char-count', props.className, {
overmax: (props.currentCharacters > props.maxCharacters)
})}
>
{props.currentCharacters}/{props.maxCharacters}
</p>
);
}
});
);
CharCount.propTypes = {
className: PropTypes.string,
currentCharacters: PropTypes.number,
maxCharacters: PropTypes.number
};
CharCount.defaultProps = {
currentCharacters: 0,
maxCharacters: 0
};
module.exports = CharCount;

View file

@ -1,25 +1,25 @@
var classNames = require('classnames');
var FRCCheckboxGroup = require('formsy-react-components').CheckboxGroup;
var React = require('react');
var defaultValidationHOC = require('./validations.jsx').defaultValidationHOC;
var inputHOC = require('./input-hoc.jsx');
const classNames = require('classnames');
const FRCCheckboxGroup = require('formsy-react-components').CheckboxGroup;
const PropTypes = require('prop-types');
const React = require('react');
const defaultValidationHOC = require('./validations.jsx').defaultValidationHOC;
const inputHOC = require('./input-hoc.jsx');
require('./row.scss');
require('./checkbox-group.scss');
var CheckboxGroup = React.createClass({
type: 'CheckboxGroup',
render: function () {
var classes = classNames(
'checkbox-group',
this.props.className
);
return (
<div className={classes}>
<FRCCheckboxGroup {... this.props} className={classes} />
const CheckboxGroup = props => (
<div className={classNames('checkbox-group', props.className)}>
<FRCCheckboxGroup
className={classNames('checkbox-group', props.className)}
{... props}
/>
</div>
);
}
});
);
CheckboxGroup.propTypes = {
className: PropTypes.string
};
module.exports = inputHOC(defaultValidationHOC(CheckboxGroup));

View file

@ -1,23 +1,23 @@
var classNames = require('classnames');
var FRCCheckbox = require('formsy-react-components').Checkbox;
var React = require('react');
var defaultValidationHOC = require('./validations.jsx').defaultValidationHOC;
var inputHOC = require('./input-hoc.jsx');
const classNames = require('classnames');
const FRCCheckbox = require('formsy-react-components').Checkbox;
const PropTypes = require('prop-types');
const React = require('react');
const defaultValidationHOC = require('./validations.jsx').defaultValidationHOC;
const inputHOC = require('./input-hoc.jsx');
require('./row.scss');
require('./checkbox.scss');
var Checkbox = React.createClass({
type: 'Checkbox',
render: function () {
var classes = classNames(
'checkbox-row',
this.props.className
);
return (
<FRCCheckbox rowClassName={classes} {... this.props} />
);
}
});
const Checkbox = props => (
<FRCCheckbox
rowClassName={classNames('checkbox-row', props.className)}
{...props}
/>
);
Checkbox.propTypes = {
className: PropTypes.string
};
module.exports = inputHOC(defaultValidationHOC(Checkbox));

View file

@ -1,47 +1,61 @@
var classNames = require('classnames');
var Formsy = require('formsy-react');
var omit = require('lodash.omit');
var React = require('react');
var validations = require('./validations.jsx').validations;
const bindAll = require('lodash.bindall');
const classNames = require('classnames');
const Formsy = require('formsy-react');
const omit = require('lodash.omit');
const PropTypes = require('prop-types');
const React = require('react');
for (var validation in validations) {
const validations = require('./validations.jsx').validations;
for (const validation in validations) {
Formsy.addValidationRule(validation, validations[validation]);
}
var Form = React.createClass({
getDefaultProps: function () {
return {
noValidate: true,
onChange: function () {}
};
},
getInitialState: function () {
return {
class Form extends React.Component {
constructor (props) {
super(props);
bindAll(this, [
'handleChange'
]);
this.state = {
allValues: {}
};
},
onChange: function (currentValues, isChanged) {
}
handleChange (currentValues, isChanged) {
this.setState({allValues: omit(currentValues, 'all')});
this.props.onChange(currentValues, isChanged);
},
render: function () {
var classes = classNames(
'form',
this.props.className
);
}
render () {
return (
<Formsy.Form {... this.props} className={classes} ref="formsy" onChange={this.onChange}>
{React.Children.map(this.props.children, function (child) {
<Formsy.Form
className={classNames('form', this.props.className)}
ref={form => {
this.formsy = form;
}}
onChange={this.handleChange}
{...this.props}
>
{React.Children.map(this.props.children, child => {
if (!child) return child;
if (child.props.name === 'all') {
return React.cloneElement(child, {value: this.state.allValues});
} else {
return child;
}
}.bind(this))}
return child;
})}
</Formsy.Form>
);
}
});
}
Form.propTypes = {
children: PropTypes.node,
className: PropTypes.string,
onChange: PropTypes.func
};
Form.defaultProps = {
noValidate: true,
onChange: function () {}
};
module.exports = Form;

View file

@ -1,5 +1,6 @@
var Formsy = require('formsy-react');
var React = require('react');
const Formsy = require('formsy-react');
const PropTypes = require('prop-types');
const React = require('react');
require('./general-error.scss');
@ -10,13 +11,18 @@ require('./general-error.scss');
* give it a name, and apply your validation error to
* the name of the GeneralError component.
*/
module.exports = Formsy.HOC(React.createClass({
render: function () {
if (!this.props.showError()) return null;
const GeneralError = props => {
if (!props.showError()) return null;
return (
<p className="general-error">
{this.props.getErrorMessage()}
{props.getErrorMessage()}
</p>
);
}
}));
};
GeneralError.propTypes = {
getErrorMessage: PropTypes.func,
showError: PropTypes.func
};
module.exports = Formsy.HOC(GeneralError);

View file

@ -1,20 +1,32 @@
var React = require('react');
const omit = require('lodash.omit');
const PropTypes = require('prop-types');
const React = require('react');
module.exports = function InputComponentMixin (Component) {
var InputComponent = React.createClass({
getDefaultProps: function () {
return {
/**
* Higher-order component for building an input field
* @param {React.Component} Component an input component
* @return {React.Component} a wrapped input component
*/
module.exports = Component => {
const InputComponent = props => (
<Component
help={props.required ? null : props.messages['general.notRequired']}
{...omit(props, ['messages'])}
/>
);
InputComponent.propTypes = {
messages: PropTypes.shape({
'general.notRequired': PropTypes.string
}),
required: PropTypes.oneOfType([PropTypes.bool, PropTypes.string])
};
InputComponent.defaultProps = {
messages: {
'general.notRequired': 'Not Required'
}
};
},
render: function () {
return (
<Component help={this.props.required ? null : this.props.messages['general.notRequired']}
{...this.props} />
);
}
});
return InputComponent;
};

View file

@ -1,46 +1,57 @@
var classNames = require('classnames');
var FRCInput = require('formsy-react-components').Input;
var React = require('react');
var defaultValidationHOC = require('./validations.jsx').defaultValidationHOC;
var inputHOC = require('./input-hoc.jsx');
const bindAll = require('lodash.bindall');
const classNames = require('classnames');
const FRCInput = require('formsy-react-components').Input;
const omit = require('lodash.omit');
const PropTypes = require('prop-types');
const React = require('react');
const defaultValidationHOC = require('./validations.jsx').defaultValidationHOC;
const inputHOC = require('./input-hoc.jsx');
require('./input.scss');
require('./row.scss');
var Input = React.createClass({
type: 'Input',
getDefaultProps: function () {
return {};
},
getInitialState: function () {
return {
class Input extends React.Component {
constructor (props) {
super(props);
bindAll(this, [
'handleInvalid',
'handleValid'
]);
this.state = {
status: ''
};
},
onValid: function () {
}
handleValid () {
this.setState({
status: 'pass'
});
},
onInvalid: function () {
}
handleInvalid () {
this.setState({
status: 'fail'
});
},
render: function () {
var classes = classNames(
}
render () {
return (
<FRCInput
className="input"
rowClassName={classNames(
this.state.status,
this.props.className,
{'no-label': (typeof this.props.label === 'undefined')}
);
return (
<FRCInput {... this.props}
className="input"
rowClassName={classes}
onValid={this.onValid}
onInvalid={this.onInvalid} />
)}
onInvalid={this.handleInvalid}
onValid={this.handleValid}
{...omit(this.props, ['className'])}
/>
);
}
});
}
Input.propTypes = {
className: PropTypes.string,
label: PropTypes.string
};
module.exports = inputHOC(defaultValidationHOC(Input));

View file

@ -1,23 +1,33 @@
var allCountries = require('react-telephone-input/lib/country_data').allCountries;
var classNames = require('classnames');
var ComponentMixin = require('formsy-react-components').ComponentMixin;
var FormsyMixin = require('formsy-react').Mixin;
var React = require('react');
var ReactPhoneInput = require('react-telephone-input/lib/withStyles');
var Row = require('formsy-react-components').Row;
const allCountries = require('react-telephone-input/lib/country_data').allCountries;
const classNames = require('classnames');
const ComponentMixin = require('formsy-react-components').ComponentMixin;
const createReactClass = require('create-react-class');
const FormsyMixin = require('formsy-react').Mixin;
const omit = require('lodash.omit');
const PropTypes = require('prop-types');
const React = require('react');
const ReactPhoneInput = require('react-telephone-input/lib/withStyles').default;
const Row = require('formsy-react-components').Row;
var defaultValidationHOC = require('./validations.jsx').defaultValidationHOC;
var inputHOC = require('./input-hoc.jsx');
var intl = require('../../lib/intl.jsx');
var validationHOCFactory = require('./validations.jsx').validationHOCFactory;
const defaultValidationHOC = require('./validations.jsx').defaultValidationHOC;
const inputHOC = require('./input-hoc.jsx');
const intl = require('../../lib/intl.jsx');
const validationHOCFactory = require('./validations.jsx').validationHOCFactory;
var allIso2 = allCountries.map(function (country) {return country.iso2;});
const allIso2 = allCountries.map(country => (country.iso2));
require('./row.scss');
require('./phone-input.scss');
var PhoneInput = React.createClass({
const PhoneInput = createReactClass({ // eslint-disable-line react/prefer-es6-class
displayName: 'PhoneInput',
propTypes: {
className: PropTypes.string,
defaultCountry: PropTypes.string,
disabled: PropTypes.bool,
name: PropTypes.string,
onChange: PropTypes.func
},
mixins: [
FormsyMixin,
ComponentMixin
@ -31,29 +41,34 @@ var PhoneInput = React.createClass({
defaultCountry: 'us'
};
},
onChangeInput: function (number, country) {
var value = {national_number: number, country_code: country};
handleChangeInput: function (number, country) {
const value = {
national_number: number,
country_code: country
};
this.setValue(value);
this.props.onChange(this.props.name, value);
},
render: function () {
var defaultCountry = PhoneInput.getDefaultProps().defaultCountry;
let defaultCountry = PhoneInput.getDefaultProps().defaultCountry;
if (allIso2.indexOf(this.props.defaultCountry.toLowerCase()) !== -1) {
defaultCountry = this.props.defaultCountry.toLowerCase();
}
return (
<Row {... this.getRowProperties()}
<Row
htmlFor={this.getId()}
rowClassName={classNames('phone-input', this.props.className)}
{...this.getRowProperties()}
>
<div className="input-group">
<ReactPhoneInput className="form-control"
{... this.props}
<ReactPhoneInput
className="form-control"
defaultCountry={defaultCountry}
onChange={this.onChangeInput}
disabled={this.isFormDisabled() || this.props.disabled}
id={this.getId()}
label={null}
disabled={this.isFormDisabled() || this.props.disabled}
onChange={this.handleChangeInput}
{...omit(this.props, ['className', 'disabled', 'onChange'])}
/>
{this.renderHelp()}
{this.renderErrorMessage()}
@ -63,7 +78,7 @@ var PhoneInput = React.createClass({
}
});
var phoneValidationHOC = validationHOCFactory({
const phoneValidationHOC = validationHOCFactory({
isPhone: <intl.FormattedMessage id="teacherRegistration.validationPhoneNumber" />
});

View file

@ -1,23 +1,23 @@
var classNames = require('classnames');
var FRCRadioGroup = require('formsy-react-components').RadioGroup;
var React = require('react');
var defaultValidationHOC = require('./validations.jsx').defaultValidationHOC;
var inputHOC = require('./input-hoc.jsx');
const classNames = require('classnames');
const FRCRadioGroup = require('formsy-react-components').RadioGroup;
const PropTypes = require('prop-types');
const React = require('react');
const defaultValidationHOC = require('./validations.jsx').defaultValidationHOC;
const inputHOC = require('./input-hoc.jsx');
require('./row.scss');
require('./radio-group.scss');
var RadioGroup = React.createClass({
type: 'RadioGroup',
render: function () {
var classes = classNames(
'radio-group',
this.props.className
);
return (
<FRCRadioGroup {... this.props} className={classes} />
);
}
});
const RadioGroup = props => (
<FRCRadioGroup
className={classNames('radio-group', props.className)}
{... props}
/>
);
RadioGroup.propTypes = {
className: PropTypes.string
};
module.exports = inputHOC(defaultValidationHOC(RadioGroup));

View file

@ -1,33 +1,31 @@
var classNames = require('classnames');
var defaults = require('lodash.defaultsdeep');
var FRCSelect = require('formsy-react-components').Select;
var React = require('react');
var defaultValidationHOC = require('./validations.jsx').defaultValidationHOC;
var inputHOC = require('./input-hoc.jsx');
const classNames = require('classnames');
const defaults = require('lodash.defaultsdeep');
const FRCSelect = require('formsy-react-components').Select;
const PropTypes = require('prop-types');
const React = require('react');
const defaultValidationHOC = require('./validations.jsx').defaultValidationHOC;
const inputHOC = require('./input-hoc.jsx');
require('./row.scss');
require('./select.scss');
var Select = React.createClass({
type: 'Select',
propTypes: {
},
render: function () {
var classes = classNames(
'select',
this.props.className
);
var props = this.props;
if (this.props.required && !this.props.value) {
props = defaults({}, this.props, {value: this.props.options[0].value});
const Select = props => {
if (props.required && !props.value) {
props = defaults({}, props, {value: props.options[0].value});
}
return (
<div className={classes}>
<FRCSelect {... props} />
<div className={classNames('select', props.className)}>
<FRCSelect {...props} />
</div>
);
}
});
};
Select.propTypes = {
className: PropTypes.string,
options: PropTypes.arrayOf(PropTypes.any),
required: PropTypes.bool,
value: PropTypes.oneOfType([PropTypes.number, PropTypes.string])
};
module.exports = inputHOC(defaultValidationHOC(Select));

View file

@ -1,25 +1,25 @@
var classNames = require('classnames');
var FRCTextarea = require('formsy-react-components').Textarea;
var React = require('react');
var defaultValidationHOC = require('./validations.jsx').defaultValidationHOC;
var inputHOC = require('./input-hoc.jsx');
const classNames = require('classnames');
const FRCTextarea = require('formsy-react-components').Textarea;
const omit = require('lodash.omit');
const PropTypes = require('prop-types');
const React = require('react');
const defaultValidationHOC = require('./validations.jsx').defaultValidationHOC;
const inputHOC = require('./input-hoc.jsx');
require('./row.scss');
require('./textarea.scss');
var TextArea = React.createClass({
type: 'TextArea',
render: function () {
var classes = classNames(
'textarea-row',
this.props.className
);
return (
<FRCTextarea {... this.props}
const TextArea = props => (
<FRCTextarea
className="textarea"
rowClassName={classes} />
);
}
});
rowClassName={classNames('textarea-row', props.className)}
{...omit(props, ['className'])}
/>
);
TextArea.propTypes = {
className: PropTypes.string
};
module.exports = inputHOC(defaultValidationHOC(TextArea));

View file

@ -1,48 +1,46 @@
var defaults = require('lodash.defaultsdeep');
var intl = require('../../lib/intl.jsx');
var libphonenumber = require('google-libphonenumber');
var phoneNumberUtil = libphonenumber.PhoneNumberUtil.getInstance();
var React = require('react');
module.exports = {};
const defaults = require('lodash.defaultsdeep');
const intl = require('../../lib/intl.jsx');
const libphonenumber = require('google-libphonenumber');
const omit = require('lodash.omit');
const phoneNumberUtil = libphonenumber.PhoneNumberUtil.getInstance();
const PropTypes = require('prop-types');
const React = require('react');
module.exports.validations = {
notEquals: function (values, value, neq) {
return value !== neq;
},
notEqualsField: function (values, value, field) {
return value !== values[field];
},
isPhone: function (values, value) {
notEquals: (values, value, neq) => (value !== neq),
notEqualsField: (values, value, field) => (value !== values[field]),
isPhone: (values, value) => {
if (typeof value === 'undefined') return true;
if (value && value.national_number === '+') return true;
try {
var parsed = phoneNumberUtil.parse(value.national_number, value.country_code.iso2);
const parsed = phoneNumberUtil.parse(value.national_number, value.country_code.iso2);
return phoneNumberUtil.isValidNumber(parsed);
} catch (err) {
return false;
}
return phoneNumberUtil.isValidNumber(parsed);
}
};
module.exports.validations.notEqualsUsername = module.exports.validations.notEquals;
module.exports.validationHOCFactory = function (defaultValidationErrors) {
return function (Component) {
var ValidatedComponent = React.createClass({
render: function () {
var validationErrors = defaults(
module.exports.validationHOCFactory = defaultValidationErrors => (Component => {
const ValidatedComponent = props => (
<Component
validationErrors={defaults(
{},
defaultValidationErrors,
this.props.validationErrors
props.validationErrors
)}
{...omit(props, ['validationErrors'])}
/>
);
return (
<Component {...this.props} validationErrors={validationErrors} />
);
}
});
return ValidatedComponent;
ValidatedComponent.propTypes = {
validationErrors: PropTypes.object // eslint-disable-line react/forbid-prop-types
};
};
return ValidatedComponent;
});
module.exports.defaultValidationHOC = module.exports.validationHOCFactory({
isDefaultRequiredValue: <intl.FormattedMessage id="form.validationRequired" />

View file

@ -1,15 +1,61 @@
var classNames = require('classnames');
var React = require('react');
const classNames = require('classnames');
const PropTypes = require('prop-types');
const React = require('react');
var Thumbnail = require('../thumbnail/thumbnail.jsx');
var FlexRow = require('../flex-row/flex-row.jsx');
const Thumbnail = require('../thumbnail/thumbnail.jsx');
const FlexRow = require('../flex-row/flex-row.jsx');
require('./grid.scss');
var Grid = React.createClass({
type: 'Grid',
getDefaultProps: function () {
return {
const Grid = props => (
<div className={classNames('grid', props.className)}>
<FlexRow>
{props.items.map((item, key) => {
const href = `/${props.itemType}/${item.id}/`;
if (props.itemType === 'projects') {
return (
<Thumbnail
avatar={`https://cdn2.scratch.mit.edu/get_image/user/${item.author.id}_32x32.png`}
creator={item.author.username}
favorites={item.stats.favorites}
href={href}
key={key}
loves={item.stats.loves}
remixes={item.stats.remixes}
showAvatar={props.showAvatar}
showFavorites={props.showFavorites}
showLoves={props.showLoves}
showRemixes={props.showRemixes}
showViews={props.showViews}
src={item.image}
title={item.title}
type={'project'}
views={item.stats.views}
/>
);
}
return (
<Thumbnail
href={href}
key={key}
owner={item.owner}
src={item.image}
title={item.title}
type={'gallery'}
/>
);
})}
</FlexRow>
</div>
);
Grid.propTypes = {
className: PropTypes.string,
itemType: PropTypes.string,
items: PropTypes.arrayOf(PropTypes.object)
};
Grid.defaultProps = {
items: require('./grid.json'),
itemType: 'projects',
showLoves: false,
@ -17,63 +63,6 @@ var Grid = React.createClass({
showRemixes: false,
showViews: false,
showAvatar: false
};
},
render: function () {
var classes = classNames(
'grid',
this.props.className
);
return (
<div className={classes}>
<FlexRow>
{this.props.items.map(function (item, key) {
var href = '/' + this.props.itemType + '/' + item.id + '/';
if (this.props.itemType == 'projects') {
return (
<Thumbnail
key={key}
showLoves={this.props.showLoves}
showFavorites={this.props.showFavorites}
showRemixes={this.props.showRemixes}
showViews={this.props.showViews}
showAvatar={this.props.showAvatar}
type={'project'}
href={href}
title={item.title}
src={item.image}
avatar={
'https://uploads.scratch.mit.edu/users/avatars/' +
item.author.id +
'.png'
}
creator={item.author.username}
loves={item.stats.loves}
favorites={item.stats.favorites}
remixes={item.stats.remixes}
views={item.stats.views}
/>
);
}
else {
return (
<Thumbnail
key={key}
type={'gallery'}
href={href}
title={item.title}
src={item.image}
srcDefault={'https://uploads.scratch.mit.edu/galleries/thumbnails/default.png'}
owner={item.owner}
/>
);
}
}.bind(this))}
</FlexRow>
</div>
);
}
});
};
module.exports = Grid;

View file

@ -1,39 +1,34 @@
var classNames = require('classnames');
var React = require('react');
var TitleBanner = require('../../components/title-banner/title-banner.jsx');
const classNames = require('classnames');
const PropTypes = require('prop-types');
const React = require('react');
const TitleBanner = require('../../components/title-banner/title-banner.jsx');
require('./informationpage.scss');
/**
/*
* Container for a table of contents
* alongside a long body of text
*/
var InformationPage = React.createClass({
type: 'InformationPage',
propTypes: {
title: React.PropTypes.string.isRequired
},
render: function () {
var classes = classNames(
'info-outer',
'inner',
this.props.className
);
return (
const InformationPage = props => (
<div className="information-page">
<TitleBanner className="masthead">
<div className="inner">
<h1 className="title-banner-h1">
{this.props.title}
{props.title}
</h1>
</div>
</TitleBanner>
<div className={classes}>
{this.props.children}
<div className={classNames('info-outer', 'inner', props.className)}>
{props.children}
</div>
</div>
);
}
});
);
InformationPage.propTypes = {
children: PropTypes.node,
className: PropTypes.string,
title: PropTypes.string.isRequired
};
module.exports = InformationPage;

View file

@ -1,17 +1,180 @@
var connect = require('react-redux').connect;
var React = require('react');
const bindAll = require('lodash.bindall');
const connect = require('react-redux').connect;
const PropTypes = require('prop-types');
const React = require('react');
var sessionActions = require('../../redux/session.js');
const sessionActions = require('../../redux/session.js');
var IframeModal = require('../modal/iframe/modal.jsx');
var Registration = require('../registration/registration.jsx');
const IframeModal = require('../modal/iframe/modal.jsx');
const Registration = require('../registration/registration.jsx');
require('./intro.scss');
var Intro = React.createClass({
type: 'Intro',
getDefaultProps: function () {
return {
class Intro extends React.Component {
constructor (props) {
super(props);
bindAll(this, [
'handleShowVideo',
'handleCloseVideo',
'handleJoinClick',
'handleCloseRegistration',
'handleCompleteRegistration'
]);
this.state = {
videoOpen: false
};
}
handleShowVideo () {
this.setState({videoOpen: true});
}
handleCloseVideo () {
this.setState({videoOpen: false});
}
handleJoinClick (e) {
e.preventDefault();
this.setState({registrationOpen: true});
}
handleCloseRegistration () {
this.setState({registrationOpen: false});
}
handleCompleteRegistration () {
this.props.dispatch(sessionActions.refreshSession());
this.closeRegistration();
}
render () {
return (
<div className="intro">
<div className="content">
<h1
dangerouslySetInnerHTML={{ // eslint-disable-line react/no-danger
__html: this.props.messages['intro.tagLine']
}}
/>
<div className="sprites">
<a
className="sprite sprite-1"
href="/projects/editor/?tip_bar=getStarted"
>
<img
alt="Scratch Cat"
className="costume costume-1"
src="//cdn.scratch.mit.edu/scratchr2/static/images/cat-a.png"
/>
<img
alt="Scratch Cat"
className="costume costume-2"
src="//cdn.scratch.mit.edu/scratchr2/static/images/cat-b.png"
/>
<div className="circle" />
<div className="text">
{this.props.messages['intro.tryItOut']}
</div>
</a>
<a
className="sprite sprite-2"
href="/starter_projects/"
>
<img
alt="Tera"
className="costume costume-1"
src="//cdn.scratch.mit.edu/scratchr2/static/images/tera-a.png"
/>
<img
alt="Tera"
className="costume costume-2"
src="//cdn.scratch.mit.edu/scratchr2/static/images/tera-b.png"
/>
<div className="circle" />
<div className="text">
{this.props.messages['intro.seeExamples']}
</div>
</a>
<a
className="sprite sprite-3"
href="#"
onClick={this.handleJoinClick}
>
<img
alt="Gobo"
className="costume costume-1"
src="//cdn.scratch.mit.edu/scratchr2/static/images/gobo-a.png"
/>
<img
alt="Gobo"
className="costume costume-2"
src="//cdn.scratch.mit.edu/scratchr2/static/images/gobo-b.png"
/>
<div className="circle" />
<div className="text">
{this.props.messages['intro.joinScratch']}
</div>
<div className="text subtext">{this.props.messages['intro.itsFree']}</div>
</a>
<Registration
isOpen={this.state.registrationOpen}
key="registration"
onRegistrationDone={this.handleCompleteRegistration}
onRequestClose={this.handleCloseRegistration}
/>
</div>
<div
className="description"
dangerouslySetInnerHTML={{ // eslint-disable-line react/no-danger
__html: this.props.messages['intro.description']
}}
/>
<div className="links">
<a href="/about/">
{this.props.messages['intro.aboutScratch']}
</a>
<a href="/educators/">
{this.props.messages['intro.forEducators']}
</a>
<a
className="last"
href="/parents/"
>
{this.props.messages['intro.forParents']}
</a>
</div>
</div>
<div className="video">
<div
className="play-button"
onClick={this.handleShowVideo}
/>
<img
alt="Intro Video"
src="//cdn.scratch.mit.edu/scratchr2/static/images/hp-video-screenshot.png"
/>
</div>
<IframeModal
className="mod-intro-video"
isOpen={this.state.videoOpen}
src="//player.vimeo.com/video/65583694?title=0&amp;byline=0&amp;portrait=0"
onRequestClose={this.handleCloseVideo}
/>
</div>
);
}
}
Intro.propTypes = {
dispatch: PropTypes.func.isRequired,
messages: PropTypes.shape({
'intro.aboutScratch': PropTypes.string,
'intro.forEducators': PropTypes.string,
'intro.forParents': PropTypes.string,
'intro.itsFree': PropTypes.string,
'intro.joinScratch': PropTypes.string,
'intro.seeExamples': PropTypes.string,
'intro.tagLine': PropTypes.string,
'intro.tryItOut': PropTypes.string,
'intro.description': PropTypes.string
})
};
Intro.defaultProps = {
messages: {
'intro.aboutScratch': 'ABOUT SCRATCH',
'intro.forEducators': 'FOR EDUCATORS',
@ -25,121 +188,12 @@ var Intro = React.createClass({
'over 14 million </span>projects shared'
},
session: {}
};
},
getInitialState: function () {
return {
videoOpen: false
};
},
showVideo: function () {
this.setState({videoOpen: true});
},
closeVideo: function () {
this.setState({videoOpen: false});
},
handleJoinClick: function (e) {
e.preventDefault();
this.setState({'registrationOpen': true});
},
closeRegistration: function () {
this.setState({'registrationOpen': false});
},
completeRegistration: function () {
this.props.dispatch(sessionActions.refreshSession());
this.closeRegistration();
},
render: function () {
return (
<div className="intro">
<div className="content">
<h1 dangerouslySetInnerHTML={{__html: this.props.messages['intro.tagLine']}}>
</h1>
<div className="sprites">
<a className="sprite sprite-1" href="/projects/editor/?tip_bar=getStarted">
<img
className="costume costume-1"
src="//cdn.scratch.mit.edu/scratchr2/static/images/cat-a.png"
alt="Scratch Cat" />
<img
className="costume costume-2"
src="//cdn.scratch.mit.edu/scratchr2/static/images/cat-b.png"
alt="Scratch Cat" />
<div className="circle"></div>
<div className="text">
{this.props.messages['intro.tryItOut']}
</div>
</a>
<a className="sprite sprite-2" href="/starter_projects/">
<img
className="costume costume-1"
src="//cdn.scratch.mit.edu/scratchr2/static/images/tera-a.png"
alt="Tera" />
<img
className="costume costume-2"
src="//cdn.scratch.mit.edu/scratchr2/static/images/tera-b.png"
alt="Tera" />
<div className="circle"></div>
<div className="text">
{this.props.messages['intro.seeExamples']}
</div>
</a>
<a className="sprite sprite-3" href="#" onClick={this.handleJoinClick}>
<img
className="costume costume-1"
src="//cdn.scratch.mit.edu/scratchr2/static/images/gobo-a.png"
alt="Gobo" />
<img
className="costume costume-2"
src="//cdn.scratch.mit.edu/scratchr2/static/images/gobo-b.png"
alt="Gobo" />
<div className="circle"></div>
<div className="text">
{this.props.messages['intro.joinScratch']}
</div>
<div className="text subtext">{this.props.messages['intro.itsFree']}</div>
</a>
<Registration key="registration"
isOpen={this.state.registrationOpen}
onRequestClose={this.closeRegistration}
onRegistrationDone={this.completeRegistration} />
</div>
<div className="description"
dangerouslySetInnerHTML={{__html: this.props.messages['intro.description']}}></div>
<div className="links">
<a href="/about/">
{this.props.messages['intro.aboutScratch']}
</a>
<a href="/educators/">
{this.props.messages['intro.forEducators']}
</a>
<a className="last" href="/parents/">
{this.props.messages['intro.forParents']}
</a>
</div>
</div>
<div className="video">
<div className="play-button" onClick={this.showVideo}></div>
<img src="//cdn.scratch.mit.edu/scratchr2/static/images/hp-video-screenshot.png"
alt="Intro Video" />
</div>
<IframeModal
className="mod-intro-video"
isOpen={this.state.videoOpen}
onRequestClose={this.closeVideo}
src="//player.vimeo.com/video/65583694?title=0&amp;byline=0&amp;portrait=0"
/>
</div>
);
}
});
var mapStateToProps = function (state) {
return {
session: state.session
};
};
var ConnectedIntro = connect(mapStateToProps)(Intro);
const mapStateToProps = state => ({
session: state.session
});
const ConnectedIntro = connect(mapStateToProps)(Intro);
module.exports = ConnectedIntro;

View file

@ -1,46 +1,57 @@
var classNames = require('classnames');
var React = require('react');
const bindAll = require('lodash.bindall');
const classNames = require('classnames');
const PropTypes = require('prop-types');
const React = require('react');
var jar = require('../../lib/jar.js');
var languages = require('../../../languages.json');
var Form = require('../forms/form.jsx');
var Select = require('../forms/select.jsx');
const jar = require('../../lib/jar.js');
const languages = require('../../../languages.json');
const Form = require('../forms/form.jsx');
const Select = require('../forms/select.jsx');
require('./languagechooser.scss');
/**
* Footer dropdown menu that allows one to change their language.
*/
var LanguageChooser = React.createClass({
type: 'LanguageChooser',
getDefaultProps: function () {
return {
languages: languages,
locale: 'en'
};
},
onSetLanguage: function (name, value) {
class LanguageChooser extends React.Component {
constructor (props) {
super(props);
bindAll(this, [
'handleSetLanguage'
]);
}
handleSetLanguage (name, value) {
jar.set('scratchlanguage', value);
window.location.reload();
},
render: function () {
var classes = classNames(
'language-chooser',
this.props.className
);
var languageOptions = Object.keys(this.props.languages).map(function (value) {
return {value: value, label: this.props.languages[value]};
}.bind(this));
}
render () {
const languageOptions = Object.keys(this.props.languages).map(value => ({
value: value,
label: this.props.languages[value]
}));
return (
<Form className={classes}>
<Select name="language"
<Form className={classNames('language-chooser', this.props.className)}>
<Select
required
name="language"
options={languageOptions}
value={this.props.locale}
onChange={this.onSetLanguage}
required />
onChange={this.handleSetLanguage}
/>
</Form>
);
}
});
}
LanguageChooser.propTypes = {
className: PropTypes.string,
languages: PropTypes.object, // eslint-disable-line react/forbid-prop-types
locale: PropTypes.string
};
LanguageChooser.defaultProps = {
languages: languages,
locale: 'en'
};
module.exports = LanguageChooser;

View file

@ -1,66 +1,108 @@
var React = require('react');
var FormattedMessage = require('react-intl').FormattedMessage;
const bindAll = require('lodash.bindall');
const FormattedMessage = require('react-intl').FormattedMessage;
const PropTypes = require('prop-types');
const React = require('react');
var log = require('../../lib/log.js');
const log = require('../../lib/log.js');
var Form = require('../forms/form.jsx');
var Input = require('../forms/input.jsx');
var Button = require('../forms/button.jsx');
var Spinner = require('../spinner/spinner.jsx');
const Form = require('../forms/form.jsx');
const Input = require('../forms/input.jsx');
const Button = require('../forms/button.jsx');
const Spinner = require('../spinner/spinner.jsx');
require('./login.scss');
var Login = React.createClass({
type: 'Login',
propTypes: {
onLogIn: React.PropTypes.func,
error: React.PropTypes.string
},
getInitialState: function () {
return {
class Login extends React.Component {
constructor (props) {
super(props);
bindAll(this, [
'handleSubmit'
]);
this.state = {
waiting: false
};
},
handleSubmit: function (formData) {
}
handleSubmit (formData) {
this.setState({waiting: true});
this.props.onLogIn(formData, function (err) {
this.props.onLogIn(formData, err => {
if (err) log.error(err);
this.setState({waiting: false});
}.bind(this));
},
render: function () {
var error;
});
}
render () {
let error;
if (this.props.error) {
error = <div className="error">{this.props.error}</div>;
}
return (
<div className="login">
<Form onSubmit={this.handleSubmit}>
<label htmlFor="username" key="usernameLabel">
<FormattedMessage id='general.username' />
<label
htmlFor="username"
key="usernameLabel"
>
<FormattedMessage id="general.username" />
</label>
<Input type="text" ref="username" name="username" maxLength="30" key="usernameInput" required />
<label htmlFor="password" key="passwordLabel">
<FormattedMessage id='general.password' />
<Input
required
key="usernameInput"
maxLength="30"
name="username"
ref={input => {
this.username = input;
}}
type="text"
/>
<label
htmlFor="password"
key="passwordLabel"
>
<FormattedMessage id="general.password" />
</label>
<Input type="password" ref="password" name="password" key="passwordInput" required />
<Input
required
key="passwordInput"
name="password"
ref={input => {
this.password = input;
}}
type="password"
/>
{this.state.waiting ? [
<Button className="submit-button white" type="submit" disabled="disabled" key="submitButton">
<Button
className="submit-button white"
disabled="disabled"
key="submitButton"
type="submit"
>
<Spinner />
</Button>
] : [
<Button className="submit-button white" type="submit" key="submitButton">
<FormattedMessage id='general.signIn' />
<Button
className="submit-button white"
key="submitButton"
type="submit"
>
<FormattedMessage id="general.signIn" />
</Button>
]}
<a className="right" href="/accounts/password_reset/" key="passwordResetLink">
<FormattedMessage id='login.needHelp' />
<a
className="right"
href="/accounts/password_reset/"
key="passwordResetLink"
>
<FormattedMessage id="login.needHelp" />
</a>
{error}
</Form>
</div>
);
}
});
}
Login.propTypes = {
error: PropTypes.string,
onLogIn: PropTypes.func
};
module.exports = Login;

View file

@ -1,60 +1,68 @@
var classNames = require('classnames');
var React = require('react');
var MediaQuery = require('react-responsive');
var frameless = require('../../lib/frameless');
const bindAll = require('lodash.bindall');
const classNames = require('classnames');
const MediaQuery = require('react-responsive').default;
const PropTypes = require('prop-types');
const React = require('react');
const frameless = require('../../lib/frameless');
require('./masonrygrid.scss');
var MasonryGrid = React.createClass({
type: 'MasonryGrid',
getDefaultProps: function () {
return {
as: 'div'
};
},
reorderColumns: function (items, cols) {
var a1 = [];
var a2 = [];
var a3 = [];
var i = 0;
//only implemented for 2 and 3 columns so far - easy to extend if needed
class MasonryGrid extends React.Component {
constructor (props) {
super(props);
bindAll(this, [
'reorderColumns'
]);
}
reorderColumns (items, cols) {
const a1 = [];
const a2 = [];
const a3 = [];
let i = 0;
// only implemented for 2 and 3 columns so far - easy to extend if needed
if (cols > 1 && cols < 4) {
for (i=0;i<items.length;i++){
var col = (i+cols)%cols;
for (i = 0; i < items.length; i++){
const col = (i + cols) % cols;
if (col === 0) {
a1.push(items[i]);
}
else if (col === 1) {
} else if (col === 1) {
a2.push(items[i]);
}
else if (col === 2) {
} else if (col === 2) {
a3.push(items[i]);
}
}
return a1.concat(a2,a3);
} else {
return a1.concat(a2, a3);
}
return items;
}
},
render: function () {
var classes = classNames(
'masonry',
this.props.className
);
render () {
return (
<this.props.as className={classes}>
<MediaQuery maxWidth={frameless.tablet - 1} >
<this.props.as className={classNames('masonry', this.props.className)}>
<MediaQuery maxWidth={frameless.tablet - 1}>
{this.props.children}
</MediaQuery>
<MediaQuery minWidth={frameless.tablet} maxWidth={frameless.desktop - 1} >
<MediaQuery
maxWidth={frameless.desktop - 1}
minWidth={frameless.tablet}
>
{this.reorderColumns(this.props.children, 2)}
</MediaQuery>
<MediaQuery minWidth={frameless.desktop} >
<MediaQuery minWidth={frameless.desktop}>
{this.reorderColumns(this.props.children, 3)}
</MediaQuery>
</this.props.as>
);
}
});
}
MasonryGrid.propTypes = {
children: PropTypes.node,
className: PropTypes.string
};
MasonryGrid.defaultProps = {
as: 'div'
};
module.exports = MasonryGrid;

View file

@ -1,39 +1,53 @@
var React = require('react');
const bindAll = require('lodash.bindall');
const PropTypes = require('prop-types');
const React = require('react');
const Box = require('../box/box.jsx');
const LegacyCarousel = require('../carousel/legacy-carousel.jsx');
const IframeModal = require('../modal/iframe/modal.jsx');
const NestedCarousel = require('../nestedcarousel/nestedcarousel.jsx');
require('./microworld.scss');
var Box = require('../box/box.jsx');
var LegacyCarousel = require('../carousel/legacy-carousel.jsx');
var IframeModal = require('../modal/iframe/modal.jsx');
var NestedCarousel = require('../nestedcarousel/nestedcarousel.jsx');
var Microworld = React.createClass({
type: 'Microworld',
propTypes: {
microworldData: React.PropTypes.node.isRequired
},
markVideoOpen: function (key) {
{/* When a video is clicked, mark it as an open video, so the video Modal will open.
Key is the number of the video, so distinguish between different videos on the page */}
var videoOpenArr = this.state.videoOpen;
videoOpenArr[key] = true;
this.setState({videoOpen: videoOpenArr});
},
markVideoClosed: function (key) {
{/* When a video's x is clicked, mark it as closed, so the video Modal will disappear.
Key is the number of the video, so distinguish between different videos on the page */}
var videoOpenArr = this.state.videoOpen;
videoOpenArr[key] = false;
this.setState({videoOpen: videoOpenArr});
},
getInitialState: function () {
return {
class Microworld extends React.Component {
constructor (props) {
super(props);
bindAll(this, [
'markVideoOpen',
'markVideoClosed',
'renderVideos',
'renderVideo',
'renderEditorWindow',
'renderTips',
'renderStarterProject',
'renderProjectIdeasBox',
'renderForum',
'renderDesignStudio'
]);
this.state = {
videoOpen: {}
};
},
renderVideos: function () {
var videos = this.props.microworldData.videos;
}
markVideoOpen (key) {
/*
When a video is clicked, mark it as an open video, so the video Modal will open.
Key is the number of the video, so distinguish between different videos on the page
*/
const videoOpenArr = this.state.videoOpen;
videoOpenArr[key] = true;
this.setState({videoOpen: videoOpenArr});
}
markVideoClosed (key) {
/*
When a video's x is clicked, mark it as closed, so the video Modal will disappear.
Key is the number of the video, so distinguish between different videos on the page
*/
const videoOpenArr = this.state.videoOpen;
videoOpenArr[key] = false;
this.setState({videoOpen: videoOpenArr});
}
renderVideos () {
const videos = this.props.microworldData.videos;
if (!videos || videos.length <= 0) {
return null;
}
@ -48,26 +62,32 @@ var Microworld = React.createClass({
</div>
</div>
);
},
renderVideo: function (video, key) {
}
renderVideo (video, key) {
return (
<div>
<div className="video">
<div className="play-button" onClick={this.markVideoOpen.bind(this, key)}>
</div>
<div
className="play-button"
onClick={() => { // eslint-disable-line react/jsx-no-bind
this.markVideoOpen(key);
}}
/>
<img src={video.image} />
</div>
<IframeModal
className="mod-microworld-video"
isOpen={this.state.videoOpen[key]}
onRequestClose={this.markVideoClosed.bind(this, key)}
src={video.link}
onRequestClose={() => { // eslint-disable-line react/jsx-no-bind
this.markVideoClosed(key);
}}
/>
</div>
);
},
renderEditorWindow: function () {
var projectId = this.props.microworldData.microworld_project_id;
}
renderEditorWindow () {
const projectId = this.props.microworldData.microworld_project_id;
if (!projectId) {
return null;
@ -75,30 +95,37 @@ var Microworld = React.createClass({
return (
<div className="editor section">
<h1 className="sectionheader">Start Creating!</h1>
<iframe src={'//scratch.mit.edu/projects/embed-editor/' + projectId + '/?isMicroworld=true'}
frameBorder="0"> </iframe>
<iframe
frameBorder="0"
src={`//scratch.mit.edu/projects/embed-editor/${projectId}/?isMicroworld=true`}
/>
{this.renderTips()}
</div>
);
},
renderTips: function () {
var tips = this.props.microworldData.tips;
}
renderTips () {
const tips = this.props.microworldData.tips;
if (!tips || tips.length <= 0) {
return null;
}
return (
<div className="box nestedcarousel">
<div className="box-header">
</div>
<div className="box-header" />
<div className="box-content">
<NestedCarousel items={tips} settings={{slidesToShow:1,slidesToScroll:1}}/>
<NestedCarousel
items={tips}
settings={{
slidesToShow: 1,
slidesToScroll: 1
}}
/>
</div>
</div>
);
},
renderStarterProject: function () {
var starterProjects = this.props.microworldData.starter_projects;
}
renderStarterProject () {
const starterProjects = this.props.microworldData.starter_projects;
if (!starterProjects || starterProjects.length <= 0){
return null;
}
@ -107,28 +134,30 @@ var Microworld = React.createClass({
<div className="project-ideas">
<h1 className="sectionheader">Check out ideas for more projects</h1>
<Box
key="starter_projects"
title="More Starter Projects"
key="starter_projects">
>
<LegacyCarousel items={starterProjects} />
</Box>
</div>
);
},
renderProjectIdeasBox: function () {
var communityProjects = this.props.microworldData.community_projects;
}
renderProjectIdeasBox () {
const communityProjects = this.props.microworldData.community_projects;
if (!communityProjects || communityProjects.size <= 0) {
return null;
}
var featured = communityProjects.featured_projects;
var all = communityProjects.newest_projects;
const featured = communityProjects.featured_projects;
const all = communityProjects.newest_projects;
var rows = [];
const rows = [];
if (featured && featured.length > 0){
rows.push(
<Box
key="community_featured_projects"
title="Featured Community Projects"
key="community_featured_projects">
>
<LegacyCarousel items={featured} />
</Box>
);
@ -136,8 +165,9 @@ var Microworld = React.createClass({
if (all && all.length > 0) {
rows.push(
<Box
key="community_all_projects"
title="All Community Projects"
key="community_all_projects">
>
<LegacyCarousel items={all} />
</Box>
);
@ -151,8 +181,8 @@ var Microworld = React.createClass({
{rows}
</div>
);
},
renderForum: function () {
}
renderForum () {
if (!this.props.microworldData.show_forum) {
return null;
}
@ -160,58 +190,79 @@ var Microworld = React.createClass({
return (
<div className="forum">
<h1 className="sectionheader">Chat with others!</h1>
<img src="/images/forum-image.png"/>
<img src="/images/forum-image.png" />
</div>
);
},
renderDesignStudio: function () {
var designChallenge = this.props.microworldData.design_challenge;
}
renderDesignStudio () {
const designChallenge = this.props.microworldData.design_challenge;
if (!designChallenge) {
return null;
}
let studioHref = '';
if (designChallenge.studio_id) {
var studioHref = 'https://scratch.mit.edu//studios/' + designChallenge.studio_id + '/';
studioHref = `https://scratch.mit.edu//studios/${designChallenge.studio_id}/`;
}
if (designChallenge.project_id) {
return (
<div className="side-by-side section">
<h1 className="sectionheader">Join our Design Challenge!</h1>
<div className="design-studio">
<iframe src={'https://scratch.mit.edu/projects/' + designChallenge.project_id +
'/#fullscreen'} frameBorder="0"> </iframe>
<iframe
frameBorder="0"
src={`https://scratch.mit.edu/projects/${designChallenge.project_id}/#fullscreen`}
/>
</div>
<div className="design-studio-projects">
<Box title="Examples"
<Box
key="scratch_design_studio"
moreHref={studioHref ? studioHref : null}
moreTitle={studioHref ? 'Visit the studio' : null}
moreHref={studioHref ? studioHref : null}>
title="Examples"
>
{/* The two carousels are used to show two rows of projects, one above the
other. This should be probably be changed, to allow better scrolling. */}
<LegacyCarousel settings={{slidesToShow:2,slidesToScroll:2}}
items={this.props.microworldData.design_challenge.studio1} />
<LegacyCarousel settings={{slidesToShow:2,slidesToScroll:2}}
items={this.props.microworldData.design_challenge.studio2} />
<LegacyCarousel
items={this.props.microworldData.design_challenge.studio1}
settings={{
slidesToShow: 2,
slidesToScroll: 2
}}
/>
<LegacyCarousel
items={this.props.microworldData.design_challenge.studio2}
settings={{
slidesToShow: 2,
slidesToScroll: 2
}}
/>
</Box>
</div>
</div>
);
} else {
}
return (
<div className="section">
<h1 className="sectionheader">Join our Design Challenge!</h1>
<Box
title="design Challenge Projects"
key="scratch_design_studio"
moreHref={studioHref ? studioHref : null}
moreTitle={studioHref ? 'Visit the studio' : null}
moreHref={studioHref ? studioHref : null}>
<LegacyCarousel items={this.props.microworldData.design_challenge.studio1.concat(
this.props.microworldData.design_challenge.studio2)} />
</Box>
title="design Challenge Projects"
>
<LegacyCarousel
items={
this.props.microworldData.design_challenge.studio1.concat(
this.props.microworldData.design_challenge.studio2
)
}
/>
`</Box>
</div>
);
}
},
render: function () {
render () {
return (
<div className="inner microworld">
<div className="top-banner section">
@ -231,6 +282,10 @@ var Microworld = React.createClass({
);
}
});
}
Microworld.propTypes = {
microworldData: PropTypes.node.isRequired
};
module.exports = Microworld;

View file

@ -1,7 +1,9 @@
var classNames = require('classnames');
var omit = require('lodash.omit');
var React = require('react');
var ReactModal = require('react-modal');
const bindAll = require('lodash.bindall');
const classNames = require('classnames');
const omit = require('lodash.omit');
const PropTypes = require('prop-types');
const React = require('react');
const ReactModal = require('react-modal');
require('./modal.scss');
@ -10,47 +12,55 @@ ReactModal.setAppElement(document.getElementById('view'));
/**
* Container for pop up windows (See: registration window)
*/
var Modal = React.createClass({
type: 'Modal',
propTypes: {
className: React.PropTypes.string,
overlayClassName: React.PropTypes.string
},
requestClose: function () {
class Modal extends React.Component {
constructor (props) {
super(props);
bindAll(this, [
'handleRequestClose'
]);
}
handleRequestClose () {
return this.modal.portal.requestClose();
},
render: function () {
var modalClasses = classNames(
'modal-content',
this.props.className
);
var overlayClasses = classNames(
'modal-overlay',
this.props.overlayClassName
);
}
render () {
return (
<ReactModal
ref={
function (component) {
appElement={document.getElementById('view')}
className={{
base: classNames('modal-content', this.props.className),
afterOpen: classNames('modal-content', this.props.className),
beforeClose: classNames('modal-content', this.props.className)
}}
overlayClassName={{
base: classNames('modal-overlay', this.props.overlayClassName),
afterOpen: classNames('modal-overlay', this.props.overlayClassName),
beforeClose: classNames('modal-overlay', this.props.overlayClassName)
}}
ref={component => {
this.modal = component;
}.bind(this)
}
className={modalClasses}
overlayClassName={overlayClasses}
}}
{...omit(this.props, ['className', 'overlayClassName'])}
>
<div className="modal-content-close" onClick={this.requestClose}>
<div
className="modal-content-close"
onClick={this.handleRequestClose}
>
<img
alt="close-icon"
className="modal-content-close-img"
src="/svgs/modal/close-x.svg"
alt="close-icon"
/>
</div>
{this.props.children}
</ReactModal>
);
}
});
}
Modal.propTypes = {
children: PropTypes.node,
className: PropTypes.string,
overlayClassName: PropTypes.string
};
module.exports = Modal;

View file

@ -21,7 +21,7 @@
background-color: transparentize($ui-blue, .3);
}
.ReactModal__Content:focus {
.modal-content:focus {
outline: none;
}

View file

@ -1,34 +1,28 @@
var classNames = require('classnames');
var omit = require('lodash.omit');
var React = require('react');
const classNames = require('classnames');
const omit = require('lodash.omit');
const PropTypes = require('prop-types');
const React = require('react');
var Modal = require('../base/modal.jsx');
const Modal = require('../base/modal.jsx');
require('./modal.scss');
var IframeModal = React.createClass({
propTypes: {
isOpen: React.PropTypes.bool,
onRequestClose: React.PropTypes.func,
className: React.PropTypes.string,
componentRef: React.PropTypes.func,
src: React.PropTypes.string
},
render: function () {
var iframeClasses = classNames(
'modal-content-iframe',
this.props.className
);
return (
<Modal {...omit(this.props, ['src'])}>
const IframeModal = props => (
<Modal {...omit(props, ['src'])}>
<iframe
ref={this.props.componentRef}
src={this.props.src}
className={iframeClasses}
className={classNames('modal-content-iframe', props.className)}
ref={props.componentRef}
src={props.src}
/>
</Modal>
);
}
});
);
IframeModal.propTypes = {
className: PropTypes.string,
componentRef: PropTypes.func,
isOpen: PropTypes.bool,
onRequestClose: PropTypes.func,
src: PropTypes.string
};
module.exports = IframeModal;

View file

@ -1,28 +1,23 @@
var FormattedMessage = require('react-intl').FormattedMessage;
var MediaQuery = require('react-responsive');
var omit = require('lodash.omit');
var React = require('react');
const FormattedMessage = require('react-intl').FormattedMessage;
const MediaQuery = require('react-responsive').default;
const omit = require('lodash.omit');
const PropTypes = require('prop-types');
const React = require('react');
var FlexRow = require('../../flex-row/flex-row.jsx');
var frameless = require('../../../lib/frameless');
var Modal = require('../base/modal.jsx');
var TitleBanner = require('../../title-banner/title-banner.jsx');
const FlexRow = require('../../flex-row/flex-row.jsx');
const frameless = require('../../../lib/frameless');
const Modal = require('../base/modal.jsx');
const TitleBanner = require('../../title-banner/title-banner.jsx');
require('../../forms/button.scss');
require('./modal.scss');
var TTTModal = React.createClass({
propTypes: {
title: React.PropTypes.string.isRequired,
description: React.PropTypes.string.isRequired,
tutorialLoc: React.PropTypes.string.isRequired,
activityLoc: React.PropTypes.string.isRequired,
guideLoc: React.PropTypes.string.isRequired,
thumbUrl: React.PropTypes.string.isRequired,
bannerUrl: React.PropTypes.string.isRequired
},
render: function () {
var modalOmit = [
const TTTModal = props => (
<Modal
className="mod-ttt"
{...omit(
props,
[
'title',
'description',
'tutorialLoc',
@ -30,32 +25,40 @@ var TTTModal = React.createClass({
'guideLoc',
'thumbUrl',
'bannerUrl'
];
return (
<Modal
className="mod-ttt"
{...omit(this.props, modalOmit)}
]
)}
>
<TitleBanner className="mod-ttt">
<MediaQuery minWidth={frameless.mobile}>
<img className="mod-ttt-img" src={this.props.bannerUrl} alt="" />
<img
alt=""
className="mod-ttt-img"
src={props.bannerUrl}
/>
</MediaQuery>
<MediaQuery maxWidth={frameless.mobile - 1}>
<img className="mod-ttt-img" src={this.props.thumbUrl} alt="" />
<img
alt=""
className="mod-ttt-img"
src={props.thumbUrl}
/>
</MediaQuery>
</TitleBanner>
<div className="ttt-title">
<h2>{this.props.title}</h2>
<p className="ttt-description">{this.props.description}</p>
<h2>{props.title}</h2>
<p className="ttt-description">{props.description}</p>
</div>
<ul className="modal-content-ttt">
<FlexRow as="li" className="mod-ttt-item">
<FlexRow
as="li"
className="mod-ttt-item"
>
<div className="modal-content-ttt-text">
<div className="modal-content-ttt-title">
<img
alt="tutorial-icon"
className="modal-content-ttt-title-img"
src="/svgs/ttt/tutorial.svg"
alt="tutorial-icon"
/>
<FormattedMessage id="ttt.tutorial" />
</div>
@ -64,19 +67,22 @@ var TTTModal = React.createClass({
</p>
</div>
<a
href={this.props.tutorialLoc}
className="button white mod-ttt-item"
href={props.tutorialLoc}
>
<FormattedMessage id="tile.tryIt" />
</a>
</FlexRow>
<FlexRow as="li" className="mod-ttt-item">
<FlexRow
as="li"
className="mod-ttt-item"
>
<div className="modal-content-ttt-text">
<div className="modal-content-ttt-title">
<img
alt="activity-cards-icon"
className="modal-content-ttt-title-img"
src="/svgs/ttt/activity-cards.svg"
alt="activity-cards-icon"
/>
<FormattedMessage id="ttt.activityTitle" />
</div>
@ -85,19 +91,22 @@ var TTTModal = React.createClass({
</p>
</div>
<a
href={this.props.activityLoc}
className="button white mod-ttt-item"
href={props.activityLoc}
>
<FormattedMessage id="ttt.open" />
</a>
</FlexRow>
<FlexRow as="li" className="mod-ttt-item">
<FlexRow
as="li"
className="mod-ttt-item"
>
<div className="modal-content-ttt-text">
<div className="modal-content-ttt-title">
<img
alt="educator-guide-icon"
className="modal-content-ttt-title-img"
src="/svgs/ttt/educator-guide.svg"
alt="educator-guide-icon"
/>
<FormattedMessage id="ttt.educatorTitle" />
</div>
@ -106,16 +115,24 @@ var TTTModal = React.createClass({
</p>
</div>
<a
href={this.props.guideLoc}
className="button white mod-ttt-item"
href={props.guideLoc}
>
<FormattedMessage id="ttt.open" />
</a>
</FlexRow>
</ul>
</Modal>
);
}
});
);
TTTModal.propTypes = {
activityLoc: PropTypes.string.isRequired,
bannerUrl: PropTypes.string.isRequired,
description: PropTypes.string.isRequired,
guideLoc: PropTypes.string.isRequired,
thumbUrl: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
tutorialLoc: PropTypes.string.isRequired
};
module.exports = TTTModal;

View file

@ -1,21 +1,18 @@
var classNames = require('classnames');
var React = require('react');
const classNames = require('classnames');
const PropTypes = require('prop-types');
const React = require('react');
require('./navigation.scss');
var NavigationBox = React.createClass({
type: 'NavigationBox',
render: function () {
var classes = classNames(
'inner',
this.props.className
);
return (
<div className={classes}>
{this.props.children}
const NavigationBox = props => (
<div className={classNames('inner', props.className)}>
{props.children}
</div>
);
}
});
);
NavigationBox.propTypes = {
children: PropTypes.node,
className: PropTypes.string
};
module.exports = NavigationBox;

View file

@ -1,21 +1,21 @@
var React = require('react');
const React = require('react');
var NavigationBox = require('../../base/navigation.jsx');
const NavigationBox = require('../../base/navigation.jsx');
require('./navigation.scss');
var Navigation = React.createClass({
type: 'Navigation',
render: function () {
return (
const Navigation = () => (
<NavigationBox>
<ul className="ul mod-2016">
<li className="li-left mod-logo mod-2016">
<a href="/conference/2016" className="logo-a">
<a
className="logo-a"
href="/conference/2016"
>
<img
src="/images/logo_sm.png"
alt="Scratch Logo"
className="logo-a-image"
src="/images/logo_sm.png"
/>
<p className="logo-a-title">Conference</p>
</a>
@ -23,20 +23,33 @@ var Navigation = React.createClass({
<li className="li-right mod-2016">
<ul className="li-right-ul mod-2016">
<li className="link expect">
<a href="/conference/2016/expect" className="link-a">What to Expect</a>
<a
className="link-a"
href="/conference/2016/expect"
>
What to Expect
</a>
</li>
<li className="link plan">
<a href="/conference/2016/plan" className="link-a">Plan Your Visit</a>
<a
className="link-a"
href="/conference/2016/plan"
>
Plan Your Visit
</a>
</li>
<li className="link schedule">
<a href="/conference/2016/schedule" className="link-a">Schedule</a>
<a
className="link-a"
href="/conference/2016/schedule"
>
Schedule
</a>
</li>
</ul>
</li>
</ul>
</NavigationBox>
);
}
});
);
module.exports = Navigation;

View file

@ -1,29 +1,27 @@
var React = require('react');
const React = require('react');
var NavigationBox = require('../../base/navigation.jsx');
const NavigationBox = require('../../base/navigation.jsx');
require('./navigation.scss');
var Navigation = React.createClass({
type: 'Navigation',
render: function () {
return (
const Navigation = () => (
<NavigationBox>
<ul className="ul mod-2017">
<li className="li-left mod-logo mod-2017">
<a href="/conference" className="logo-a">
<a
className="logo-a"
href="/conference"
>
<img
src="/images/logo_sm.png"
alt="Scratch Logo"
className="logo-a-image"
src="/images/logo_sm.png"
/>
<p className="logo-a-title">Conferences</p>
</a>
</li>
</ul>
</NavigationBox>
);
}
});
);
module.exports = Navigation;

View file

@ -1,29 +1,27 @@
var React = require('react');
const React = require('react');
var NavigationBox = require('../../base/navigation.jsx');
const NavigationBox = require('../../base/navigation.jsx');
require('./navigation.scss');
var Navigation = React.createClass({
type: 'Navigation',
render: function () {
return (
const Navigation = () => (
<NavigationBox>
<ul className="ul mod-2018">
<li className="li-left mod-logo mod-2018">
<a href="/" className="logo-a">
<a
className="logo-a"
href="/"
>
<img
src="/images/logo_sm.png"
alt="Scratch Logo"
className="logo-a-image"
src="/images/logo_sm.png"
/>
<p className="logo-a-title">Conferences</p>
</a>
</li>
</ul>
</NavigationBox>
);
}
});
);
module.exports = Navigation;

View file

@ -1,31 +1,48 @@
var classNames = require('classnames');
var connect = require('react-redux').connect;
var React = require('react');
var ReactIntl = require('react-intl');
var FormattedMessage = ReactIntl.FormattedMessage;
var injectIntl = ReactIntl.injectIntl;
const bindAll = require('lodash.bindall');
const classNames = require('classnames');
const connect = require('react-redux').connect;
const FormattedMessage = require('react-intl').FormattedMessage;
const injectIntl = require('react-intl').injectIntl;
const intlShape = require('react-intl').intlShape;
const PropTypes = require('prop-types');
const React = require('react');
var messageCountActions = require('../../../redux/message-count.js');
var sessionActions = require('../../../redux/session.js');
const messageCountActions = require('../../../redux/message-count.js');
const sessionActions = require('../../../redux/session.js');
var api = require('../../../lib/api');
var Avatar = require('../../avatar/avatar.jsx');
var Button = require('../../forms/button.jsx');
var Dropdown = require('../../dropdown/dropdown.jsx');
var Form = require('../../forms/form.jsx');
var Input = require('../../forms/input.jsx');
var log = require('../../../lib/log.js');
var Login = require('../../login/login.jsx');
var Modal = require('../../modal/base/modal.jsx');
var NavigationBox = require('../base/navigation.jsx');
var Registration = require('../../registration/registration.jsx');
const api = require('../../../lib/api');
const Avatar = require('../../avatar/avatar.jsx');
const Button = require('../../forms/button.jsx');
const Dropdown = require('../../dropdown/dropdown.jsx');
const Form = require('../../forms/form.jsx');
const Input = require('../../forms/input.jsx');
const log = require('../../../lib/log.js');
const Login = require('../../login/login.jsx');
const Modal = require('../../modal/base/modal.jsx');
const NavigationBox = require('../base/navigation.jsx');
const Registration = require('../../registration/registration.jsx');
require('./navigation.scss');
var Navigation = React.createClass({
type: 'Navigation',
getInitialState: function () {
return {
class Navigation extends React.Component {
constructor (props) {
super(props);
bindAll(this, [
'getProfileUrl',
'handleJoinClick',
'handleLoginClick',
'handleCloseLogin',
'handleLogIn',
'handleLogOut',
'handleAccountNavClick',
'handleCloseAccountNav',
'showCanceledDeletion',
'handleCloseCanceledDeletion',
'handleCloseRegistration',
'handleCompleteRegistration',
'handleSearchSubmit'
]);
this.state = {
accountNavOpen: false,
canceledDeletionOpen: false,
loginOpen: false,
@ -33,155 +50,153 @@ var Navigation = React.createClass({
registrationOpen: false,
messageCountIntervalId: -1 // javascript method interval id for getting messsage count.
};
},
getDefaultProps: function () {
return {
session: {},
unreadMessageCount: 0, // bubble number to display how many notifications someone has.
searchTerm: ''
};
},
componentDidMount: function () {
if (this.props.session.session.user) {
var intervalId = setInterval(function () {
this.props.dispatch(messageCountActions.getCount(this.props.session.session.user.username));
}.bind(this), 120000); // check for new messages every 2 mins.
this.setState({'messageCountIntervalId': intervalId});
}
},
componentDidUpdate: function (prevProps) {
if (prevProps.session.session.user != this.props.session.session.user) {
this.setState({
'loginOpen': false,
'accountNavOpen': false
componentDidMount () {
if (this.props.session.session.user) {
const intervalId = setInterval(() => {
this.props.dispatch(
messageCountActions.getCount(this.props.session.session.user.username)
);
}, 120000); // check for new messages every 2 mins.
this.setState({ // eslint-disable-line react/no-did-mount-set-state
messageCountIntervalId: intervalId
});
}
}
componentDidUpdate (prevProps) {
if (prevProps.session.session.user !== this.props.session.session.user) {
this.setState({ // eslint-disable-line react/no-did-update-set-state
loginOpen: false,
accountNavOpen: false
});
if (this.props.session.session.user) {
var intervalId = setInterval(function () {
this.props.dispatch(messageCountActions.getCount(this.props.session.session.user.username));
}.bind(this), 120000); // check for new messages every 2 mins.
this.setState({'messageCountIntervalId': intervalId});
const intervalId = setInterval(() => {
this.props.dispatch(
messageCountActions.getCount(this.props.session.session.user.username)
);
}, 120000); // check for new messages every 2 mins.
this.setState({ // eslint-disable-line react/no-did-update-set-state
messageCountIntervalId: intervalId
});
} else {
// clear message count check, and set to default id.
clearInterval(this.state.messageCountIntervalId);
this.props.dispatch(messageCountActions.setCount(0));
this.setState({
'messageCountIntervalId': -1
this.setState({ // eslint-disable-line react/no-did-update-set-state
messageCountIntervalId: -1
});
}
}
},
componentWillUnmount: function () {
}
componentWillUnmount () {
// clear message interval if it exists
if (this.state.messageCountIntervalId != -1) {
if (this.state.messageCountIntervalId !== -1) {
clearInterval(this.state.messageCountIntervalId);
this.props.dispatch(messageCountActions.setCount(0));
this.setState({
'messageCountIntervalId': -1
messageCountIntervalId: -1
});
}
},
getProfileUrl: function () {
}
getProfileUrl () {
if (!this.props.session.session.user) return;
return '/users/' + this.props.session.session.user.username + '/';
},
handleJoinClick: function (e) {
return `/users/${this.props.session.session.user.username}/`;
}
handleJoinClick (e) {
e.preventDefault();
this.setState({'registrationOpen': true});
},
handleLoginClick: function (e) {
this.setState({registrationOpen: true});
}
handleLoginClick (e) {
e.preventDefault();
this.setState({'loginOpen': !this.state.loginOpen});
},
closeLogin: function () {
this.setState({'loginOpen': false});
},
handleLogIn: function (formData, callback) {
this.setState({'loginError': null});
formData['useMessages'] = true;
this.setState({loginOpen: !this.state.loginOpen});
}
handleCloseLogin () {
this.setState({loginOpen: false});
}
handleLogIn (formData, callback) {
this.setState({loginError: null});
formData.useMessages = true;
api({
method: 'post',
host: '',
uri: '/accounts/login/',
json: formData,
useCsrf: true
}, function (err, body) {
if (err) this.setState({'loginError': err.message});
}, (err, body) => {
if (err) this.setState({loginError: err.message});
if (body) {
body = body[0];
if (!body.success) {
if (body.success) {
this.handleCloseLogin();
body.messages.map(message => { // eslint-disable-line array-callback-return
if (message.message === 'canceled-deletion') {
this.showCanceledDeletion();
}
});
this.props.dispatch(sessionActions.refreshSession());
} else {
if (body.redirect) {
window.location = body.redirect;
}
// Update login error message to a friendlier one if it exists
this.setState({'loginError': body.msg});
} else {
this.closeLogin();
body.messages.map(function (message) {
if (message.message == 'canceled-deletion') {
this.showCanceledDeletion();
}
}.bind(this));
this.props.dispatch(sessionActions.refreshSession());
this.setState({loginError: body.msg});
}
}
// JS error already logged by api mixin
callback();
}.bind(this));
},
handleLogOut: function (e) {
});
}
handleLogOut (e) {
e.preventDefault();
api({
host: '',
method: 'post',
uri: '/accounts/logout/',
useCsrf: true
}, function (err) {
}, err => {
if (err) log.error(err);
this.closeLogin();
this.handleCloseLogin();
window.location = '/';
}.bind(this));
},
handleAccountNavClick: function (e) {
});
}
handleAccountNavClick (e) {
e.preventDefault();
this.setState({'accountNavOpen': true});
},
closeAccountNav: function () {
this.setState({'accountNavOpen': false});
},
showCanceledDeletion: function () {
this.setState({'canceledDeletionOpen': true});
},
closeCanceledDeletion: function () {
this.setState({'canceledDeletionOpen': false});
},
closeRegistration: function () {
this.setState({'registrationOpen': false});
},
completeRegistration: function () {
this.setState({accountNavOpen: true});
}
handleCloseAccountNav () {
this.setState({accountNavOpen: false});
}
showCanceledDeletion () {
this.setState({canceledDeletionOpen: true});
}
handleCloseCanceledDeletion () {
this.setState({canceledDeletionOpen: false});
}
handleCloseRegistration () {
this.setState({registrationOpen: false});
}
handleCompleteRegistration () {
this.props.dispatch(sessionActions.refreshSession());
this.closeRegistration();
},
onSearchSubmit: function (formData) {
window.location.href = '/search/projects?q=' + encodeURIComponent(formData.q);
},
render: function () {
var classes = classNames({
'logged-in': this.props.session.session.user
});
var messageClasses = classNames({
'message-count': true,
'show': this.props.unreadMessageCount > 0
});
var dropdownClasses = classNames({
'user-info': true,
'open': this.state.accountNavOpen
});
var formatMessage = this.props.intl.formatMessage;
var createLink = this.props.session.session.user ? '/projects/editor/' : '/projects/editor/?tip_bar=home';
this.handleCloseRegistration();
}
handleSearchSubmit (formData) {
window.location.href = `/search/projects?q=${encodeURIComponent(formData.q)}`;
}
render () {
const createLink = this.props.session.session.user ? '/projects/editor/' : '/projects/editor/?tip_bar=home';
return (
<NavigationBox className={classes}>
<NavigationBox
className={classNames({
'logged-in': this.props.session.session.user
})}
>
<ul>
<li className="logo"><a href="/" aria-label="Scratch"></a></li>
<li className="logo">
<a
aria-label="Scratch"
href="/"
/>
</li>
<li className="link create">
<a href={createLink}>
@ -205,47 +220,76 @@ var Navigation = React.createClass({
</li>
<li className="search">
<Form onSubmit={this.onSearchSubmit}>
<Button type="submit" className="btn-search" />
<Input type="text"
<Form onSubmit={this.handleSearchSubmit}>
<Button
className="btn-search"
type="submit"
/>
<Input
aria-label={this.props.intl.formatMessage({id: 'general.search'})}
name="q"
placeholder={this.props.intl.formatMessage({id: 'general.search'})}
type="text"
value={this.props.searchTerm}
aria-label={formatMessage({id: 'general.search'})}
placeholder={formatMessage({id: 'general.search'})}
name="q" />
/>
</Form>
</li>
{this.props.session.status === sessionActions.Status.FETCHED ? (
this.props.session.session.user ? [
<li className="link right messages" key="messages">
<li
className="link right messages"
key="messages"
>
<a
href="/messages/"
title={formatMessage({id: 'general.messages'})}>
<span className={messageClasses}>{this.props.unreadMessageCount}</span>
title={this.props.intl.formatMessage({id: 'general.messages'})}
>
<span
className={classNames({
'message-count': true,
'show': this.props.unreadMessageCount > 0
})}
>{this.props.unreadMessageCount}</span>
<FormattedMessage id="general.messages" />
</a>
</li>,
<li className="link right mystuff" key="mystuff">
<li
className="link right mystuff"
key="mystuff"
>
<a
href="/mystuff/"
title={formatMessage({id: 'general.myStuff'})}>
title={this.props.intl.formatMessage({id: 'general.myStuff'})}
>
<FormattedMessage id="general.myStuff" />
</a>
</li>,
<li className="link right account-nav" key="account-nav">
<a className={dropdownClasses}
href="#" onClick={this.handleAccountNavClick}>
<Avatar src={this.props.session.session.user.thumbnailUrl} alt="" />
<span className='profile-name'>
<li
className="link right account-nav"
key="account-nav"
>
<a
className={classNames({
'user-info': true,
'open': this.state.accountNavOpen
})}
href="#"
onClick={this.handleAccountNavClick}
>
<Avatar
alt=""
src={this.props.session.session.user.thumbnailUrl}
/>
<span className="profile-name">
{this.props.session.session.user.username}
</span>
</a>
<Dropdown
as="ul"
className={process.env.SCRATCH_ENV}
isOpen={this.state.accountNavOpen}
onRequestClose={this.closeAccountNav}
className={process.env.SCRATCH_ENV}>
onRequestClose={this.handleCloseAccountNav}
>
<li>
<a href={this.getProfileUrl()}>
<FormattedMessage id="general.profile" />
@ -264,8 +308,8 @@ var Navigation = React.createClass({
</li>
] : []}
{this.props.permissions.student ? [
<li>
<a href={'/classes/' + this.props.session.session.user.classroomId + '/'}>
<li key="my-class-li">
<a href={`/classes/${this.props.session.session.user.classroomId}/`}>
<FormattedMessage id="general.myClass" />
</a>
</li>
@ -276,69 +320,123 @@ var Navigation = React.createClass({
</a>
</li>
<li className="divider">
<a href="#" onClick={this.handleLogOut}>
<a
href="#"
onClick={this.handleLogOut}
>
<FormattedMessage id="navigation.signOut" />
</a>
</li>
</Dropdown>
</li>
] : [
<li className="link right join" key="join">
<a href="#" onClick={this.handleJoinClick}>
<li
className="link right join"
key="join"
>
<a
href="#"
onClick={this.handleJoinClick}
>
<FormattedMessage id="general.joinScratch" />
</a>
</li>,
<Registration
key="registration"
isOpen={this.state.registrationOpen}
onRequestClose={this.closeRegistration}
onRegistrationDone={this.completeRegistration} />,
<li className="link right login-item" key="login">
key="registration"
onRegistrationDone={this.handleCompleteRegistration}
onRequestClose={this.handleCloseRegistration}
/>,
<li
className="link right login-item"
key="login"
>
<a
href="#"
onClick={this.handleLoginClick}
className="ignore-react-onclickoutside"
key="login-link">
href="#"
key="login-link"
onClick={this.handleLoginClick}
>
<FormattedMessage id="general.signIn" />
</a>
<Dropdown
className="login-dropdown with-arrow"
isOpen={this.state.loginOpen}
onRequestClose={this.closeLogin}
key="login-dropdown">
key="login-dropdown"
onRequestClose={this.handleCloseLogin}
>
<Login
error={this.state.loginError}
onLogIn={this.handleLogIn}
error={this.state.loginError} />
/>
</Dropdown>
</li>
]) : [
]}
]) : []}
</ul>
<Modal isOpen={this.state.canceledDeletionOpen}
onRequestClose={this.closeCanceledDeletion}
style={{content:{padding: 15}}}>
<Modal
isOpen={this.state.canceledDeletionOpen}
style={{
content: {
padding: 15
}
}}
onRequestClose={this.handleCloseCanceledDeletion}
>
<h4>Your Account Will Not Be Deleted</h4>
<h4><FormattedMessage id="general.noDeletionTitle" /></h4>
<p>
Your account was scheduled for deletion but you logged in. Your account has been reactivated.
If you didnt request for your account to be deleted, you should
{' '}<a href="/accounts/password_reset/">change your password</a>{' '}
to make sure your account is secure.
<FormattedMessage
id="general.noDeletionDescription"
values={{
resetLink: <a href="/accounts/password_reset/">
{this.props.intl.formatMessage({id: 'general.noDeletionLink'})}
</a>
}}
/>
</p>
</Modal>
</NavigationBox>
);
}
});
}
var mapStateToProps = function (state) {
return {
Navigation.propTypes = {
dispatch: PropTypes.func,
intl: intlShape,
permissions: PropTypes.shape({
admin: PropTypes.bool,
social: PropTypes.bool,
educator: PropTypes.bool,
educator_invitee: PropTypes.bool,
student: PropTypes.bool
}),
searchTerm: PropTypes.string,
session: PropTypes.shape({
session: PropTypes.shape({
user: PropTypes.shape({
classroomId: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
thumbnailUrl: PropTypes.string,
username: PropTypes.string
})
}),
status: PropTypes.string
}),
unreadMessageCount: PropTypes.oneOfType([PropTypes.number, PropTypes.string])
};
Navigation.defaultProps = {
session: {},
unreadMessageCount: 0, // bubble number to display how many notifications someone has.
searchTerm: ''
};
const mapStateToProps = state => ({
session: state.session,
permissions: state.permissions,
unreadMessageCount: state.messageCount.messageCount,
searchTerm: state.navigation
};
};
});
var ConnectedNavigation = connect(mapStateToProps)(Navigation);
const ConnectedNavigation = connect(mapStateToProps)(Navigation);
module.exports = injectIntl(ConnectedNavigation);

View file

@ -1,35 +1,25 @@
var classNames = require('classnames');
var defaults = require('lodash.defaults');
var React = require('react');
var Slider = require('react-slick');
const classNames = require('classnames');
const defaults = require('lodash.defaults');
const PropTypes = require('prop-types');
const React = require('react');
const Slider = require('react-slick');
var Thumbnail = require('../thumbnail/thumbnail.jsx');
const Thumbnail = require('../thumbnail/thumbnail.jsx');
require('slick-carousel/slick/slick.scss');
require('slick-carousel/slick/slick-theme.scss');
require('./nestedcarousel.scss');
{/*
/*
NestedCarousel is used to show a carousel, where each slide is composed of a few
thumbnails (for example, to show step-by-syep tips, where each stage has a few steps).
It creates the thumbnails without links.
Each slide has a title, and then a list of thumbnails, that will be shown together.
*/}
var NestedCarousel = React.createClass({
type: 'NestedCarousel',
propTypes: {
items: React.PropTypes.array
},
getDefaultProps: function () {
return {
items: require('./nestedcarousel.json')
};
},
render: function () {
var settings = this.props.settings || {};
defaults(settings, {
*/
const NestedCarousel = props => {
defaults(props.settings, {
dots: true,
infinite: false,
lazyLoad: true,
@ -38,38 +28,57 @@ var NestedCarousel = React.createClass({
variableWidth: false
});
var arrows = this.props.items.length > settings.slidesToShow;
const arrows = props.items.length > props.settings.slidesToShow;
const stages = [];
var classes = classNames(
'nestedcarousel',
'carousel',
this.props.className
);
var stages = [];
for (var i=0; i < this.props.items.length; i++) {
var items = this.props.items[i].thumbnails;
var thumbnails = [];
for (var j=0; j < items.length; j++) {
var item = items[j];
for (let i = 0; i < props.items.length; i++) {
const items = props.items[i].thumbnails;
const thumbnails = [];
for (let j = 0; j < items.length; j++) {
const item = items[j];
thumbnails.push(
<Thumbnail key={'inner_' + i + '_' + j}
title={item.title}
<Thumbnail
key={`inner_${i}_${j}`}
linkTitle={false}
src={item.thumbnailUrl}
linkTitle = {false} />);
title={item.title}
/>
);
}
stages.push(
<div key={'outer_' + i}>
<h3>{this.props.items[i].title}</h3>
<div key={`outer_${i}`}>
<h3>{props.items[i].title}</h3>
{thumbnails}
</div>);
</div>
);
}
return (
<Slider className={classes} arrows={arrows} {... settings}>
<Slider
arrows={arrows}
className={classNames('nestedcarousel', 'carousel', props.className)}
{...props.settings}
>
{stages}
</Slider>
);
}
});
};
NestedCarousel.propTypes = {
className: PropTypes.string,
items: PropTypes.arrayOf(PropTypes.object),
settings: PropTypes.shape({
dots: PropTypes.bool,
infinite: PropTypes.bool,
lazyLoad: PropTypes.bool,
slidesToShow: PropTypes.number,
slidesToScroll: PropTypes.number,
variableWidth: PropTypes.bool
})
};
NestedCarousel.defaultProps = {
settings: {},
items: require('./nestedcarousel.json')
};
module.exports = NestedCarousel;

View file

@ -1,41 +1,27 @@
var React = require('react');
const PropTypes = require('prop-types');
const React = require('react');
var Box = require('../box/box.jsx');
const Box = require('../box/box.jsx');
require('./news.scss');
var News = React.createClass({
type: 'News',
propTypes: {
items: React.PropTypes.array
},
getDefaultProps: function () {
return {
items: require('./news.json'),
messages: {
'general.viewAll': 'View All',
'news.scratchNews': 'Scratch News'
}
};
},
render: function () {
return (
const News = props => (
<Box
className="news"
title={this.props.messages['news.scratchNews']}
moreTitle={this.props.messages['general.viewAll']}
moreHref="/discuss/5/">
moreHref="/discuss/5/"
moreTitle={props.messages['general.viewAll']}
title={props.messages['news.scratchNews']}
>
<ul>
{this.props.items.map(function (item) {
return (
{props.items.map(item => (
<li key={item.id}>
<a href={item.url}>
<img src={item.image}
className="news-image"
width="53"
height="53"
<img
alt=""
className="news-image"
height="53"
src={item.image}
width="53"
/>
<div className="news-description">
<h4>{item.headline}</h4>
@ -43,12 +29,25 @@ var News = React.createClass({
</div>
</a>
</li>
);
})}
))}
</ul>
</Box>
);
);
News.propTypes = {
items: PropTypes.arrayOf(PropTypes.object),
messages: PropTypes.shape({
'general.viewAll': PropTypes.string,
'news.scratchNews': PropTypes.string
})
};
News.defaultProps = {
items: require('./news.json'),
messages: {
'general.viewAll': 'View All',
'news.scratchNews': 'Scratch News'
}
});
};
module.exports = News;

View file

@ -1,27 +1,27 @@
var React = require('react');
const PropTypes = require('prop-types');
const React = require('react');
var Navigation = require('../../../navigation/conference/2016/navigation.jsx');
var Footer = require('../../../footer/conference/2016/footer.jsx');
const Navigation = require('../../../navigation/conference/2016/navigation.jsx');
const Footer = require('../../../footer/conference/2016/footer.jsx');
require('../page.scss');
var Page = React.createClass({
type: 'Page',
render: function () {
return (
const Page = props => (
<div className="page mod-conference">
<div id="navigation">
<Navigation />
</div>
<div id="view">
{this.props.children}
{props.children}
</div>
<div id="footer">
<Footer />
</div>
</div>
);
}
});
);
Page.propTypes = {
children: PropTypes.node
};
module.exports = Page;

View file

@ -1,27 +1,27 @@
var React = require('react');
const PropTypes = require('prop-types');
const React = require('react');
var Navigation = require('../../../navigation/conference/2017/navigation.jsx');
var Footer = require('../../../footer/conference/2017/footer.jsx');
const Navigation = require('../../../navigation/conference/2017/navigation.jsx');
const Footer = require('../../../footer/conference/2017/footer.jsx');
require('../page.scss');
var Page = React.createClass({
type: 'Page',
render: function () {
return (
const Page = props => (
<div className="page mod-conference">
<div id="navigation">
<Navigation />
</div>
<div id="view">
{this.props.children}
{props.children}
</div>
<div id="footer">
<Footer />
</div>
</div>
);
}
});
);
Page.propTypes = {
children: PropTypes.node
};
module.exports = Page;

View file

@ -1,27 +1,27 @@
var React = require('react');
const PropTypes = require('prop-types');
const React = require('react');
var Navigation = require('../../../navigation/conference/2018/navigation.jsx');
var Footer = require('../../../footer/conference/2018/footer.jsx');
const Navigation = require('../../../navigation/conference/2018/navigation.jsx');
const Footer = require('../../../footer/conference/2018/footer.jsx');
require('../page.scss');
var Page = React.createClass({
type: 'Page',
render: function () {
return (
const Page = props => (
<div className="page mod-conference">
<div id="navigation">
<Navigation />
</div>
<div id="view">
{this.props.children}
{props.children}
</div>
<div id="footer">
<Footer />
</div>
</div>
);
}
});
);
Page.propTypes = {
children: PropTypes.node
};
module.exports = Page;

View file

@ -1,29 +1,31 @@
var React = require('react');
var classNames = require('classnames');
const classNames = require('classnames');
const PropTypes = require('prop-types');
const React = require('react');
var Navigation = require('../../navigation/www/navigation.jsx');
var Footer = require('../../footer/www/footer.jsx');
const Navigation = require('../../navigation/www/navigation.jsx');
const Footer = require('../../footer/www/footer.jsx');
var Page = React.createClass({
type: 'Page',
render: function () {
var classes = classNames({
'staging': process.env.SCRATCH_ENV == 'staging'
});
return (
const Page = props => (
<div className="page">
<div id="navigation" className={classes}>
<div
className={classNames({
staging: process.env.SCRATCH_ENV === 'staging'
})}
id="navigation"
>
<Navigation />
</div>
<div id="view">
{this.props.children}
{props.children}
</div>
<div id="footer">
<Footer />
</div>
</div>
);
}
});
);
Page.propTypes = {
children: PropTypes.node
};
module.exports = Page;

View file

@ -1,42 +1,45 @@
var classNames = require('classnames');
var React = require('react');
const classNames = require('classnames');
const PropTypes = require('prop-types');
const React = require('react');
module.exports = React.createClass({
displayName: 'Progression',
propTypes: {
step: function (props, propName, componentName) {
var stepValidator = function (props, propName) {
if (props[propName] > -1 && props[propName] < props.children.length) {
return null;
} else {
return new Error('Prop `step` out of range');
}
const Progression = props => {
const childProps = {
activeStep: props.step,
totalSteps: React.Children.count(props.children)
};
return (
React.PropTypes.number.isRequired(props, propName, componentName) ||
<div
className={classNames('progression', props.className)}
{...props}
>
{React.Children.map(props.children, (child, id) => {
if (id === props.step) {
return React.cloneElement(child, childProps);
}
})}
</div>
);
};
Progression.propTypes = {
children: PropTypes.node,
className: PropTypes.string,
step: function (props, propName, componentName) {
const stepValidator = (propz, name) => {
if (propz[name] > -1 && propz[name] < propz.children.length) {
return null;
}
return new Error('Prop `step` out of range');
};
return (
(typeof props[propName] === 'number' ? null : new Error('Not a number')) ||
stepValidator(props, propName, componentName)
);
}
},
getDefaultProps: function () {
return {
};
Progression.defaultProps = {
step: 0
};
},
render: function () {
var childProps = {
activeStep: this.props.step,
totalSteps: React.Children.count(this.props.children)
};
return (
<div {... this.props}
className={classNames('progression', this.props.className)}>
{React.Children.map(this.props.children, function (child, id) {
if (id === this.props.step) {
return React.cloneElement(child, childProps);
}
}, this)}
</div>
);
}
});
};
module.exports = Progression;

View file

@ -1,53 +1,62 @@
var React = require('react');
var IframeModal = require('../modal/iframe/modal.jsx');
const bindAll = require('lodash.bindall');
const PropTypes = require('prop-types');
const React = require('react');
const IframeModal = require('../modal/iframe/modal.jsx');
require('./registration.scss');
var Registration = React.createClass({
propTypes: {
isOpen: React.PropTypes.bool,
onRegistrationDone: React.PropTypes.func,
onRequestClose: React.PropTypes.func
},
onMessage: function (e) {
if (e.origin != window.location.origin) return;
if (e.source != this.registrationIframe.contentWindow) return;
if (e.data == 'registration-done') this.props.onRegistrationDone();
if (e.data == 'registration-relaunch') {
class Registration extends React.Component {
constructor (props) {
super(props);
bindAll(this, [
'handleMessage',
'toggleMessageListener'
]);
}
componentDidMount () {
if (this.props.isOpen) this.toggleMessageListener(true);
}
componentDidUpdate (prevProps) {
this.toggleMessageListener(this.props.isOpen && !prevProps.isOpen);
}
componentWillUnmount () {
this.toggleMessageListener(false);
}
handleMessage (e) {
if (e.origin !== window.location.origin) return;
if (e.source !== this.registrationIframe.contentWindow) return;
if (e.data === 'registration-done') this.props.onRegistrationDone();
if (e.data === 'registration-relaunch') {
this.registrationIframe.contentWindow.location.reload();
}
},
toggleMessageListener: function (present) {
if (present) {
window.addEventListener('message', this.onMessage);
} else {
window.removeEventListener('message', this.onMessage);
}
},
componentDidMount: function () {
if (this.props.isOpen) this.toggleMessageListener(true);
},
componentDidUpdate: function (prevProps) {
this.toggleMessageListener(this.props.isOpen && !prevProps.isOpen);
},
componentWillUnmount: function () {
this.toggleMessageListener(false);
},
render: function () {
toggleMessageListener (present) {
if (present) {
window.addEventListener('message', this.handleMessage);
} else {
window.removeEventListener('message', this.handleMessage);
}
}
render () {
return (
<IframeModal
isOpen={this.props.isOpen}
onRequestClose={this.props.onRequestClose}
className="mod-registration"
componentRef={
function (iframe) {
componentRef={iframe => { // eslint-disable-line react/jsx-no-bind
this.registrationIframe = iframe;
}.bind(this)
}
}}
isOpen={this.props.isOpen}
src="/accounts/standalone-registration/"
onRequestClose={this.props.onRequestClose}
/>
);
}
});
}
Registration.propTypes = {
isOpen: PropTypes.bool,
onRegistrationDone: PropTypes.func,
onRequestClose: PropTypes.func
};
module.exports = Registration;

File diff suppressed because it is too large Load diff

View file

@ -1,17 +1,18 @@
var classNames = require('classnames');
var React = require('react');
const classNames = require('classnames');
const PropTypes = require('prop-types');
const React = require('react');
require('./slide.scss');
var Slide = React.createClass({
displayName: 'Slide',
render: function () {
return (
<div className={classNames(['slide', this.props.className])}>
{this.props.children}
const Slide = props => (
<div className={classNames(['slide', props.className])}>
{props.children}
</div>
);
}
});
);
Slide.propTypes = {
children: PropTypes.node,
className: PropTypes.string
};
module.exports = Slide;

View file

@ -1,57 +1,47 @@
var classNames = require('classnames');
var FormattedRelative = require('react-intl').FormattedRelative;
var React = require('react');
const classNames = require('classnames');
const FormattedRelative = require('react-intl').FormattedRelative;
const PropTypes = require('prop-types');
const React = require('react');
var FlexRow = require('../flex-row/flex-row.jsx');
const FlexRow = require('../flex-row/flex-row.jsx');
require('./social-message.scss');
var SocialMessage = React.createClass({
type: 'SocialMessage',
propTypes: {
as: React.PropTypes.string,
datetime: React.PropTypes.string.isRequired,
iconSrc: React.PropTypes.string,
iconAlt: React.PropTypes.string,
imgClassName: React.PropTypes.string
},
getDefaultProps: function () {
return {
as: 'li'
};
},
render: function () {
var classes = classNames(
'social-message',
this.props.className
);
var imgClass = classNames(
'social-message-icon',
this.props.imgClassName
);
return (
<this.props.as className={classes}>
const SocialMessage = props => (
<props.as className={classNames('social-message', props.className)}>
<FlexRow className="mod-social-message">
<div className="social-message-content">
{typeof this.props.iconSrc !== 'undefined' ? [
{typeof props.iconSrc === 'undefined' ? [] : [
<img
alt={props.iconAlt}
className={classNames('social-message-icon', props.imgClassName)}
key="social-message-icon"
className={imgClass}
src={this.props.iconSrc}
alt={this.props.iconAlt}
src={props.iconSrc}
/>
] : []}
]}
<div>
{this.props.children}
{props.children}
</div>
</div>
<span className="social-message-date">
<FormattedRelative value={new Date(this.props.datetime)} />
<FormattedRelative value={new Date(props.datetime)} />
</span>
</FlexRow>
</this.props.as>
);
}
});
</props.as>
);
SocialMessage.propTypes = {
as: PropTypes.string, // eslint-disable-line react/no-unused-prop-types
children: PropTypes.node,
className: PropTypes.string,
datetime: PropTypes.string.isRequired,
iconAlt: PropTypes.string,
iconSrc: PropTypes.string,
imgClassName: PropTypes.string
};
SocialMessage.defaultProps = {
as: 'li'
};
module.exports = SocialMessage;

View file

@ -1,20 +1,18 @@
var range = require('lodash.range');
var React = require('react');
const range = require('lodash.range');
const React = require('react');
require('./spinner.scss');
var Spinner = React.createClass({
// Adapted from http://tobiasahlin.com/spinkit/
type: 'Spinner',
render: function () {
return (
// Adapted from http://tobiasahlin.com/spinkit/
const Spinner = () => (
<div className="spinner">
{range(1,13).map(function (id) {
return <div className={'circle' + id + ' circle'}></div>;
})}
{range(1, 13).map(id => (
<div
className={`circle${id} circle`}
key={`circle${id}`}
/>
))}
</div>
);
}
});
);
module.exports = Spinner;

View file

@ -1,28 +1,29 @@
var classNames = require('classnames');
var React = require('react');
const classNames = require('classnames');
const PropTypes = require('prop-types');
const React = require('react');
require('./stepnavigation.scss');
var StepNavigation = React.createClass({
type: 'Navigation',
render: function () {
return (
<ul className={classNames('step-navigation', this.props.className)}>
{Array.apply(null, Array(this.props.steps)).map(function (v, step) {
return (
<li key={step}
const StepNavigation = props => (
<ul className={classNames('step-navigation', props.className)}>
{Array.apply(null, Array(props.steps)).map((v, step) => (
<li
className={classNames({
active: step < this.props.active,
selected: step === this.props.active
active: step < props.active,
selected: step === props.active
})}
key={step}
>
<div className="indicator" />
</li>
);
}.bind(this))}
))}
</ul>
);
}
});
);
StepNavigation.propTypes = {
active: PropTypes.number,
className: PropTypes.string,
steps: PropTypes.number
};
module.exports = StepNavigation;

View file

@ -1,36 +1,38 @@
var classNames = require('classnames');
var React = require('react');
const classNames = require('classnames');
const PropTypes = require('prop-types');
const React = require('react');
require('./subnavigation.scss');
/**
/*
* Container for a custom, horizontal list of navigation elements
* that can be displayed within a view or component.
*/
var SubNavigation = React.createClass({
type: 'SubNavigation',
getDefaultProps: function () {
return {
align: 'middle'
};
},
render: function () {
var classes = classNames(
const SubNavigation = props => (
<div
className={classNames(
[
'sub-nav',
this.props.className
props.className
],
{
'sub-nav-align-left': this.props.align === 'left',
'sub-nav-align-right': this.props.align === 'right'
'sub-nav-align-left': props.align === 'left',
'sub-nav-align-right': props.align === 'right'
}
);
return (
<div className={classes}>
{this.props.children}
)}
>
{props.children}
</div>
);
}
});
);
SubNavigation.propTypes = {
align: PropTypes.string,
children: PropTypes.node,
className: PropTypes.string
};
SubNavigation.defaultProps = {
align: 'middle'
};
module.exports = SubNavigation;

View file

@ -1,28 +1,26 @@
var classNames = require('classnames');
var SubNavigation = require('../../components/subnavigation/subnavigation.jsx');
var React = require('react');
const classNames = require('classnames');
const PropTypes = require('prop-types');
const React = require('react');
const SubNavigation = require('../../components/subnavigation/subnavigation.jsx');
require('./tabs.scss');
/**
/*
* Container for a custom, horizontal list of navigation elements
* that can be displayed within a view or component.
*/
var Tabs = React.createClass({
type: 'Tabs',
render: function () {
var classes = classNames(
'tabs',
this.props.className
);
return (
<div className='tab-background'>
<SubNavigation className={classes}>
{this.props.children}
const Tabs = props => (
<div className="tab-background">
<SubNavigation className={classNames('tabs', props.className)}>
{props.children}
</SubNavigation>
</div>
);
}
});
);
Tabs.propTypes = {
children: PropTypes.node,
className: PropTypes.string
};
module.exports = Tabs;

View file

@ -1,19 +1,85 @@
var classNames = require('classnames');
var connect = require('react-redux').connect;
var React = require('react');
const classNames = require('classnames');
const connect = require('react-redux').connect;
const PropTypes = require('prop-types');
const React = require('react');
var sessionActions = require('../../redux/session.js');
const sessionActions = require('../../redux/session.js');
var TitleBanner = require('../title-banner/title-banner.jsx');
var Button = require('../forms/button.jsx');
var FlexRow = require('../flex-row/flex-row.jsx');
const TitleBanner = require('../title-banner/title-banner.jsx');
const Button = require('../forms/button.jsx');
const FlexRow = require('../flex-row/flex-row.jsx');
require('./teacher-banner.scss');
var TeacherBanner = React.createClass({
type: 'TeacherBanner',
getDefaultProps: function () {
return {
const TeacherBanner = props => (
<TitleBanner className={classNames('teacher-banner', props.className)}>
<FlexRow className="inner">
<div className="welcome">
{props.sessionStatus === sessionActions.Status.FETCHED ? (
props.user ? [
<h3 key="greeting">
{props.messages['teacherbanner.greeting']},{' '}
{props.user.username}
</h3>,
<p
className="title-banner-p"
key="subgreeting"
>
{props.messages['teacherbanner.subgreeting']}
</p>
] : []
) : []}
</div>
<FlexRow className="quick-links">
{props.sessionStatus === sessionActions.Status.FETCHED ? (
props.user ? [
<a
href="/educators/classes"
key="classes-button"
>
<Button>
{props.messages['teacherbanner.classesButton']}
</Button>
</a>,
<a
href="/info/educators"
key="resources-button"
>
<Button>
{props.messages['teacherbanner.resourcesButton']}
</Button>
</a>,
<a
href="/educators/faq"
key="faq-button"
>
<Button>
{props.messages['teacherbanner.faqButton']}
</Button>
</a>
] : []
) : []}
</FlexRow>
</FlexRow>
</TitleBanner>
);
TeacherBanner.propTypes = {
className: PropTypes.string,
messages: PropTypes.shape({
'teacherbanner.greeting': PropTypes.string,
'teacherbanner.subgreeting': PropTypes.string,
'teacherbanner.classesButton': PropTypes.string,
'teacherbanner.resourcesButton': PropTypes.string,
'teacherbanner.faqButton': PropTypes.string
}),
sessionStatus: PropTypes.string,
user: PropTypes.shape({
username: PropTypes.string
})
};
TeacherBanner.defaultProps = {
messages: {
'teacherbanner.greeting': 'Hi',
'teacherbanner.subgreeting': 'Teacher Account',
@ -21,66 +87,14 @@ var TeacherBanner = React.createClass({
'teacherbanner.resourcesButton': 'Educator Resources',
'teacherbanner.faqButton': 'Teacher Account FAQ'
},
session: {}
};
},
render: function () {
var classes = classNames(
'teacher-banner',
this.props.className
);
return (
<TitleBanner className={classes}>
<FlexRow className="inner">
<div className="welcome">
{this.props.session.status === sessionActions.Status.FETCHED ? (
this.props.session.session.user ? [
<h3 key="greeting">
{this.props.messages['teacherbanner.greeting']},{' '}
{this.props.session.session.user.username}
</h3>,
<p
key="subgreeting"
className="title-banner-p"
>
{this.props.messages['teacherbanner.subgreeting']}
</p>
] : []
): []}
</div>
<FlexRow className="quick-links">
{this.props.session.status === sessionActions.Status.FETCHED ? (
this.props.session.session.user ? [
<a href="/educators/classes" key="classes-button">
<Button>
{this.props.messages['teacherbanner.classesButton']}
</Button>
</a>,
<a href="/info/educators" key="resources-button">
<Button>
{this.props.messages['teacherbanner.resourcesButton']}
</Button>
</a>,
<a href="/educators/faq" key="faq-button">
<Button>
{this.props.messages['teacherbanner.faqButton']}
</Button>
</a>
] : []
): []}
</FlexRow>
</FlexRow>
</TitleBanner>
);
}
});
var mapStateToProps = function (state) {
return {
session: state.session
};
user: {}
};
var ConnectedTeacherBanner = connect(mapStateToProps)(TeacherBanner);
const mapStateToProps = state => ({
sessionStatus: state.session.status,
user: state.session.session.user
});
const ConnectedTeacherBanner = connect(mapStateToProps)(TeacherBanner);
module.exports = ConnectedTeacherBanner;

View file

@ -1,175 +1,122 @@
var classNames = require('classnames');
var React = require('react');
const classNames = require('classnames');
const PropTypes = require('prop-types');
const React = require('react');
require('./thumbnail.scss');
var Thumbnail = React.createClass({
type: 'Thumbnail',
propTypes: {
src: React.PropTypes.string
},
getInitialState: function () {
return {
srcFallback: false,
avatarFallback: false
};
},
getDefaultProps: function () {
return {
href: '#',
title: 'Project',
src: '',
srcDefault: 'https://uploads.scratch.mit.edu/projects/thumbnails/default.png',
avatar: '',
avatarDefault: 'https://uploads.scratch.mit.edu/users/avatars/default.png',
type: 'project',
showLoves: false,
showFavorites: false,
showRemixes: false,
showViews: false,
showAvatar: false,
linkTitle: true,
alt: ''
};
},
handleSrcError: function () {
this.setState({srcFallback: true});
},
handleAvatarError: function () {
this.setState({avatarFallback: true});
},
render: function () {
var classes = classNames(
'thumbnail',
this.props.type,
this.props.className
);
var extra = [];
var info = [];
const Thumbnail = props => {
const extra = [];
const info = [];
if (this.props.loves && this.props.showLoves) {
if (props.loves && props.showLoves) {
extra.push(
<div
key="loves"
className="thumbnail-loves"
title={this.props.loves + ' loves'}>
{this.props.loves}
key="loves"
title={`${props.loves} loves`}
>
{props.loves}
</div>
);
}
if (this.props.favorites && this.props.showFavorites) {
if (props.favorites && props.showFavorites) {
extra.push(
<div
key="favorites"
className="thumbnail-favorites"
title={this.favorites + ' favorites'}>
{this.props.favorites}
key="favorites"
title={`${props.favorites} favorites`}
>
{props.favorites}
</div>
);
}
if (this.props.remixes && this.props.showRemixes) {
if (props.remixes && props.showRemixes) {
extra.push(
<div
key="remixes"
className="thumbnail-remixes"
title={this.props.remixes + ' remixes'}
key="remixes"
title={`${props.remixes} remixes`}
>
{this.props.remixes}
{props.remixes}
</div>
);
}
if (this.props.views && this.props.showViews) {
if (props.views && props.showViews) {
extra.push(
<div
key="views"
className="thumbnail-views"
title={this.props.views + ' views'}
key="views"
title={`${props.views} views`}
>
{this.props.views}
{props.views}
</div>
);
}
var imgElement, titleElement, avatarElement;
if (this.props.linkTitle) {
if (this.state.srcFallback) {
let imgElement;
let titleElement;
let avatarElement;
if (props.linkTitle) {
imgElement = (
<a
className="thumbnail-image"
href={this.props.href}
href={props.href}
key="imgElement"
>
<img
alt={this.props.alt}
src={this.props.srcDefault}
alt={props.alt}
src={props.src}
/>
</a>
);
} else {
imgElement = (
<a
className="thumbnail-image"
href={this.props.href}
key="imgElement"
>
<img
alt={this.props.alt}
src={this.props.src}
onError={this.handleSrcError}
/>
</a>
);
}
titleElement = (
<a href={this.props.href} key="titleElement">
{this.props.title}
<a
href={props.href}
key="titleElement"
>
{props.title}
</a>
);
} else {
imgElement = <img src={this.props.src} />;
titleElement = this.props.title;
imgElement = <img src={props.src} />;
titleElement = props.title;
}
info.push(titleElement);
if (this.props.creator) {
if (props.creator) {
info.push(
<div key="creator" className="thumbnail-creator">
<a href={'/users/' + this.props.creator + '/'}>{this.props.creator}</a>
<div
className="thumbnail-creator"
key="creator"
>
<a href={`/users/${props.creator}/`}>{props.creator}</a>
</div>
);
}
if (this.props.avatar && this.props.showAvatar) {
if (this.state.avatarFallback) {
if (props.avatar && props.showAvatar) {
avatarElement = (
<a
className="creator-image"
href={'/users/' + this.props.creator + '/'}
href={`/users/${props.creator}/`}
>
<img
alt={this.props.creator}
src={this.props.avatarDefault}
alt={props.creator}
src={props.avatar}
/>
</a>
);
} else {
avatarElement = (
<a
className="creator-image"
href={'/users/' + this.props.creator + '/'}
>
<img
alt={this.props.creator}
src={this.props.avatar}
onError={this.handleAvatarError}
/>
</a>
);
}
}
return (
<div className={classes} >
<div
className={classNames(
'thumbnail',
props.type,
props.className
)}
>
{imgElement}
<div className="thumbnail-info">
{avatarElement}
@ -180,7 +127,42 @@ var Thumbnail = React.createClass({
{extra}
</div>
);
}
});
};
Thumbnail.propTypes = {
alt: PropTypes.string,
avatar: PropTypes.string,
className: PropTypes.string,
creator: PropTypes.string,
favorites: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
href: PropTypes.string,
linkTitle: PropTypes.bool,
loves: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
remixes: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
showAvatar: PropTypes.bool,
showFavorites: PropTypes.bool,
showLoves: PropTypes.bool,
showRemixes: PropTypes.bool,
showViews: PropTypes.bool,
src: PropTypes.string,
title: PropTypes.string,
type: PropTypes.string,
views: PropTypes.oneOfType([PropTypes.number, PropTypes.string])
};
Thumbnail.defaultProps = {
alt: '',
avatar: '',
href: '#',
linkTitle: true,
showAvatar: false,
showFavorites: false,
showLoves: false,
showRemixes: false,
showViews: false,
src: '',
title: 'Project',
type: 'project'
};
module.exports = Thumbnail;

View file

@ -1,21 +1,18 @@
var classNames = require('classnames');
var React = require('react');
const classNames = require('classnames');
const PropTypes = require('prop-types');
const React = require('react');
require('./title-banner.scss');
var TitleBanner = React.createClass({
type: 'TitleBanner',
render: function () {
var classes = classNames(
'title-banner',
this.props.className
);
return (
<div className={classes}>
{this.props.children}
const TitleBanner = props => (
<div className={classNames('title-banner', props.className)}>
{props.children}
</div>
);
}
});
);
TitleBanner.propTypes = {
children: PropTypes.node,
className: PropTypes.string
};
module.exports = TitleBanner;

View file

@ -1,33 +1,39 @@
var classNames = require('classnames');
var React = require('react');
const classNames = require('classnames');
const PropTypes = require('prop-types');
const React = require('react');
require('./tooltip.scss');
var Tooltip = React.createClass({
type: 'Tooltip',
getDefaultProps: function () {
return {
title: '',
tipContent: ''
};
},
render: function () {
var classes = classNames(
const Tooltip = props => (
<span
className={classNames(
'tooltip',
this.props.className,
{overmax: (this.props.currentCharacters > this.props.maxCharacters)}
);
return (
<span className={classes}>
props.className,
{overmax: (props.currentCharacters > props.maxCharacters)}
)}
>
<span className="tip">
<img src="/svgs/tooltip/info.svg" alt="info icon" />
<img
alt="info icon"
src="/svgs/tooltip/info.svg"
/>
</span>
<span className="expand">
{this.props.tipContent}
{props.tipContent}
</span>
</span>
);
}
});
);
Tooltip.propTypes = {
className: PropTypes.string,
currentCharacters: PropTypes.number,
maxCharacters: PropTypes.number,
tipContent: PropTypes.node
};
Tooltip.defaultProps = {
title: '',
tipContent: ''
};
module.exports = Tooltip;

View file

@ -1,30 +1,21 @@
var classNames = require('classnames');
var React = require('react');
var FormattedMessage = require('react-intl').FormattedMessage;
const classNames = require('classnames');
const FormattedMessage = require('react-intl').FormattedMessage;
const PropTypes = require('prop-types');
const React = require('react');
require('../forms/button.scss');
require('./ttt-tile.scss');
var TTTTile = React.createClass({
type: 'TTTTile',
propTypes: {
title: React.PropTypes.string.isRequired,
description: React.PropTypes.string,
thumbUrl: React.PropTypes.string.isRequired,
tutorialLoc: React.PropTypes.string.isRequired,
onGuideClick: React.PropTypes.func
},
render: function () {
var classes = classNames(
'ttt-tile',
this.props.className
);
return (
<div className={classes} >
<a href={this.props.tutorialLoc}>
const TTTTile = props => (
<div className={classNames('ttt-tile', props.className)}>
<a href={props.tutorialLoc}>
<div className="ttt-tile-tutorial">
<div className="ttt-tile-image">
<img className="ttt-tile-image-img" src={this.props.thumbUrl} alt="" />
<img
alt=""
className="ttt-tile-image-img"
src={props.thumbUrl}
/>
<div className="ttt-tile-image-try">
<div className="button mod-ttt-try-button">
<FormattedMessage id="tile.tryIt" />
@ -34,25 +25,44 @@ var TTTTile = React.createClass({
<div className="ttt-tile-info">
<div className="ttt-tile-tag">
<FormattedMessage id='ttt.tutorial' defaultMessage='Tutorial'/>
<FormattedMessage
defaultMessage="Tutorial"
id="ttt.tutorial"
/>
</div>
<h4 className="ttt-tile-title">{this.props.title}</h4>
<h4 className="ttt-tile-title">{props.title}</h4>
<p className="ttt-tile-description">
{this.props.description}
{props.description}
</p>
</div>
</div>
</a>
{this.props.onGuideClick && (
<div className="ttt-tile-guides" onClick={this.props.onGuideClick}>
<FormattedMessage id='tile.guides' defaultMessage='See Cards and Guides'/>
<img className="ttt-tile-open-modal" src="/svgs/modal/open-blue.svg" />
{props.onGuideClick && (
<div
className="ttt-tile-guides"
onClick={props.onGuideClick}
>
<FormattedMessage
defaultMessage="See Cards and Guides"
id="tile.guides"
/>
<img
className="ttt-tile-open-modal"
src="/svgs/modal/open-blue.svg"
/>
</div>
)}
</div>
);
}
});
);
TTTTile.propTypes = {
className: PropTypes.string,
description: PropTypes.string,
onGuideClick: PropTypes.func,
thumbUrl: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
tutorialLoc: PropTypes.string.isRequired
};
module.exports = TTTTile;

View file

@ -1,69 +1,81 @@
var React = require('react');
const PropTypes = require('prop-types');
const React = require('react');
var Box = require('../box/box.jsx');
const Box = require('../box/box.jsx');
require('./welcome.scss');
var Welcome = React.createClass({
type: 'Welcome',
propTypes: {
onDismiss: React.PropTypes.func
},
getDefaultProps: function () {
return {
const Welcome = props => (
<Box
className="welcome"
moreHref="#"
moreProps={{
className: 'close',
title: 'Dismiss',
onClick: props.onDismiss
}}
moreTitle="x"
title={props.messages['welcome.welcomeToScratch']}
>
<div className="welcome-col blue">
<h4>
<a href="/projects/editor/?tip_bar=getStarted">
{props.messages['welcome.learn']}
</a>
</h4>
<a href="/projects/editor/?tip_bar=getStarted">
<img
alt="Get Started"
src="/images/welcome-learn.png"
/>
</a>
</div>
<div className="welcome-col green">
<h4>
<a href="/starter_projects/">
{props.messages['welcome.tryOut']}
</a>
</h4>
<a href="/starter_projects/">
<img
alt="Starter Projects"
src="/images/welcome-try.png"
/>
</a>
</div>
<div className="welcome-col pink">
<h4>
<a href="/studios/146521/">
{props.messages['welcome.connect']}
</a>
</h4>
<a href="/studios/146521/">
<img
alt="Connect"
src="/images/welcome-connect.png"
/>
</a>
</div>
</Box>
);
Welcome.propTypes = {
messages: PropTypes.shape({
'welcome.welcomeToScratch': PropTypes.string,
'welcome.learn': PropTypes.string,
'welcome.tryOut': PropTypes.string,
'welcome.connect': PropTypes.string
}),
onDismiss: PropTypes.func
};
Welcome.defaultProps = {
messages: {
'welcome.welcomeToScratch': 'Welcome to Scratch!',
'welcome.learn': 'Learn how to make a project in Scratch',
'welcome.tryOut': 'Try out starter projects',
'welcome.connect': 'Connect with other Scratchers'
}
};
},
render: function () {
return (
<Box title={this.props.messages['welcome.welcomeToScratch']}
className="welcome"
moreTitle="x"
moreHref="#"
moreProps={{
className: 'close',
title: 'Dismiss',
onClick: this.props.onDismiss
}}>
<div className="welcome-col blue">
<h4>
<a href="/projects/editor/?tip_bar=getStarted">
{this.props.messages['welcome.learn']}
</a>
</h4>
<a href="/projects/editor/?tip_bar=getStarted">
<img src="/images/welcome-learn.png" alt="Get Started" />
</a>
</div>
<div className="welcome-col green">
<h4>
<a href="/starter_projects/">
{this.props.messages['welcome.tryOut']}
</a>
</h4>
<a href="/starter_projects/">
<img src="/images/welcome-try.png" alt="Starter Projects" />
</a>
</div>
<div className="welcome-col pink">
<h4>
<a href="/studios/146521/">
{this.props.messages['welcome.connect']}
</a>
</h4>
<a href="/studios/146521/">
<img src="/images/welcome-connect.png" alt="Connect" />
</a>
</div>
</Box>
);
}
});
};
module.exports = Welcome;

View file

@ -1,12 +1,12 @@
var jar = require('./lib/jar');
var Raven = require('raven-js');
const jar = require('./lib/jar');
const Raven = require('raven-js');
/**
* -----------------------------------------------------------------------------
* Error handling
* -----------------------------------------------------------------------------
*/
(function () {
(() => {
if (process.env.SENTRY_DSN !== '') {
Raven.config(process.env.SENTRY_DSN).install();
}
@ -17,22 +17,22 @@ var Raven = require('raven-js');
* L10N
* -----------------------------------------------------------------------------
*/
(function () {
/**
(() => {
/*
* Bind locale code from cookie if available. Uses navigator language API as a fallback.
*
* @return {string}
*/
function updateLocale () {
var obj = jar.get('scratchlanguage');
const updateLocale = () => {
let obj = jar.get('scratchlanguage');
if (typeof obj === 'undefined') {
obj = window.navigator.userLanguage || window.navigator.language;
if (['pt','pt-pt','PT','PT-PT'].indexOf(obj) !== -1) {
if (['pt', 'pt-pt', 'PT', 'PT-PT'].indexOf(obj) !== -1) {
obj = 'pt-br'; // default Portuguese users to Brazilian Portuguese due to our user base. Added in 2.2.5.
}
}
return obj;
}
};
window._locale = updateLocale();
})();

View file

@ -47,6 +47,9 @@
"general.myClass": "My Class",
"general.myClasses": "My Classes",
"general.myStuff": "My Stuff",
"general.noDeletionTitle": "Your Account Will Not Be Deleted",
"general.noDeletionDescription": "Your account was scheduled for deletion but you logged in. Your account has been reactivated. If you didnt request for your account to be deleted, you should {resetLink} to make sure your account is secure.",
"general.noDeletionLink": "change your password",
"general.notRequired": "Not Required",
"general.other": "Other",
"general.offlineEditor": "Offline Editor",

View file

@ -1,9 +1,9 @@
var defaults = require('lodash.defaults');
var xhr = require('xhr');
const defaults = require('lodash.defaults');
const xhr = require('xhr');
var jar = require('./jar');
var log = require('./log');
var urlParams = require('./url-params');
const jar = require('./jar');
const log = require('./log');
const urlParams = require('./url-params');
/**
* Helper method that constructs requests to the scratch api.
@ -12,9 +12,11 @@ var urlParams = require('./url-params');
* CSRF forgeries (see: https://www.squarefree.com/securitytips/web-developers.html#CSRF)
*
* It also takes in other arguments specified in the xhr library spec.
*
* @param {object} opts optional xhr args (see above)
* @param {Function} callback [description]
*/
module.exports = function (opts, callback) {
module.exports = (opts, callback) => {
defaults(opts, {
host: process.env.API_HOST,
headers: {},
@ -40,26 +42,29 @@ module.exports = function (opts, callback) {
opts.headers['Content-Type'] = 'application/x-www-form-urlencoded';
}
var apiRequest = function (opts) {
if (opts.host !== '') {
const apiRequest = options => {
if (options.host !== '') {
if ('withCredentials' in new XMLHttpRequest()) {
opts.useXDR = false;
options.useXDR = false;
} else {
// For IE < 10, we must use XDR for cross-domain requests. XDR does not support
// custom headers.
opts.useXDR = true;
delete opts.headers;
if (opts.authentication) {
var authenticationParams = ['x-token=' + opts.authentication];
var parts = opts.uri.split('?');
var qs = (parts[1] || '').split('&').concat(authenticationParams).join('&');
opts.uri = parts[0] + '?' + qs;
options.useXDR = true;
delete options.headers;
if (options.authentication) {
const authenticationParams = [`x-token=${options.authentication}`];
const parts = options.uri.split('?');
const qs = (parts[1] || '')
.split('&')
.concat(authenticationParams)
.join('&');
options.uri = `${parts[0]}?${qs}`;
}
}
}
xhr(opts, function (err, res, body) {
xhr(options, (err, res, body) => {
if (err) log.error(err);
if (opts.responseType === 'json' && typeof body === 'string') {
if (options.responseType === 'json' && typeof body === 'string') {
// IE doesn't parse responses as JSON without the json attribute,
// even with responseType: 'json'.
// See https://github.com/Raynos/xhr/issues/123
@ -73,25 +78,25 @@ module.exports = function (opts, callback) {
// [{success: true, redirect: "/location/to/redirect"}]
try {
if ('redirect' in body[0]) window.location = body[0].redirect;
} catch (err) {
} catch (e) {
// do nothing
}
callback(err, body, res);
});
}.bind(this);
};
if (typeof jar.get('scratchlanguage') !== 'undefined') {
opts.headers['Accept-Language'] = jar.get('scratchlanguage') + ', en;q=0.8';
opts.headers['Accept-Language'] = `${jar.get('scratchlanguage')}, en;q=0.8`;
}
if (opts.authentication) {
opts.headers['X-Token'] = opts.authentication;
}
if (opts.useCsrf) {
jar.use('scratchcsrftoken', '/csrf_token/', function (err, csrftoken) {
jar.use('scratchcsrftoken', '/csrf_token/', (err, csrftoken) => {
if (err) return log.error('Error while retrieving CSRF token', err);
opts.headers['X-CSRFToken'] = csrftoken;
apiRequest(opts);
}.bind(this));
});
} else {
apiRequest(opts);
}

View file

@ -1,22 +1,28 @@
module.exports = {};
var countries = module.exports.data = require('iso-3166-2').data;
const countries = module.exports.data = require('iso-3166-2').data;
module.exports.countryOptions = Object.keys(countries).map(function (code) {
return {value: code.toLowerCase(), label: countries[code].name};
}).sort(function (a, b) {
return a.label < b.label ? -1 : 1;
});
module.exports.countryOptions = Object.keys(countries).map(code => ({
value: code.toLowerCase(),
label: countries[code].name
}))
.sort((a, b) => {
if (a.label < b.label) {
return -1;
}
return 1;
});
module.exports.subdivisionOptions =
Object.keys(countries).reduce(function (subByCountry, code) {
subByCountry[code.toLowerCase()] = Object.keys(countries[code].sub).map(function (subCode) {
return {
module.exports.subdivisionOptions = Object.keys(countries).reduce((subByCountry, code) => {
subByCountry[code.toLowerCase()] = Object.keys(countries[code].sub).map(subCode => ({
value: subCode.toLowerCase(),
label: countries[code].sub[subCode].name,
type: countries[code].sub[subCode].type
};
}).sort(function (a, b) {
return a.label < b.label ? -1 : 1;
}))
.sort((a, b) => {
if (a.label < b.label) {
return -1;
}
return 1;
});
return subByCountry;
}, {});

View file

@ -1,34 +0,0 @@
/**
* Takes an ISO time and returns a string representing how long ago the date
* represents. For example, "2015-01-01T00:00:00" becomes "1 minute ago".
*
* Based on "JavaScript Pretty Date"
* Copyright (c) 2011 John Resig (ejohn.org)
* Licensed under the MIT and GPL licenses.
*/
var Format = {
date: function (stamp) {
stamp = (stamp || '').replace(/-/g,'/').replace(/[TZ]/g,' ');
var date = new Date(stamp);
var diff = (((new Date()).getTime() - date.getTime()) / 1000);
var day_diff = Math.floor(diff / 86400);
if (isNaN(day_diff) || day_diff < 0 || day_diff >= 31) {
return 'A while ago';
}
return day_diff == 0 && (
diff < 60 && 'Just now' ||
diff < 120 && '1 minute ago' ||
diff < 3600 && Math.floor( diff / 60 ) + ' minutes ago' ||
diff < 7200 && '1 hour ago' ||
diff < 86400 && Math.floor( diff / 3600 ) + ' hours ago') ||
day_diff == 1 && 'Yesterday' ||
day_diff < 7 && day_diff + ' days ago' ||
day_diff < 31 && Math.ceil( day_diff / 7 ) + ' weeks ago';
}
};
module.exports = Format;

View file

@ -1,9 +1,10 @@
/* This file contains breakpoints from _frameless.scss, to be used in MediaQuery elements.
* All units are in px, as per _frameless.scss and the MediaQuery default arguments.
*/
module.exports = {
const frameless = {
desktop: 942,
tablet: 640,
mobile: 480
};
module.exports = frameless;

View file

@ -1,20 +1,20 @@
var requireAll = require('./require-all');
var ReactIntl = require('react-intl');
const requireAll = require('./require-all');
const ReactIntl = require('react-intl');
var allLocaleData = requireAll(require.context('react-intl/locale-data', true, /^\.\/.*\.js$/));
var customLocaleData = require('../../custom-locales.json');
const allLocaleData = requireAll(require.context('react-intl/locale-data', true, /^\.\/.*\.js$/));
const customLocaleData = require('../../custom-locales.json');
/**
* Add all locales
*/
for (var locale in allLocaleData) {
for (const locale in allLocaleData) {
ReactIntl.addLocaleData(allLocaleData[locale]);
}
/**
* Add custom locales to react-intl if it doesn't have them.
*/
for (var customLocale in customLocaleData) {
for (const customLocale in customLocaleData) {
ReactIntl.addLocaleData(customLocaleData[customLocale]);
}

View file

@ -1,7 +1,7 @@
var cookie = require('cookie');
var defaults = require('lodash.defaults');
var xhr = require('xhr');
var pako = require('pako');
const cookie = require('cookie');
const defaults = require('lodash.defaults');
const xhr = require('xhr');
const pako = require('pako');
/**
* Module that handles coookie interactions.
@ -11,16 +11,16 @@ var pako = require('pako');
* set(name, value) synchronously sets the cookie
* use(name, uri, callback) can by sync or async, gets cookie from the uri if not there.
*/
var Jar = {
unsign: function (value, callback) {
const Jar = {
unsign: (value, callback) => {
// Return the usable content portion of a signed, compressed cookie generated by
// Django's signing module
// https://github.com/django/django/blob/stable/1.8.x/django/core/signing.py
if (typeof value === 'undefined') return callback(null, value);
try {
var b64Data = value.split(':')[0];
var decompress = false;
let b64Data = value.split(':')[0];
let decompress = false;
if (b64Data[0] === '.') {
decompress = true;
b64Data = b64Data.substring(1);
@ -29,24 +29,29 @@ var Jar = {
// Django makes its base64 strings url safe by replacing + and / with - and _ respectively
// using base64.urlsafe_b64encode
// https://docs.python.org/2/library/base64.html#base64.b64encode
b64Data = b64Data.replace(/[-_]/g, function (c) {return {'-':'+', '_':'/'}[c]; });
var strData = atob(b64Data);
b64Data = b64Data.replace(
/[-_]/g,
c => ({
'-': '+',
'_': '/'
}[c])
);
let strData = atob(b64Data);
if (decompress) {
var charData = strData.split('').map(function (c) { return c.charCodeAt(0); });
var binData = new Uint8Array(charData);
var data = pako.inflate(binData);
const charData = strData.split('').map(c => (c.charCodeAt(0)));
const binData = new Uint8Array(charData);
const data = pako.inflate(binData);
strData = String.fromCharCode.apply(null, new Uint16Array(data));
}
return callback(null, strData);
} catch (e) {
return callback(e);
}
},
get: function (name, callback) {
get: (name, callback) => {
// Get cookie by name
var obj = cookie.parse(document.cookie) || {};
const obj = cookie.parse(document.cookie) || {};
// Handle optional callback
if (typeof callback === 'function') {
@ -56,44 +61,44 @@ var Jar = {
return obj[name];
},
use: function (name, uri, callback) {
use: (name, uri, callback) => {
// Attempt to get cookie
Jar.get(name, function (err, obj) {
Jar.get(name, (err, obj) => {
if (typeof obj !== 'undefined') return callback(null, obj);
// Make XHR request to cookie setter uri
xhr({
uri: uri
}, function (err) {
if (err) return callback(err);
}, e => {
if (e) return callback(e);
Jar.get(name, callback);
});
});
},
set: function (name, value, opts) {
set: (name, value, opts) => {
opts = opts || {};
defaults(opts, {
expires: new Date(new Date().setYear(new Date().getFullYear() + 1))
});
opts.path = '/';
var obj = cookie.serialize(name, value, opts);
const obj = cookie.serialize(name, value, opts);
document.cookie = obj;
},
getUnsignedValue: function (cookieName, signedValue, callback) {
getUnsignedValue: (cookieName, signedValue, callback) => {
// Get a value from a signed object
Jar.get(cookieName, function (err, value) {
Jar.get(cookieName, (err, value) => {
if (err) return callback(err);
if (typeof value === 'undefined') return callback(null, value);
Jar.unsign(value, function (err, contents) {
if (err) return callback(err);
Jar.unsign(value, (e, contents) => {
if (e) return callback(e);
try {
var data = JSON.parse(contents);
} catch (err) {
return callback(err);
}
const data = JSON.parse(contents);
return callback(null, data[signedValue]);
} catch (error) {
return callback(error);
}
});
});
}

View file

@ -1,4 +1,4 @@
var minilog = require('minilog');
const minilog = require('minilog');
minilog.enable();
module.exports = minilog('www');

View file

@ -1,21 +1,27 @@
var redux = require('redux');
var thunk = require('redux-thunk').default;
const redux = require('redux');
const thunk = require('redux-thunk').default;
// JSX syntax transforms to React.createElement
var React = require('react'); // eslint-disable-line
var ReactDOM = require('react-dom');
var StoreProvider = require('react-redux').Provider;
const React = require('react'); // eslint-disable-line
const ReactDOM = require('react-dom');
const StoreProvider = require('react-redux').Provider;
var IntlProvider = require('./intl.jsx').IntlProvider;
var permissionsActions = require('../redux/permissions.js');
var sessionActions = require('../redux/session.js');
var reducer = require('../redux/reducer.js');
const IntlProvider = require('./intl.jsx').IntlProvider;
const permissionsActions = require('../redux/permissions.js');
const sessionActions = require('../redux/session.js');
const reducer = require('../redux/reducer.js');
require('../main.scss');
var render = function (jsx, element, reducers) {
/**
* Function to render views into a full page
* @param {object} jsx jsx component of the view
* @param {object} element html element to render to on the template
* @param {array} reducers list of view-specific reducers
*/
const render = (jsx, element, reducers) => {
// Get locale and messages from global namespace (see "init.js")
var locale = window._locale || 'en';
var messages = {};
let locale = window._locale || 'en';
let messages = {};
if (typeof window._messages !== 'undefined') {
if (typeof window._messages[locale] === 'undefined') {
// Fall back on the split
@ -28,8 +34,8 @@ var render = function (jsx, element, reducers) {
messages = window._messages[locale];
}
var allReducers = reducer(reducers);
var store = redux.createStore(
const allReducers = reducer(reducers);
const store = redux.createStore(
allReducers,
redux.applyMiddleware(thunk)
);
@ -37,7 +43,10 @@ var render = function (jsx, element, reducers) {
// Render view component
ReactDOM.render(
<StoreProvider store={store}>
<IntlProvider locale={locale} messages={messages}>
<IntlProvider
locale={locale}
messages={messages}
>
{jsx}
</IntlProvider>
</StoreProvider>,

View file

@ -1,5 +1,5 @@
var requireAll = function (requireContext) {
return requireContext.keys().map(requireContext);
};
const requireAll = requireContext => (
requireContext.keys().map(requireContext)
);
module.exports = requireAll;

View file

@ -1,16 +1,12 @@
/*
* Function that shuffles an array using a Fisher-Yates shuffle.
*/
module.exports.shuffle = function (arr) {
var i, j = 0;
var temp = null;
module.exports.shuffle = arr => {
let i = 0;
let j = 0;
let temp = null;
if (arr) {
var tempArray = arr.slice(0);
} else {
return arr;
}
const tempArray = arr.slice(0);
for (i = arr.length - 1; i > 0; i -= 1) {
j = Math.floor(Math.random() * (i + 1));
temp = tempArray[i];
@ -18,4 +14,6 @@ module.exports.shuffle = function (arr) {
tempArray[j] = temp;
}
return tempArray;
}
return arr;
};

View file

@ -2,21 +2,18 @@
* urlParams({a: 1, b: 2, c: 3})
* // a=1&b=2&c=3
*/
module.exports = function urlParams (values) {
return Object
.keys(values)
.map(function (key) {
var value = typeof values[key] === 'undefined' ? '' : values[key];
function encodeKeyValuePair (value) {
return [key, value]
.map(encodeURIComponent)
.join('=');
}
const params = values => (
Object.keys(values).map(key => {
const value = typeof values[key] === 'undefined' ? '' : values[key];
const encodeKeyValuePair = val => (
[key, val].map(encodeURIComponent).join('=')
);
if (Array.isArray(value)) {
return value.map(encodeKeyValuePair).join('&');
} else {
return encodeKeyValuePair(value);
}
return encodeKeyValuePair(value);
})
.join('&');
};
.join('&')
);
module.exports = params;

View file

@ -1,13 +1,13 @@
var keyMirror = require('keymirror');
var api = require('../lib/api');
const keyMirror = require('keymirror');
const api = require('../lib/api');
var Types = keyMirror({
const Types = keyMirror({
SET_DETAILS: null,
SET_DETAILS_FETCHING: null,
SET_DETAILS_ERROR: null
});
module.exports.detailsReducer = function (state, action) {
module.exports.detailsReducer = (state, action) => {
if (typeof state === 'undefined') {
state = {};
}
@ -23,49 +23,35 @@ module.exports.detailsReducer = function (state, action) {
}
};
module.exports.setDetailsError = function (error) {
return {
module.exports.setDetailsError = error => ({
type: Types.SET_DETAILS_ERROR,
error: error
};
};
});
module.exports.setDetails = function (details) {
return {
module.exports.setDetails = details => ({
type: Types.SET_DETAILS,
details: details
};
};
});
module.exports.setDetailsFetching = function () {
return {
module.exports.setDetailsFetching = () => ({
type: Types.SET_DETAILS_FETCHING,
fetching: true
};
};
});
module.exports.startGetDetails = function (id) {
return function (dispatch) {
dispatch(module.exports.setDetailsFetching());
dispatch(module.exports.getDetails(id));
};
};
module.exports.getDetails = function (id) {
return function (dispatch) {
module.exports.getDetails = id => (dispatch => {
api({
uri: '/conference/' + id + '/details'
}, function (err, body) {
uri: `/conference/${id}/details`
}, (err, body) => {
if (err) {
dispatch(module.exports.setDetailsError(err));
return;
}
if (typeof body !== 'undefined') {
var columns = body.columns;
const columns = body.columns;
if (body.rows) {
var details = body.rows[0];
var detailsObject = details.reduce(function (prev, cur, index) {
const details = body.rows[0];
const detailsObject = details.reduce((prev, cur, index) => {
prev[columns[index]] = cur;
return prev;
}, {});
@ -74,10 +60,13 @@ module.exports.getDetails = function (id) {
dispatch(module.exports.setDetailsError('Not Found'));
}
return;
} else {
}
dispatch(module.exports.setDetailsError('An unexpected error occurred'));
return;
}
});
};
};
});
module.exports.startGetDetails = id => (dispatch => {
dispatch(module.exports.setDetailsFetching());
dispatch(module.exports.getDetails(id));
});

View file

@ -1,13 +1,13 @@
var keyMirror = require('keymirror');
var api = require('../lib/api');
const keyMirror = require('keymirror');
const api = require('../lib/api');
var Types = keyMirror({
const Types = keyMirror({
SET_SCHEDULE: null,
SET_SCHEDULE_FETCHING: null,
SET_SCHEDULE_ERROR: null
});
module.exports.scheduleReducer = function (state, action) {
module.exports.scheduleReducer = (state, action) => {
if (typeof state === 'undefined') {
state = {
timeSlots: [],
@ -26,40 +26,27 @@ module.exports.scheduleReducer = function (state, action) {
}
};
module.exports.setSchedule = function (schedule) {
return {
module.exports.setSchedule = schedule => ({
type: Types.SET_SCHEDULE,
schedule: schedule
};
};
});
module.exports.setScheduleFetching = function () {
return {
module.exports.setScheduleFetching = () => ({
type: Types.SET_SCHEDULE_FETCHING,
fetching: true
};
};
});
module.exports.setScheduleError = function (error) {
return {
module.exports.setScheduleError = error => ({
type: Types.SET_SCHEDULE_ERROR,
error: error
};
};
module.exports.startGetSchedule = function (day) {
return function (dispatch) {
dispatch(module.exports.setScheduleFetching());
dispatch(module.exports.getDaySchedule(day));
};
};
});
// group periods of time by start time
module.exports.sortTimeSlots = function (timeSlot1, timeSlot2) {
var timeSlot1Am = (timeSlot1.time.substr(timeSlot1.time.length - 1, timeSlot1.time.length) === 'a') ? true : false;
var timeSlot2Am = (timeSlot2.time.substr(timeSlot2.time.length - 1, timeSlot2.time.length) === 'a') ? true : false;
var timeSlot1Time = parseInt(timeSlot1.time.substr(0, timeSlot1.time.length - 1));
var timeSlot2Time = parseInt(timeSlot2.time.substr(0, timeSlot2.time.length - 1));
module.exports.sortTimeSlots = (timeSlot1, timeSlot2) => {
const timeSlot1Am = (timeSlot1.time.substr(timeSlot1.time.length - 1, timeSlot1.time.length) === 'a');
const timeSlot2Am = (timeSlot2.time.substr(timeSlot2.time.length - 1, timeSlot2.time.length) === 'a');
let timeSlot1Time = parseInt(timeSlot1.time.substr(0, timeSlot1.time.length - 1), 10);
let timeSlot2Time = parseInt(timeSlot2.time.substr(0, timeSlot2.time.length - 1), 10);
// convert to 24-hour for sorting
if (timeSlot1Time !== 12 && !timeSlot1Am) {
@ -71,40 +58,38 @@ module.exports.sortTimeSlots = function (timeSlot1, timeSlot2) {
if (timeSlot1Time < timeSlot2Time) {
return -1;
} else {
return 1;
}
return 1;
};
/**
* Gets the schedule for the given day from the api
* @param {String} day Day of the conference (Thursday, Friday or Satrurday)
* @param {string} day Day of the conference (Thursday, Friday or Satrurday)
*
* @return {Object} Schedule for the day, broken into chunks
* @return {object} Schedule for the day, broken into chunks
*/
module.exports.getDaySchedule = function (day) {
return function (dispatch) {
module.exports.getDaySchedule = day => (dispatch => {
api({
uri: '/conference/schedule/' + day
}, function (err, body) {
uri: `/conference/schedule/${day}`
}, (err, body) => {
if (err) {
dispatch(module.exports.setScheduleError(err));
return;
}
if (typeof body !== 'undefined') {
var columns = body.columns;
var rows = body.rows || [];
const columns = body.columns;
const rows = body.rows || [];
// Group events by the time period in which they occur (for presentation)
var scheduleByTimeSlot = rows.reduce(function (prev, cur) {
var cleanedRow = {};
for (var i = 0; i < columns.length; i++) {
const scheduleByTimeSlot = rows.reduce((prev, cur) => {
const cleanedRow = {};
for (let i = 0; i < columns.length; i++) {
if (cur[i].length > 0) {
cleanedRow[columns[i]] = cur[i];
}
}
cleanedRow['uri'] = '/conference/2016/' + cleanedRow.rowid + '/details';
var timeSlot = cleanedRow.Chunk + cleanedRow.Start;
cleanedRow.uri = `/conference/2016/${cleanedRow.rowid}/details`;
const timeSlot = cleanedRow.Chunk + cleanedRow.Start;
if (typeof prev.timeSlots[timeSlot] === 'undefined') {
prev.timeSlots[timeSlot] = [cleanedRow];
prev.info.push({
@ -118,21 +103,22 @@ module.exports.getDaySchedule = function (day) {
}, {timeSlots: [], info: []});
scheduleByTimeSlot.info.sort(module.exports.sortTimeSlots);
var schedule = scheduleByTimeSlot.info.map(function (timeSlot) {
return {
const schedule = scheduleByTimeSlot.info.map(timeSlot => ({
info: timeSlot,
items: scheduleByTimeSlot.timeSlots[timeSlot.name + timeSlot.time]
};
});
}));
dispatch(module.exports.setSchedule({
timeSlots: schedule,
day: day
}));
return;
} else {
}
dispatch(module.exports.setScheduleError('An unexpected error occurred'));
return;
}
});
};
};
});
module.exports.startGetSchedule = day => (dispatch => {
dispatch(module.exports.setScheduleFetching());
dispatch(module.exports.getDaySchedule(day));
});

View file

@ -1,22 +1,22 @@
var keyMirror = require('keymirror');
var defaults = require('lodash.defaults');
const keyMirror = require('keymirror');
const defaults = require('lodash.defaults');
var api = require('../lib/api');
const api = require('../lib/api');
var Types = keyMirror({
const Types = keyMirror({
SET_MESSAGE_COUNT: null,
SET_MESSAGE_COUNT_ERROR: null,
SET_STATUS: null
});
module.exports.getInitialState = function (){
return {messageCount: 0};
};
const getInitialState = () => ({
messageCount: 0
});
module.exports.messageCountReducer = function (state, action) {
module.exports.messageCountReducer = (state, action) => {
// Reducer for handling changes to session state
if (typeof state === 'undefined') {
state = module.exports.getInitialState();
state = getInitialState();
}
switch (action.type) {
case Types.SET_MESSAGE_COUNT:
@ -31,40 +31,32 @@ module.exports.messageCountReducer = function (state, action) {
}
};
module.exports.setSessionError = function (error) {
return {
module.exports.setSessionError = error => ({
type: Types.SET_MESSAGE_COUNT_ERROR,
error: error
};
};
});
module.exports.setCount = function (count) {
return {
module.exports.setCount = count => ({
type: Types.SET_MESSAGE_COUNT,
count: count
};
};
});
module.exports.setStatus = function (status){
return {
module.exports.setStatus = status => ({
type: Types.SET_STATUS,
status: status
};
};
});
module.exports.getCount = function (username) {
return function (dispatch) {
module.exports.getCount = username => (dispatch => {
api({
method: 'get',
uri: '/users/' + username + '/messages/count'
}, function (err, body) {
uri: `/users/${username}/messages/count`
}, (err, body) => {
if (err) {
dispatch(module.exports.setCount(0));
dispatch(module.exports.setSessionError(err));
return;
}
var count = parseInt(body.count, 10);
const count = parseInt(body.count, 10);
dispatch(module.exports.setCount(count));
});
};
};
});

View file

@ -1,10 +1,10 @@
var defaults = require('lodash.defaults');
var defaultsDeep = require('lodash.defaultsdeep');
var keyMirror = require('keymirror');
const defaults = require('lodash.defaults');
const defaultsDeep = require('lodash.defaultsdeep');
const keyMirror = require('keymirror');
var api = require('../lib/api');
var log = require('../lib/log');
var messageCountActions = require('./message-count.js');
const api = require('../lib/api');
const log = require('../lib/log');
const messageCountActions = require('./message-count.js');
module.exports.Status = keyMirror({
FETCHED: null,
@ -17,8 +17,7 @@ module.exports.Status = keyMirror({
DELETE_ERROR: null
});
module.exports.getInitialState = function () {
return {
module.exports.getInitialState = () => ({
status: {
admin: module.exports.Status.NOT_FETCHED,
message: module.exports.Status.NOT_FETCHED,
@ -30,10 +29,9 @@ module.exports.getInitialState = function () {
social: [],
invite: {}
}
};
};
});
module.exports.messagesReducer = function (state, action) {
module.exports.messagesReducer = (state, action) => {
if (typeof state === 'undefined') {
state = module.exports.getInitialState();
}
@ -68,61 +66,48 @@ module.exports.messagesReducer = function (state, action) {
}
};
module.exports.setMessagesError = function (error) {
return {
module.exports.setMessagesError = error => ({
type: 'ERROR',
error: error
};
};
});
module.exports.setMessages = function (messages) {
return {
module.exports.setMessages = messages => ({
type: 'SET_MESSAGES',
messages: messages
};
};
});
module.exports.setMessagesOffset = function (offset) {
return {
module.exports.setMessagesOffset = offset => ({
type: 'SET_MESSAGES_OFFSET',
offset: offset
};
};
});
module.exports.setAdminMessages = function (messages) {
return {
module.exports.setAdminMessages = messages => ({
type: 'SET_ADMIN_MESSAGES',
messages: messages
};
};
});
module.exports.setScratcherInvite = function (invite) {
return {
module.exports.setScratcherInvite = invite => ({
type: 'SET_SCRATCHER_INVITE',
invite: invite
};
};
});
module.exports.setStatus = function (type, status){
return {
module.exports.setStatus = (type, status) => ({
type: type,
status: status
};
};
});
/**
* Sends a request to mark one's unread messages count as cleared.
* @return {null} returns nothing
*/
module.exports.clearMessageCount = function () {
return function (dispatch) {
module.exports.clearMessageCount = () => (dispatch => {
dispatch(module.exports.setStatus('CLEAR_STATUS', module.exports.Status.FETCHING));
api({
host: '',
uri: '/site-api/messages/messages-clear/',
method: 'POST',
useCsrf: true
}, function (err, body) {
}, (err, body) => {
if (err) {
dispatch(module.exports.setStatus('CLEAR_STATUS', module.exports.Status.CLEAR_ERROR));
dispatch(module.exports.setMessagesError(err));
@ -135,8 +120,7 @@ module.exports.clearMessageCount = function () {
}
dispatch(module.exports.setStatus('CLEAR_STATUS', module.exports.Status.FETCHED));
});
};
};
});
/**
* Marks an admin message as read, dismissing it from the page
@ -146,8 +130,8 @@ module.exports.clearMessageCount = function () {
* @param {object[]} adminMessages current list of admin messages retrieved
* @return {null} returns nothing
*/
module.exports.clearAdminMessage = function (messageType, messageId, messageCount, adminMessages) {
return function (dispatch) {
module.exports.clearAdminMessage = (messageType, messageId, messageCount, adminMessages) => (
dispatch => {
dispatch(module.exports.setStatus('CLEAR_STATUS', module.exports.Status.FETCHING));
api({
host: '',
@ -158,7 +142,7 @@ module.exports.clearAdminMessage = function (messageType, messageId, messageCoun
alertType: messageType,
alertId: messageId
}
}, function (err, body) {
}, (err, body) => {
if (err) {
dispatch(module.exports.setStatus('DELETE_STATUS', module.exports.Status.DELETE_ERROR));
dispatch(module.exports.setMessagesError(err));
@ -175,9 +159,9 @@ module.exports.clearAdminMessage = function (messageType, messageId, messageCoun
dispatch(module.exports.setScratcherInvite({}));
} else {
// find the admin message and remove it
var toRemove = -1;
for (var i in adminMessages) {
if (adminMessages[i].id === messageId) {
let toRemove = -1;
for (const i of adminMessages) {
if (i.id === messageId) {
toRemove = i;
break;
}
@ -188,8 +172,8 @@ module.exports.clearAdminMessage = function (messageType, messageId, messageCoun
dispatch(messageCountActions.setCount(messageCount - 1));
dispatch(module.exports.setStatus('DELETE_STATUS', module.exports.Status.FETCHED));
});
};
};
}
);
/**
* Gets a user's messages to be displayed on the /messages page
@ -201,7 +185,7 @@ module.exports.clearAdminMessage = function (messageType, messageId, messageCoun
* @param {string} [opts.filter] type of messages to return
* @return {null} returns nothing
*/
module.exports.getMessages = function (username, token, opts) {
module.exports.getMessages = (username, token, opts) => {
opts = defaults(opts, {
messages: [],
offset: 0,
@ -209,17 +193,17 @@ module.exports.getMessages = function (username, token, opts) {
clearCount: true
});
var filterArg = '';
let filterArg = '';
if (opts.filter.length > 0) {
filterArg = '&filter=' + opts.filter;
filterArg = `&filter=${opts.filter}`;
}
return function (dispatch) {
return dispatch => {
dispatch(module.exports.setStatus('MESSAGE_STATUS', module.exports.Status.FETCHING));
api({
uri: '/users/' + username + '/messages?limit=40&offset=' + opts.offset + filterArg,
uri: `/users/${username}/messages?limit=40&offset=${opts.offset}${filterArg}`,
authentication: token
}, function (err, body) {
}, (err, body) => {
if (err) {
dispatch(module.exports.setStatus('MESSAGE_STATUS', module.exports.Status.MESSAGES_ERROR));
dispatch(module.exports.setMessagesError(err));
@ -246,13 +230,12 @@ module.exports.getMessages = function (username, token, opts) {
* @param {string} token the user's unique token for auth
* @return {null} returns nothing
*/
module.exports.getAdminMessages = function (username, token) {
return function (dispatch) {
module.exports.getAdminMessages = (username, token) => (dispatch => {
dispatch(module.exports.setStatus('ADMIN_STATUS', module.exports.Status.FETCHING));
api({
uri: '/users/' + username + '/messages/admin',
uri: `/users/${username}/messages/admin`,
authentication: token
}, function (err, body) {
}, (err, body) => {
if (err) {
dispatch(module.exports.setStatus('ADMIN_STATUS', module.exports.Status.ADMIN_ERROR));
dispatch(module.exports.setMessagesError(err));
@ -268,8 +251,7 @@ module.exports.getAdminMessages = function (username, token) {
dispatch(module.exports.setAdminMessages(body));
dispatch(module.exports.setStatus('ADMIN_STATUS', module.exports.Status.FETCHED));
});
};
};
});
/**
* Gets the invitation to become a Scratcher for a user, if one exists
@ -277,12 +259,11 @@ module.exports.getAdminMessages = function (username, token) {
* @param {string} token the user's unique token for auth
* @return {null} returns nothing
*/
module.exports.getScratcherInvite = function (username, token) {
return function (dispatch) {
module.exports.getScratcherInvite = (username, token) => (dispatch => {
api({
uri: '/users/' + username + '/invites',
uri: `/users/${username}/invites`,
authentication: token
}, function (err, body) {
}, (err, body) => {
if (err) {
dispatch(module.exports.setStatus('ADMIN_STATUS', module.exports.Status.INVITE_ERROR));
dispatch(module.exports.setMessagesError(err));
@ -292,5 +273,4 @@ module.exports.getScratcherInvite = function (username, token) {
if (typeof body === 'undefined') return dispatch(module.exports.setMessagesError('No session content'));
dispatch(module.exports.setScratcherInvite(body));
});
};
};
});

View file

@ -1,11 +1,11 @@
var keyMirror = require('keymirror');
const keyMirror = require('keymirror');
var Types = keyMirror({
const Types = keyMirror({
SET_SEARCH_TERM: null
});
module.exports.navigationReducer = function (state, action) {
if(typeof state === 'undefined') {
module.exports.navigationReducer = (state, action) => {
if (typeof state === 'undefined') {
state = '';
}
switch (action.type) {
@ -16,9 +16,7 @@ module.exports.navigationReducer = function (state, action) {
}
};
module.exports.setSearchTerm = function (searchTerm) {
return {
module.exports.setSearchTerm = searchTerm => ({
type: Types.SET_SEARCH_TERM,
searchTerm: searchTerm
};
};
});

View file

@ -1,14 +1,14 @@
var keyMirror = require('keymirror');
var jar = require('../lib/jar.js');
const keyMirror = require('keymirror');
const jar = require('../lib/jar.js');
var Types = keyMirror({
const Types = keyMirror({
SET_PERMISSIONS: null,
SET_PERMISSIONS_ERROR: null
});
module.exports.permissionsReducer = function (state, action) {
module.exports.permissionsReducer = (state, action) => {
if (typeof state === 'undefined') {
state = '';
state = {};
}
switch (action.type) {
case Types.SET_PERMISSIONS:
@ -20,21 +20,20 @@ module.exports.permissionsReducer = function (state, action) {
}
};
module.exports.storePermissions = function (permissions) {
module.exports.storePermissions = permissions => {
permissions = permissions || {};
return function (dispatch) {
return dispatch => {
jar.set('permissions', permissions, {
encode: function (value) {
return encodeURIComponent(JSON.stringify(value));
}
encode: value => (
encodeURIComponent(JSON.stringify(value))
)
});
return dispatch(module.exports.setPermissions(permissions));
};
};
module.exports.getPermissions = function () {
return function (dispatch) {
jar.get('permissions', function (err, value) {
module.exports.getPermissions = () => (dispatch => {
jar.get('permissions', (err, value) => {
if (err) return dispatch(module.exports.setPermissionsError(err));
try {
@ -44,19 +43,14 @@ module.exports.getPermissions = function () {
}
return dispatch(module.exports.setPermissions(value));
});
};
};
});
module.exports.setPermissions = function (permissions) {
return {
module.exports.setPermissions = permissions => ({
type: Types.SET_PERMISSIONS,
permissions: permissions
};
};
});
module.exports.setPermissionsError = function (error) {
return {
module.exports.setPermissionsError = error => ({
type: Types.SET_PERMISSIONS_ERROR,
error: error
};
};
});

View file

@ -1,9 +1,9 @@
var combineReducers = require('redux').combineReducers;
var defaults = require('lodash.defaults');
const combineReducers = require('redux').combineReducers;
const defaults = require('lodash.defaults');
var messageCountReducer = require('./message-count.js').messageCountReducer;
var permissionsReducer = require('./permissions.js').permissionsReducer;
var sessionReducer = require('./session.js').sessionReducer;
const messageCountReducer = require('./message-count.js').messageCountReducer;
const permissionsReducer = require('./permissions.js').permissionsReducer;
const sessionReducer = require('./session.js').sessionReducer;
/**
* Returns a combined reducer to be used for a page in `render.jsx`.
@ -11,11 +11,11 @@ var sessionReducer = require('./session.js').sessionReducer;
* - and any reducers specific to the page should be passed into
* `render()` as an object (which will then be passed to the function
* below).
* @param {Object} opts key/value where the key is the name of the
* @param {object} opts key/value where the key is the name of the
* redux state, value is the reducer function.
* @return {Object} combined reducer to be used in the redux store
* @return {object} combined reducer to be used in the redux store
*/
module.exports = function (opts) {
module.exports = opts => {
opts = opts || {};
return combineReducers(defaults(opts, {
session: sessionReducer,

Some files were not shown because too many files have changed in this diff Show more