up to date

This commit is contained in:
LiFaytheGoblin 2018-01-15 20:23:24 +01:00
commit 10fb7e032a
120 changed files with 2471 additions and 801 deletions
.github
.gitignore.travis.yml
.tx
README.md
bin
package.json
src
static/images

View file

@ -11,7 +11,7 @@ We are always excited to have people join us in working to make Scratch a wonder
* [README](https://github.com/LLK/scratch-www/blob/develop/README.md) (if youre to read only one me in this repo, make it this one it has all of the necessary information for getting a local Scratch UI running on your machine!)
* [Community Guidelines](https://github.com/LLK/scratch-www/wiki/Community-Guidelines) (we find it important to maintain a constructive and welcoming community, just like on Scratch)
* [Issues](https://github.com/LLK/scratch-www/issues) where we keep track of all the things that need fixin on the website
Road map
Roadmap
Beyond this repo, there are also some other resources that you might want to take a look at:
[Scratch](https://scratch.mit.edu/) (the thing we work on)

12
.github/ISSUE_TEMPLATE.md vendored Normal file
View file

@ -0,0 +1,12 @@
_Describe the problem. Please make sure you can reproduce the bug. It's also
helpful to include screenshots or animated gifs of the problem._
### Steps to Reproduce
_Explain what someone needs to do in order to see what's described above. The
more detailed and explicit the steps are the better._
### Operating System and Browser
_e.g. Mac OS 10.11.6 Safari 10.0_

11
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View file

@ -0,0 +1,11 @@
### Resolves:
_What Github issue does this resolve (please include link)? Please do not submit PRs that only partially implement an issue. Please do not submit PRs that are not associated with a Github issue, or that only partially implement an issue._
### Changes:
_Describe what this Pull Request does. Be sure to include a brief description of the issue the Pull Requests solves too._
### Test Coverage:
_Please show how you have added tests to cover your changes or describe how you have tested the changes (include a screenshot if possible)._

1
.gitignore vendored
View file

@ -22,3 +22,4 @@ ENV
# Test
/.nyc_output
/coverage
/bin/lib/localized-urls.json

View file

@ -1,7 +1,8 @@
language: node_js
node_js:
- '4.2'
- '8'
sudo: required
group: deprecated-2017Q4
cache:
directories:
- /home/travis/virtualenv/python2.7/lib/python2.7/site-packages

View file

@ -92,8 +92,8 @@ source_file = src/views/microworldshomepage/l10n.json
source_lang = en
type = KEYVALUEJSON
[scratch-website.conference-index-l10njson]
file_filter = localizations/conference-index/<lang>.json
[scratch-website.conference-index-2017-l10njson]
file_filter = localizations/conference-index-2017/<lang>.json
source_file = src/views/conference/2017/index/l10n.json
source_lang = en
type = KEYVALUEJSON
@ -121,3 +121,15 @@ file_filter = localizations/camp/<lang>.json
source_file = src/views/camp/l10n.json
source_lang = en
type = KEYVALUEJSON
[scratch-website.messages-l10njson]
file_filter = localizations/messages/<lang>.json
source_file = src/views/messages/l10n.json
source_lang = en
type = KEYVALUEJSON
[scratch-website.conference-index-l10njson]
file_filter = localizations/conference-index/<lang>.json
source_file = src/views/conference/2018/index/l10n.json
source_lang = en
type = KEYVALUEJSON

View file

@ -49,11 +49,11 @@ These currently exist in static/js/lib
npm start
```
During development, `npm start` watches any update you make to files in either `./static` or `./src` and triggers a rebuild of the project. In development the build is stored in memory, and not served from the `./build` directory.
During development, `npm start` watches any update you make to files in either `./static` or `./src` and triggers a rebuild of the project. In development, the build is stored in memory, and not served from the `./build` directory.
When running `npm start`, here are some important log messages to keep an eye out for:
* `webpack: bundle is now VALID.` the bundle has been loaded into memory and is now viewable in the browser. This will show up both once `npm start` has completed its setup, and also once updates you make to files have been re-compiled for viewing in the browser.
* `webpack: bundle is now INVALID.` if you see this, then it means you have made updates to files that are still being compiled for browser viewing. Pages will still be viewable, but they will not see any updates you made yet.
* `webpack: bundle is now VALID.` The bundle has been loaded into memory and is now viewable in the browser. This will show up both once `npm start` has completed its setup, and also once updates you make to files have been re-compiled for viewing in the browser.
* `webpack: bundle is now INVALID.` If you see this, then it means you have made updates to files that are still being compiled for browser viewing. Pages will still be viewable, but they will not see any updates you made yet.
Once running, open `http://localhost:8333` in your browser. If you wish to have the server reload automatically, you can install either [nodemon](https://github.com/remy/nodemon) or [forever](https://github.com/foreverjs/forever).
@ -105,7 +105,7 @@ We're currently in the process of transitioning into this web client from Scratc
#### FALLBACK
On top of migrating to using this as our web client, Scratch is also transitioning into using a new API backend, Scratch REST API. As that is also currently in development and incomplete, we are set up to fall back to using existing Scratch endpoints if an API endpoint does not exist which is where the `FALLBACK` comes in.
Most of the issues we have currently revolve around the use of `FALLBACK`. This variable is used to specify what url to fall back onto should a request fail within the context of this webclient, or when using the `API_HOST`. If not specified in the process, it will not be used, and any request that is not made through the web client or the API will be unreachable.
Most of the issues we have currently revolve around the use of `FALLBACK`. This variable is used to specify what URL to fall back onto should a request fail within the context of this web client, or when using the `API_HOST`. If not specified in the process, it will not be used, and any request that is not made through the web client or the API will be unreachable.
Setting `FALLBACK=https://scratch.mit.edu` allows the web client to retrieve data from the Scratch website in your development environment. However, because of security concerns, trying to send data to Scratch through your development environment won't work. This means the following things will be broken for the time being:
* Login on the splash page (*In the process of being fixed*)
@ -114,4 +114,4 @@ Setting `FALLBACK=https://scratch.mit.edu` allows the web client to retrieve dat
Additionally, if you set `FALLBACK=https://scratch.mit.edu`, be aware that clicking on links to parts of the website not yet migrated over (currently such as `Explore`, `Discuss`, `Profile`, etc.) will take you to the Scratch website itself.
#### Windows
Some users have experienced difficulties when trying to get our web client to work on Windows. One solution could be to use [Cygwin](https://www.cygwin.com/). If that doesn't work, you might want to use [Wubi](https://wiki.ubuntu.com/WubiGuide) (Windows XP, Vista, 7) or [Wubiuefi](https://github.com/hakuna-m/wubiuefi) (Windows 8 or higher). Wubi(uefi) is a Windows Installer for Ubuntu that allows you to have Ubuntu and Windows on one disk, without the need of an extra partition.
Some users have experienced difficulties when trying to get our web client to work on Windows. One solution could be to use [Cygwin](https://www.cygwin.com/). If that doesn't work, you might want to use [Wubi](https://wiki.ubuntu.com/WubiGuide) (Windows XP, Vista, 7) or [Wubiuefi](https://github.com/hakuna-m/wubiuefi) (Windows 8 or higher). Wubi(uefi) is a Windows Installer for Ubuntu that allows you to have Ubuntu and Windows on one disk, without the need of an extra partition.

View file

@ -1,6 +1,7 @@
var async = require('async');
var defaults = require('lodash.defaults');
var fastlyConfig = require('./lib/fastly-config-methods');
const languages = require('../languages.json');
var route_json = require('../src/routes.json');
@ -41,23 +42,44 @@ async.auto({
// on any of those route conditions.
var notPassStatement = fastlyConfig.getAppRouteCondition('../build/*', routes, extraAppRoutes, __dirname);
// For all the routes in routes.json, construct a varnish-style regex that matches
// only if NONE of those routes are matched.
var passStatement = fastlyConfig.negateConditionStatement(notPassStatement);
// For a non-pass condition, point backend at s3
var backendCondition = fastlyConfig.setBackend(
'F_s3',
S3_BUCKET_NAME,
notPassStatement
);
// For a pass condition, set forwarding headers
var forwardCondition = fastlyConfig.setForwardHeaders(passStatement);
var recvCondition = '' +
'if (' + notPassStatement + ') {\n' +
' set req.backend = F_s3;\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' +
' } else {\n' +
' set req.http.Fastly-Temp-XFF = client.ip;\n' +
' }\n' +
' } else {\n' +
' set req.http.Fastly-Temp-XFF = req.http.X-Forwarded-For;\n' +
' }\n' +
' set req.grace = 60s;\n' +
' if (req.http.Cookie:scratchlanguage) {\n' +
' set req.http.Accept-Language = req.http.Cookie:scratchlanguage;\n' +
' } else {\n' +
' set req.http.Accept-Language = accept.language_lookup("' +
Object.keys(languages).join(':') + '", ' +
'"en", ' +
'std.tolower(req.http.Accept-Language)' +
');\n' +
' }\n' +
' if (req.url ~ "^(/projects/|/fragment/account-nav.json|/session/)" && ' +
'!req.http.Cookie:scratchsessionsid) {\n' +
' set req.http.Cookie = "scratchlanguage=" req.http.Cookie:scratchlanguage;\n' +
' } else {\n' +
' return(pass);\n' +
' }\n' +
'}\n';
fastly.setCustomVCL(
results.version,
'recv-condition',
backendCondition + forwardCondition,
recvCondition,
cb
);
}],
@ -156,10 +178,12 @@ async.auto({
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.purgeAll(FASTLY_SERVICE_ID, function (err) {
if (err) throw new Error(err);
process.stdout.write('Purged all.\n');
});
if (process.env.FASTLY_PURGE_ALL) {
fastly.purgeAll(FASTLY_SERVICE_ID, function (err) {
if (err) throw new Error(err);
process.stdout.write('Purged all.\n');
});
}
});
}
}

View file

@ -36,7 +36,7 @@ https.get('https://resources.scratch.mit.edu/localized-urls.json', (res) => {
res.on('end', () => {
try {
var urlJson = JSON.parse(urlData);
fs.writeFile(filename, JSON.stringify(urlJson, null, ' '));
fs.writeFileSync(filename, JSON.stringify(urlJson, null, ' '));
} catch (e) {
process.stdout.write(`Failed parsing url data: ${e.message}\n`);
process.exit(1);

View file

@ -84,45 +84,6 @@ var FastlyConfigMethods = {
return 'redirects/' + route.pattern;
},
/**
* Returns custom vcl configuration as a string for setting the backend
* of a request to the given backend/host.
*
* @param {string} backend name of the backend declared in fastly
* @param {string} host name of the s3 bucket to be set as the host
* @param {string} condition condition under which backend should be set
*/
setBackend: function (backend, host, condition) {
return '' +
'if (' + condition + ') {\n' +
' set req.backend = ' + backend + ';\n' +
' set req.http.host = \"' + host + '\";\n' +
'}\n';
},
/**
* Returns custom vcl configuration as a string for headers that
* should be added for the condition in which a request is forwarded.
*
* @param {string} condition condition under which to set pass headers
*/
setForwardHeaders: function (condition) {
return '' +
'if (' + condition + ') {\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' +
' } else {\n' +
' set req.http.Fastly-Temp-XFF = client.ip;\n' +
' }\n' +
' } else {\n' +
' set req.http.Fastly-Temp-XFF = req.http.X-Forwarded-For;\n' +
' }\n' +
' set req.grace = 60s;\n' +
' return(pass);\n' +
'}\n';
},
/**
* Returns custom vcl configuration as a string that sets the varnish
* Time to Live (TTL) for responses that come from s3.
@ -132,9 +93,16 @@ var FastlyConfigMethods = {
setResponseTTL: function (condition) {
return '' +
'if (' + condition + ') {\n' +
' set beresp.ttl = 0s;\n' +
' set beresp.grace = 0s;\n' +
' return(pass);\n' +
' if (req.url ~ "^(/projects/|/fragment/account-nav.json|/session/)" && ' +
'!req.http.Cookie:scratchsessionsid) {\n' +
' set beresp.http.Vary = "Accept-Encoding, Accept-Language";\n' +
' unset beresp.http.set-cookie;\n' +
' return(deliver);\n' +
' } else {\n' +
' set beresp.ttl = 0s;\n' +
' set beresp.grace = 0s;\n' +
' return(pass);\n' +
' }\n' +
'}\n';
}
};

View file

@ -1,113 +0,0 @@
{
"ar": {
"cards.Scratch2CardsLink": "https://resources.scratch.mit.edu/www/cards/ar/Scratch2Cards.pdf"
},
"ca": {
"cards.Scratch2CardsLink": "https://resources.scratch.mit.edu/www/cards/ca/Scratch2Cards.pdf",
"cards.ScratchCardsAllLink": "https://resources.scratch.mit.edu/www/cards/ca/ScratchCardsAll.pdf",
"cards.catchCardsLink": "https://resources.scratch.mit.edu/www/cards/ca/catchCards.pdf",
"cards.danceCardsLink": "https://resources.scratch.mit.edu/www/cards/ca/danceCards.pdf",
"cards.fashionCardsLink": "https://resources.scratch.mit.edu/www/cards/ca/fashionCards.pdf",
"cards.flyCardsLink": "https://resources.scratch.mit.edu/www/cards/ca/flyCards.pdf",
"cards.hide-seekCardsLink": "https://resources.scratch.mit.edu/www/cards/ca/hide-seekCards.pdf",
"cards.musicCardsLink": "https://resources.scratch.mit.edu/www/cards/ca/musicCards.pdf",
"cards.nameCardsLink": "https://resources.scratch.mit.edu/www/cards/ca/nameCards.pdf",
"cards.petCardsLink": "https://resources.scratch.mit.edu/www/cards/ca/petCards.pdf",
"cards.pongCardsLink": "https://resources.scratch.mit.edu/www/cards/ca/pongCards.pdf",
"cards.raceCardsLink": "https://resources.scratch.mit.edu/www/cards/ca/raceCards.pdf",
"cards.storyCardsLink": "https://resources.scratch.mit.edu/www/cards/ca/storyCards.pdf"
},
"cs": {
"cards.Scratch2CardsLink": "https://resources.scratch.mit.edu/www/cards/cs/Scratch2Cards.pdf"
},
"de": {
"cards.Scratch2CardsLink": "https://resources.scratch.mit.edu/www/cards/de/Scratch2Cards.pdf"
},
"en": {
"cards.Scratch2CardsLink": "https://resources.scratch.mit.edu/www/cards/en/Scratch2Cards.pdf",
"cards.ScratchCardsAllLink": "https://resources.scratch.mit.edu/www/cards/en/ScratchCardsAll.pdf",
"cards.catchCardsLink": "https://resources.scratch.mit.edu/www/cards/en/catchCards.pdf",
"cards.danceCardsLink": "https://resources.scratch.mit.edu/www/cards/en/danceCards.pdf",
"cards.dressupCardsLink": "https://resources.scratch.mit.edu/www/cards/en/dressupCards.pdf",
"cards.fashionCardsLink": "https://resources.scratch.mit.edu/www/cards/en/fashionCards.pdf",
"cards.flyCardsLink": "https://resources.scratch.mit.edu/www/cards/en/flyCards.pdf",
"cards.hide-seekCardsLink": "https://resources.scratch.mit.edu/www/cards/en/hide-seekCards.pdf",
"cards.musicCardsLink": "https://resources.scratch.mit.edu/www/cards/en/musicCards.pdf",
"cards.nameCardsLink": "https://resources.scratch.mit.edu/www/cards/en/nameCards.pdf",
"cards.petCardsLink": "https://resources.scratch.mit.edu/www/cards/en/petCards.pdf",
"cards.pongCardsLink": "https://resources.scratch.mit.edu/www/cards/en/pongCards.pdf",
"cards.raceCardsLink": "https://resources.scratch.mit.edu/www/cards/en/raceCards.pdf",
"cards.storyCardsLink": "https://resources.scratch.mit.edu/www/cards/en/storyCards.pdf",
"guides.AnimateYourNameGuideLink": "https://resources.scratch.mit.edu/www/guides/en/AnimateYourNameGuide.pdf",
"guides.CatchGuideLink": "https://resources.scratch.mit.edu/www/guides/en/CatchGuide.pdf",
"guides.DanceGuideLink": "https://resources.scratch.mit.edu/www/guides/en/DanceGuide.pdf",
"guides.FashionGuideLink": "https://resources.scratch.mit.edu/www/guides/en/FashionGuide.pdf",
"guides.FlyGuideLink": "https://resources.scratch.mit.edu/www/guides/en/FlyGuide.pdf",
"guides.Getting-Started-Guide-Scratch2Link": "https://resources.scratch.mit.edu/www/guides/en/Getting-Started-Guide-Scratch2.pdf",
"guides.HideandSeekGuideLink": "https://resources.scratch.mit.edu/www/guides/en/HideandSeekGuide.pdf",
"guides.MusicGuideLink": "https://resources.scratch.mit.edu/www/guides/en/MusicGuide.pdf",
"guides.NameGuideLink": "https://resources.scratch.mit.edu/www/guides/en/NameGuide.pdf",
"guides.PetGuideLink": "https://resources.scratch.mit.edu/www/guides/en/PetGuide.pdf",
"guides.PongGuideLink": "https://resources.scratch.mit.edu/www/guides/en/PongGuide.pdf",
"guides.RaceGuideLink": "https://resources.scratch.mit.edu/www/guides/en/RaceGuide.pdf",
"guides.StoryGuideLink": "https://resources.scratch.mit.edu/www/guides/en/StoryGuide.pdf"
},
"es": {
"cards.Scratch2CardsLink": "https://resources.scratch.mit.edu/www/cards/es/Scratch2Cards.pdf",
"cards.catchCardsLink": "https://resources.scratch.mit.edu/www/cards/es/catchCards.pdf",
"cards.danceCardsLink": "https://resources.scratch.mit.edu/www/cards/es/danceCards.pdf",
"cards.fashionCardsLink": "https://resources.scratch.mit.edu/www/cards/es/fashionCards.pdf",
"cards.flyCardsLink": "https://resources.scratch.mit.edu/www/cards/es/flyCards.pdf",
"cards.hide-seekCardsLink": "https://resources.scratch.mit.edu/www/cards/es/hide-seekCards.pdf",
"cards.musicCardsLink": "https://resources.scratch.mit.edu/www/cards/es/musicCards.pdf",
"cards.nameCardsLink": "https://resources.scratch.mit.edu/www/cards/es/nameCards.pdf",
"cards.petCardsLink": "https://resources.scratch.mit.edu/www/cards/es/petCards.pdf",
"cards.pongCardsLink": "https://resources.scratch.mit.edu/www/cards/es/pongCards.pdf",
"cards.raceCardsLink": "https://resources.scratch.mit.edu/www/cards/es/raceCards.pdf",
"cards.storyCardsLink": "https://resources.scratch.mit.edu/www/cards/es/storyCards.pdf"
},
"fr": {
"cards.Scratch2CardsLink": "https://resources.scratch.mit.edu/www/cards/fr/Scratch2Cards.pdf"
},
"hr": {
"cards.Scratch2CardsLink": "https://resources.scratch.mit.edu/www/cards/hr/Scratch2Cards.pdf"
},
"it": {
"cards.Scratch2CardsLink": "https://resources.scratch.mit.edu/www/cards/it/Scratch2Cards.pdf"
},
"ja": {
"cards.Scratch2CardsLink": "https://resources.scratch.mit.edu/www/cards/ja/Scratch2Cards.pdf"
},
"ja-hr": {
"cards.Scratch2CardsLink": "https://resources.scratch.mit.edu/www/cards/ja-hr/Scratch2Cards.pdf"
},
"ko": {
"cards.Scratch2CardsLink": "https://resources.scratch.mit.edu/www/cards/ko/Scratch2Cards.pdf"
},
"nl": {
"cards.Scratch2CardsLink": "https://resources.scratch.mit.edu/www/cards/nl/Scratch2Cards.pdf"
},
"pt-br": {
"cards.Scratch2CardsLink": "https://resources.scratch.mit.edu/www/cards/pt-br/Scratch2Cards.pdf"
},
"sl": {
"cards.Scratch2CardsLink": "https://resources.scratch.mit.edu/www/cards/sl/Scratch2Cards.pdf"
},
"sv": {
"cards.Scratch2CardsLink": "https://resources.scratch.mit.edu/www/cards/sv/Scratch2Cards.pdf"
},
"zh-tw": {
"cards.ScratchCardsAllLink": "https://resources.scratch.mit.edu/www/cards/zh-tw/ScratchCardsAll.pdf",
"cards.catchCardsLink": "https://resources.scratch.mit.edu/www/cards/zh-tw/catchCards.pdf",
"cards.danceCardsLink": "https://resources.scratch.mit.edu/www/cards/zh-tw/danceCards.pdf",
"cards.fashionCardsLink": "https://resources.scratch.mit.edu/www/cards/zh-tw/fashionCards.pdf",
"cards.flyCardsLink": "https://resources.scratch.mit.edu/www/cards/zh-tw/flyCards.pdf",
"cards.hide-seekCardsLink": "https://resources.scratch.mit.edu/www/cards/zh-tw/hide-seekCards.pdf",
"cards.musicCardsLink": "https://resources.scratch.mit.edu/www/cards/zh-tw/musicCards.pdf",
"cards.nameCardsLink": "https://resources.scratch.mit.edu/www/cards/zh-tw/nameCards.pdf",
"cards.petCardsLink": "https://resources.scratch.mit.edu/www/cards/zh-tw/petCards.pdf",
"cards.pongCardsLink": "https://resources.scratch.mit.edu/www/cards/zh-tw/pongCards.pdf",
"cards.raceCardsLink": "https://resources.scratch.mit.edu/www/cards/zh-tw/raceCards.pdf",
"cards.storyCardsLink": "https://resources.scratch.mit.edu/www/cards/zh-tw/storyCards.pdf"
}
}

View file

@ -24,8 +24,8 @@
"dependencies": {
"bunyan": "1.7.1",
"compression": "1.6.1",
"express": "4.13.4",
"express-http-proxy": "0.6.0",
"express": "4.16.1",
"express-http-proxy": "1.1.0",
"lodash.defaults": "4.0.1",
"newrelic": "1.25.4",
"raven": "0.10.0"
@ -38,6 +38,7 @@
"babel-loader": "6.2.4",
"babel-preset-es2015": "6.9.0",
"babel-preset-react": "6.11.1",
"cheerio": "1.0.0-rc.2",
"classnames": "2.1.3",
"cookie": "0.2.2",
"copy-webpack-plugin": "0.2.0",
@ -66,7 +67,7 @@
"lodash.range": "3.0.1",
"minilog": "2.0.8",
"node-dir": "0.1.16",
"node-sass": "3.3.3",
"node-sass": "4.6.1",
"pako": "0.2.8",
"po2icu": "0.0.2",
"postcss-loader": "0.8.2",

View file

@ -1,66 +0,0 @@
[
{
"obj_id": 82475328,
"datetime_created": "2015-10-20T15:13:36",
"actor": {
"username": "ceebee",
"pk": 2755634,
"thumbnail_url": "//cdn.scratch.mit.edu/static/site/users/avatars/275/5634.png",
"admin": true
},
"pk": 186757838,
"message": "\nfavorited\n <a href=\"/projects/82475328/\">miner man</a>",
"extra_data": {
"project_title": "miner man"
},
"type": 3
},
{
"obj_id": 82475328,
"datetime_created": "2015-10-20T15:13:36",
"actor": {
"username": "ceebee",
"pk": 2755634,
"thumbnail_url": "//cdn.scratch.mit.edu/static/site/users/avatars/275/5634.png",
"admin": true
},
"pk": 186757836,
"message": "\nloved\n <a href=\"/projects/82475328/\">miner man</a>",
"extra_data": {
"project_title": "miner man"
},
"type": 2
},
{
"obj_id": 82475328,
"datetime_created": "2015-10-20T15:12:39",
"actor": {
"username": "speakvisually",
"pk": 3484484,
"thumbnail_url": "//cdn.scratch.mit.edu/static/site/users/avatars/348/4484.png",
"admin": true
},
"pk": 186757510,
"message": "\nfavorited\n <a href=\"/projects/82475328/\">miner man</a>",
"extra_data": {
"project_title": "miner man"
},
"type": 3
},
{
"obj_id": 82475328,
"datetime_created": "2015-10-20T15:12:37",
"actor": {
"username": "speakvisually",
"pk": 3484484,
"thumbnail_url": "//cdn.scratch.mit.edu/static/site/users/avatars/348/4484.png",
"admin": true
},
"pk": 186757500,
"message": "\nloved\n <a href=\"/projects/82475328/\">miner man</a>",
"extra_data": {
"project_title": "miner man"
},
"type": 2
}
]

View file

@ -1,80 +0,0 @@
var React = require('react');
var ReactIntl = require('react-intl');
var defineMessages = ReactIntl.defineMessages;
var FormattedMessage = ReactIntl.FormattedMessage;
var FormattedRelative = ReactIntl.FormattedRelative;
var injectIntl = ReactIntl.injectIntl;
var Box = require('../box/box.jsx');
require('./activity.scss');
var defaultMessages = defineMessages({
whatsHappening: {
id: 'general.whatsHappening'
}
});
var Activity = React.createClass({
type: 'Activity',
propTypes: {
items: React.PropTypes.array
},
getDefaultProps: function () {
return {
items: require('./activity.json')
};
},
render: function () {
var formatMessage = this.props.intl.formatMessage;
return (
<Box
className="activity"
title={formatMessage(defaultMessages.whatsHappening)}>
{this.props.items && this.props.items.length > 0 ? [
<ul key="activity-ul">
{this.props.items.map(function (item) {
if (item.message.replace(/\s/g, '')) {
var actorProfileUrl = '/users/' + item.actor.username + '/';
var actionDate = new Date(item.datetime_created + 'Z');
var activityMessageHTML = (
'<a href=' + actorProfileUrl + '>' + item.actor.username + '</a>' +
item.message
);
return (
<li key={item.pk}>
<a href={actorProfileUrl}>
<img src={item.actor.thumbnail_url} width="34" height="34" alt="" />
<p dangerouslySetInnerHTML={{__html: activityMessageHTML}}></p>
<p>
<span className="stamp">
<FormattedRelative value={actionDate} />
</span>
</p>
</a>
</li>
);
}
})}
</ul>
] : [
<div className="empty" key="activity-empty">
<h4>
<FormattedMessage
id="activity.seeUpdates"
defaultMessage="This is where you will see updates from Scratchers you follow" />
</h4>
<a href="/studios/146521/">
<FormattedMessage
id="activity.checkOutScratchers"
defaultMessage="Check out some Scratchers you might like to follow" />
</a>
</div>
]}
</Box>
);
}
});
module.exports = injectIntl(Activity);

View file

@ -1,45 +0,0 @@
@import "../../colors";
.activity {
ul {
display: block;
margin: 0;
padding: 0;
}
li {
display: block;
clear: both;
margin: 12px 0;
padding: 0;
a {
&:hover {
text-decoration: none;
}
}
img {
display: block;
float: left;
padding-right: 10px;
}
p {
display: block;
margin: 0;
padding: 0;
line-height: normal;
white-space: nowrap;
font-size: .9rem;
overflow-x: hidden;
}
.stamp {
color: $ui-dark-gray;
font-size: .65rem;
}
}
}

View file

@ -12,7 +12,7 @@
position: absolute;
top: -1px;
left: -13px;
border-top: 12px solid $ui-border;
border-bottom: 12px solid $ui-border;
border-left: 13px solid $ui-border;
border-radius: 0 0 0 13px;
width: 0;
@ -24,7 +24,7 @@
position: absolute;
top: 0;
left: -12px;
border-top: 10px solid $ui-white;
border-bottom: 10px solid $ui-white;
border-left: 12px solid $ui-white;
border-radius: 0 0 0 12px;
width: 0;
@ -33,6 +33,7 @@
.emoji-text.mod-comment {
margin: 0;
overflow: hidden;
}
.comment-text-timestamp {

View file

@ -0,0 +1,84 @@
var React = require('react');
var ReactIntl = require('react-intl');
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');
require('../footer.scss');
var ConferenceFooter = React.createClass({
type: 'ConferenceFooter',
render: function () {
return (
<FooterBox>
<FlexRow className="scratch-links">
<div className="family">
<h4><FormattedMessage id='footer.scratchFamily' /></h4>
<FlexRow>
<FlexRow as="ul" className="column">
<li>
<a href="https://scratch.mit.edu" target="_blank">Scratch</a>
</li>
<li>
<a href="http://www.scratchjr.org/" target="_blank">ScratchJr</a>
</li>
</FlexRow>
<FlexRow as="ul" className="column">
<li>
<a href="http://www.scratchfoundation.org/" target="_blank">Scratch Foundation</a>
</li>
<li>
<a href="http://scratched.gse.harvard.edu/" target="_blank">ScratchEd</a>
</li>
</FlexRow>
<FlexRow as="ul" className="column">
<li>
<a href="http://day.scratch.mit.edu" target="_blank">Scratch Day</a>
</li>
</FlexRow>
</FlexRow>
<p className="legal">
<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">
Email Us
</a>
</p>
</div>
<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>
</li>
<li>
<a href="//www.facebook.com/scratchteam" target="_blank">
<img src="/images/conference/footer/facebook.png" alt="scratch facebook" />
</a>
</li>
<li>
<a href="http://medium.com/scratchfoundation-blog" target="_blank">
<img src="/images/conference/footer/medium.png" alt="scratch foundation blog" />
</a>
</li>
</FlexRow>
</div>
</div>
</FlexRow>
<LanguageChooser locale={this.props.intl.locale} />
</FooterBox>
);
}
});
module.exports = injectIntl(ConferenceFooter);

View file

@ -32,33 +32,41 @@ var Grid = React.createClass({
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://cdn2.scratch.mit.edu/get_image/user/'
+ item.author.id + '_32x32.png'}
creator={item.author.username}
loves={item.stats.loves}
favorites={item.stats.favorites}
remixes={item.stats.remixes}
views={item.stats.views} />
<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}
owner={item.owner} />
<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))}

View file

@ -16,6 +16,7 @@ var Intro = React.createClass({
'intro.aboutScratch': 'ABOUT SCRATCH',
'intro.forEducators': 'FOR EDUCATORS',
'intro.forParents': 'FOR PARENTS',
'intro.itsFree': 'it\'s free!',
'intro.joinScratch': 'JOIN SCRATCH',
'intro.seeExamples': 'SEE EXAMPLES',
'intro.tagLine': 'Create stories, games, and animations<br /> Share with others around the world',
@ -96,7 +97,7 @@ var Intro = React.createClass({
<div className="text">
{this.props.messages['intro.joinScratch']}
</div>
<div className="text subtext">( it&rsquo;s free )</div>
<div className="text subtext">{this.props.messages['intro.itsFree']}</div>
</a>
<Registration key="registration"
isOpen={this.state.registrationOpen}

View file

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

View file

@ -0,0 +1,39 @@
@import "../../../../colors";
@import "../../../../frameless";
#navigation {
.ul.mod-2018 {
display: flex;
justify-content: space-between;
flex-flow: row nowrap;
align-items: center;
list-style-type: none;
}
.li-left.mod-2018 {
margin-top: 0;
margin-right: 10px;
color: $type-white;
}
.logo-a {
display: flex;
height: 100%;
align-items: center;
}
.logo-a-image {
margin-right: 10px;
border-right: 2px solid $active-gray;
padding-right: 10px;
width: 80px;
}
.logo-a-title {
text-decoration: none;
white-space: nowrap;
color: $type-white;
font-size: .85rem;
font-weight: bold;
}
}

View file

@ -43,9 +43,9 @@ var Navigation = React.createClass({
},
componentDidMount: function () {
if (this.props.session.session.user) {
var intervalId = setInterval(
this.props.dispatch(messageCountActions.getCount(this.props.session.session.user.username), 120000)
); // check for new messages every 2 mins.
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});
}
},
@ -56,9 +56,9 @@ var Navigation = React.createClass({
'accountNavOpen': false
});
if (this.props.session.session.user) {
var intervalId = setInterval(
this.props.dispatch(messageCountActions.getCount(this.props.session.session.user.username), 120000)
); // check for new messages every 2 mins.
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});
} else {
// clear message count check, and set to default id.
@ -138,7 +138,7 @@ var Navigation = React.createClass({
}, function (err) {
if (err) log.error(err);
this.closeLogin();
this.props.dispatch(sessionActions.refreshSession());
window.location = '/';
}.bind(this));
},
handleAccountNavClick: function (e) {

View file

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

View file

@ -66,6 +66,11 @@
input[type="radio"] {
margin-right: 1rem;
&:focus {
transition: all .5s ease;
border: 1px solid $ui-blue;
}
}
.demographics-step-input-other {

View file

@ -10,7 +10,10 @@ var SocialMessage = React.createClass({
type: 'SocialMessage',
propTypes: {
as: React.PropTypes.string,
datetime: React.PropTypes.string.isRequired
datetime: React.PropTypes.string.isRequired,
iconSrc: React.PropTypes.string,
iconAlt: React.PropTypes.string,
imgClassName: React.PropTypes.string
},
getDefaultProps: function () {
return {
@ -22,11 +25,25 @@ var SocialMessage = React.createClass({
'social-message',
this.props.className
);
var imgClass = classNames(
'social-message-icon',
this.props.imgClassName
);
return (
<this.props.as className={classes}>
<FlexRow className="mod-social-message">
<div className="social-message-content">
{this.props.children}
{typeof this.props.iconSrc !== 'undefined' ? [
<img
key="social-message-icon"
className={imgClass}
src={this.props.iconSrc}
alt={this.props.iconAlt}
/>
] : []}
<div>
{this.props.children}
</div>
</div>
<span className="social-message-date">
<FormattedRelative value={new Date(this.props.datetime)} />

View file

@ -8,8 +8,16 @@
list-style-type: none;
}
.social-message-icon {
opacity: .25;
}
.social-message.mod-unread {
background-color: $ui-gray;
background-color: lighten($ui-blue, 40);
}
.social-message.mod-unread .social-message-icon {
opacity: 1;
}
.flex-row.mod-social-message {
@ -18,7 +26,15 @@
}
.social-message-content {
max-width: 60%;
display: flex;
max-width: 38.75rem;
text-align: left;
align-items: flex-start;
}
.social-message-icon {
margin: .2rem 1rem 0 0;
min-width: 1.25rem;
}
a.social-messages-profile-link {

View file

@ -8,12 +8,20 @@ var Thumbnail = React.createClass({
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,
@ -24,6 +32,12 @@ var Thumbnail = React.createClass({
alt: ''
};
},
handleSrcError: function () {
this.setState({srcFallback: true});
},
handleAvatarError: function () {
this.setState({avatarFallback: true});
},
render: function () {
var classes = classNames(
'thumbnail',
@ -58,7 +72,8 @@ var Thumbnail = React.createClass({
<div
key="remixes"
className="thumbnail-remixes"
title={this.props.remixes + ' remixes'}>
title={this.props.remixes + ' remixes'}
>
{this.props.remixes}
</div>
);
@ -68,19 +83,48 @@ var Thumbnail = React.createClass({
<div
key="views"
className="thumbnail-views"
title={this.props.views + ' views'}>
title={this.props.views + ' views'}
>
{this.props.views}
</div>
);
}
var imgElement,titleElement,avatarElement;
var imgElement, titleElement, avatarElement;
if (this.props.linkTitle) {
imgElement = <a className="thumbnail-image" href={this.props.href} key="imgElement">
<img src={this.props.src} alt={this.props.alt} />
</a>;
titleElement = <a href={this.props.href} key="titleElement">
{this.props.title}
</a>;
if (this.state.srcFallback) {
imgElement = (
<a
className="thumbnail-image"
href={this.props.href}
key="imgElement"
>
<img
alt={this.props.alt}
src={this.props.srcDefault}
/>
</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>
);
} else {
imgElement = <img src={this.props.src} />;
titleElement = this.props.title;
@ -97,10 +141,32 @@ var Thumbnail = React.createClass({
}
if (this.props.avatar && this.props.showAvatar) {
avatarElement =
<a className="creator-image" href={'/users/' + this.props.creator + '/'}>
<img src={this.props.avatar} alt={this.props.creator} />
</a>;
if (this.state.avatarFallback) {
avatarElement = (
<a
className="creator-image"
href={'/users/' + this.props.creator + '/'}
>
<img
alt={this.props.creator}
src={this.props.avatarDefault}
/>
</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} >

View file

@ -47,7 +47,7 @@ var TTTTile = React.createClass({
{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-see-more" src="/svgs/ttt/see-more.svg" />
<img className="ttt-tile-open-modal" src="/svgs/modal/open-blue.svg" />
</div>
)}
</div>

View file

@ -105,14 +105,15 @@
font-size: .75rem;
font-weight: 500;
&:hover {
background-color: lighten($link-blue, 40%);
}
}
.ttt-tile-see-more {
.ttt-tile-open-modal {
display: inline-block;
padding: 0 .25rem;
vertical-align: middle;
width: 1rem;
height: 1rem;
vertical-align: text-bottom;
}

View file

@ -71,10 +71,11 @@ var Jar = {
});
},
set: function (name, value, opts) {
opts = opts || {};
defaults(opts, {
expires: new Date(new Date().setYear(new Date().getFullYear() + 1)),
path: '/'
expires: new Date(new Date().setYear(new Date().getFullYear() + 1))
});
opts.path = '/';
var obj = cookie.serialize(name, value, opts);
document.cookie = obj;
},

View file

@ -56,14 +56,14 @@ module.exports.getCount = function (username) {
return function (dispatch) {
api({
method: 'get',
uri: '/proxy/users/' + username + '/activity/count'
uri: '/users/' + username + '/messages/count'
}, function (err, body) {
if (err) {
dispatch(module.exports.setCount(0));
dispatch(module.exports.setSessionError(err));
return;
}
var count = parseInt(body.msg_count, 10);
var count = parseInt(body.count, 10);
dispatch(module.exports.setCount(count));
});
};

View file

@ -1,3 +1,4 @@
var defaults = require('lodash.defaults');
var defaultsDeep = require('lodash.defaultsdeep');
var keyMirror = require('keymirror');
@ -39,21 +40,18 @@ module.exports.messagesReducer = function (state, action) {
switch (action.type) {
case 'SET_MESSAGES':
return defaultsDeep({
messages: {social: action.messages}
}, state);
state.messages.social = action.messages;
return state;
case 'SET_ADMIN_MESSAGES':
return defaultsDeep({
messages: {admin: action.messages}
}, state);
state.messages.admin = action.messages;
return state;
case 'SET_MESSAGES_OFFSET':
return defaultsDeep({
messages: {socialOffset: action.offset}
}, state);
case 'SET_SCRATCHER_INVITE':
return defaultsDeep({
messages: {invite: action.invite}
}, state);
state.messages.invite = action.invite;
return state;
case 'ADMIN_STATUS':
return defaultsDeep({status: {admin: action.status}}, state);
case 'MESSAGE_STATUS':
@ -112,6 +110,10 @@ module.exports.setStatus = function (type, status){
};
};
/**
* Sends a request to mark one's unread messages count as cleared.
* @return {null} returns nothing
*/
module.exports.clearMessageCount = function () {
return function (dispatch) {
dispatch(module.exports.setStatus('CLEAR_STATUS', module.exports.Status.FETCHING));
@ -126,7 +128,7 @@ module.exports.clearMessageCount = function () {
dispatch(module.exports.setMessagesError(err));
return;
}
if (!body.success) {
if (typeof body !== 'undefined' && !body.success) {
dispatch(module.exports.setStatus('CLEAR_STATUS', module.exports.Status.CLEAR_ERROR));
dispatch(module.exports.setMessagesError('messages not cleared'));
return;
@ -136,6 +138,14 @@ module.exports.clearMessageCount = function () {
};
};
/**
* Marks an admin message as read, dismissing it from the page
* @param {string} messageType type of message to delete (invite or notification)
* @param {number} messageId id of the message to delete
* @param {number} messageCount current number of unread notifications
* @param {object[]} adminMessages current list of admin messages retrieved
* @return {null} returns nothing
*/
module.exports.clearAdminMessage = function (messageType, messageId, messageCount, adminMessages) {
return function (dispatch) {
dispatch(module.exports.setStatus('CLEAR_STATUS', module.exports.Status.FETCHING));
@ -181,11 +191,33 @@ module.exports.clearAdminMessage = function (messageType, messageId, messageCoun
};
};
module.exports.getMessages = function (username, token, messages, offset) {
/**
* Gets a user's messages to be displayed on the /messages page
* @param {string} username username of the user for whom the messages should be gotten
* @param {string} token the user's unique token for auth
* @param {object} opts optional args for the method
* @param {object[]} [opts.messages] an array of existing messages on the page, if there are any
* @param {number} [opts.offset] offset of messages to get, based on the number retrieved already
* @param {string} [opts.filter] type of messages to return
* @return {null} returns nothing
*/
module.exports.getMessages = function (username, token, opts) {
opts = defaults(opts, {
messages: [],
offset: 0,
filter: '',
clearCount: true
});
var filterArg = '';
if (opts.filter.length > 0) {
filterArg = '&filter=' + opts.filter;
}
return function (dispatch) {
dispatch(module.exports.setStatus('MESSAGE_STATUS', module.exports.Status.FETCHING));
api({
uri: '/users/' + username + '/messages?limit=40&offset=' + offset,
uri: '/users/' + username + '/messages?limit=40&offset=' + opts.offset + filterArg,
authentication: token
}, function (err, body) {
if (err) {
@ -199,13 +231,21 @@ module.exports.getMessages = function (username, token, messages, offset) {
return;
}
dispatch(module.exports.setStatus('MESSAGE_STATUS', module.exports.Status.FETCHED));
dispatch(module.exports.setMessages(messages.concat(body)));
dispatch(module.exports.setMessagesOffset(offset + 40));
dispatch(module.exports.clearMessageCount(token)); // clear count once messages loaded
dispatch(module.exports.setMessages(opts.messages.concat(body)));
dispatch(module.exports.setMessagesOffset(opts.offset + 40));
if (opts.clearCount) {
dispatch(module.exports.clearMessageCount(token)); // clear count once messages loaded
}
});
};
};
/**
* Gets the messages from the Scratch Team for a user
* @param {string} username user's username for whom to get the admin messages
* @param {string} token the user's unique token for auth
* @return {null} returns nothing
*/
module.exports.getAdminMessages = function (username, token) {
return function (dispatch) {
dispatch(module.exports.setStatus('ADMIN_STATUS', module.exports.Status.FETCHING));
@ -231,6 +271,12 @@ module.exports.getAdminMessages = function (username, token) {
};
};
/**
* Gets the invitation to become a Scratcher for a user, if one exists
* @param {string} username user's username for whom to get the invite
* @param {string} token the user's unique token for auth
* @return {null} returns nothing
*/
module.exports.getScratcherInvite = function (username, token) {
return function (dispatch) {
api({

View file

@ -1,4 +1,5 @@
var combineReducers = require('redux').combineReducers;
var defaults = require('lodash.defaults');
var messageCountReducer = require('./message-count.js').messageCountReducer;
var permissionsReducer = require('./permissions.js').permissionsReducer;
@ -16,7 +17,7 @@ var sessionReducer = require('./session.js').sessionReducer;
*/
module.exports = function (opts) {
opts = opts || {};
return combineReducers(Object.assign(opts, {
return combineReducers(defaults(opts, {
session: sessionReducer,
permissions: permissionsReducer,
messageCount: messageCountReducer

View file

@ -97,7 +97,9 @@ module.exports.refreshSession = function () {
// get the permissions from the updated session
dispatch(permissionsActions.storePermissions(body.permissions));
dispatch(messageCountActions.getCount(body.user.username));
if (typeof body.user !== 'undefined') {
dispatch(messageCountActions.getCount(body.user.username));
}
return;
}
});

213
src/redux/splash.js Normal file
View file

@ -0,0 +1,213 @@
var keyMirror = require('keymirror');
var api = require('../lib/api');
var log = require('../lib/log');
module.exports.Status = keyMirror({
FETCHED: null,
NOT_FETCHED: null,
FETCHING: null,
ERROR: null
});
module.exports.getInitialState = function () {
return {
activity: {
status: module.exports.Status.NOT_FETCHED,
rows: []
},
featured: {
status: module.exports.Status.NOT_FETCHED,
rows: {}
},
shared: {
status: module.exports.Status.NOT_FETCHED,
rows: []
},
loved: {
status: module.exports.Status.NOT_FETCHED,
rows: []
},
studios: {
status: module.exports.Status.NOT_FETCHED,
rows: []
}
};
};
module.exports.splashReducer = function (state, action) {
if (typeof state === 'undefined') {
state = module.exports.getInitialState();
}
switch (action.type) {
case 'SET_ROWS':
state = JSON.parse(JSON.stringify(state));
state[action.rowType].rows = action.rows;
return state;
case 'SET_FETCH_STATUS':
state = JSON.parse(JSON.stringify(state));
state[action.rowType].status = action.status;
return state;
case 'ERROR':
log.error(action.error);
return state;
default:
return state;
}
};
module.exports.setError = function (error) {
return {
type: 'ERROR',
error: error
};
};
module.exports.setRows = function (type, rows) {
return {
type: 'SET_ROWS',
rowType: type,
rows: rows
};
};
module.exports.setFetchStatus = function (type, status){
return {
type: 'SET_FETCH_STATUS',
rowType: type,
status: status
};
};
module.exports.getActivity = function (username, token) {
return function (dispatch) {
dispatch(module.exports.setFetchStatus('activity', module.exports.Status.FETCHING));
api({
uri: '/users/' + username + '/following/users/activity?limit=5',
authentication: token
}, function (err, body) {
if (err) {
dispatch(module.exports.setFetchStatus('activity', module.exports.Status.ERROR));
dispatch(module.exports.setError(err));
return;
}
if (typeof body === 'undefined') {
dispatch(module.exports.setFetchStatus('activity', module.exports.Status.ERROR));
dispatch(module.exports.setError('No session content'));
return;
}
dispatch(module.exports.setFetchStatus('activity', module.exports.Status.FETCHED));
dispatch(module.exports.setRows('activity', body));
});
};
};
/**
* Get global homepage rows
*/
module.exports.getFeaturedGlobal = function () {
return function (dispatch) {
dispatch(module.exports.setFetchStatus('featured', module.exports.Status.FETCHING));
api({
uri: '/proxy/featured'
}, function (err, body) {
if (err) {
dispatch(module.exports.setFetchStatus('featured', module.exports.Status.ERROR));
dispatch(module.exports.setError(err));
return;
}
if (typeof body === 'undefined') {
dispatch(module.exports.setFetchStatus('featured', module.exports.Status.ERROR));
dispatch(module.exports.setError('No session content'));
return;
}
dispatch(module.exports.setFetchStatus('featured', module.exports.Status.FETCHED));
dispatch(module.exports.setRows('featured', body));
});
};
};
/**
* Get list of projects shared by users the given user is following.
* @param {string} username username of the Scratcher for whom to get projects
* @param {string} token authentication
*/
module.exports.getSharedByFollowing = function (username, token) {
return function (dispatch) {
dispatch(module.exports.setFetchStatus('shared', module.exports.Status.FETCHING));
api({
uri: '/users/' + username + '/following/users/projects',
authentication: token
}, function (err, body) {
if (err) {
dispatch(module.exports.setFetchStatus('shared', module.exports.Status.Status.ERROR));
dispatch(module.exports.setError(err));
return;
}
if (typeof body === 'undefined') {
dispatch(module.exports.setFetchStatus('shared', module.exports.Status.ERROR));
dispatch(module.exports.setError('No session content'));
return;
}
dispatch(module.exports.setFetchStatus('shared', module.exports.Status.FETCHED));
dispatch(module.exports.setRows('shared', body));
});
};
};
/**
* Get list of projects in studios that the given user is following.
* @param {string} username username of the Scratcher for whom to get projects
* @param {string} token authentication
*/
module.exports.getInStudiosFollowing = function (username, token) {
return function (dispatch) {
dispatch(module.exports.setFetchStatus('studios', module.exports.Status.FETCHING));
api({
uri: '/users/' + username + '/following/studios/projects',
authentication: token
}, function (err, body) {
if (err) {
dispatch(module.exports.setFetchStatus('studios', module.exports.Status.ERROR));
dispatch(module.exports.setError(err));
return;
}
if (typeof body === 'undefined') {
dispatch(module.exports.setFetchStatus('studios', module.exports.Status.ERROR));
dispatch(module.exports.setError('No session content'));
return;
}
dispatch(module.exports.setFetchStatus('studios', module.exports.Status.FETCHED));
dispatch(module.exports.setRows('studios', body));
});
};
};
/**
* Get list of projects loved by users the given user is following.
* @param {string} username username of the Scratcher for whom to get projects
* @param {string} token authentication
*/
module.exports.getLovedByFollowing = function (username, token) {
return function (dispatch) {
dispatch(module.exports.setFetchStatus('loved', module.exports.Status.FETCHING));
api({
uri: '/users/' + username + '/following/users/loves',
authentication: token
}, function (err, body) {
if (err) {
dispatch(module.exports.setFetchStatus('loved', module.exports.Status.ERROR));
dispatch(module.exports.setError(err));
return;
}
if (typeof body === 'undefined') {
dispatch(module.exports.setFetchStatus('loved', module.exports.Status.ERROR));
dispatch(module.exports.setError('No session content'));
return;
}
dispatch(module.exports.setFetchStatus('loved', module.exports.Status.FETCHED));
dispatch(module.exports.setRows('loved', body));
});
};
};

View file

@ -43,7 +43,15 @@
},
{
"name": "conference-index",
"pattern": "^/conference/?$",
"pattern": "^/conference/?(\\?.*)?$",
"routeAlias": "/conference(?!/201[4-5])",
"view": "conference/2018/index/index",
"title": "Scratch Conference",
"viewportWidth": "device-width"
},
{
"name": "conference-index-2017",
"pattern": "^/conference/2017/?$",
"routeAlias": "/conference(?!/201[4-5])",
"view": "conference/2017/index/index",
"title": "Scratch Conference",
@ -144,7 +152,7 @@
{
"name": "messages",
"pattern": "^/messages/?$",
"routeAlias": "/messages",
"routeAlias": "/messages(?!/ajax)",
"view": "messages/container",
"title": "Messages"
},
@ -183,6 +191,13 @@
"view": "microworldshomepage/microworldshomepage",
"title": "Microworlds"
},
{
"name": "preview-faq",
"pattern": "^/preview-faq/?$",
"routeAlias": "/preview-faq/?$",
"view": "preview-faq/preview-faq",
"title": "Scratch 3.0 Preview FAQ"
},
{
"name": "privacypolicy",
"pattern": "^/privacy_policy/?$",
@ -302,6 +317,12 @@
"routeAlias": "/hoc/?\\??",
"redirect": "/tips"
},
{
"name": "hoc2014-redirect",
"pattern": "^/hoc2014/?(\\?.*)?$",
"routeAlias": "/hoc2014/?\\??",
"redirect": "/tips"
},
{
"name": "info-redirect",
"pattern": "^/info/?(\\?.*)?$",

View file

@ -1,5 +1,4 @@
var React = require('react');
var FormattedHTMLMessage = require('react-intl').FormattedHTMLMessage;
var FormattedMessage = require('react-intl').FormattedMessage;
var render = require('../../lib/render.jsx');
@ -42,7 +41,7 @@ var About = React.createClass({
<li>
<h3><FormattedMessage id='about.whoUsesScratch' /></h3>
<img src="/images/about/who-uses-scratch.jpg" alt="" />
<p><FormattedHTMLMessage id='about.whoUsesScratchDescription' /></p>
<p><FormattedMessage id='about.whoUsesScratchDescription' /></p>
</li>
<li>
@ -53,31 +52,75 @@ var About = React.createClass({
webkitAllowFullScreen
mozallowfullscreen
allowFullScreen />
<p><FormattedHTMLMessage id='about.literacyDescription' /></p>
<p><FormattedMessage id='about.literacyDescription' /></p>
</li>
<li>
<h3><FormattedMessage id='about.aroundTheWorld' /></h3>
<img src="/images/about/around-the-world.png" alt="" />
<p><FormattedHTMLMessage id='about.aroundTheWorldDescription' /></p>
<p><FormattedMessage
id='about.aroundTheWorldDescription'
values={{
translationLink: (
<a href='http://wiki.scratch.mit.edu/wiki/How_to_Translate_Scratch'>
<FormattedMessage id='about.translationLinkText' />
</a>
)
}}
/></p>
</li>
<li>
<h3><FormattedMessage id='about.schools' /></h3>
<img src="/images/about/scratch-in-schools.jpg" alt="" />
<p><FormattedHTMLMessage id='about.schoolsDescription' /></p>
<p><FormattedMessage
id='about.schoolsDescription'
values={{
scratchedLink: (
<a href='http://scratched.gse.harvard.edu/'>
<FormattedMessage id='about.scratchedLinkText' />
</a>
)
}}
/></p>
</li>
<li>
<h3><FormattedMessage id='about.quotes' /></h3>
<img src="/images/about/quotes.gif" alt="Quotes about Scratch" />
<p><FormattedHTMLMessage id='about.quotesDescription' /></p>
<p><FormattedMessage
id='about.quotesDescription'
values={{
quotesLink: (
<a href='http://wiki.scratch.mit.edu/wiki/How_to_Translate_Scratch'>
<FormattedMessage id='about.quotesLinkText'/>
</a>
)
}}
/></p>
</li>
<li>
<h3><FormattedMessage id='about.research' /></h3>
<img src="/images/about/research-remix.png" alt="" />
<p><FormattedHTMLMessage id='about.researchDescription' /></p>
<p><FormattedMessage
id='about.researchDescription'
values={{
researchLink: (
<a href='/info/research'>
<FormattedMessage id='about.researchLinkText'/>
</a>
),
spfaLink: (
<a href='http://web.media.mit.edu/~mres/papers/Scratch-CACM-final.pdf'>
<FormattedMessage id='about.spfaLinkText'/>
</a>
),
statisticsLink: (
<a href='/statistics'>
<FormattedMessage id='about.statisticsLinkText'/>
</a>
)
}}
/></p>
</li>
<li>
@ -102,8 +145,28 @@ var About = React.createClass({
<li>
<h3><FormattedMessage id='about.support' /></h3>
<p><FormattedHTMLMessage id='about.supportDescription' /></p>
</li>
<p><FormattedMessage
id='about.supportDescription'
values={{
supportersList: 'National Science Foundation, Scratch Foundation, Siegel Family Endowment, Google, LEGO Foundation, Intel, Cartoon Network, Lemann Foundation, MacArthur Foundation', // eslint-disable-line max-len
creditsLink: (
<a href='//scratch.mit.edu/info/credits'>
<FormattedMessage id='about.creditsLinkText'/>
</a>
),
donateLink: (
<a href='//secure.donationpay.org/scratchfoundation/'>
<FormattedMessage id='about.donateLinkText'/>
</a>
),
donateemail: (
<a href='mailto:donate@scratch.mit.edu'>
donate@scratch.mit.edu
</a>
)
}}
/></p>
</li>
</ul>
</div>
</div>

View file

@ -7,9 +7,11 @@
"about.whoUsesScratch": "Who Uses Scratch?",
"about.whoUsesScratchDescription": "Scratch is designed especially for ages 8 to 16, but is used by people of all ages. Millions of people are creating Scratch projects in a wide variety of settings, including homes, schools, museums, libraries, and community centers.",
"about.aroundTheWorld": "Around the World",
"about.aroundTheWorldDescription": "Scratch is used in more than 150 different countries and available in more than 40 languages. To change languages, click the menu at the bottom of the page. Or, in the Project Editor, click the globe at the top of the page. To add or improve a translation, see the <a href=\"http://wiki.scratch.mit.edu/wiki/How_to_Translate_Scratch\">translation</a> page.",
"about.aroundTheWorldDescription": "Scratch is used in more than 150 different countries and available in more than 40 languages. To change languages, click the menu at the bottom of the page. Or, in the Project Editor, click the globe at the top of the page. To add or improve a translation, see the {translationLink} page.",
"about.translationLinkText": "translation",
"about.quotes": "Quotes",
"about.quotesDescription": "The Scratch Team has received many emails from youth, parents, and educators expressing thanks for Scratch. Want to see what people are saying? You can read a collection of the <a href=\"/info/quotes\">quotes</a> we&#39;ve received.",
"about.quotesDescription": "The Scratch Team has received many emails from youth, parents, and educators expressing thanks for Scratch. Want to see what people are saying? You can read a collection of the {quotesLink} we've received.",
"about.quotesLinkText": "quotes",
"about.learnMore": "Learn More About Scratch",
"about.learnMoreHelp": "Tips Page",
"about.learnMoreFaq": "Frequently Asked Questions",
@ -18,9 +20,15 @@
"about.literacy": "Learn to Code, Code to Learn",
"about.literacyDescription": "The ability to code computer programs is an important part of literacy in todays society. When people learn to code in Scratch, they learn important strategies for solving problems, designing projects, and communicating ideas.",
"about.schools": "Scratch in Schools",
"about.schoolsDescription": "Students are learning with Scratch at all levels (from elementary school to college) and across disciplines (such as math, computer science, language arts, social studies). Educators share stories, exchange resources, ask questions, and find people on the <a href=\"http://scratched.gse.harvard.edu/\">ScratchEd website</a>.",
"about.schoolsDescription": "Students are learning with Scratch at all levels (from elementary school to college) and across disciplines (such as math, computer science, language arts, social studies). Educators share stories, exchange resources, ask questions, and find people on the {scratchedLink}.",
"about.scratchedLinkText": "ScratchEd website",
"about.research": "Research",
"about.researchDescription": "The MIT Scratch Team and collaborators are researching how people use and learn with Scratch (for an introduction, see <a href=\"http://web.media.mit.edu/~mres/papers/Scratch-CACM-final.pdf\">Scratch: Programming for All</a>). Find out more about Scratch <a href=\"/info/research\">research</a> and <a href=\"/statistics\">statistics</a> about Scratch.",
"about.researchDescription": "The MIT Scratch Team and collaborators are researching how people use and learn with Scratch (for an introduction, see {spfaLink}. Find out more about Scratch {researchLink} and {statisticsLink} about Scratch.",
"about.spfaLinkText": "Scratch: Programming for All",
"about.researchLinkText": "research",
"about.statisticsLinkText": "statistics",
"about.support": "Support and Funding",
"about.supportDescription": "The Scratch project has received financial support from the National Science Foundation, Scratch Foundation, Google, LEGO Foundation, Intel, Cartoon Network, Lemann Foundation, and MacArthur Foundation. See the <a href=\"/info/credits\">credits page</a> for more information. If you&#39;d like to support Scratch, please see the Scratch Foundation <a href=\"https://secure.donationpay.org/scratchfoundation/\">donate page</a>, or contact us at <a href=\"mailto:donate@scratch.mit.edu\">donate@scratch.mit.edu</a>."
"about.supportDescription": "The Scratch project has received financial support from the following organizations: {supportersList}. See the {creditsLink} for more information. If you'd like to support Scratch, please see the Scratch Foundation {donateLink}, or contact us at {donateemail}.",
"about.donateLinkText": "donations page",
"about.creditsLinkText": "credits page"
}

View file

@ -1,7 +1,6 @@
var React = require('react');
var render = require('../../lib/render.jsx');
var Activity = require('../../components/activity/activity.jsx');
var Page = require('../../components/page/www/page.jsx');
var Box = require('../../components/box/box.jsx');
var Button = require('../../components/forms/button.jsx');
@ -34,10 +33,6 @@ var Components = React.createClass({
title="Carousel component in a box!">
<Carousel />
</Box>
<h1>{'What\'s Happening??'}</h1>
<Activity />
<h1>{'Nothing!!!'}</h1>
<Activity items={[]} />
<h1>This is a Spinner</h1>
<Spinner />
<h1>Colors</h1>

View file

@ -217,13 +217,6 @@ var ConferenceSplash = React.createClass({
</td>
<td><FormattedMessage id='conference-2017.date' /></td>
<td>
<FormattedDate
value={new Date(2017, 10, 10)}
year='numeric'
month='long'
day='2-digit'
/>
{' - '}
<FormattedDate
value={new Date(2017, 10, 12)}
year='numeric'
@ -267,8 +260,8 @@ var ConferenceSplash = React.createClass({
</tr>
</tbody>
</table>
<a className='button mod-2017-panel' href='mailto:admissions@treeoflifelearning.com'>
<FormattedMessage id='conference-2017.contact' />
<a className='button mod-2017-panel' href='https://scratchcostarica.com/'>
<FormattedMessage id='conference-2017.website' />
</a>
</section>
<section className='conf2017-panel mod-chile'>

View file

@ -8,7 +8,6 @@
"conference-2017.audience": "Audience",
"conference-2017.language": "Language",
"conference-2017.website": "Visit Website",
"conference-2017.contact": "Contact Organizer",
"conference-2017.franceTitle": "Scratch2017BDX",
"conference-2017.franceSubTitle": "Opening, Inspiring, Connecting",

View file

@ -0,0 +1,152 @@
var FormattedDate = require('react-intl').FormattedDate;
var FormattedMessage = require('react-intl').FormattedMessage;
var React = require('react');
var render = require('../../../../lib/render.jsx');
var FlexRow = require('../../../../components/flex-row/flex-row.jsx');
var Page = require('../../../../components/page/conference/2018/page.jsx');
var TitleBanner = require('../../../../components/title-banner/title-banner.jsx');
require('../../../../components/forms/button.scss');
require('./index.scss');
var ConferenceSplash = React.createClass({
type: 'ConferenceSplash',
render: function () {
return (
<div className='index mod-2018'>
<TitleBanner className='mod-conference mod-2018'>
<div className='title-banner-image mod-2018'></div>
<h1 className='title-banner-h1 mod-2018'>
<center>
<FormattedMessage id='conference-2018.title' />
<br />
<FormattedMessage id='conference-2018.subtitle' />
</center>
</h1>
<h3 className='title-banner-h3 mod-2018'>
<FormattedMessage id='conference-2018.dateDesc' />
</h3>
</TitleBanner>
<div className='inner'>
<section className='conf2018-panel mod-desc'>
<p className='conf2018-panel-desc'>
<FormattedMessage id='conference-2018.desc1' />
<br />
<br />
<FormattedMessage id='conference-2018.desc2' />
</p>
<table className='conf2018-panel-details'>
<tbody>
<tr className='conf2018-panel-row'>
<td className='conf2018-panel-row-icon'>
<img
className='conf2018-panel-row-icon-image'
src='/svgs/conference/index/calendar-icon.svg'
alt='Calendar Icon'
/>
</td>
<td><FormattedMessage id='conference-2018.date' /></td>
<td>
<FormattedDate
value={new Date(2018, 6, 26)}
year='numeric'
month='long'
day='2-digit'
/>
{' - '}
<FormattedDate
value={new Date(2018, 6, 28)}
year='numeric'
month='long'
day='2-digit'
/>
<FormattedMessage id='conference-2018.dateDescMore' />
</td>
</tr>
<tr className='conf2018-panel-row'>
<td className='conf2018-panel-row-icon'>
<img
className='conf2018-panel-row-icon-image'
src='/svgs/conference/index/map-icon.svg'
alt='Map Icon'
/>
</td>
<td><FormattedMessage id='conference-2018.location' /></td>
<td><FormattedMessage id='conference-2018.locationDetails' /></td>
</tr>
</tbody>
</table>
<p>
<FormattedMessage id='conference-2018.registrationDate' />
</p>
</section>
<section className='conf2018-panel'>
<p className='conf2018-panel-desc'>
<FormattedMessage id='conference-2018.sessionDesc' />
</p>
<p className='conf2018-panel-session'>
<p className='conf2018-panel-session'>
<b>
<FormattedMessage id='conference-2018.sessionItem1Title' />
</b>{' '}
<FormattedMessage id='conference-2018.sessionItem1Desc' />
</p>
<p className='conf2018-panel-session'>
<b>
<FormattedMessage id='conference-2018.sessionItem2Title' />
</b>{' '}
<FormattedMessage id='conference-2018.sessionItem2Desc' />
</p>
<p className='conf2018-panel-session'>
<b>
<FormattedMessage id='conference-2018.sessionItem3Title' />
</b>{' '}
<FormattedMessage id='conference-2018.sessionItem3Desc' />
</p>
<p className='conf2018-panel-session'>
<b>
<FormattedMessage id='conference-2018.sessionItem4Title' />
</b>{' '}
<FormattedMessage id='conference-2018.sessionItem4Desc' />
</p>
<p className='conf2018-panel-deadline'>
<FormattedMessage id='conference-2018.deadline' />
</p>
</p>
<a className='button mod-2018-panel' href='https://docs.google.com/forms/d/e/1FAIpQLSd7SkuQ-dfW-P3aArSQokK9GkKAUKufTVBHod_ElNIiFE9iBQ/viewform?usp=sf_link'>
<FormattedMessage id='conference-2018.proposal' />
</a>
</section>
<section className='conf2018-panel mod-registration'>
<FlexRow className='conf2018-panel-title'>
<div className='conf2018-panel-title-text'>
<h3><FormattedMessage id='conference-2018.registrationTitle' /></h3>
</div>
</FlexRow>
<p className='conf2018-panel-desc'>
<FormattedMessage id='conference-2018.registrationEarly' />
<br/>
<FormattedMessage id='conference-2018.registrationStandard' />
</p>
</section>
<section className='conf2018-panel mod-questions'>
<p className='conf2018-panel-desc'>
<FormattedMessage
id='conference-2018.questions'
values={{
emailLink: <a href='mailto:conference@scratch.mit.edu'>
conference@scratch.mit.edu
</a>
}}
/>
</p>
</section>
</div>
</div>
);
}
});
render(<Page><ConferenceSplash /></Page>, document.getElementById('app'));

View file

@ -0,0 +1,161 @@
@import "../../../../colors";
@import "../../../../frameless";
.title-banner.mod-conference.mod-2018 {
padding-top: 0;
}
.title-banner-image.mod-2018 {
opacity: .75;
margin-bottom: 1.75rem;
background-image: url("/images/conference/index/2018/title-banner.jpg");
background-position: center;
background-size: cover;
width: 100%;
height: 20rem;
}
.title-banner-h1.mod-2018 {
line-height: 1.25em;
}
.conf2018-panel,
.title-banner-h3.mod-2018 {
margin: auto;
width: 48.75rem;
}
.title-banner-h3.mod-2018 {
text-align: center;
color: $type-white;
}
.conf2018-panel {
border-bottom: 1px solid $ui-border;
}
.conf2018-panel.mod-last {
border-bottom: 0;
}
.flex-row.conf2018-panel-title {
justify-content: flex-start;
align-items: center;
}
.conf2018-panel-desc {
margin: 2rem 0;
}
td {
padding: .75rem 1.25rem;
vertical-align: top;
}
.conf2018-panel-row-icon-image {
margin-top: .125rem;
width: 1rem;
height: 1rem;
}
.button.mod-2018-panel {
display: block;
margin: 2rem auto 0;
background-color: $ui-orange;
padding: 1rem 0;
width: 13.75rem;
text-align: center;
color: $type-white;
}
@media only screen and (max-width: $mobile - 1) {
.index.mod-2018 {
text-align: left;
}
.title-banner-image.mod-2018 {
height: 10rem;
}
.conf2018-panel,
.title-banner-h3.mod-2018 {
width: initial;
}
.conf2018-panel {
margin: auto .5rem;
}
.title-banner-h3.mod-2018 {
margin: 1rem .5rem .5rem;
font-size: 1.1rem;
}
.flex-row.conf2018-panel-title {
flex-direction: row;
}
.conf2018-panel-title-text {
max-width: 14rem;
}
.conf2018-panel-row > td {
padding: .75rem .375rem .75rem 0;
}
}
@media only screen and (min-width: $mobile) and (max-width: $tablet - 1) {
.index.mod-2018 {
text-align: left;
}
.title-banner-image.mod-2018 {
height: 10rem;
}
.conf2018-panel,
.title-banner-h3.mod-2018 {
margin: auto .5rem ;
width: initial;
}
.title-banner-h3.mod-2018 {
font-size: 1.1rem;
}
.flex-row.conf2018-panel-title {
flex-direction: row;
}
.conf2018-panel-title-text {
max-width: 18.75rem;
}
.button.mod-2018-panel {
width: 5.75rem;
}
}
@media only screen and (min-width: $tablet) and (max-width: $desktop - 1) {
.index.mod-2018 {
text-align: left;
}
.title-banner-image.mod-2018 {
height: 15rem;
}
.conf2018-panel,
.title-banner-h3.mod-2018 {
margin: auto;
width: 38.75rem;
}
.title-banner-h3.mod-2018 {
font-size: 1.1rem;
}
.button.mod-2018-panel {
width: 8.75rem;
}
}

View file

@ -0,0 +1,36 @@
{
"conference-2018.title": "Scratch Conference 2018:",
"conference-2018.subtitle": "The Next Generation",
"conference-2018.dateDesc": "July 26-28, 2018 | Cambridge, MA, USA",
"conference-2018.dateDescMore": " (with opening reception the evening of July 25)",
"conference-2018.locationDetails": "MIT Media Lab, Cambridge, MA",
"conference-2018.seeBelow": "Learn more about conference dates and locations below.",
"conference-2018.date": "When:",
"conference-2018.location": "Where:",
"conference-2018.desc1": "Join us for the Scratch@MIT conference, a playful gathering of educators, researchers, developers, and other members of the worldwide Scratch community.",
"conference-2018.desc2": "We're planning a very participatory conference, with an entire day of hands-on workshops and lots of opportunities for peer-to-peer discussion and collaboration. The conference is intended primarily for adults who support young people learning Scratch.",
"conference-2018.registrationDate": "Registration opens March 1, 2018.",
"conference-2018.sessionDesc": "Interested in offering a session? We invite four types of proposals:",
"conference-2018.sessionItem1Title": "Poster/demonstration (90 minutes).",
"conference-2018.sessionItem1Desc": "Show off your project in an exhibition setting, alongside other presenters. You will be provided with display space for a poster and table space for a computer or handouts.",
"conference-2018.sessionItem2Title": "Hands-on workshop (90 minutes).",
"conference-2018.sessionItem2Desc": "Engage participants in hands-on activities, highlighting new ways of creating and collaborating with Scratch.",
"conference-2018.sessionItem3Title": "Interactive panel (60 minutes).",
"conference-2018.sessionItem3Desc": "Discuss a Scratch-related topic in a panel with three or more people. Your proposal should describe how you will engage the audience during the session.",
"conference-2018.sessionItem4Title": "Ignite talk (5 minutes).",
"conference-2018.sessionItem4Desc": "Share what you've been doing in a short, lively presentation.",
"conference-2018.deadline": "Deadline for proposals is February 5, 2018.",
"conference-2018.proposal": " Submit Your Proposal",
"conference-2018.proposalDeadline": "Deadline for proposals: February 5",
"conference-2018.proposalAccept": "Notification of acceptance: March 1",
"conference-2018.registrationTitle": "Registration:",
"conference-2018.registrationEarly": "Early Bird Registration (March 1-May 1): $200",
"conference-2018.registrationStandard": "Standard Registration (after May 1): $300",
"conference-2018.questions": "Questions? Contact the Scratch Team at {emailLink}"
}

View file

@ -26,6 +26,11 @@ var Credits = React.createClass({
<img src="//cdn.scratch.mit.edu/get_image/user/3581881_170x170.png" alt="Carl Avatar" />
<span className="name">Carl Bowman</span>
</li>
<li>
<img src="//cdn2.scratch.mit.edu/get_image/user/27383273_60x60.png" alt="Karishma Avatar" />
<span className="name">Karishma Chadha</span>
</li>
<li>
<img src="//cdn.scratch.mit.edu/get_image/user/900283_170x170.png" alt="Champika Avatar" />
@ -72,6 +77,11 @@ var Credits = React.createClass({
<span className="name">Carmelo Presicce</span>
</li>
<li>
<img src="//cdn2.scratch.mit.edu/get_image/user/25500116_170x170.png" alt="Tina Avatar" />
<span className="name">Tina Quach</span>
</li>
<li>
<img src="//cdn.scratch.mit.edu/get_image/user/167_170x170.png" alt="Mitchel Avatar" />
<span className="name">Mitchel Resnick</span>
@ -152,18 +162,13 @@ var Credits = React.createClass({
</li>
<li>
<img src="//cdn.scratch.mit.edu/get_image/user/36977_170x170.png" alt="Connor Avatar" />
<span className="name">Connor Hudson</span>
<img src="//cdn2.scratch.mit.edu/get_image/user/26249744_60x60.png" alt="Joel Avatar" />
<span className="name">Joel Gritter</span>
</li>
<li>
<img src="//cdn.scratch.mit.edu/get_image/user/14110644_170x170.png" alt="Lily Avatar" />
<span className="name">Lily Kim</span>
</li>
<li>
<img src="//cdn.scratch.mit.edu/get_image/user/13639421_170x170.png" alt="Tauntaun Avatar" />
<span className="name">Tauntaun Kim</span>
<img src="//cdn2.scratch.mit.edu/get_image/user/5311910_60x60.png" alt="Carolina Avatar" />
<span className="name">Carolina Kaufman</span>
</li>
<li>
@ -171,11 +176,6 @@ var Credits = React.createClass({
<span className="name">Dalton Miner</span>
</li>
<li>
<img src="//cdn.scratch.mit.edu/get_image/user/17618638_170x170.png" alt="Hanako Avatar" />
<span className="name">Hanako Tjia</span>
</li>
<li>
<img src="//cdn.scratch.mit.edu/get_image/user/159139_170x170.png" alt="Franchette Avatar" />
<span className="name">Franchette Viloria</span>
@ -206,10 +206,13 @@ var Credits = React.createClass({
Dave Feinberg,
Chris Graves,
Megan Haddadi,
Connor Hudson,
Christina Huang,
Tony Hwang,
Abdulrahman Idlbi,
Randy Jou,
Lily Kim,
Tauntaun Kim,
Saskia Leggett,
Tim Mickel,
Amon Millner,
@ -218,6 +221,7 @@ var Credits = React.createClass({
Jay Silver,
Tammy Stern,
Lis Sylvan,
Hanako Tjia,
Claudia Urrea,
Oren Zuckerman
</p>
@ -232,19 +236,17 @@ var Credits = React.createClass({
<p>
<FormattedHTMLMessage id='credits.acknowledgementsContributors' />
&nbsp;
Susan Abend, Robbie Berg, Lauren Bessen, Keith Braadfladt,
Susan Carillo, Will Denton, Nathan Dinsmore, Catherine Feldman,
Jodi Finch, Ioana Fineberg, JT Galla, Rachel Garber, Chris Garrity,
Cassy Gibbs, Brian Harvey, Roland Hebert, Tracy Ho, Benjamin Howe,
Kapaya Katongo, Evan Karatzas, Christine Kim, Joren Lauwers,
Mike Lee, Jeff Lieberman, Mark Loughridge, Kelly Liu, Anthony Lu,
Danny Lutz, David Malan, Wayne Marshall, John McIntosh,
Paul Medlock-Walton, Dongfang (Tian) Mi, Ximena Miranda,
Jens Moenig, Evan Moore, Geetha Narayanan, Kate Nazemi,
Liddy Nevile, Wing Ngan, Derek O'Connell, Tim Radvan, Karen Randall,
Ian Reynolds, Miriam Ruiz, Chinua Shaw, Ed Shems, Cynthia Solomon,
Daniel Strimpel, Kilmer Sweazy, John Henry Thompson, Ubong Ukoh,
Vladimir Vuksan, Han Xu.
Susan Abend, Robbie Berg, Lauren Bessen, Keith Braadfladt, Susan Carillo,
Will Denton, Nathan Dinsmore, Catherine Feldman, Jodi Finch, Ioana Fineberg,
JT Galla, Rachel Garber, Chris Garrity, Cassy Gibbs, Brian Harvey,
Roland Hebert, Tracy Ho, Benjamin Howe, Kapaya Katongo, Evan Karatzas,
Christine Kim, Joren Lauwers, Mike Lee, Jeff Lieberman, Mark Loughridge,
Kelly Liu, Anthony Lu, Danny Lutz, David Malan, Wayne Marshall,
John McIntosh, Paul Medlock-Walton, Dongfang (Tian) Mi, Ximena Miranda,
Jens Moenig, Evan Moore, Geetha Narayanan, Kate Nazemi, Liddy Nevile,
Wing Ngan, Derek O'Connell, Tim Radvan, Karen Randall, Ian Reynolds,
Miriam Ruiz, Chinua Shaw, Ed Shems, Cynthia Solomon, Daniel Strimpel,
Kilmer Sweazy, John Henry Thompson, Ubong Ukoh, Vladimir Vuksan, Han Xu.
&nbsp;
<FormattedHTMLMessage id='credits.acknowledgementsTranslators' />
</p>
@ -315,4 +317,4 @@ var Credits = React.createClass({
}
});
render(<Page><Credits /></Page>, document.getElementById('app'));
render(<Page><Credits /></Page>, document.getElementById('app'));

View file

@ -1,7 +1,6 @@
var React = require('react');
var render = require('../../lib/render.jsx');
var FormattedHTMLMessage = require('react-intl').FormattedHTMLMessage;
var FormattedMessage = require('react-intl').FormattedMessage;
var Page = require('../../components/page/www/page.jsx');
@ -22,7 +21,16 @@ var Developers = React.createClass({
<FormattedMessage id='developers.title' />
</h1>
<p className="title-banner-p intro">
<FormattedHTMLMessage id='developers.intro' />
<FormattedMessage
id='developers.intro'
values={{
introLink: (
<a href="/info/credits">
<FormattedMessage id='developers.introLinkText' />
</a>
)
}}
/>
</p>
</div>
<div className="band">
@ -64,7 +72,7 @@ var Developers = React.createClass({
<div className="inner">
<section id="projects">
<span className="nav-spacer"></span>
<h2>Projects</h2>
<h2><FormattedMessage id='developers.projectsTitle' /></h2>
<p className="intro">
<FormattedMessage id='developers.projectsIntro' />
</p>
@ -72,7 +80,25 @@ var Developers = React.createClass({
<div className="body-copy column">
<h3><FormattedMessage id='developers.scratchBlocksTitle' /></h3>
<p>
<FormattedHTMLMessage id='developers.scratchBlocksIntro' />
<FormattedMessage
id='developers.scratchBlocksIntro'
values={{
blocklyLink: (
<a href="https://developers.google.com/blockly/">
<FormattedMessage
id='developers.scratchBlocksIntroBlocklyLinkText'
/>
</a>
),
githubLink: (
<a href="https://github.com/LLK/scratch-blocks">
<FormattedMessage
id='developers.hereLinkText'
/>
</a>
)
}}
/>
</p>
<p>
<FormattedMessage id='developers.scratchBlocksBody' />
@ -84,7 +110,16 @@ var Developers = React.createClass({
<div className="body-copy column">
<h3><FormattedMessage id='developers.wwwTitle' /></h3>
<p>
<FormattedHTMLMessage id='developers.wwwIntro' />
<FormattedMessage
id='developers.wwwIntro'
values={{
wwwIntroLink: (
<a href="https://github.com/LLK/scratch-www">
GitHub
</a>
)
}}
/>
</p>
</div>
@ -94,13 +129,21 @@ var Developers = React.createClass({
<div className="body-copy column">
<h3>ScratchJr</h3>
<p>
ScratchJr is an introductory programming language{' '}
that enables young children (ages 5-7) to create{' '}
their own interactive stories and games. For more{' '}
information, visit the{' '}
<a href="https://www.scratchjr.org/">ScratchJr website</a>{' '}
or access the code and documentation{' '}
<a href="https://github.com/LLK/scratchjr">here</a>.
<FormattedMessage
id='developers.jrBody'
values={{
websiteLink: (
<a href="https://www.scratchjr.org">
<FormattedMessage id='developers.jrBodyWebsiteLinkText' />
</a>
),
githubLink: (
<a href="https://github.com/LLK/scratchjr">
GitHub
</a>
)
}}
/>
</p>
</div>
</FlexRow>
@ -110,12 +153,22 @@ var Developers = React.createClass({
<span className="nav-spacer"></span>
<h2><FormattedMessage id='developers.principlesTitle' /></h2>
<p className="intro">
<FormattedHTMLMessage id='developers.principlesIntro' />
<FormattedMessage
id='developers.principlesIntro'
values={{
learningPrinciples: (
<b><FormattedMessage id='developers.LearningPrinciples' /></b>
),
designPrinciples: (
<b><FormattedMessage id='developers.DesignPrinciples' /></b>
)
}}
/>
</p>
<FlexRow className="sidebar-row">
<div className="body-copy column">
<h3><FormattedMessage id='developers.learningPrinciplesTitle' /></h3>
<h3><FormattedMessage id='developers.LearningPrinciples' /></h3>
<dl>
<dt><FormattedMessage id='developers.projectsTitle' /></dt>
<dd>
@ -139,7 +192,7 @@ var Developers = React.createClass({
<FlexRow className="sidebar-row">
<div className="body-copy column">
<h3><FormattedMessage id='developers.designPrinciplesTitle' /></h3>
<h3><FormattedMessage id='developers.DesignPrinciples' /></h3>
<dl>
<dt><FormattedMessage id='developers.designPrinciplesRoomTitle' /></dt>
<dd>
@ -165,14 +218,39 @@ var Developers = React.createClass({
<section id="join">
<span className="nav-spacer"></span>
<h2><FormattedMessage id='developers.joinTitle' /></h2>
<p><FormattedHTMLMessage id='developers.joinBody' /></p>
<p>
<FormattedMessage
id='developers.joinBody'
values={{
jobsPageLink: (
<a href="/jobs">
<FormattedMessage id='developers.joinBodyJobsLinkText' />
</a>
),
emailLink: (
<a href="mailto:jobs+developers@scratch.mit.edu">
jobs+developers@scratch.mit.edu
</a>
)
}}
/>
</p>
</section>
<section id="donate">
<span className="nav-spacer"></span>
<h2><FormattedMessage id='developers.donateTitle' /></h2>
<p>
<FormattedHTMLMessage id='developers.donateIntro' />
<FormattedMessage
id='developers.donateIntro'
values={{
donateLink: (
<a href="https://secure.donationpay.org/scratchfoundation/">
<FormattedMessage id='developers.donateIntroLinkText' />
</a>
)
}}
/>
</p>
<p>
<FormattedMessage id='developers.donateBody' />
@ -207,7 +285,26 @@ var Developers = React.createClass({
<div className="faq column">
<h4><FormattedMessage id='developers.faqAboutTitle' /></h4>
<p>
<FormattedHTMLMessage id='developers.faqAboutBody' />
<FormattedMessage
id='developers.faqAboutBody'
values={{
llkLink: (
<a href="https://www.media.mit.edu/groups/lifelong-kindergarten/overview">
<FormattedMessage id='developers.faqAboutBodyLLKLinkText' />
</a>
),
mitLink: (
<a href="http://media.mit.edu/">
<FormattedMessage id='developers.faqAboutBodyMITLinkText' />
</a>
),
aboutLink: (
<a href="/about">
<FormattedMessage id='developers.hereLinkText' />
</a>
)
}}
/>
</p>
</div>
<div className="faq column">
@ -239,7 +336,19 @@ var Developers = React.createClass({
<div className="faq column">
<h4><FormattedMessage id='developers.faqCollabTitle' /></h4>
<p>
<FormattedHTMLMessage id='developers.faqCollabBody' />
<FormattedMessage
id='developers.faqCollabBody'
values={{
githubLink: (
<a href="https://github.com/LLK/">GitHub</a>
),
emailLink: (
<a href="mailto:help@scratch.mit.edu">
help@scratch.mit.edu
</a>
)
}}
/>
</p>
</div>
</FlexRow>

View file

@ -1,6 +1,8 @@
{
"developers.hereLinkText": "here",
"developers.title": "Scratch for Developers",
"developers.intro": "On this page, youll find information about open source projects created and maintained by the <a href=\"/info/credits\">Scratch Team at MIT</a>, as well as our thoughts on best practices for designing learning experiences for children.",
"developers.introLinkText": "Scratch Team at MIT",
"developers.intro": "On this page, youll find information about open source projects created and maintained by the {introLink}, as well as our thoughts on best practices for designing learning experiences for children.",
"developers.projectsTitle": "Projects",
"developers.principlesTitle": "Principles",
"developers.joinTitle": "Join Us",
@ -9,12 +11,16 @@
"developers.faqTitle": "FAQ",
"developers.projectsIntro": "The following projects are open source and available for any purpose.",
"developers.scratchBlocksTitle": "Scratch Blocks",
"developers.scratchBlocksIntro": "Scratch Blocks is a new development project for the next generation of graphical programming blocks, based on a collaboration between Google and MITs Scratch Team — building on Googles <a href=\"https://developers.google.com/blockly/\">Blockly technology</a> and informed by the Scratch Teams expertise in developing creative learning tools for young people. Scratch Blocks will provide a framework for building programming blocks in both vertical (text-based) and horizontal (icon-based) formats. You can access the code (currently as a developer-preview) and documentation <a href=\"https://github.com/llk/scratch-blocks\">here</a>.",
"developers.scratchBlocksIntroBlocklyLinkText": "Blockly technology",
"developers.scratchBlocksIntro": "Scratch Blocks is a new development project for the next generation of graphical programming blocks, based on a collaboration between Google and MITs Scratch Team — building on Googles {blocklyLink} and informed by the Scratch Teams expertise in developing creative learning tools for young people. Scratch Blocks will provide a framework for building programming blocks in both vertical (text-based) and horizontal (icon-based) formats. You can access the code (currently as a developer-preview) and documentation {githubLink}.",
"developers.scratchBlocksBody": "This first release includes code for Scratchs Horizontal Grammar. Looking ahead, we plan to release additional code including but not limited to the Vertical Grammar (currently used by Scratch), a new Rendering Engine to support sprites and graphic effects, and a new Audio Engine to support creation with sound and music.",
"developers.wwwTitle": "Scratch WWW",
"developers.wwwIntro": "Scratch-www is a standalone web client for the Scratch Community, built using React and Redux. Access the code and documentation <a href=\"https://github.com/LLK/scratch-www\">here</a>.",
"developers.principlesIntro": "We created Scratch to empower young people to think creatively, reason systematically, and work collaboratively. We are guided by a set of <b>Learning Principles</b> and <b>Design Principles</b> that we hope you will follow as you develop new tools and technologies with Scratch Blocks.",
"developers.learningPrinciplesTitle": "Learning Principles",
"developers.wwwIntro": "Scratch-www is a standalone web client for the Scratch Community, built using React and Redux. Access the code and documentation through {wwwIntroLink}.",
"developers.LearningPrinciples": "Learning Principles",
"developers.DesignPrinciples": "Design Principles",
"developers.principlesIntro": "We created Scratch to empower young people to think creatively, reason systematically, and work collaboratively. We are guided by a set of {learningPrinciples} and {designPrinciples} that we hope you will follow as you develop new tools and technologies with Scratch Blocks.",
"developers.jrBodyWebsiteLinkText": "ScratchJr website",
"developers.jrBody": "ScratchJr is an introductory programming language that enables young children (ages 5-7) to create their own interactive stories and games. For more information, visit the {websiteLink} or access the code and documentation on {githubLink}.",
"developers.learningPrinciplesProjectsBody": "People learn best when they are actively working on projects — generating new ideas, designing prototypes, making improvements and creating final products.",
"developers.learningPrinciplesPassionTitle": "Passion",
"developers.learningPrinciplesPassionBody": "When people focus on things they care about, they work longer and harder, persist in the face of challenges, and learn more in the process.",
@ -22,7 +28,6 @@
"developers.learningPrinciplesPeersBody": "Learning flourishes as a social activity, with people sharing ideas, collaborating on projects, and building on one another's work.",
"developers.learningPrinciplesPlayTitle": "Play",
"developers.learningPrinciplesPlayBody": "Learning involves playful experimentation — trying new things, tinkering with materials, testing boundaries, taking risks, iterating again and again.",
"developers.designPrinciplesTitle": "Design Principles",
"developers.designPrinciplesRoomTitle": "Low Floor & Wide Walls",
"developers.designPrinciplesRoomBody": "In order to encourage a varied and diverse set of interactions, we explicitly include elements and features that are easy for kids to understand (low floor), but general enough to support diverse uses (wide walls).",
"developers.designPrinciplesSimpleTitle": "Make it as Simple as Possible — And Maybe Even Simpler",
@ -31,13 +36,17 @@
"developers.designPrinciplesGlobalBody": "Many math and science activities have traditionally been biased towards specific populations. By paying special attention to creating accessible and appealing technologies, we are working to close the gap.",
"developers.designPrinciplesTinkerTitle": "Design for Tinkerability",
"developers.designPrinciplesTinkerBody": "We believe that the learning process is inherently iterative. Tinkerers start by exploring and experimenting, then revising and refining their goals and creations. To support this style of interaction, we design our interfaces to encourage quick experimentation and rapid cycles of iteration.",
"developers.joinBody": "We're a diverse group of educators, designers, and engineers, who work together in a playful, creative environment full of LEGO bricks, craft materials, and maker tools. We strongly value diversity, collaboration, and respect in the workplace. If you're interested in joining us, take a look at our open positions on our <a href=\"/jobs\">Jobs Page</a>, or send us an email at <a href=\"mailto:jobs+developers@scratch.mit.edu\">jobs+developers@scratch.mit.edu</a>.",
"developers.donateIntro": "We are pleased to provide Scratch free of charge. If you enjoy using Scratch, please consider <a href=\"https://secure.donationpay.org/scratchfoundation/\">making a donation to support Scratch</a>. Donations of any size are appreciated.",
"developers.joinBodyJobsLinkText": "Jobs Page",
"developers.joinBody": "We're a diverse group of educators, designers, and engineers, who work together in a playful, creative environment full of LEGO bricks, craft materials, and maker tools. We strongly value diversity, collaboration, and respect in the workplace. If you're interested in joining us, take a look at our open positions on our {jobsPageLink}, or send us an email at {emailLink}.",
"developers.donateIntroLinkText": "making a donation to support Scratch",
"developers.donateIntro": "We are pleased to provide Scratch free of charge. If you enjoy using Scratch, please consider {donateLink}. Donations of any size are appreciated.",
"developers.donateBody": "Your donation to the Scratch Foundation will be used to support future development of Scratch software and the Scratch website.",
"developers.donateThanks": "Thanks for supporting Scratch!",
"developers.partnersIntro": "The creation and maintenance of this open source code would not be possible without generous technical and financial support from our partners:",
"developers.faqAboutTitle": "Where can I learn more about Scratch?",
"developers.faqAboutBody": "Scratch is a free programming language and online community where young people can create their own interactive stories, games, and animations. Scratch is a project of the <a href=\"https://llk.media.mit.edu/\">Lifelong Kindergarten</a> Group at the <a href=\"http://media.mit.edu/\">MIT Media Lab</a>. You can learn more about Scratch <a href=\"/about\">here</a>.",
"developers.faqAboutBodyLLKLinkText": "Lifelong Kindergarten",
"developers.faqAboutBodyMITLinkText": "MIT Media Lab",
"developers.faqAboutBody": "Scratch is a free programming language and online community where young people can create their own interactive stories, games, and animations. Scratch is a project of the {llkLink} Group at the {mitLink}. You can learn more about Scratch {aboutLink}.",
"developers.faqRulesTitle": "Are there rules to using this code in my application?",
"developers.faqRulesBody": "You may use this code in accordance with the license which governs each project. We also strongly encourage you to consider the learning and design principles (above, on this page) when building creative learning experiences for kids of all ages.",
"developers.faqNameTitle": "Am I allowed to use the name \"Scratch Blocks\" in the description of my app and other public messaging?",
@ -47,5 +56,5 @@
"developers.faqDifferencesTitle": "Whats the difference between Blockly and Scratch Blocks?",
"developers.faqDifferencesBody": "Scratch Blocks builds upon the Blockly code base, and is specifically designed with our principles in mind to support creative learning experiences.",
"developers.faqCollabTitle": "Id like to collaborate. How do I get in touch?",
"developers.faqCollabBody": "You can reach us over on <a href=\"https://github.com/LLK/\">github</a> or you can send an email to <a href=\"mailto:help@scratch.mit.edu\">help@scratch.mit.edu.</a> We look forward to hearing from you!"
"developers.faqCollabBody": "You can reach us over on {githubLink} or you can send an email to {emailLink}. We look forward to hearing from you!"
}

View file

@ -3,6 +3,7 @@ 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 api = require('../../lib/api');
var Page = require('../../components/page/www/page.jsx');
@ -13,7 +14,7 @@ var SubNavigation = require('../../components/subnavigation/subnavigation.jsx');
require('./download.scss');
require('../../components/forms/button.scss');
var Download = React.createClass({
var Download = injectIntl(React.createClass({
type: 'Download',
getInitialState: function () {
return {
@ -21,9 +22,13 @@ var Download = React.createClass({
};
},
componentDidMount: function () {
var uri = '/scratchr2/static/sa/version.xml';
if (this.props.intl.locale === 'pt-br') {
uri = '/scratchr2/static/sa/pt-br/version.xml';
}
api({
host: '',
uri: '/scratchr2/static/sa/version.xml',
uri: uri,
responseType: 'string'
}, function (err, body, res) {
if (err || res.statusCode >= 400) {
@ -39,12 +44,16 @@ var Download = React.createClass({
}.bind(this));
},
render: function () {
var downloadPath = '/scratchr2/static/sa/Scratch-';
if (this.props.intl.locale === 'pt-br') {
downloadPath = '/scratchr2/static/sa/pt-br/Scratch-';
}
if (this.state.swfVersion.length > 0 && this.state.swfVersion !== -1) {
var downloadUrls = {
mac: '/scratchr2/static/sa/Scratch-'+ this.state.swfVersion + '.dmg',
mac105: '/scratchr2/static/sa/Scratch-'+ this.state.swfVersion + '.air',
windows: '/scratchr2/static/sa/Scratch-'+ this.state.swfVersion + '.exe',
linux: '/scratchr2/static/sa/Scratch-'+ this.state.swfVersion + '.air'
mac: downloadPath + this.state.swfVersion + '.dmg',
mac105: downloadPath + this.state.swfVersion + '.air',
windows: downloadPath + this.state.swfVersion + '.exe',
linux: downloadPath + this.state.swfVersion + '.air'
};
}
@ -222,6 +231,7 @@ var Download = React.createClass({
<p><FormattedMessage id='download.knownIssuesOne' /></p>
<p><FormattedMessage id='download.knownIssuesTwo' /></p>
<p><FormattedHTMLMessage id='download.knownIssuesThree' /></p>
<p><FormattedMessage id='download.knownIssuesFour' /></p>
<a href="https://scratch.mit.edu/discuss/3/" className='button mod-link'>
<FormattedMessage id='download.reportBugs' />
</a>
@ -231,6 +241,6 @@ var Download = React.createClass({
</div>
);
}
});
}));
render(<Page><Download /></Page>, document.getElementById('app'));

View file

@ -1,7 +1,7 @@
{
"download.title": "Scratch 2.0 Offline Editor",
"download.intro": "You can install the Scratch 2.0 editor to work on projects without an internet connection. This version will work on Mac, Windows, and some versions of Linux (32 bit).",
"download.introMac": "<b>Note for Mac Users:</b> the latest version of Scratch 2.0 Offline requires Adobe Air 20. To upgrade to Adobe Air 20 manually, go <a href=\"https://get.adobe.com/air/\">here</a>.",
"download.introMac": "<b>Note for Mac Users:</b> the latest version of Scratch 2.0 Offline requires Adobe AIR 20. To upgrade to Adobe AIR 20 manually, go <a href=\"https://get.adobe.com/air/\">here</a>.",
"download.installation": "Installation",
"download.airTitle": "Adobe AIR",
"download.airBody": "If you don't already have it, download and install the latest <a href=\"http://get.adobe.com/air/\">Adobe AIR</a>",
@ -24,9 +24,10 @@
"download.otherVersionsOlder": "If you have an older computer, or cannot install the Scratch 2.0 offline editor, you can try installing <a href=\"http://scratch.mit.edu/scratch_1.4/\">Scratch 1.4</a>.",
"download.otherVersionsAdmin": "If you are a network administrator: a Scratch 2.0 MSI has been created and maintained by a member of the community and hosted for public download <a href=\"http://llk.github.io/scratch-msi/\">here</a>.",
"download.knownIssuesTitle": "Known issues",
"download.knownIssuesOne": "If your offline editor is crashing directly after Scratch is opened, install the Scratch 2 offline editor again (see step 2 above). This issue is due to a bug introduced in Adobe Air version 14 (released April 2014).",
"download.knownIssuesOne": "If your offline editor is crashing directly after Scratch is opened, install the Scratch 2 offline editor again (see step 2 above). This issue is due to a bug introduced in Adobe AIR version 14 (released April 2014).",
"download.knownIssuesTwo": "Graphic effects blocks (in \"Looks\") may slow down projects due to a known Flash bug.",
"download.knownIssuesThree": "The <b>backpack</b> is not yet available.",
"download.knownIssuesFour": "On Mac OS you may see a prompt indicating that \"Scratch 2 is trying to install a new helper tool\" and asking for your user name and password. We are currently investigating a solution to this problem.",
"download.reportBugs": "Report Bugs and Glitches",
"download.notAvailable": "Hmm, editor downloads are not available right now - please refresh the page to try again."
}

View file

@ -40,7 +40,7 @@
"faq.viewUnsharedTitle":"Can the Scratch Team view unshared projects on my 'My Stuff' page?",
"faq.viewUnsharedBody":"Since the Scratch Team is responsible for moderation, we have access to all content stored on the Scratch website - including unshared projects. If you prefer to work on projects in complete privacy, you can use either the <a href=\"/scratch2download\">Scratch 2.0 offline editor</a> or <a href=\"/scratch_1.4\">Scratch 1.4</a>.",
"faq.remixDefinitionTitle":"What is a remix?",
"faq.remixDefinitionBody":"When a Scratcher makes a copy of someone elses project and modifies it to add their own ideas (for example, by changing scripts or costumes), the resulting project is called a \"remix.\" Every project shared to the Scratch website can be remixed. We consider even a minor change to be a valid remix, as long as credit is given to the original project creator and others who made significant contributions to the remix.",
"faq.remixDefinitionBody":"When a Scratcher makes a copy of someone elses project and modifies it to add their own ideas (for example, by changing scripts or costumes), the resulting project is called a \"remix\". Every project shared to the Scratch website can be remixed. We consider even a minor change to be a valid remix, as long as credit is given to the original project creator and others who made significant contributions to the remix.",
"faq.remixableTitle":"Why does the Scratch Team require that all projects be “remixable”?",
"faq.remixableBody":"We believe that viewing and remixing interesting projects is a great way to learn to program, and leads to cool new ideas. Thats why the source code is visible for every project shared to the Scratch website.",
"faq.creativeCommonsTitle":"What if I dont want others to remix my projects?",
@ -50,12 +50,12 @@
"faq.confirmedAccountTitle":"What is a “confirmed” Scratch account?",
"faq.confirmedAccountBody":"A confirmed account on Scratch lets you share projects, write comments, and create studios. Confirming your account also lets you receive email updates from the Scratch Team.",
"faq.checkConfirmedTitle":"How can I check whether my account has been confirmed?",
"faq.checkConfirmedBody":"To check whether your account is confirmed, you must first log into your Scratch account in the top right of the screen. Once logged in, click on your username in the top right and select \"Account Settings\", then \"Email Settings\" on the left hand side. Confirmed email addresses will show a small green checkmark. Otherwise, you will see the text \"Your email address is unconfirmed\" in orange.",
"faq.checkConfirmedBody":"To check whether your account is confirmed, you must first log into your Scratch account in the top right of the screen. Once logged in, click on your username in the top right and select \"Account Settings\", then \"Email\" on the left hand side. Confirmed email addresses will show a small green checkmark. Otherwise, you will see the text \"Your email address is unconfirmed\" in orange.",
"faq.howToConfirmTitle":"How do I confirm my account?",
"faq.howToConfirmBody":"After registering for Scratch, you will receive an email with a link to confirm your account. If you cannot find the email, check your Spam folder. To resend the email, go to your Account Settings, click the Email tab, and follow the instructions there. Please note that it may take up to an hour for the email to arrive. If you still don't see the email after an hour, <a href=\"/contact-us\">let us know</a>.",
"faq.requireConfirmTitle":"Do I have to confirm my account?",
"faq.requireConfirmBody":"You can still use many aspects of Scratch without confirming your account, including creating and saving projects (without sharing them). Note: If you created an account before February 11, 2015, then you can still use social features on Scratch without confirming your account.",
"faq.forgotPasswordTitle":"I forgot my password, how can I reset it?",
"faq.forgotPasswordTitle":"I forgot my password. How can I reset it?",
"faq.forgotPasswordBody":"Enter your account name on the <a href=\"/accounts/password_reset/\">password reset page</a>. The website will send an email to the address associated with the account containing a link you can use to reset your password.",
"faq.changePasswordTitle":"How do I change my password?",
"faq.changePasswordBody":"Go to the Scratch website, login, and then click your username in the upper right corner of the window. Choose \"account settings\", and click the link to change your password.",
@ -75,14 +75,14 @@
"faq.deleteAccountBody":"Log in to Scratch, and then click your username in the top right-hand corner. Select “Account Settings,” then click the <em>“I want to delete my account”</em> link at the bottom of the page.",
"faq.scratchFreeTitle":"Is Scratch free?",
"faq.scratchFreeBody":"Yes! Scratch is available free of charge. You can use it in your school, and you can teach a course about it (even a course that costs money). You don't need to buy a license - it's free.",
"faq.scratchScreenshotTitle":"Can I use Scratch and / or screenshots of Scratch in a textbook or a CD?",
"faq.scratchScreenshotBody":"Yes, you can even write a book or chapter about Scratch. You may also use the Scratch logo when referring to Scratch. You may create screenshots / images of the Scratch application and website, and consider them to be licensed under the <a href=\"http://creativecommons.org/licenses/by-sa/2.0/deed.en\">Creative Commons Attribution-ShareAlike license</a>. We ask that you include a note on your textbook / CD / what have you that says \"Scratch is developed by the Lifelong Kindergarten Group at the MIT Media Lab. See http://scratch.mit.edu\".",
"faq.scratchDescriptionTitle":"Can I include a description of Scratch and the Scratch logo in brochures or other materials?",
"faq.scratchDescriptionBody":"Sure! We recommend the following description: \"Scratch is a programming language and online community where you can create your own interactive stories, games, and animations -- and share your creations with others around the world. In the process of designing and programming Scratch projects, young people learn to think creatively, reason systematically, and work collaboratively. Scratch is a project of the Lifelong Kindergarten group at the MIT Media Lab. It is available for free at http://scratch.mit.edu\"",
"faq.scratchScreenshotTitle":"Can I use screenshots of Scratch in a book or presentation?",
"faq.scratchScreenshotBody":"Yes, you can write a book or chapter about Scratch. You may create screenshots / images of the Scratch application and website, and consider them to be licensed under the <a href=\"http://creativecommons.org/licenses/by-sa/2.0/deed.en\">Creative Commons Attribution-ShareAlike license</a>. We ask that you include a note on your materials that says, \"Scratch is developed by the Lifelong Kindergarten Group at the MIT Media Lab. It is available for free at https://scratch.mit.edu\".",
"faq.scratchDescriptionTitle":"Can I include a description of Scratch in brochures or other materials?",
"faq.scratchDescriptionBody":"Sure! We recommend the following description: \"Scratch is a programming language and online community where you can create your own interactive stories, games, and animations -- and share your creations with others around the world. In the process of designing and programming Scratch projects, young people learn to think creatively, reason systematically, and work collaboratively. Scratch is a project of the Lifelong Kindergarten group at the MIT Media Lab. It is available for free at https://scratch.mit.edu\"",
"faq.presentScratchTitle":"Can I present Scratch at a conference?",
"faq.presentScratchBody":"Please feel free to make presentations about Scratch to educators or other groups. We grant our permission to make presentations.",
"faq.supportMaterialTitle":"May I use / remix Scratch support materials, sprites, images, sounds or sample projects Ive found on the website?",
"faq.supportMaterialBody":"Yes - Scratch support materials made available on the Scratch website by the Scratch Team are available under the <a href=\"http://creativecommons.org/licenses/by-sa/2.0/deed.en\">Creative Commons Attribution-ShareAlike license</a>, with the exception of the Scratch Logo, Scratch Cat, and Gobo (Scratch trademarks).",
"faq.supportMaterialBody":"Yes - Scratch support materials made available on the Scratch website by the Scratch Team are available under the <a href=\"http://creativecommons.org/licenses/by-sa/2.0/deed.en\">Creative Commons Attribution-ShareAlike license</a>, with the exception of the Scratch Logo, Scratch Cat, Gobo, Pico, Nano, and Tera which are Scratch trademarks.",
"faq.sellProjectsTitle":"Can I sell my Scratch projects?",
"faq.sellProjectsBody":"Certainly - your project is your creation. Keep in mind that once you share your project on Scratch, everyone is free to download, remix, and reuse it as per the terms of the <a href=\"http://creativecommons.org/licenses/by-sa/2.0/deed.en\">CC-BY-SA 2.0 license</a>. So if you intend to sell your project, you may want to un-share it from Scratch.",
"faq.sourceCodeTitle":"Where can I find the source code for Scratch?",
@ -110,7 +110,7 @@
"faq.chatRoomTitle":"Can I make chat rooms with cloud data?",
"faq.chatRoomBody":"While it is technically possible to create chat rooms with cloud data, they are not currently allowed. We will reconsider this policy once we have a better sense of our capability for moderating and managing reports on cloud data.",
"faq.makeCloudVarTitle":"How do I add a cloud variable when I'm making a project?",
"faq.makeCloudVarBody":"When you make a variable, you can check the box that says \"Cloud variable.\" Any data you store will be saved on the server and visible to others.",
"faq.makeCloudVarBody":"When you make a variable, you can check the box that says \"Cloud variable\". Any data you store will be saved on the server and visible to others.",
"faq.changeCloudVarTitle":"Who can change the information in a cloud variable?",
"faq.changeCloudVarBody":"Only your project can store data in its cloud variable. If people change or remix your code, it creates a different variable in their project with the same name.",
"faq.newScratcherCloudTitle":"I am logged in, but I still cannot use projects with cloud data. What is going on?",

View file

@ -31,16 +31,16 @@ var Jobs = React.createClass({
<h3><FormattedMessage id='jobs.openings' /></h3>
<ul>
<li>
<a href="/jobs/moderator">
Community Moderator (Remote)
<a href="https://www.media.mit.edu/about/job-opportunities/web-designer-scratch/">
Designer
</a>
<span>
MIT Media Lab, Cambridge, MA (or Remote)
MIT Media Lab, Cambridge, MA
</span>
</li>
<li>
<a href="https://www.media.mit.edu/about/job-opportunities/junior-web-designer-scratch/">
Junior Designer
<a href="https://www.media.mit.edu/about/job-opportunities/qa-engineer-scratch/">
QA Engineer
</a>
<span>
MIT Media Lab, Cambridge, MA
@ -54,6 +54,14 @@ var Jobs = React.createClass({
MIT Media Lab, Cambridge, MA
</span>
</li>
<li>
<a href="https://www.media.mit.edu/about/job-opportunities/learning-resources/">
Learning Resource Designer
</a>
<span>
MIT Media Lab, Cambridge, MA
</span>
</li>
</ul>
</div>
</div>

View file

@ -46,7 +46,7 @@ var Moderator = React.createClass({
<ul>
<li>
Active participation in online communities, forums, or
other webbased media
other web-based media
</li>
<li>
Excellent writing and communication skills

View file

@ -12,8 +12,7 @@ var Messages = React.createClass({
type: 'ConnectedMessages',
getInitialState: function () {
return {
filterValues: [],
displayedMessages: []
filter: ''
};
},
getDefaultProps: function () {
@ -26,14 +25,17 @@ var Messages = React.createClass({
};
},
componentDidUpdate: function (prevProps) {
if (this.props.user != prevProps.user) {
if (this.props.user.username !== prevProps.user.username) {
if (this.props.user.token) {
this.props.dispatch(
messageActions.getMessages(
this.props.user.username,
this.props.user.token,
this.props.messages,
this.props.messageOffset
{
messages: this.props.messages,
offset: this.props.messageOffset,
filter: this.state.filter
}
)
);
this.props.dispatch(
@ -44,6 +46,12 @@ var Messages = React.createClass({
this.props.dispatch(
messageActions.getScratcherInvite(this.props.user.username, this.props.user.token)
);
} else {
// user is logged out, empty messages
this.props.dispatch(messageActions.setMessages([]));
this.props.dispatch(messageActions.setAdminMessages([]));
this.props.dispatch(messageActions.setScratcherInvite({}));
this.props.dispatch(messageActions.setMessagesOffset(0));
}
}
},
@ -53,8 +61,11 @@ var Messages = React.createClass({
messageActions.getMessages(
this.props.user.username,
this.props.user.token,
this.props.messages,
this.props.messageOffset
{
messages: this.props.messages,
offset: this.props.messageOffset,
filter: this.state.filter
}
)
);
this.props.dispatch(
@ -68,26 +79,19 @@ var Messages = React.createClass({
}
},
handleFilterClick: function (field, choice) {
switch (choice) {
case 'comments':
return this.setState({filterValues: ['addcomment']});
case 'projects':
return this.setState({filterValues: [
'loveproject',
'favoriteproject',
'remixproject'
]});
case 'studios':
return this.setState({filterValues: [
'curatorinvite',
'studioactivity',
'becomeownerstudio'
]});
case 'forums':
return this.setState({filterValues: ['forumpost']});
default:
return this.setState({filterValues: []});
if (this.props.user.token) {
this.props.dispatch(
messageActions.getMessages(
this.props.user.username,
this.props.user.token,
{
filter: choice,
clearCount: false
}
)
);
}
this.setState({filter: choice});
},
handleMessageDismiss: function (messageType, messageId) {
var adminMessages = null;
@ -105,36 +109,26 @@ var Messages = React.createClass({
messageActions.getMessages(
this.props.user.username,
this.props.user.token,
this.props.messages,
this.props.messageOffset
{
messages: this.props.messages,
offset: this.props.messageOffset,
filter: this.state.filter,
clearCount: false
}
)
);
},
filterMessages: function (messages, typesAllowed) {
var filteredMessages = [];
for (var i in messages) {
if (typesAllowed.indexOf(messages[i].type) > -1) {
filteredMessages.push(messages[i]);
}
}
return filteredMessages;
},
render: function () {
var loadMore = true;
if (this.props.messageOffset > this.props.messages.length && this.props.messageOffset > 0) {
loadMore = false;
}
var messages = this.props.messages;
if (this.state.filterValues.length > 0) {
messages = this.filterMessages(messages, this.state.filterValues);
}
return(
<MessagesPresentation
sessionStatus={this.props.sessionStatus}
user={this.props.user}
messages={messages}
messages={this.props.messages}
adminMessages={this.props.adminMessages}
scratcherInvite={this.props.invite}
numNewMessages={this.props.numNewMessages}
@ -143,6 +137,7 @@ var Messages = React.createClass({
loadMore={loadMore}
loadMoreMethod={this.handleLoadMoreMessages}
requestStatus={this.props.requestStatus}
filter={this.props.filter}
/>
);
}

View file

@ -16,7 +16,7 @@
"messages.messageTitle": "Messages",
"messages.profileComment": "{profileLink} commented on {commentLink}",
"messages.commentReply": "{profileLink} replied to your comment on {commentLink}",
"messages.profileOther": "{username}'s reply",
"messages.profileOther": "{username}'s profile",
"messages.profileSelf": "your profile",
"messages.projectComment": "{profileLink} commented on your project {commentLink}",
"messages.remixText": "{profileLink} remixed your project {remixedProjectLink} as {projectLink}",
@ -26,5 +26,5 @@
"messages.studioCommentReply": "{profileLink} replied to your comment in {commentLink}",
"messages.userJoinText": "Welcome to Scratch! After you make projects and comments, you'll get messages about them here. Go {exploreLink} or {makeProjectLink}.",
"messages.userJoinMakeProject": "make a project",
"messages.requestError": "oops! Looks like there was a problem getting some of your messages. Please try to reload this page"
"messages.requestError": "Oops! Looks like there was a problem getting some of your messages. Please try to reload this page."
}

View file

@ -24,6 +24,8 @@ var BecomeManagerMessage = React.createClass({
<SocialMessage
className={classes}
datetime={this.props.datetimePromoted}
iconSrc="/svgs/messages/owner-invite.svg"
iconAlt="become owner notification image"
>
<FormattedMessage
id='messages.becomeManagerText'

View file

@ -1,4 +1,5 @@
var classNames = require('classnames');
var connect = require('react-redux').connect;
var FormattedMessage = require('react-intl').FormattedMessage;
var injectIntl = require('react-intl').injectIntl;
var React = require('react');
@ -50,21 +51,21 @@ var CommentMessage = injectIntl(React.createClass({
/>;
}
} else if (objectType === 1) {
var profileLink = '/users/' + this.props.objectId + '/#comments-' + this.props.commentId;
var profileLink = '/users/' + this.props.objectTitle + '/#comments-' + this.props.commentId;
var linkText = '';
if (typeof commentee !== 'undefined' && commentee === this.props.user.username) {
// is a profile comment, and is a reply
if (this.props.objectTitle === this.props.user.username) {
linkText = this.props.intl.formatMessage({
id: 'messages.profileSelf'
});
linkText = <FormattedMessage
id='messages.profileSelf'
/>;
} else {
linkText = this.props.intl.formatMessage({
id: 'messages.profileOther',
values: {
username: this.props.objectId
}
});
linkText = <FormattedMessage
id='messages.profileOther'
values={{
username: this.props.objectTitle
}}
/>;
}
return <FormattedMessage
id='messages.commentReply'
@ -132,6 +133,7 @@ var CommentMessage = injectIntl(React.createClass({
var messageText = this.getMessageText(this.props.objectType, this.props.commentee);
var commentorAvatar = 'https://cdn2.scratch.mit.edu/get_image/user/' + this.props.actorId + '_32x32.png';
var commentorAvatarAlt = this.props.actorUsername + '\'s avatar';
var url = '/users/' + this.props.actorUsername;
var classes = classNames(
'mod-comment-message',
@ -141,14 +143,18 @@ var CommentMessage = injectIntl(React.createClass({
<SocialMessage
className={classes}
datetime={this.props.commentDateTime}
iconSrc="/svgs/messages/comment.svg"
iconAlt="comment notification image"
>
<p className="comment-message-info">{messageText}</p>
<FlexRow className="mod-comment-message">
<img
className="comment-message-info-img"
src={commentorAvatar}
alt={commentorAvatarAlt}
/>
<a href={url}>
<img
className="comment-message-info-img"
src={commentorAvatar}
alt={commentorAvatarAlt}
/>
</a>
<Comment
comment={this.props.commentText}
/>
@ -158,4 +164,11 @@ var CommentMessage = injectIntl(React.createClass({
}
}));
module.exports = CommentMessage;
var mapStateToProps = function (state) {
return {
user: state.session.session.user
};
};
var ConnectedCommentMessage = connect(mapStateToProps)(CommentMessage);
module.exports = ConnectedCommentMessage;

View file

@ -15,6 +15,7 @@ var CuratorInviteMessage = injectIntl(React.createClass({
},
render: function () {
var studioLink = '/studios/' + this.props.studioId + '/';
var tabLink = '/studios/' + this.props.studioId + '/curators/';
var actorLink = '/users/' + this.props.actorUsername + '/';
var tabText = this.props.intl.formatMessage({id: 'messages.curatorTabText'});
@ -26,6 +27,8 @@ var CuratorInviteMessage = injectIntl(React.createClass({
<SocialMessage
className={classes}
datetime={this.props.datetimePromoted}
iconSrc="/svgs/messages/curator-invite.svg"
iconAlt="curator invite notification image"
>
<FormattedMessage
id='messages.curatorInviteText'
@ -37,7 +40,7 @@ var CuratorInviteMessage = injectIntl(React.createClass({
{this.props.actorUsername}
</a>,
studioLink: <a href={studioLink}>{this.props.studioTitle}</a>,
tabLink: <a href={studioLink}>{tabText}</a>
tabLink: <a href={tabLink}>{tabText}</a>
}}
/>
</SocialMessage>

View file

@ -24,6 +24,8 @@ var FavoriteProjectMessage = React.createClass({
<SocialMessage
className={classes}
datetime={this.props.favoriteDateTime}
iconSrc="/svgs/messages/favorite.svg"
iconAlt="favorite notification image"
>
<FormattedMessage
id='messages.favoriteText'

View file

@ -21,6 +21,8 @@ var FollowUserMessage = React.createClass({
<SocialMessage
className={classes}
datetime={this.props.followDateTime}
iconSrc="/svgs/messages/follow.svg"
iconAlt="follow notification image"
>
<FormattedMessage
id='messages.followText'

View file

@ -16,13 +16,15 @@ var ForumPostMessage = React.createClass({
var topicLink = '/discuss/topic/' + this.props.topicId + '/unread/';
var classes = classNames(
'mod-studio-activity',
'mod-forum-activity',
this.props.className
);
return (
<SocialMessage
className={classes}
datetime={this.props.datetimeCreated}
iconSrc="/svgs/messages/forum-activity.svg"
iconAlt="forum activity notification image"
>
<FormattedMessage
id='messages.forumPostText'

View file

@ -24,6 +24,8 @@ var LoveProjectMessage = React.createClass({
<SocialMessage
className={classes}
datetime={this.props.loveDateTime}
iconSrc="/svgs/messages/love.svg"
iconAlt="love notification image"
>
<FormattedMessage
id='messages.loveText'

View file

@ -27,6 +27,8 @@ var RemixProjectMessage = React.createClass({
<SocialMessage
className={classes}
datetime={this.props.remixDate}
iconSrc="/svgs/messages/remix.svg"
iconAlt="remix notification image"
>
<FormattedMessage
id='messages.remixText'

View file

@ -12,7 +12,7 @@ var StudioActivityMessage = React.createClass({
datetimeCreated: React.PropTypes.string.isRequired
},
render: function () {
var studioLink = '/studios/' + this.props.studioId;
var studioLink = '/studios/' + this.props.studioId + '/activity';
var classes = classNames(
'mod-studio-activity',
@ -22,6 +22,8 @@ var StudioActivityMessage = React.createClass({
<SocialMessage
className={classes}
datetime={this.props.datetimeCreated}
iconSrc="/svgs/messages/studio-activity.svg"
iconAlt="studio activity notification image"
>
<FormattedMessage
id='messages.studioActivityText'

View file

@ -97,10 +97,12 @@
.comment-message-info {
margin-top: 0;
line-height: 1.5rem;
}
.comment-text {
background-color: $ui-white;
max-width: 30.25rem;
}
.flex-row.mod-comment-message {
@ -108,6 +110,10 @@
align-items: flex-start;
}
.comment-message-info {
text-align: left;
}
.comment-message-info-img {
width: 40px;
height: 40px;
@ -118,6 +124,10 @@
margin: 1rem auto;
}
.emoji-text.mod-comment {
word-wrap: break-word;
}
@media only screen and (max-width: $mobile - 1) {
.flex-row.admin-message-header,
.flex-row.mod-comment-message {
@ -125,7 +135,7 @@
}
.comment-text {
max-width: 60%;
max-width: 9.75rem;
}
}
@ -134,4 +144,14 @@
.flex-row.mod-comment-message {
flex-direction: row;
}
.comment-text {
max-width: 19.5rem;
}
}
@media only screen and (min-width: $tablet) and (max-width: $desktop - 1) {
.comment-text {
max-width: 23.75rem;
}
}

View file

@ -84,7 +84,7 @@ var SocialMessagesList = React.createClass({
commentText={message.comment_fragment}
commentDateTime={message.datetime_created}
objectTitle={message.comment_obj_title}
commentee={message.commentee}
commentee={message.commentee_username}
/>;
case 'curatorinvite':
return <CuratorInviteMessage
@ -163,6 +163,14 @@ var SocialMessagesList = React.createClass({
}
return null;
},
renderMessageCounter: function (numNewMessages) {
if (numNewMessages > 0) {
return <div className="messages-header-unread">
<FormattedNumber value={numNewMessages} />
</div>;
}
return null;
},
render: function () {
if (this.props.loadStatus === messageStatuses.MESSAGES_ERROR) {
return (
@ -183,16 +191,14 @@ var SocialMessagesList = React.createClass({
<div className="messages-social-title" key="messages-social-title">
<h4 className="messages-header">
<FormattedMessage id='messages.messageTitle' />
<div className="messages-header-unread">
<FormattedNumber value={this.props.numNewMessages} />
</div>
{this.renderMessageCounter(this.props.numNewMessages)}
</h4>
</div>,
<ul className="messages-social-list" key="messages-social-list">
{this.renderSocialMessages(this.props.messages, (this.props.numNewMessages - 1))}
</ul>,
this.renderLoadMore(this.props.loadMore)
</ul>
] : []}
{this.renderLoadMore(this.props.loadMore)}
</section>
);
}
@ -211,12 +217,14 @@ var MessagesPresentation = injectIntl(React.createClass({
handleAdminDismiss: React.PropTypes.func.isRequired,
loadMore: React.PropTypes.bool.isRequired,
loadMoreMethod: React.PropTypes.func,
requestStatus: React.PropTypes.object.isRequired
requestStatus: React.PropTypes.object.isRequired,
filter: React.PropTypes.string
},
getDefaultProps: function () {
return {
numNewMessages: 0,
filterOpen: false
filterOpen: false,
filter: ''
};
},
render: function () {
@ -225,6 +233,9 @@ var MessagesPresentation = injectIntl(React.createClass({
adminMessageLength = adminMessageLength + 1;
}
var numNewSocialMessages = this.props.numNewMessages - adminMessageLength;
if (numNewSocialMessages < 0) {
numNewSocialMessages = 0;
}
return (
<div className="messages">
@ -261,6 +272,7 @@ var MessagesPresentation = injectIntl(React.createClass({
value: 'forums'
}
]}
value={this.props.filter}
/>
</Form>
</div>

View file

@ -0,0 +1,3 @@
{
"preview-faq.title": "Scratch 3.0 Preview FAQ"
}

View file

@ -0,0 +1,42 @@
var cheerio = require('cheerio');
var injectIntl = require('react-intl').injectIntl;
var React = require('react');
var xhr = require('xhr');
var InformationPage = require('../../components/informationpage/informationpage.jsx');
var Page = require('../../components/page/www/page.jsx');
var render = require('../../lib/render.jsx');
require('./preview-faq.scss');
var PreviewFaq = injectIntl(React.createClass({
type: 'PreviewFaq',
getInitialState: function () {
return {
faqDoc: {__html: ''}
};
},
componentDidMount: function () {
xhr({
method: 'GET',
uri: 'https://docs.google.com/document/d/e/2PACX-1vQZFrpOagYqEwcrBBCplIomiyguPAodIJVnCq9Sr11WDI_aa2b-JtDWak-Aiu-cwWobTXftRMF6wBbd/pub?embedded=true'
}, function (error, response, body) {
var $ = cheerio.load(body);
this.setState({faqDoc: {__html: $('html').html()}});
}.bind(this));
},
render: function () {
return (
<InformationPage title={this.props.intl.formatMessage({id: 'preview-faq.title'})}>
<div className="inner">
<div
className="preview-faq"
dangerouslySetInnerHTML={this.state.faqDoc}
/>
</div>
</InformationPage>
);
}
}));
render(<Page><PreviewFaq /></Page>, document.getElementById('app'));

View file

@ -0,0 +1,18 @@
#view {
padding: 0;
}
.preview-faq {
margin-bottom: 5rem;
}
.title-banner-h1 {
line-height: 1.7em !important;
font-family: "Helvetica Neue", "Helvetica", Arial, sans-serif !important;
font-size: 2.5rem !important;
font-weight: 900 !important;
}
.preview-faq li {
margin: 0 2rem !important;
}

View file

@ -0,0 +1,49 @@
var classNames = require('classnames');
var FormattedMessage = require('react-intl').FormattedMessage;
var React = require('react');
var SocialMessage = require('../../../components/social-message/social-message.jsx');
var BecomeCuratorMessage = React.createClass({
type: 'BecomeCuratorMessage',
propTypes: {
actorUsername: React.PropTypes.string.isRequired,
studioId: React.PropTypes.number.isRequired,
studioTitle: React.PropTypes.string.isRequired,
datetimePromoted: React.PropTypes.string.isRequired
},
render: function () {
var actorUri = '/users/' + this.props.actorUsername + '/';
var studioUri = '/studios/' + this.props.studioId + '/';
var classes = classNames(
'mod-become-curator',
this.props.className
);
return (
<SocialMessage
as="div"
className={classes}
datetime={this.props.datetimePromoted}
>
<FormattedMessage
id='messages.becomeCuratorText'
values={{
username: (
<a
href={actorUri}
>
{this.props.actorUsername}
</a>
),
studio: (
<a href={studioUri}>{this.props.studioTitle}</a>
)
}}
/>
</SocialMessage>
);
}
});
module.exports = BecomeCuratorMessage;

View file

@ -0,0 +1,49 @@
var classNames = require('classnames');
var FormattedMessage = require('react-intl').FormattedMessage;
var React = require('react');
var SocialMessage = require('../../../components/social-message/social-message.jsx');
var BecomeManagerMessage = React.createClass({
type: 'BecomeManagerMessage',
propTypes: {
recipientUsername: React.PropTypes.string.isRequired,
studioId: React.PropTypes.number.isRequired,
studioTitle: React.PropTypes.string.isRequired,
datetimePromoted: React.PropTypes.string.isRequired
},
render: function () {
var recipientUri = '/users/' + this.props.recipientUsername + '/';
var studioUri = '/studios/' + this.props.studioId + '/';
var classes = classNames(
'mod-become-manager',
this.props.className
);
return (
<SocialMessage
as="div"
className={classes}
datetime={this.props.datetimePromoted}
>
<FormattedMessage
id='messages.becomeManagerText'
values={{
username: (
<a
href={recipientUri}
>
{this.props.recipientUsername}
</a>
),
studio: (
<a href={studioUri}>{this.props.studioTitle}</a>
)
}}
/>
</SocialMessage>
);
}
});
module.exports = BecomeManagerMessage;

View file

@ -0,0 +1,49 @@
var classNames = require('classnames');
var FormattedMessage = require('react-intl').FormattedMessage;
var React = require('react');
var SocialMessage = require('../../../components/social-message/social-message.jsx');
var FavoriteProjectMessage = React.createClass({
type: 'FavoriteProjectMessage',
propTypes: {
actorUsername: React.PropTypes.string.isRequired,
projectId: React.PropTypes.number.isRequired,
projectTitle: React.PropTypes.string.isRequired,
favoriteDateTime: React.PropTypes.string.isRequired
},
render: function () {
var projectLink = '/projects/' + this.props.projectId;
var profileLink = '/users/' + this.props.actorUsername;
var classes = classNames(
'mod-love-favorite',
this.props.className
);
return (
<SocialMessage
as="div"
className={classes}
datetime={this.props.favoriteDateTime}
>
<FormattedMessage
id='messages.favoriteText'
values={{
profileLink: (
<a
href={profileLink}
>
{this.props.actorUsername}
</a>
),
projectLink: (
<a href={projectLink}>{this.props.projectTitle}</a>
)
}}
/>
</SocialMessage>
);
}
});
module.exports = FavoriteProjectMessage;

View file

@ -0,0 +1,62 @@
var classNames = require('classnames');
var FormattedMessage = require('react-intl').FormattedMessage;
var React = require('react');
var SocialMessage = require('../../../components/social-message/social-message.jsx');
var FollowMessage = React.createClass({
type: 'FollowMessage',
propTypes: {
followerUsername: React.PropTypes.string.isRequired,
followeeId: React.PropTypes.string.isRequired,
followeeTitle: React.PropTypes.string,
followDateTime: React.PropTypes.string.isRequired
},
render: function () {
var profileLink = '/users/' + this.props.followerUsername; + '/';
var followeeLink = '';
var followeeTitle = '';
if (typeof this.props.followeeTitle !== 'undefined') {
followeeLink = '/studios/' + this.props.followeeId;
followeeTitle = this.props.followeeTitle;
} else {
followeeLink = '/users/' + this.props.followeeId;
followeeTitle = this.props.followeeId;
}
var classes = classNames(
'mod-follow-user',
this.props.className
);
return (
<SocialMessage
as="div"
className={classes}
datetime={this.props.followDateTime}
>
<FormattedMessage
id='messages.followText'
values={{
profileLink: (
<a
href={profileLink}
>
{this.props.followerUsername}
</a>
),
followeeLink: (
<a
href={followeeLink}
>
{followeeTitle}
</a>
)
}}
/>
</SocialMessage>
);
}
});
module.exports = FollowMessage;

View file

@ -0,0 +1,49 @@
var classNames = require('classnames');
var FormattedMessage = require('react-intl').FormattedMessage;
var React = require('react');
var SocialMessage = require('../../../components/social-message/social-message.jsx');
var LoveProjectMessage = React.createClass({
type: 'LoveProjectMessage',
propTypes: {
actorUsername: React.PropTypes.string.isRequired,
projectId: React.PropTypes.number.isRequired,
projectTitle: React.PropTypes.string.isRequired,
loveDateTime: React.PropTypes.string.isRequired
},
render: function () {
var projectLink = '/projects/' + this.props.projectId;
var profileLink = '/users/' + this.props.actorUsername;
var classes = classNames(
'mod-love-project',
this.props.className
);
return (
<SocialMessage
as="div"
className={classes}
datetime={this.props.loveDateTime}
>
<FormattedMessage
id='messages.loveText'
values={{
profileLink: (
<a
href={profileLink}
>
{this.props.actorUsername}
</a>
),
projectLink: (
<a href={projectLink}>{this.props.projectTitle}</a>
)
}}
/>
</SocialMessage>
);
}
});
module.exports = LoveProjectMessage;

View file

@ -0,0 +1,55 @@
var classNames = require('classnames');
var FormattedMessage = require('react-intl').FormattedMessage;
var React = require('react');
var SocialMessage = require('../../../components/social-message/social-message.jsx');
var RemixProjectMessage = React.createClass({
type: 'RemixProjectMessage',
propTypes: {
actorUsername: React.PropTypes.string.isRequired,
projectId: React.PropTypes.number.isRequired,
projectTitle: React.PropTypes.string.isRequired,
parentId: React.PropTypes.number.isRequired,
parentTitle: React.PropTypes.string.isRequired,
remixDate: React.PropTypes.string.isRequired
},
render: function () {
var projectLink = '/projects/' + this.props.projectId;
var profileLink = '/users/' + this.props.actorUsername;
var remixedProjectLink = '/projects/' + this.props.parentId;
var classes = classNames(
'mod-remix-project',
this.props.className
);
return (
<SocialMessage
as="div"
className={classes}
datetime={this.props.remixDate}
>
<FormattedMessage
id='messages.remixText'
values={{
profileLink: (
<a
href={profileLink}
>
{this.props.actorUsername}
</a>
),
projectLink: (
<a href={projectLink}>{this.props.projectTitle}</a>
),
remixedProjectLink: (
<a href={remixedProjectLink}>{this.props.parentTitle}</a>
)
}}
/>
</SocialMessage>
);
}
});
module.exports = RemixProjectMessage;

View file

@ -0,0 +1,49 @@
var classNames = require('classnames');
var FormattedMessage = require('react-intl').FormattedMessage;
var React = require('react');
var SocialMessage = require('../../../components/social-message/social-message.jsx');
var ShareProjectMessage = React.createClass({
type: 'ShareProjectMessage',
propTypes: {
actorUsername: React.PropTypes.string.isRequired,
projectId: React.PropTypes.number.isRequired,
projectTitle: React.PropTypes.string.isRequired,
loveDateTime: React.PropTypes.string.isRequired
},
render: function () {
var projectLink = '/projects/' + this.props.projectId;
var profileLink = '/users/' + this.props.actorUsername;
var classes = classNames(
'mod-love-project',
this.props.className
);
return (
<SocialMessage
as="div"
className={classes}
datetime={this.props.loveDateTime}
>
<FormattedMessage
id='messages.shareText'
values={{
profileLink: (
<a
href={profileLink}
>
{this.props.actorUsername}
</a>
),
projectLink: (
<a href={projectLink}>{this.props.projectTitle}</a>
)
}}
/>
</SocialMessage>
);
}
});
module.exports = ShareProjectMessage;

View file

@ -11,9 +11,18 @@
"splash.communityRemixing": "What the Community is Remixing",
"splash.communityLoving": "What the Community is Loving",
"messages.becomeCuratorText": "{username} became a curator of {studio}",
"messages.becomeManagerText": "{username} was promoted to manager of {studio}",
"messages.favoriteText": "{profileLink} favorited {projectLink}",
"messages.followText": "{profileLink} is now following {followeeLink}",
"messages.loveText": "{profileLink} loved {projectLink}",
"messages.remixText": "{profileLink} remixed {remixedProjectLink} as {projectLink}",
"messages.shareText": "{profileLink} shared the project {projectLink}",
"intro.aboutScratch": "ABOUT SCRATCH",
"intro.forEducators": "FOR EDUCATORS",
"intro.forParents": "FOR PARENTS",
"intro.itsFree": "it's free!",
"intro.joinScratch": "JOIN SCRATCH",
"intro.seeExamples": "SEE EXAMPLES",
"intro.tagLine": "Create stories, games, and animations<br /> Share with others around the world",

View file

@ -1,10 +1,10 @@
var FormattedMessage = require('react-intl').FormattedMessage;
var injectIntl = require('react-intl').injectIntl;
var React = require('react');
var sessionActions = require('../../redux/session.js');
var shuffle = require('../../lib/shuffle.js').shuffle;
var Activity = require('../../components/activity/activity.jsx');
var AdminPanel = require('../../components/adminpanel/adminpanel.jsx');
var DropdownBanner = require('../../components/dropdown-banner/banner.jsx');
var Box = require('../../components/box/box.jsx');
@ -17,11 +17,150 @@ var News = require('../../components/news/news.jsx');
var TeacherBanner = require('../../components/teacher-banner/teacher-banner.jsx');
var Welcome = require('../../components/welcome/welcome.jsx');
// Activity Components
var BecomeCuratorMessage = require('./activity-rows/become-curator.jsx');
var BecomeManagerMessage = require('./activity-rows/become-manager.jsx');
var FavoriteProjectMessage = require('./activity-rows/favorite-project.jsx');
var FollowMessage = require('./activity-rows/follow.jsx');
var LoveProjectMessage = require('./activity-rows/love-project.jsx');
var RemixProjectMessage = require('./activity-rows/remix-project.jsx');
var ShareProjectMessage = require('./activity-rows/share-project.jsx');
var MediaQuery = require('react-responsive');
var frameless = require('../../lib/frameless');
require('./splash.scss');
var ActivityList = injectIntl(React.createClass({
propTypes: {
items: React.PropTypes.array
},
getComponentForMessage: function (message) {
var key = message.type + '_' + message.id;
switch (message.type) {
case 'followuser':
return <FollowMessage
key={key}
followerUsername={message.actor_username}
followeeId={message.followed_username}
followDateTime={message.datetime_created}
/>;
case 'followstudio':
return <FollowMessage
key={key}
followerUsername={message.actor_username}
followeeId={message.gallery_id}
followeeTitle={message.title}
followDateTime={message.datetime_created}
/>;
case 'loveproject':
return <LoveProjectMessage
key={key}
actorUsername={message.actor_username}
projectId={message.project_id}
projectTitle={message.title}
loveDateTime={message.datetime_created}
/>;
case 'favoriteproject':
return <FavoriteProjectMessage
key={key}
actorUsername={message.actor_username}
projectId={message.project_id}
projectTitle={message.project_title}
favoriteDateTime={message.datetime_created}
/>;
case 'remixproject':
return <RemixProjectMessage
key={key}
actorUsername={message.actor_username}
projectId={message.project_id}
projectTitle={message.title}
parentId={message.parent_id}
parentTitle={message.parent_title}
remixDate={message.datetime_created}
/>;
case 'becomecurator':
return <BecomeCuratorMessage
key={key}
actorUsername={message.actor_username}
studioId={message.gallery_id}
studioTitle={message.title}
datetimePromoted={message.datetime_created}
/>;
case 'becomeownerstudio':
return <BecomeManagerMessage
key={key}
recipientUsername={message.recipient_username}
studioId={message.gallery_id}
studioTitle={message.gallery_title}
datetimePromoted={message.datetime_created}
/>;
case 'shareproject':
return <ShareProjectMessage
key={key}
actorUsername={message.actor_username}
projectId={message.project_id}
projectTitle={message.title}
loveDateTime={message.datetime_created}
/>;
}
},
render: function () {
var formatMessage = this.props.intl.formatMessage;
return (
<Box
className="activity"
title={formatMessage({id: 'general.whatsHappening'})}
>
{this.props.items && this.props.items.length > 0 ? [
<ul
className="activity-ul"
key="activity-ul"
>
{this.props.items.map(function (item) {
var profileLink = '/users/' + item.actor_username; + '/';
var profileThumbUrl = '//uploads.scratch.mit.edu/users/avatars/' + item.actor_id + '.png';
if (item.type === 'becomeownerstudio') {
profileLink = '/users/' + item.recipient_username; + '/';
profileThumbUrl = '//uploads.scratch.mit.edu/users/avatars/'
+ item.recipient_id
+ '.png';
}
return (
<li className="activity-li">
<a href={profileLink}>
<img
alt=""
className="activity-img"
src={profileThumbUrl}
/>
</a>
{this.getComponentForMessage(item)}
</li>
);
}.bind(this))}
</ul>
] : [
<div className="empty" key="activity-empty">
<h4>
<FormattedMessage
id="activity.seeUpdates"
defaultMessage="This is where you will see updates from Scratchers you follow" />
</h4>
<a href="/studios/146521/">
<FormattedMessage
id="activity.checkOutScratchers"
defaultMessage="Check out some Scratchers you might like to follow" />
</a>
</div>
]}
</Box>
);
}
}));
var SplashPresentation = injectIntl(React.createClass({
type: 'Splash',
propTypes: {
@ -69,7 +208,6 @@ var SplashPresentation = injectIntl(React.createClass({
},
renderHomepageRows: function () {
var formatMessage = this.props.intl.formatMessage;
var rows = [
<Box
title={formatMessage({id: 'splash.featuredProjects'})}
@ -208,6 +346,7 @@ var SplashPresentation = injectIntl(React.createClass({
'intro.aboutScratch': formatMessage({id: 'intro.aboutScratch'}),
'intro.forEducators': formatMessage({id: 'intro.forEducators'}),
'intro.forParents': formatMessage({id: 'intro.forParents'}),
'intro.itsFree': formatMessage({id: 'intro.itsFree'}),
'intro.joinScratch': formatMessage({id: 'intro.joinScratch'}),
'intro.seeExamples': formatMessage({id: 'intro.seeExamples'}),
'intro.tagLine': formatHTMLMessage({id: 'intro.tagLine'}),
@ -257,11 +396,13 @@ var SplashPresentation = injectIntl(React.createClass({
Object.keys(this.props.user).length !== 0 ? [
<div key="header" className="splash-header">
{this.props.shouldShowWelcome ? [
<Welcome key="welcome"
onDismiss={this.props.handleDismiss.bind(this, 'welcome')}
messages={messages} />
<Welcome
key="welcome"
onDismiss={this.props.handleDismiss.bind(this, 'welcome')}
messages={messages}
/>
] : [
<Activity key="activity" items={this.props.activity} />
<ActivityList key="activity" items={this.props.activity} />
]}
<News items={this.props.news} messages={messages} />
</div>
@ -271,7 +412,7 @@ var SplashPresentation = injectIntl(React.createClass({
</MediaQuery>
]) : []
}
{featured}
{this.props.isAdmin ? [

View file

@ -6,6 +6,7 @@ var api = require('../../lib/api');
var log = require('../../lib/log');
var render = require('../../lib/render.jsx');
var sessionActions = require('../../redux/session.js');
var splashActions = require('../../redux/splash.js');
var Page = require('../../components/page/www/page.jsx');
var SplashPresentation = require('./presentation.jsx');
@ -15,12 +16,7 @@ var Splash = injectIntl(React.createClass({
getInitialState: function () {
return {
projectCount: 20000000, // gets the shared project count
activity: [], // recent social actions taken by users someone is following
news: [], // gets news posts from the scratch Tumblr
sharedByFollowing: [], // "Projects by Scratchers I'm Following"
lovedByFollowing: [], // "Projects Loved by Scratchers I'm Following"
inStudiosFollowing: [], // "Projects in Studios I'm Following"
featuredGlobal: {}, // global homepage rows, such as "Featured Projects"
emailConfirmationModalOpen: false, // flag that determines whether to show banner to request email conf.
refreshCacheStatus: 'notrequested'
};
@ -37,16 +33,16 @@ var Splash = injectIntl(React.createClass({
componentDidUpdate: function (prevProps) {
if (this.props.user != prevProps.user) {
if (this.props.user.username) {
this.getActivity(this.props.user.username);
this.getSharedByFollowing(this.props.user.username, this.props.user.token);
this.getInStudiosFollowing(this.props.user.username, this.props.user.token);
this.getLovedByFollowing(this.props.user.username, this.props.user.token);
this.props.getActivity(this.props.user.username, this.props.user.token);
this.props.getSharedByFollowing(this.props.user.username, this.props.user.token);
this.props.getInStudiosFollowing(this.props.user.username, this.props.user.token);
this.props.getLovedByFollowing(this.props.user.username, this.props.user.token);
this.getNews();
} else {
this.setState({sharedByFollowing: []});
this.setState({lovedByFollowing: []});
this.setState({inStudiosFollowing: []});
this.setState({activity: []});
this.props.setRows('shared', []);
this.props.setRows('loved', []);
this.props.setRows('studios', []);
this.props.setRows('activity', []);
this.setState({news: []});
this.getProjectCount();
}
@ -58,60 +54,17 @@ var Splash = injectIntl(React.createClass({
}
},
componentDidMount: function () {
this.getFeaturedGlobal();
this.props.getFeaturedGlobal();
if (this.props.user.username) {
this.getActivity(this.props.user.username);
this.getSharedByFollowing(this.props.user.username, this.props.user.token);
this.getInStudiosFollowing(this.props.user.username, this.props.user.token);
this.getLovedByFollowing(this.props.user.username, this.props.user.token);
this.props.getActivity(this.props.user.username, this.props.user.token);
this.props.getSharedByFollowing(this.props.user.username, this.props.user.token);
this.props.getInStudiosFollowing(this.props.user.username, this.props.user.token);
this.props.getLovedByFollowing(this.props.user.username, this.props.user.token);
this.getNews();
} else {
this.getProjectCount();
}
},
getActivity: function (username) {
api({
uri: '/proxy/users/' + username + '/activity?limit=5'
}, function (err, body) {
if (!body) return log.error('No response body');
if (!err) return this.setState({activity: body});
}.bind(this));
},
getFeaturedGlobal: function () {
api({
uri: '/proxy/featured'
}, function (err, body) {
if (!body) return log.error('No response body');
if (!err) return this.setState({featuredGlobal: body});
}.bind(this));
},
getSharedByFollowing: function (username, token) {
api({
uri: '/users/' + username + '/following/users/projects',
authentication: token
}, function (err, body) {
if (!body) return log.error('No response body');
if (!err) return this.setState({sharedByFollowing: body});
}.bind(this));
},
getInStudiosFollowing: function (username, token) {
api({
uri: '/users/' + username + '/following/studios/projects',
authentication: token
}, function (err, body) {
if (!body) return log.error('No response body');
if (!err) return this.setState({inStudiosFollowing: body});
}.bind(this));
},
getLovedByFollowing: function (username, token) {
api({
uri: '/users/' + username + '/following/users/loves',
authentication: token
}, function (err, body) {
if (!body) return log.error('No response body');
if (!err) return this.setState({lovedByFollowing: body});
}.bind(this));
},
getNews: function () {
api({
uri: '/news?limit=3'
@ -207,12 +160,12 @@ var Splash = injectIntl(React.createClass({
hideEmailConfirmationModal={this.hideEmailConfirmationModal}
shouldShowWelcome={showWelcome}
projectCount={this.state.projectCount}
activity={this.state.activity}
activity={this.props.activity}
news={this.state.news}
sharedByFollowing={this.state.sharedByFollowing}
lovedByFollowing={this.state.lovedByFollowing}
inStudiosFollowing={this.state.inStudiosFollowing}
featuredGlobal={this.state.featuredGlobal}
sharedByFollowing={this.props.shared}
lovedByFollowing={this.props.loved}
inStudiosFollowing={this.props.studios}
featuredGlobal={this.props.featured}
refreshCacheStatus={homepageRefreshStatus}
/>
);
@ -225,10 +178,45 @@ var mapStateToProps = function (state) {
user: state.session.session.user,
flags: state.session.session.flags,
isEducator: state.permissions.educator,
isAdmin: state.permissions.admin
isAdmin: state.permissions.admin,
activity: state.splash.activity.rows,
featured: state.splash.featured.rows,
shared: state.splash.shared.rows,
loved: state.splash.loved.rows,
studios: state.splash.studios.rows
};
};
var ConnectedSplash = connect(mapStateToProps)(Splash);
var mapDispatchToProps = function (dispatch) {
return {
getFeaturedGlobal: function () {
dispatch(splashActions.getFeaturedGlobal());
},
getActivity: function (username, token) {
dispatch(splashActions.getActivity(username, token));
},
getSharedByFollowing: function (username, token) {
dispatch(splashActions.getSharedByFollowing(username, token));
},
getInStudiosFollowing: function (username, token) {
dispatch(splashActions.getInStudiosFollowing(username, token));
},
getLovedByFollowing: function (username, token) {
dispatch(splashActions.getLovedByFollowing(username, token));
},
setRows: function (type, rows) {
dispatch(splashActions.setRows(type, rows));
}
};
};
render(<Page><ConnectedSplash /></Page>, document.getElementById('app'));
var ConnectedSplash = connect(
mapStateToProps,
mapDispatchToProps
)(Splash);
render(
<Page><ConnectedSplash /></Page>,
document.getElementById('app'),
{splash: splashActions.splashReducer}
);

View file

@ -1,3 +1,4 @@
@import "../../colors";
@import "../../frameless";
#view {
@ -56,6 +57,44 @@
min-height: 20.625rem;
}
.activity-ul {
margin: 0;
padding: 0;
}
.activity-li {
display: flex;
margin: .75rem 0;
list-style: none;
align-items: center;
}
.flex-row.mod-social-message {
line-height: 1.25rem;
flex-direction: column;
}
.social-message {
border: 0;
padding: 0;
}
.activity-img {
padding-right: .825rem;
width: 2rem;
height: 2rem;
vertical-align: middle;
}
.social-message-content {
font-size: .9rem;
}
.social-message-date {
color: $ui-dark-gray;
font-size: .65rem;
}
//4 columns
@media only screen and (max-width: $mobile - 1) {
.splash {

View file

@ -8,7 +8,7 @@
"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?",
"teacherfaq.teacherPersonalBody": "We use this information to verify the account creator is an educator. We will not share this information with anyone else, and it will not be shared publicly on the site.",
"teacherfaq.teacherGoogleTitle": "Are teacher accounts integrated with Google Classroom or any other classroom managment service?",
"teacherfaq.teacherGoogleTitle": "Are teacher accounts integrated with Google Classroom or any other classroom management service?",
"teacherfaq.teacherGoogleBody": "Scratch Teacher accounts are not integrated with any classroom management services.",
"teacherfaq.teacherEdTitle": "Are Scratch Teacher accounts linked to ScratchEd accounts?",
"teacherfaq.teacherEdBody": "No, Scratch Teacher accounts are not linked to <a href=\"http://scratched.gse.harvard.edu/\">ScratchEd</a> accounts.",
@ -43,7 +43,7 @@
"teacherfaq.commHiddenTitle": "Can I create a hidden class?",
"teacherfaq.commHiddenBody": "No. All content shared within your class will be accessible to the Scratch community.",
"teacherfaq.commWhoTitle": "Who can my students interact with on Scratch?",
"teacherfaq.commWhoBody": "Student accounts have the same community privileges as a regular Scratch account, such as sharing projects, commenting, creating studios, and the like. As a teacher, you can see all of your students activity and perform light moderation actions within your class.",
"teacherfaq.commWhoBody": "Student accounts have the same community privileges as a regular Scratch account, such as sharing projects, commenting, creating studios, and the like. As a teacher, you can see all of your students' activity and perform light moderation actions within your class.",
"teacherfaq.commInappropriateTitle": "What can I do if I see something inappropriate?",
"teacherfaq.commInappropriateBody": "You can manually remove inappropriate comments and projects created by your students. If you find inappropriate content created by non-students, please notify the Scratch Team by clicking the report button or sending a message to <a href=\"/contact-us\">Contact Us</a>."
}

View file

@ -274,7 +274,7 @@ var Terms = React.createClass({
5.4 The Scratch name, Scratch logo, Scratch Day logo, Scratch Cat, and Gobo
are Trademarks owned by the Scratch Team. The MIT name and logo are Trademarks
owned by the Massachusetts Institute of Technology. Unless you are licensed by
Scratch under a specific licensing program or agreement, you many not use
Scratch under a specific licensing program or agreement, you may not use
these logos to label, promote, or endorse any product or service. You may use
the Scratch Logo to refer to the Scratch website and programming language.
</p>

View file

@ -13,7 +13,6 @@
"cards.pongCardsLink": "https://resources.scratch.mit.edu/www/cards/en/pongCards.pdf",
"cards.raceCardsLink": "https://resources.scratch.mit.edu/www/cards/en/raceCards.pdf",
"cards.storyCardsLink": "https://resources.scratch.mit.edu/www/cards/en/storyCards.pdf",
"guides.AnimateYourNameGuideLink": "https://resources.scratch.mit.edu/www/guides/en/AnimateYourNameGuide.pdf",
"guides.CatchGuideLink": "https://resources.scratch.mit.edu/www/guides/en/CatchGuide.pdf",
"guides.DanceGuideLink": "https://resources.scratch.mit.edu/www/guides/en/DanceGuide.pdf",
"guides.FashionGuideLink": "https://resources.scratch.mit.edu/www/guides/en/FashionGuide.pdf",

View file

@ -6,7 +6,7 @@
"bannerUrl": "/images/ttt/animate-your-name-banner.jpg",
"tutorialLoc": "/projects/editor/?tip_bar=name",
"activityLoc": "cards.nameCardsLink",
"guideLoc": "guides.AnimateYourNameGuideLink"
"guideLoc": "guides.NameGuideLink"
},
{
"title": "ttt.MakeItFlyTitle",

Binary file not shown.

After

(image error) Size: 331 KiB

Binary file not shown.

Before

(image error) Size: 4.3 KiB

Binary file not shown.

Before

(image error) Size: 4.8 KiB

Binary file not shown.

Before

(image error) Size: 36 KiB

Binary file not shown.

Before

(image error) Size: 28 KiB

Binary file not shown.

Before

(image error) Size: 23 KiB

Binary file not shown.

Before

(image error) Size: 193 KiB

Binary file not shown.

Before

(image error) Size: 41 KiB

Binary file not shown.

Before

(image error) Size: 4.3 KiB

Binary file not shown.

Before

(image error) Size: 14 KiB

Binary file not shown.

Before

(image error) Size: 224 KiB

Binary file not shown.

Before

(image error) Size: 94 KiB

Binary file not shown.

Before

(image error) Size: 27 KiB

Binary file not shown.

Before

(image error) Size: 15 KiB

Binary file not shown.

Before

(image error) Size: 9.4 KiB

Binary file not shown.

Before

(image error) Size: 19 KiB

Binary file not shown.

Before

(image error) Size: 2.7 KiB

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