Merge pull request #703 from LLK/release/2.2.10

[Develop] Release 2.2.10
This commit is contained in:
Matthew Taylor 2016-07-08 09:03:11 -04:00 committed by GitHub
commit 99c76ee3bf
41 changed files with 579 additions and 371 deletions

View file

@ -76,12 +76,14 @@ Helpers.getMD5Map = function (ICUIdMap) {
* key: '<react-intl string id>'
* value: translated version of string
*/
Helpers.getTranslationsForLanguage = function (lang, idsWithICU, md5WithIds) {
Helpers.getTranslationsForLanguage = function (lang, idsWithICU, md5WithIds, separator) {
var poUiDir = path.resolve(__dirname, '../../node_modules/scratchr2_translations/ui');
var jsFile = path.resolve(poUiDir, lang + '/LC_MESSAGES/djangojs.po');
var pyFile = path.resolve(poUiDir, lang + '/LC_MESSAGES/django.po');
var translations = {};
separator = separator || ':';
try {
fs.accessSync(jsFile, fs.R_OK);
var jsTranslations = po2icu.poFileToICUSync(lang, jsFile);
@ -104,7 +106,7 @@ Helpers.getTranslationsForLanguage = function (lang, idsWithICU, md5WithIds) {
var translationsByView = {};
for (var id in translations) {
var ids = id.split('-'); // [viewName, stringId]
var ids = id.split(separator); // [viewName, stringId]
var viewName = ids[0];
var stringId = ids[1];
@ -127,20 +129,24 @@ Helpers.writeTranslationsToJS = function (outputDir, viewName, translationObject
// Returns a FormattedMessage id with english string as value. Use for default values in translations
// Sample structure: { 'general-general.blah': 'blah', 'about-about.blah': 'blahblah' }
Helpers.idToICUMap = function (viewName, ids) {
Helpers.idToICUMap = function (viewName, ids, separator) {
var idsToICU = {};
separator = separator || ':';
for (var id in ids) {
idsToICU[viewName + '-' + id] = ids[id];
idsToICU[viewName + separator + id] = ids[id];
}
return idsToICU;
};
// Reuturns reverse (i.e. english string with message key as the value) object for searching po files.
// Sample structure: { 'blah': 'general-general.blah', 'blahblah': 'about-about.blah' }
Helpers.icuToIdMap = function (viewName, ids) {
Helpers.icuToIdMap = function (viewName, ids, separator) {
var icuToIds = {};
separator = separator || ':';
for (var id in ids) {
icuToIds[ids[id]] = viewName + '-' + id;
icuToIds[ids[id]] = viewName + separator + id;
}
return icuToIds;
};

View file

@ -55,7 +55,7 @@ var Carousel = React.createClass({
}
return (
<Thumbnail key={item.id}
<Thumbnail key={[this.key, item.id].join('.')}
showLoves={this.props.showLoves}
showRemixes={this.props.showRemixes}
type={item.type}

View file

@ -9,7 +9,9 @@ var Deck = React.createClass({
return (
<div className={classNames(['deck', this.props.className])}>
<div className="inner">
<img src="/images/logo_sm.png" />
<a href="/" aria-label="Scratch">
<img src="/images/logo_sm.png" />
</a>
{this.props.children}
</div>
</div>

View file

@ -14,141 +14,138 @@
.step-navigation {
margin-top: 2rem;
}
}
.slide {
max-width: 28.75rem;
h2,
p {
text-align: center;
color: $type-white;
}
.description {
margin-top: 0;
margin-bottom: 2rem;
.slide {
max-width: 28.75rem;
h2,
.description {
text-align: center;
color: $type-white;
}
.description {
margin-top: 0;
margin-bottom: 2rem;
}
}
}
.card {
margin: 0 auto;
width: 23.75rem;
&,
h2,
p {
color: $type-gray;
.card {
margin: 0 auto;
width: 23.75rem;
}
}
.step-navigation {
text-align: center;
}
.form {
padding: 3rem 4rem;
.form {
padding: 3rem 4rem;
.form-group {
margin-bottom: 1.2rem;
.form-group {
margin-bottom: 1.2rem;
&.has-error {
.input {
border: 1px solid $ui-orange;
}
}
}
&.has-error {
.input {
border: 1px solid $ui-orange;
.button {
margin: 0 0 -3rem -4rem;
border-radius: .5rem;
box-shadow: none;
width: 23.75rem;
height: 4rem;
&.card-button {
display: block;
border-top-left-radius: 0;
border-top-right-radius: 0;
background-color: $ui-aqua;
}
&:hover {
box-shadow: none;
}
}
}
.button {
margin: 0 0 -3rem -4rem;
border-radius: .5rem;
box-shadow: none;
width: 23.75rem;
height: 4rem;
&.card-button {
display: block;
border-top-left-radius: 0;
border-top-right-radius: 0;
background-color: $ui-aqua;
}
&:hover {
box-shadow: none;
}
.input {
width: $cols5;
}
}
.input {
width: $cols5;
}
.help-block {
$arrow-border-width: 1rem;
display: block;
position: absolute;
margin-left: $arrow-border-width;
border: 1px solid $active-gray;
border-radius: 5px;
background-color: $ui-orange;
padding: 1rem;
max-width: 18.75rem;
min-height: 1rem;
max-height: 3rem;
overflow: visible;
color: $type-white;
&:before {
.help-block {
$arrow-border-width: 1rem;
display: block;
position: absolute;
top: 1rem;
left: -$arrow-border-width / 2;
transform: rotate(45deg);
border-bottom: 1px solid $active-gray;
border-left: 1px solid $active-gray;
margin-left: $arrow-border-width;
border: 1px solid $active-gray;
border-radius: 5px;
background-color: $ui-orange;
width: $arrow-border-width;
height: $arrow-border-width;
padding: 1rem;
max-width: 18.75rem;
min-height: 1rem;
max-height: 3rem;
overflow: visible;
color: $type-white;
content: "";
&:before {
display: block;
position: absolute;
top: 1rem;
left: -$arrow-border-width / 2;
transform: rotate(45deg);
border-bottom: 1px solid $active-gray;
border-left: 1px solid $active-gray;
border-radius: 5px;
background-color: $ui-orange;
width: $arrow-border-width;
height: $arrow-border-width;
content: "";
}
}
}
@media only screen and (max-width: $mobile - 1) {
.card {
width: 22.5rem;
}
.form {
text-align: left;
.button {
.deck {
.card {
width: 22.5rem;
}
.form {
text-align: left;
.button {
width: 22.5rem;
}
}
}
}
@media only screen and (max-width: $tablet - 1) {
.input {
width: 90%;
.deck {
.input {
width: 90%;
}
}
}
@media only screen and (max-width: $desktop - 1) {
.help-block {
position: relative;
transform: none;
margin: inherit;
width: 100%;
height: inherit;
.deck {
.help-block {
position: relative;
transform: none;
margin: inherit;
width: 100%;
height: inherit;
&:before {
display: none;
&:before {
display: none;
}
}
}
}

View file

@ -15,6 +15,10 @@
font-size: .8125rem;
font-weight: normal;
&.staging {
background-color: $ui-orange;
}
&.open {
display: block;
}

View file

@ -1,13 +1,11 @@
@import "../../colors";
p {
&.char-count {
letter-spacing: 1px;
color: lighten($type-gray, 30%);
font-weight: 500;
.char-count {
letter-spacing: 1px;
color: lighten($type-gray, 30%);
font-weight: 500;
&.overmax {
color: $ui-orange;
}
&.overmax {
color: $ui-orange;
}
}

View file

@ -1,9 +1,11 @@
.checkbox-group {
.col-sm-9 {
flex-flow: column wrap;
.row {
.col-sm-9 {
flex-flow: column wrap;
.checkbox {
margin: .5rem 0;
.checkbox {
margin: .5rem 0;
}
}
}
}

View file

@ -6,37 +6,37 @@
label {
font-weight: 300;
}
}
}
input {
&[type=checkbox] {
display: block;
float: left;
margin-right: 1rem;
border: 1px solid $active-gray;
border-radius: 3px;
width: 1.25rem;
height: 1.25rem;
appearance: none;
input {
&[type=checkbox] {
display: block;
float: left;
margin-right: 1rem;
border: 1px solid $active-gray;
border-radius: 3px;
width: 1.25rem;
height: 1.25rem;
appearance: none;
&:checked,
&:focus {
outline: none;
}
&:checked,
&:focus {
transition: all .5s ease;
outline: none;
box-shadow: 0 0 0 .25rem $active-gray;
}
&:checked {
transition: all .5s ease;
box-shadow: 0 0 0 .25rem $active-gray;
background-color: $ui-blue;
text-align: center;
text-indent: .125rem;
line-height: 1.25rem;
font-size: .75rem;
&:checked {
background-color: $ui-blue;
text-align: center;
text-indent: .125rem;
line-height: 1.25rem;
font-size: .75rem;
&:after {
color: $type-white;
content: "\2714";
&:after {
color: $type-white;
content: "\2714";
}
}
}
}
}

View file

@ -1,7 +1,7 @@
var classNames = require('classnames');
var Formsy = require('formsy-react');
var omit = require('lodash.omit');
var React = require('react');
var GeneralError = require('./general-error.jsx');
var validations = require('./validations.jsx').validations;
for (var validation in validations) {
@ -11,18 +11,34 @@ for (var validation in validations) {
var Form = React.createClass({
getDefaultProps: function () {
return {
noValidate: true
noValidate: true,
onChange: function () {}
};
},
getInitialState: function () {
return {
allValues: {}
};
},
onChange: function (currentValues, isChanged) {
this.setState({allValues: omit(currentValues, 'all')});
this.props.onChange(currentValues, isChanged);
},
render: function () {
var classes = classNames(
'form',
this.props.className
);
return (
<Formsy.Form {... this.props} className={classes}>
<GeneralError name="all" />
{this.props.children}
<Formsy.Form {... this.props} className={classes} ref="formsy" onChange={this.onChange}>
{React.Children.map(this.props.children, function (child) {
if (!child) return child;
if (child.props.name === 'all') {
return React.cloneElement(child, {value: this.state.allValues});
} else {
return child;
}
}.bind(this))}
</Formsy.Form>
);
}

View file

@ -1,6 +1,8 @@
var Formsy = require('formsy-react');
var React = require('react');
require('./general-error.scss');
/*
* A special formsy-react component that only outputs
* error messages. If you want to display errors that
@ -12,7 +14,7 @@ module.exports = Formsy.HOC(React.createClass({
render: function () {
if (!this.props.showError()) return null;
return (
<p className="error">
<p className="general-error">
{this.props.getErrorMessage()}
</p>
);

View file

@ -0,0 +1,9 @@
@import "../../colors";
.general-error {
border: 1px solid $active-gray;
border-radius: 4px;
background-color: $ui-orange;
padding: 1rem;
color: $type-white;
}

View file

@ -6,39 +6,39 @@
font-weight: 300;
}
}
}
.col-sm-9 {
display: flex;
flex-flow: row wrap;
.col-sm-9 {
display: flex;
flex-flow: row wrap;
input {
&[type="radio"] {
margin-top: 1px;
border: 1px solid $active-gray;
border-radius: 50%;
width: .875rem;
height: .875rem;
appearance: none;
input {
&[type="radio"] {
margin-top: 1px;
border: 1px solid $active-gray;
border-radius: 50%;
width: .875rem;
height: .875rem;
appearance: none;
&:checked,
&:focus {
outline: none;
}
&:checked,
&:focus {
outline: none;
}
&:checked {
transition: all .25s ease;
box-shadow: 0 0 0 .25rem $active-gray;
background-color: $ui-blue;
&:checked {
transition: all .25s ease;
box-shadow: 0 0 0 .25rem $active-gray;
background-color: $ui-blue;
&:after {
display: block;
transform: translate(.25rem, .25rem);
border-radius: 50%;
background-color: $ui-white;
width: .25rem;
height: .25rem;
content: "";
&:after {
display: block;
transform: translate(.25rem, .25rem);
border-radius: 50%;
background-color: $ui-white;
width: .25rem;
height: .25rem;
content: "";
}
}
}
}

View file

@ -5,34 +5,38 @@
label {
font-weight: 500;
}
}
select {
transition: all .5s ease;
margin: .75rem 0;
border: 1px solid $active-gray;
border-radius: 5px;
background: $ui-light-gray url("../../../static/svgs/forms/carot.svg") no-repeat right center;
padding-right: 4rem;
width: 100%;
height: 3rem;
text-indent: 1rem;
font-size: .875rem;
appearance: none;
&:focus {
select {
transition: all .5s ease;
outline: none;
border: 1px solid $ui-blue;
}
&:focus,
&:hover {
background: $ui-light-gray url("../../../static/svgs/forms/carot-hover.svg") no-repeat right center;
}
> option {
background-color: $ui-white;
margin: .75rem 0;
border: 1px solid $active-gray;
border-radius: 5px;
background: $ui-light-gray url("../../../static/svgs/forms/carot.svg") no-repeat right center;
padding-right: 4rem;
width: 100%;
height: 3rem;
text-indent: 1rem;
font-size: .875rem;
appearance: none;
&::-ms-expand {
display: none;
}
&:focus {
transition: all .5s ease;
outline: none;
border: 1px solid $ui-blue;
}
&:focus,
&:hover {
background: $ui-light-gray url("../../../static/svgs/forms/carot-hover.svg") no-repeat right center;
}
> option {
background-color: $ui-white;
width: 100%;
}
}
}

View file

@ -29,6 +29,7 @@ module.exports.validationHOCFactory = function (defaultValidationErrors) {
var ValidatedComponent = React.createClass({
render: function () {
var validationErrors = defaults(
{},
defaultValidationErrors,
this.props.validationErrors
);

View file

@ -6,6 +6,8 @@ var languages = require('../../../languages.json');
var Form = require('../forms/form.jsx');
var Select = require('../forms/select.jsx');
require('./languagechooser.scss');
/**
* Footer dropdown menu that allows one to change their language.
*/
@ -33,7 +35,7 @@ var LanguageChooser = React.createClass({
<Form className={classes}>
<Select name="language"
options={languageOptions}
defaultValue={this.props.locale}
value={this.props.locale}
onChange={this.onSetLanguage}
required />
</Form>

View file

@ -0,0 +1,10 @@
@import "../../frameless";
.language-chooser {
.select {
select {
width: 13.75rem;
/* 3 columns */
}
}
}

View file

@ -11,6 +11,10 @@
box-shadow: 0 0 3px $box-shadow-gray;
background-color: $ui-blue;
&.staging {
background-color: $ui-orange;
}
width: 100%;
/* NOTE: Height should match offset settings in main.scss file */

View file

@ -178,6 +178,10 @@ var Navigation = React.createClass({
'message-count': true,
'show': this.state.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';
return (
@ -241,14 +245,16 @@ var Navigation = React.createClass({
</a>
</li>,
<li className="link right account-nav" key="account-nav">
<a className="user-info" href="#" onClick={this.handleAccountNavClick}>
<a className={dropdownClasses}
href="#" onClick={this.handleAccountNavClick}>
<Avatar src={this.props.session.session.user.thumbnailUrl} alt="" />
{this.props.session.session.user.username}
</a>
<Dropdown
as="ul"
isOpen={this.state.accountNavOpen}
onRequestClose={this.closeAccountNav}>
onRequestClose={this.closeAccountNav}
className={process.env.SCRATCH_ENV}>
<li>
<a href={this.getProfileUrl()}>
<FormattedMessage id="general.profile" />
@ -259,14 +265,14 @@ var Navigation = React.createClass({
<FormattedMessage id="general.myStuff" />
</a>
</li>
{this.props.session.session.permissions.educator ? [
<li>
{this.props.permissions.educator ? [
<li key="my-classes-li">
<a href="/educators/classes/">
<FormattedMessage id="general.myClasses" />
</a>
</li>
] : []}
{this.props.session.session.permissions.student ? [
{this.props.permissions.student ? [
<li>
<a href={'/classes/' + this.props.session.session.user.classroomId + '/'}>
<FormattedMessage id="general.myClass" />
@ -335,7 +341,8 @@ var Navigation = React.createClass({
var mapStateToProps = function (state) {
return {
session: state.session
session: state.session,
permissions: state.permissions
};
};

View file

@ -1,6 +1,18 @@
@import "../../../colors";
#navigation {
&.staging {
.messages {
.message-count {
display: none;
&.show {
background-color: $ui-blue;
}
}
}
}
.logo {
margin-right: 10px;
@ -147,6 +159,21 @@
.login-dropdown {
width: 200px;
.button {
padding: .75em;
}
}
.dropdown {
.row {
margin-bottom: 1.25rem;
input {
margin: 0;
height: 2.25rem;
}
}
}
.account-nav {
@ -170,6 +197,10 @@
vertical-align: middle;
}
&.open {
background-color: $active-gray;
}
&:after {
display: inline-block;
margin-left: 8px;
@ -186,9 +217,11 @@
}
.dropdown {
top: 50px;
padding: 0;
padding-top: 5px;
width: 100%;
box-sizing: border-box;
}
}
}

View file

@ -1,4 +1,5 @@
var React = require('react');
var classNames = require('classnames');
var Navigation = require('../../navigation/www/navigation.jsx');
var Footer = require('../../footer/www/footer.jsx');
@ -6,9 +7,12 @@ var Footer = require('../../footer/www/footer.jsx');
var Page = React.createClass({
type: 'Page',
render: function () {
var classes = classNames({
'staging': process.env.SCRATCH_ENV == 'staging'
});
return (
<div className="page">
<div id="navigation">
<div id="navigation" className={classes}>
<Navigation />
</div>
<div id="view">

View file

@ -12,6 +12,7 @@ var CharCount = require('../../components/forms/charcount.jsx');
var Checkbox = require('../../components/forms/checkbox.jsx');
var CheckboxGroup = require('../../components/forms/checkbox-group.jsx');
var Form = require('../../components/forms/form.jsx');
var GeneralError = require('../../components/forms/general-error.jsx');
var Input = require('../../components/forms/input.jsx');
var PhoneInput = require('../../components/forms/phone-input.jsx');
var RadioGroup = require('../../components/forms/radio-group.jsx');
@ -102,7 +103,7 @@ module.exports = {
</p>
<Card>
<Form onValidSubmit={this.onValidSubmit}>
<Input label={formatMessage({id: 'general.username'})}
<Input label={formatMessage({id: 'general.createUsername'})}
className={this.state.validUsername}
type="text"
name="user.username"
@ -148,6 +149,7 @@ module.exports = {
onChange={this.onChangeShowPassword}
help={null}
name="showPassword" />
<GeneralError name="all" />
<NextStepButton waiting={this.props.waiting || this.state.waiting}
text={<intl.FormattedMessage id="teacherRegistration.nextStep" />} />
</Form>
@ -284,6 +286,14 @@ module.exports = {
waiting: false
};
},
onValidSubmit: function (formData, reset, invalidate) {
if (formData.phone.national_number.length !== formData.phone.country_code.format.length) {
return invalidate({
'phone': this.props.intl.formatMessage({id: 'teacherRegistration.validationPhoneNumber'})
});
}
return this.props.onNextStep(formData);
},
render: function () {
var formatMessage = this.props.intl.formatMessage;
return (
@ -291,13 +301,13 @@ module.exports = {
<h2>
<intl.FormattedMessage id="teacherRegistration.phoneStepTitle" />
</h2>
<p>
<p className="description">
<intl.FormattedMessage id="teacherRegistration.phoneStepDescription" />
<Tooltip title={'?'}
tipContent={formatMessage({id: 'teacherRegistration.nameStepTooltip'})} />
</p>
<Card>
<Form onValidSubmit={this.props.onNextStep}>
<Form onValidSubmit={this.onValidSubmit}>
<PhoneInput label={formatMessage({id: 'teacherRegistration.phoneNumber'})}
name="phone"
defaultCountry={this.props.defaultCountry}
@ -352,6 +362,14 @@ module.exports = {
onChooseOrganization: function (name, values) {
this.setState({otherDisabled: values.indexOf(this.organizationL10nStems.indexOf('orgChoiceOther')) === -1});
},
onValidSubmit: function (formData, reset, invalidate) {
if (formData.organization.type.length < 1) {
return invalidate({
'organization.type': this.props.intl.formatMessage({id: 'teacherRegistration.validationRequired'})
});
}
return this.props.onNextStep(formData);
},
render: function () {
var formatMessage = this.props.intl.formatMessage;
return (
@ -359,13 +377,13 @@ module.exports = {
<h2>
<intl.FormattedMessage id="teacherRegistration.orgStepTitle" />
</h2>
<p>
<p className="description">
<intl.FormattedMessage id="teacherRegistration.orgStepDescription" />
<Tooltip title={'?'}
tipContent={formatMessage({id: 'teacherRegistration.nameStepTooltip'})} />
</p>
<Card>
<Form onValidSubmit={this.props.onNextStep}>
<Form onValidSubmit={this.onValidSubmit}>
<Input label={formatMessage({id: 'teacherRegistration.organization'})}
type="text"
name="organization.name"
@ -448,7 +466,7 @@ module.exports = {
return this.props.onNextStep(formData);
} else {
return invalidate({
'all': <FormattedMessage id="teacherRegistration.addressValidationError" />
'all': this.props.intl.formatMessage({id: 'teacherRegistration.addressValidationError'})
});
}
}.bind(this));
@ -508,6 +526,7 @@ module.exports = {
type="text"
name="address.zip"
required />
<GeneralError name="all" />
<NextStepButton waiting={this.props.waiting || this.state.waiting}
text={<intl.FormattedMessage id="teacherRegistration.nextStep" />} />
</Form>
@ -631,6 +650,7 @@ module.exports = {
equalsField: formatMessage({id: 'general.validationEmailMatch'})
}}
required />
<GeneralError name="all" />
<NextStepButton waiting={this.props.waiting}
text={<intl.FormattedMessage id="teacherRegistration.nextStep" />} />
</Form>
@ -693,7 +713,7 @@ module.exports = {
<Slide className="error-step">
<h2>Something went wrong</h2>
<Card>
<h2>There was an error while processing your registration</h2>
<h4>There was an error while processing your registration</h4>
<p>
{this.props.registrationError}
</p>

View file

@ -1,4 +1,5 @@
var classNames = require('classnames');
var connect = require('react-redux').connect;
var React = require('react');
var sessionActions = require('../../redux/session.js');
@ -34,11 +35,11 @@ var TeacherBanner = React.createClass({
<div className="welcome">
{this.props.session.status === sessionActions.Status.FETCHED ? (
this.props.session.session.user ? [
<h3>
<h3 key="greeting">
{this.props.messages['teacherbanner.greeting']},{' '}
{this.props.session.session.user.username}
</h3>,
<p>
<p key="subgreeting">
{this.props.messages['teacherbanner.subgreeting']}
</p>
] : []
@ -47,17 +48,17 @@ var TeacherBanner = React.createClass({
<FlexRow className="quick-links">
{this.props.session.status === sessionActions.Status.FETCHED ? (
this.props.session.session.user ? [
<a href="/educators/classes">
<a href="/educators/classes" key="classes-button">
<Button>
{this.props.messages['teacherbanner.classesButton']}
</Button>
</a>,
<a href="/info/educators">
<a href="/info/educators" key="resources-button">
<Button>
{this.props.messages['teacherbanner.resourcesButton']}
</Button>
</a>,
<a href="/info/educators/faq">
<a href="/educators/faq" key="faq-button">
<Button>
{this.props.messages['teacherbanner.faqButton']}
</Button>
@ -71,4 +72,12 @@ var TeacherBanner = React.createClass({
}
});
module.exports = TeacherBanner;
var mapStateToProps = function (state) {
return {
session: state.session
};
};
var ConnectedTeacherBanner = connect(mapStateToProps)(TeacherBanner);
module.exports = ConnectedTeacherBanner;

View file

@ -12,6 +12,7 @@
"general.copyright": "Scratch is a project of the Lifelong Kindergarten Group at the MIT Media Lab",
"general.country": "Country",
"general.create": "Create",
"general.createUsername": "Create a Username",
"general.credits": "Credits",
"general.discuss": "Discuss",
"general.dmca": "DMCA",
@ -92,6 +93,9 @@
"general.stories": "Stories",
"general.results": "Results",
"general.teacherAccounts": "Teacher Accounts",
"footer.discuss": "Discussion Forums",
"footer.help": "Help Page",
"footer.scratchFamily": "Scratch Family",

View file

@ -54,6 +54,16 @@ module.exports = function (opts, callback) {
}
xhr(opts, function (err, res, body) {
if (err) log.error(err);
if (opts.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
try {
body = JSON.parse(body);
} catch (e) {
// Not parseable anyway, don't worry about it
}
}
// Legacy API responses come as lists, and indicate to redirect the client like
// [{success: true, redirect: "/location/to/redirect"}]
try {

View file

@ -15,7 +15,8 @@ var Jar = {
// 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 (!value) return callback('No value to unsign');
if (typeof value === 'undefined') return callback(null, value);
try {
var b64Data = value.split(':')[0];
var decompress = false;
@ -78,8 +79,11 @@ var Jar = {
// Get a value from a signed object
Jar.get(cookieName, function (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);
try {
var data = JSON.parse(contents);
} catch (err) {

View file

@ -6,6 +6,7 @@ var ReactDOM = require('react-dom');
var 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');
@ -42,7 +43,8 @@ var render = function (jsx, element) {
element
);
// Get initial session
// Get initial session & permissions
store.dispatch(permissionsActions.getPermissions());
store.dispatch(sessionActions.refreshSession());
};

View file

@ -24,6 +24,8 @@ module.exports.getPermissions = function () {
return function (dispatch) {
jar.getUnsignedValue('scratchsessionsid', 'permissions', function (err, value) {
if (err) return dispatch(module.exports.setPermissionsError(err));
value = value || {};
return dispatch(module.exports.setPermissions(value));
});
};

View file

@ -2,6 +2,7 @@ var keyMirror = require('keymirror');
var defaults = require('lodash.defaults');
var api = require('../lib/api');
var permissionsActions = require('./permissions.js');
var tokenActions = require('./token.js');
var Types = keyMirror({
@ -75,6 +76,9 @@ module.exports.refreshSession = function () {
dispatch(tokenActions.getToken());
dispatch(module.exports.setSession(body));
dispatch(module.exports.setStatus(module.exports.Status.FETCHED));
// get the permissions from the updated session
dispatch(permissionsActions.getPermissions());
return;
}
}

View file

@ -27,6 +27,8 @@ module.exports.getToken = function () {
return function (dispatch) {
jar.getUnsignedValue('scratchsessionsid', 'token', function (err, value) {
if (err) return dispatch(module.exports.setTokenError(err));
value = value || '';
return dispatch(module.exports.setToken(value));
});
};

View file

@ -33,7 +33,7 @@ var ConferenceExpectations = React.createClass({
<p className="intro">
The Scratch community keeps growing and growing.{' '}
Young people around the world have shared more than{' '}
14 million projects in the Scratch online community{' '}
15 million projects in the Scratch online community{' '}
with 20,000 new projects every day.
</p>
<p className="intro">
@ -58,7 +58,7 @@ var ConferenceExpectations = React.createClass({
<p className="intro">
We are planning a very participatory conference, with lots of{' '}
hands-on workshops and opportunities for collaboration and sharing.{' '}
We hope youll join us. Lets learn together!
Lets learn together!
</p>
</div>
</FlexRow>

View file

@ -1,3 +1,4 @@
var classNames = require('classnames');
var injectIntl = require('react-intl').injectIntl;
var FormattedMessage = require('react-intl').FormattedMessage;
var React = require('react');
@ -17,7 +18,14 @@ require('./explore.scss');
var Explore = injectIntl(React.createClass({
type: 'Explore',
getDefaultProps: function () {
var categoryOptions = ['all','animations','art','games','music','stories'];
var categoryOptions = {
all: '*',
animations: 'animations',
art: 'art',
games: 'games',
music: 'music',
stories: 'stories'
};
var typeOptions = ['projects','studios'];
var pathname = window.location.pathname.toLowerCase();
@ -28,7 +36,7 @@ var Explore = injectIntl(React.createClass({
var currentCategory = pathname.substring(slash + 1,pathname.length);
var typeStart = pathname.indexOf('explore/');
var type = pathname.substring(typeStart + 8,slash);
if (categoryOptions.indexOf(currentCategory) === -1 || typeOptions.indexOf(type) === -1) {
if (Object.keys(categoryOptions).indexOf(currentCategory) === -1 || typeOptions.indexOf(type) === -1) {
window.location = window.location.origin + '/explore/projects/all/';
}
@ -50,14 +58,13 @@ var Explore = injectIntl(React.createClass({
this.getExploreMore();
},
getExploreMore: function () {
var qText = '';
if (this.props.tab != 'all') {
qText = '&q=' + this.props.category;
}
var qText = '&q=' + this.props.acceptableTabs[this.props.category] || '*';
api({
uri: '/search/' + this.props.itemType +
'?limit=' + this.props.loadNumber +
'&offset=' + this.state.offset +
'&language=' + this.props.intl.locale +
qText
}, function (err, body) {
if (!err) {
@ -80,34 +87,28 @@ var Explore = injectIntl(React.createClass({
window.location = window.location.origin + '/explore/' + newType + '/' + this.props.tab;
},
getBubble: function (type) {
var allBubble = <a href={'/explore/' + this.props.itemType + '/' + type + '/'}>
<li>
<FormattedMessage id={'general.' + type} />
</li>
</a>;
if (this.props.category === type) {
allBubble = <a href={'/explore/' + this.props.itemType+'/' + type + '/'}>
<li className='active'>
<FormattedMessage id={'general.' + type} />
</li>
</a>;
}
return allBubble;
var classes = classNames({
active: (this.props.category === type)
});
return (
<a href={'/explore/' + this.props.itemType + '/' + type + '/'}>
<li className={classes}>
<FormattedMessage id={'general.' + type} />
</li>
</a>
);
},
getTab: function (type) {
var allTab = <a href={'/explore/' + type + '/' + this.props.category + '/'}>
<li>
<FormattedMessage id={'general.' + type} />
</li>
</a>;
if (this.props.itemType === type) {
allTab = <a href={'/explore/' + type +' /' + this.props.category + '/'}>
<li className='active'>
<FormattedMessage id={'general.' + type} />
</li>
</a>;
}
return allTab;
var classes = classNames({
active: (this.props.itemType === type)
});
return (
<a href={'/explore/' + type + '/' + this.props.category + '/'}>
<li className={classes}>
<FormattedMessage id={'general.' + type} />
</li>
</a>
);
},
render: function () {
return (

View file

@ -121,7 +121,7 @@
"faq.cloudLagBody":"It depends. If both Scratchers have a reasonably fast Internet connection (DSL/Cable), and there are no restrictive firewalls on the computers/network, updates should be transmitted in milliseconds. However, a lot of computers have firewall software running in them, and if the firewall software blocks outgoing connections to TCP port 531 and TCP port 843, the time-lag becomes one-second. We are currently trying to figure out ways in which we can work around this limitation.",
"faq.schoolsTitle":"Scratch in Schools",
"faq.howTitle":"How is Scratch used in schools?",
"faq.howBody":"Scratch is used in thousands of schools around the world, in many different subject areas (including language arts, science, history, math, and computer science). You can learn more about strategies and resources for using Scratch in schools and other learning environments (such as museums, libraries, and community centers) on our <a href=\"/educators\">Educators Page</a>. You can also join the <a href=\"//scratched.gse.harvard.edu/\">ScratchEd</a> online community for educators, which is managed by our friends at the Harvard Graduate School of Education.",
"faq.howBody":"Scratch is used in thousands of schools around the world, in many different subject areas (including language arts, science, history, math, and computer science). You can learn more about strategies and resources for using Scratch in schools and other learning environments (such as museums, libraries, and community centers) on our <a href=\"/educators\">Educators Page</a>. You can also join the <a href=\"http://scratched.gse.harvard.edu/\">ScratchEd</a> online community for educators, which is managed by our friends at the Harvard Graduate School of Education.",
"faq.ageTitle":"What is the age range for Scratch?",
"faq.ageBody":"Scratch was developed especially for young people 8 to 16 years old, so it is used most often in elementary schools and middle schools, but people of all ages create and share Scratch projects. Scratch is even used in some introductory computer-science courses in colleges. Younger children may want to try <a href=\"//www.scratchjr.org/\">ScratchJr</a>, a simplified version of Scratch designed for ages 5 to 7.",
"faq.noInternetTitle":"Is there a way for students to use Scratch without an internet connection?",
@ -133,7 +133,7 @@
"faq.requestTitle":"How do I request a Scratch Teacher Account?",
"faq.requestBody":"You may request a Scratch Teacher Account from the <a href=\"/educators\">Educators Page</a> on Scratch. We ask for additional information during the registration process in order to verify your role as an educator.",
"faq.edTitle":"What is the difference between a Scratch Teacher Account and a ScratchEd Account?",
"faq.edBody":"Scratch Teacher Accounts are special user accounts on Scratch that have access to additional features to facilitate the creation and management of student accounts. ScratchEd Accounts are accounts on the <a href=\"//scratched.gse.harvard.edu/\">ScratchEd community</a>, a separate website (managed by the Harvard Graduate School of Education) where educators share stories, exchange resources, ask questions, and meet other Scratch educators.",
"faq.edBody":"Scratch Teacher Accounts are special user accounts on Scratch that have access to additional features to facilitate the creation and management of student accounts. ScratchEd Accounts are accounts on the <a href=\"http://scratched.gse.harvard.edu/\">ScratchEd community</a>, a separate website (managed by the Harvard Graduate School of Education) where educators share stories, exchange resources, ask questions, and meet other Scratch educators.",
"faq.dataTitle":"What data does Scratch collect about students?",
"faq.dataBody":"When a student first signs up on Scratch, we ask for basic demographic data including gender, age (birth month and year), country, and an email address for verification. This data is used (in aggregated form) in research studies intended to improve our understanding of how people learn with Scratch. When an educator uses a Scratch Teacher Account to create student accounts in bulk, no student demographic data is required for account setup.",
"faq.schoolsMoreInfo":"For more more questions about Teacher Accounts, see the <a href=\"/educators/faq\">Teacher Account FAQ</a>"

View file

@ -61,6 +61,7 @@ var Search = injectIntl(React.createClass({
uri: '/search/' + this.props.tab +
'?limit=' + this.props.loadNumber +
'&offset=' + this.state.offset +
'&language=' + this.props.intl.locale +
termText
}, function (err, body) {
var loadedSoFar = this.state.loaded;

View file

@ -4,7 +4,6 @@ var omit = require('lodash.omit');
var React = require('react');
var api = require('../../lib/api');
var permissionsActions = require('../../redux/permissions.js');
var render = require('../../lib/render.jsx');
var sessionActions = require('../../redux/session.js');
var shuffle = require('../../lib/shuffle.js').shuffle;
@ -62,10 +61,6 @@ var Splash = injectIntl(React.createClass({
}
}
},
componentWillMount: function () {
// Determine whether to show the teacher banner or not
this.props.dispatch(permissionsActions.getPermissions());
},
componentDidMount: function () {
this.getFeaturedGlobal();
if (this.props.session.session.user) {
@ -382,7 +377,7 @@ var Splash = injectIntl(React.createClass({
</Modal>
] : []}
{this.props.permissions.educator ? [
<TeacherBanner messages={messages} />
<TeacherBanner key="teacherbanner" messages={messages} />
] : []}
<div key="inner" className="inner">
{this.props.session.status === sessionActions.Status.FETCHED ? (

View file

@ -20,6 +20,7 @@
"teacherRegistration.phoneNumber": "Phone Number",
"teacherRegistration.phoneStepDescription": "Your phone number will not be displayed publicly, and will be kept confidential and secure",
"teacherRegistration.phoneConsent": "Yes, the Scratch Team may call me to verify my Teacher Account if needed",
"teacherRegistration.validationPhoneNumber": "Please enter a valid phone number",
"teacherRegistration.validationPhoneConsent": "You must consent to verification of your Teacher Account",
"teacherRegistration.orgStepTitle": "Organization",
"teacherRegistration.orgStepDescription": "Your information will not be displayed publicly, and will be kept confidential and secure",
@ -38,7 +39,7 @@
"teacherRegistration.orgChoiceOther": " ",
"teacherRegistration.notRequired": "Not Required",
"teacherRegistration.selectCountry": "select country",
"teacherRegistration.validationAddress": "This doesn't look like a real address",
"teacherRegistration.addressValidationError": "This doesn't look like a real address",
"teacherRegistration.addressLine1": "Address Line 1",
"teacherRegistration.addressLine2": "Address Line 2 (Optional)",
"teacherRegistration.zipCode": "ZIP",
@ -52,5 +53,6 @@
"teacherRegistration.howUseScratch": "How do you plan to use Scratch at your organization?",
"teacherRegistration.emailStepTitle": "Email Address",
"teacherRegistration.emailStepDescription": "We will send you a confirmation email that will allow you to access your Scratch Teacher Account.",
"teacherRegistration.validationEmailMatch": "The emails do not match"
"teacherRegistration.validationEmailMatch": "The emails do not match",
"teacherRegistration.validationRequired": "This field is required"
}

View file

@ -30,7 +30,7 @@ body {
.address-step,
.email-step {
.help-block {
transform: translate(15.5rem, -4rem);
transform: translate(15.75rem, -4rem);
}
}
@ -40,7 +40,7 @@ body {
}
.help-block {
transform: translate(14rem, -4rem);
transform: translate(13rem, -2rem);
}
.radio {
@ -65,7 +65,7 @@ body {
}
.help-block {
margin-top: .7rem;
margin-top: .5rem;
}
.checkbox-row {
@ -80,6 +80,12 @@ body {
transform: translate(16rem, -4rem);
}
.checkbox-group {
.help-block {
transform: translate(16rem, -16rem);
}
}
.organization-type,
.url-input {
p {
@ -100,6 +106,14 @@ body {
}
}
.address-step {
.select {
.help-block {
transform: translate(0, .5rem);
}
}
}
.usescratch-step {
.form {
.form-group {
@ -128,7 +142,8 @@ body {
}
}
.last-step {
.last-step,
.error-step {
&.slide {
max-width: 38.75rem;
}
@ -159,7 +174,8 @@ body {
}
}
.last-step {
.last-step,
.error-step {
.card {
margin: 0 auto;
width: 18.75rem;

View file

@ -9,7 +9,7 @@
"teacherfaq.teacherGoogleTitle": "Are teacher accounts integrated with Google Classroom or any other classroom managment service?",
"teacherfaq.teacherGoogleBody": "Scratch Teacher accounts are not integrated with any classroom management services.",
"teacherfaq.teacherEdTitle": "Are ScratchEd & Scratch Teacher accounts the same thing?",
"teacherfaq.teacherEdBody": "<a href=\"//scratched.gse.harvard.edu/\">ScratchEd</a> accounts are not linked to Scratch Teacher accounts.",
"teacherfaq.teacherEdBody": "<a href=\"http://scratched.gse.harvard.edu/\">ScratchEd</a> accounts are not linked to Scratch Teacher accounts.",
"teacherfaq.teacherWaitTitle": "Why do I have to wait 24 hours for my account?",
"teacherfaq.teacherWaitBody": "The Scratch Team uses this time to manually review account creation submissions to verify the account creator is an educator.",
"teacherfaq.teacherPersonalTitle": "Why do you need to know my personal information during registration?",
@ -33,7 +33,7 @@
"teacherfaq.studentMultipleTitle": "Can a student be in multiple classes? ",
"teacherfaq.studentMultipleBody": "A student can only be a part of one class. However, we are looking into adding this functionality in later versions.",
"teacherfaq.studentDiscussTitle": "Is there a space to discuss Teacher Accounts with other teachers?",
"teacherfaq.studentDiscussBody": "Yes, you can engage in discussions with other teachers at <a href=\"//scratched.gse.harvard.edu/\">ScratchEd</a>, an online community for Scratch educators. Check out their forums to join conversations about a <a href=\"//scratched.gse.harvard.edu/discussions\">number of topics</a>, including but not limited to Teacher Accounts. ScratchEd is developed and supported by the Harvard Graduate School of Education.",
"teacherfaq.studentDiscussBody": "Yes, you can engage in discussions with other teachers at <a href=\"http://scratched.gse.harvard.edu/\">ScratchEd</a>, an online community for Scratch educators. Check out their forums to join conversations about a <a href=\"http://scratched.gse.harvard.edu/discussions\">number of topics</a>, including but not limited to Teacher Accounts. ScratchEd is developed and supported by the Harvard Graduate School of Education.",
"teacherfaq.commTitle": "Community",
"teacherfaq.commHiddenTitle": "Can I create a hidden class?",

View file

@ -0,0 +1,26 @@
{
"teacherlanding.title": "Scratch for Educators",
"teacherlanding.intro": "Your students can use Scratch to code their own interactive stories, animations, and games. In the process, they learn to think creatively, reason systematically, and work collaboratively — essential skills for everyone in todays society.",
"teacherlanding.inPracticeAnchor": "In Practice",
"teacherlanding.resourcesAnchor": "Resources",
"teacherlanding.inPracticeTitle": "Who Uses Scratch?",
"teacherlanding.inPracticeIntro": "Educators are using Scratch in a wide variety of: ",
"teacherlanding.generalUsageSettings": "<b>Settings:</b> schools, museums, libraries, community centers",
"teacherlanding.generalUsageGradeLevels": "<b>Grade Levels:</b> elementary, middle, and high school (and some colleges too!)",
"teacherlanding.generalUsageSubjectAreas": "<b>Subject Areas:</b> language arts, science, social studies, math, computer science, foreign languages, and the arts",
"teacherlanding.ingridTitle": "Instructional Technology Specialist",
"teacherlanding.dylanTitle": "Educational Technologist",
"teacherlanding.afterSchoolTitle": "After-School Program",
"teacherlanding.resourcesTitle": "Educator Resources",
"teacherlanding.scratchEdTitle": "A Community for Educators",
"teacherlanding.scratchEdDescription": "<a href=\"http://scratched.gse.harvard.edu/\">ScratchEd</a> is an online community where Scratch educators <a href=\"http://scratched.gse.harvard.edu/stories\">share stories</a>, exchange resources, ask questions, and find people. ScratchEd is developed and supported by the Harvard Graduate School of Education.",
"teacherlanding.meetupTitle": "In-Person Gatherings",
"teacherlanding.meetupDescription": "<a href=\"http://www.meetup.com/pro/scratched/\">Scratch Educator Meetups</a> from each other, sharing their ideas and strategies for supporting computational creativity in all its forms.",
"teacherlanding.guidesTitle": "Guides & Tutorials",
"teacherlanding.helpPage": "On the <a href=\"/help\">Help Page</a>, you can find workshop guides, Scratch Cards, videos, and other resources.",
"teacherlanding.tipsWindow" : "The <a href=\"/projects/editor/?tip_bar=home\">Tips Window</a> features step-by-step tutorials for getting started in Scratch.",
"teacherlanding.creativeComputing": "The <a href=\"http://scratched.gse.harvard.edu/guide/\">Creative Computing Curriculum Guide</a> provides plans, activities, and strategies for introducing creative computing.",
"teacherlanding.accountsTitle": "Teacher Accounts in Scratch",
"teacherlanding.accountsDescription": "As an educator, you can request a Scratch Teacher Account, which makes it easier to create accounts for groups of students and to manage your students projects and comments. To learn more, see the <a href=\"/educators/faq\">Teacher Account FAQ page</a>.",
"teacherlanding.accountsButton": "Coming Soon"
}

View file

@ -1,49 +1,50 @@
var React = require('react');
var render = require('../../../lib/render.jsx');
var FormattedHTMLMessage = require('react-intl').FormattedHTMLMessage;
var FormattedMessage = require('react-intl').FormattedMessage;
var injectIntl = require('react-intl').injectIntl;
var Page = require('../../../components/page/www/page.jsx');
var FlexRow = require('../../../components/flex-row/flex-row.jsx');
var SubNavigation = require('../../../components/subnavigation/subnavigation.jsx');
var TitleBanner = require('../../../components/title-banner/title-banner.jsx');
var Button = require('../../../components/forms/button.jsx');
require('./landing.scss');
var Landing = React.createClass({
var Landing = injectIntl(React.createClass({
type: 'Landing',
render: function () {
return (
<div className="educators">
<TitleBanner className="masthead">
<div className="inner">
<h1>Scratch for Educators</h1>
<h1><FormattedMessage id="teacherlanding.title" /></h1>
<FlexRow className="masthead-info">
<p className="intro">
Your students can use Scratch to code their own{' '}
interactive stories, animations, and games.{' '}
In the process, they learn to think creatively,{' '}
reason systematically, and work{' '}
collaboratively essential skills for everyone in todays society.
<FormattedMessage id="teacherlanding.intro" />
</p>
<iframe src="https://www.youtube.com/embed/uPSuG063jhA"
frameborder="0" allowfullscreen></iframe>
<div className="ted-talk">
<iframe src="https://www.youtube.com/embed/uPSuG063jhA?border=0&wmode=transparent"
frameBorder="0" allowFullScreen></iframe>
</div>
</FlexRow>
</div>
<div className="band">
<SubNavigation className="inner">
<a href="#in-practice">
<li>
In Practice
<FormattedMessage id="teacherlanding.inPracticeAnchor" />
</li>
</a>
<a href="#resources">
<li>
Resources
<FormattedMessage id="teacherlanding.resourcesAnchor" />
</li>
</a>
<a href="#teacher-accounts">
<li>
Teacher Accounts
<FormattedMessage id="general.teacherAccounts" />
</li>
</a>
</SubNavigation>
@ -53,96 +54,81 @@ var Landing = React.createClass({
<div className="inner">
<section id="in-practice">
<span className="nav-spacer"></span>
<h2>Who Uses Scratch?</h2>
<p className="intro">Educators are using Scratch in a wide variety of: </p>
<h2><FormattedMessage id="teacherlanding.inPracticeTitle" /></h2>
<p className="intro"><FormattedMessage id="teacherlanding.inPracticeIntro" /></p>
<FlexRow className="general-usage">
<p><b>Settings:</b> schools, museums, libraries, community centers</p>
<p><b>Grade Levels:</b> elementary, middle, and high school (and some colleges too!)</p>
<p><b>Subject Areas:</b> language arts, science, social studies,{' '}
math, computer science, foreign languages, and the arts</p>
<p><FormattedHTMLMessage id="teacherlanding.generalUsageSettings" /></p>
<p><FormattedHTMLMessage id="teacherlanding.generalUsageGradeLevels" /></p>
<p><FormattedHTMLMessage id="teacherlanding.generalUsageSubjectAreas"/></p>
</FlexRow>
<FlexRow className="stories">
<div className="story">
<img src="/images/teachers/stories/ingrid.jpg" alt="ingrid's story" />
<div className="story-info">
<a href="//bit.ly/28SBsa9">Ingrid Gustafson</a>
<p>Instructional Technology Specialist</p>
</div>
</div>
<div className="story">
<a href="//bit.ly/28SBsa9" className="story">
<img src="/images/teachers/stories/ingrid.jpg" alt="ingrid's story" />
<div className="story-info">
<p className="name">Ingrid Gustafson</p>
<p><FormattedMessage id="teacherlanding.ingridTitle" /></p>
</div>
</a>
<a href="//bit.ly/28Q5l6P" className="story">
<img src="/images/teachers/stories/dylan.jpg" alt="dylan's story" />
<div className="story-info">
<a href="//bit.ly/28Q5l6P">Dylan Ryder</a>
<p>Educational Technologist</p>
<p className="name">Dylan Ryder</p>
<p><FormattedMessage id="teacherlanding.dylanTitle" /></p>
</div>
</div>
<div className="story">
</a>
<a href="//bit.ly/28SC1AY" className="story">
<img src="/images/teachers/stories/plug-in-studio.jpg"
alt="plug in studio's story" />
<div className="story-info">
<a href="//bit.ly/28SC1AY">Plug-In Studios</a>
<p>After-School Program</p>
<p className="name">Plug-In Studios</p>
<p><FormattedMessage id="teacherlanding.afterSchoolTitle" /></p>
</div>
</div>
<div className="story">
</a>
<a href="//bit.ly/28UzapJ" className="story">
<img src="/images/teachers/stories/ghana-code-club.jpg"
alt="ghana code club's story" />
<div className="story-info">
<a href="//bit.ly/28UzapJ">Ghana Code Club</a>
<p>After-School Program</p>
<p className="name">Ghana Code Club</p>
<p><FormattedMessage id="teacherlanding.afterSchoolTitle" /></p>
</div>
</div>
</a>
</FlexRow>
</section>
<section id="resources">
<span className="nav-spacer"></span>
<h2>Educator Resources</h2>
<h2><FormattedMessage id="teacherlanding.resourcesTitle" /></h2>
<FlexRow className="educator-community">
<div>
<h3>A Community for Educators</h3>
<h3><FormattedMessage id="teacherlanding.scratchEdTitle" /></h3>
<p>
<a href="http://scratched.gse.harvard.edu/">ScratchEd</a> is an{' '}
online community where Scratch educators{' '}
<a href="http://scratched.gse.harvard.edu/stories">share stories</a>,
exchange resources, ask questions, and{' '}
find people. ScratchEd is developed and supported by{' '}
the Harvard Graduate School of Education.
<FormattedHTMLMessage id="teacherlanding.scratchEdDescription" />
</p>
</div>
<div>
<h3>In-Person Gatherings</h3>
<h3><FormattedMessage id="teacherlanding.meetupTitle" /></h3>
<p>
<a href="http://www.meetup.com/pro/scratched/">Scratch Educator Meetups</a>{' '}
are gatherings of Scratch Educators who want to learn with and{' '}
from each other, sharing their ideas and strategies{' '}
for supporting computational creativity in all its forms.
<FormattedHTMLMessage id="teacherlanding.meetupDescription" />
</p>
</div>
</FlexRow>
<h3 id="guides-header">Guides & Tutorials</h3>
<h3 id="guides-header"><FormattedMessage id="teacherlanding.guidesTitle" /></h3>
<FlexRow className="guides-and-tutorials">
<div>
<img src="/svgs/teachers/resources.svg" alt="resources icon" />
<p>
On the <a href="https://scratch.mit.edu/help/">Help Page</a>,{' '}
you can find workshop guides, Scratch Cards, videos, and other resources.
<FormattedHTMLMessage id="teacherlanding.helpPage" />
</p>
</div>
<div>
<img src="/svgs/teachers/tips-window.svg" alt="tips window icon" />
<p>
The{' '}
<a href="https://scratch.mit.edu/projects/editor/?tip_bar=home">Tips Window</a>{' '}
features step-by-step tutorials for getting started in Scratch.
<FormattedHTMLMessage id="teacherlanding.tipsWindow" />
</p>
</div>
<div>
<img src="/svgs/teachers/creative-computing.svg" alt="creative computing icon" />
<p>
The <a href="http://scratched.gse.harvard.edu/guide/">Creative Computing{' '}
Curriculum Guide</a>{' '}
provides plans, activities, and{' '}
strategies for introducing creative computing.
<FormattedHTMLMessage id="teacherlanding.creativeComputing" />
</p>
</div>
</FlexRow>
@ -151,15 +137,11 @@ var Landing = React.createClass({
<div id="teacher-accounts">
<div className="inner account-flex">
<div id="left">
<h2>Teacher Accounts in Scratch</h2>
<h2><FormattedMessage id="teacherlanding.accountsTitle" /></h2>
<p>
As an educator, you can request a Scratch Teacher Account,{' '}
which makes it easier to create accounts for{' '}
groups of students and to manage your students{' '}
projects and comments. To learn more, see the{' '}
<a href="faq">Teacher Account FAQ page</a>.
<FormattedHTMLMessage id="teacherlanding.accountsDescription" />
</p>
<a href="register"><Button>Request Account</Button></a>
<div className="coming-soon"><FormattedMessage id="teacherlanding.accountsButton" /></div>
</div>
<img src="/images/teachers/teacher-account.png" alt="teacher account" id="teacher-icon"/>
</div>
@ -167,6 +149,6 @@ var Landing = React.createClass({
</div>
);
}
});
}));
render(<Page><Landing /></Page>, document.getElementById('app'));

View file

@ -48,12 +48,21 @@ $story-width: $cols3;
color: $ui-white;
}
}
}
.ted-talk {
position: relative;
margin-bottom: $gutter;
border: 2px solid $ui-border;
border-radius: 10px;
width: $cols4;
height: $cols4 * .5625;
overflow: hidden;
iframe {
margin-bottom: $gutter;
border: 2px solid $ui-border;
border-radius: 10px;
width: $cols4;
border: 0;
width: inherit;
height: inherit;
}
}
@ -150,6 +159,14 @@ $story-width: $cols3;
padding-top: 10px;
padding-left: 10px;
.name {
margin: 0;
line-height: initial;
color: $ui-blue;
font-size: initial;
font-weight: 500;
}
p {
margin: 10px 0;
font-size: .75rem;
@ -213,13 +230,22 @@ $story-width: $cols3;
margin-bottom: 3.5rem;
}
button {
background-color: $ui-white;
.coming-soon {
border: 2px solid $ui-white;
border-radius: 50px;
box-shadow: none;
background-color: transparent;
padding: 16px 16px;
width: $cols5 / 2;
color: $ui-blue;
text-align: center;
color: $ui-white;
font-size: 16px;
font-weight: 500;
box-sizing: border-box;
&:hover {
box-shadow: none;
}
}
}

View file

@ -116,7 +116,8 @@ module.exports = {
'process.env.NODE_ENV': '"' + (process.env.NODE_ENV || 'development') + '"',
'process.env.SENTRY_DSN': '"' + (process.env.SENTRY_DSN || '') + '"',
'process.env.API_HOST': '"' + (process.env.API_HOST || 'https://api.scratch.mit.edu') + '"',
'process.env.SMARTY_STREETS_API_KEY': '"' + (process.env.SMARTY_STREETS_API_KEY || '') + '"'
'process.env.SMARTY_STREETS_API_KEY': '"' + (process.env.SMARTY_STREETS_API_KEY || '') + '"',
'process.env.SCRATCH_ENV': '"'+ (process.env.SCRATCH_ENV || 'development') + '"'
}),
new webpack.optimize.CommonsChunkPlugin('common', 'js/common.bundle.js'),
new webpack.optimize.OccurenceOrderPlugin()