Merge pull request #1648 from LLK/release/2.2.26

[Master] Release 2.2.26
This commit is contained in:
Matthew Taylor 2017-11-03 12:04:00 -04:00 committed by GitHub
commit eef64e139e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 132 additions and 140 deletions

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!) * [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) * [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 * [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: 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) [Scratch](https://scratch.mit.edu/) (the thing we work on)

View file

@ -49,11 +49,11 @@ These currently exist in static/js/lib
npm start 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: 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 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 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). 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 #### 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. 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: 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*) * Login on the splash page (*In the process of being fixed*)

View file

@ -33,6 +33,7 @@
.emoji-text.mod-comment { .emoji-text.mod-comment {
margin: 0; margin: 0;
overflow: hidden;
} }
.comment-text-timestamp { .comment-text-timestamp {

View file

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

View file

@ -1,3 +1,4 @@
var defaults = require('lodash.defaults');
var defaultsDeep = require('lodash.defaultsdeep'); var defaultsDeep = require('lodash.defaultsdeep');
var keyMirror = require('keymirror'); var keyMirror = require('keymirror');
@ -39,13 +40,11 @@ module.exports.messagesReducer = function (state, action) {
switch (action.type) { switch (action.type) {
case 'SET_MESSAGES': case 'SET_MESSAGES':
return defaultsDeep({ state.messages.social = action.messages;
messages: {social: action.messages} return state;
}, state);
case 'SET_ADMIN_MESSAGES': case 'SET_ADMIN_MESSAGES':
return defaultsDeep({ state.messages.admin = action.messages;
messages: {admin: action.messages} return state;
}, state);
case 'SET_MESSAGES_OFFSET': case 'SET_MESSAGES_OFFSET':
return defaultsDeep({ return defaultsDeep({
messages: {socialOffset: action.offset} messages: {socialOffset: action.offset}
@ -196,15 +195,29 @@ module.exports.clearAdminMessage = function (messageType, messageId, messageCoun
* Gets a user's messages to be displayed on the /messages page * 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} username username of the user for whom the messages should be gotten
* @param {string} token the user's unique token for auth * @param {string} token the user's unique token for auth
* @param {object[]} messages an array of existing messages on the page, if there are any * @param {object} opts optional args for the method
* @param {number} offset offset of messages to get, based on the number retrieved already * @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 * @return {null} returns nothing
*/ */
module.exports.getMessages = function (username, token, messages, offset) { 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) { return function (dispatch) {
dispatch(module.exports.setStatus('MESSAGE_STATUS', module.exports.Status.FETCHING)); dispatch(module.exports.setStatus('MESSAGE_STATUS', module.exports.Status.FETCHING));
api({ api({
uri: '/users/' + username + '/messages?limit=40&offset=' + offset, uri: '/users/' + username + '/messages?limit=40&offset=' + opts.offset + filterArg,
authentication: token authentication: token
}, function (err, body) { }, function (err, body) {
if (err) { if (err) {
@ -218,9 +231,11 @@ module.exports.getMessages = function (username, token, messages, offset) {
return; return;
} }
dispatch(module.exports.setStatus('MESSAGE_STATUS', module.exports.Status.FETCHED)); dispatch(module.exports.setStatus('MESSAGE_STATUS', module.exports.Status.FETCHED));
dispatch(module.exports.setMessages(messages.concat(body))); dispatch(module.exports.setMessages(opts.messages.concat(body)));
dispatch(module.exports.setMessagesOffset(offset + 40)); dispatch(module.exports.setMessagesOffset(opts.offset + 40));
if (opts.clearCount) {
dispatch(module.exports.clearMessageCount(token)); // clear count once messages loaded dispatch(module.exports.clearMessageCount(token)); // clear count once messages loaded
}
}); });
}; };
}; };

View file

@ -72,6 +72,11 @@ var Credits = React.createClass({
<span className="name">Carmelo Presicce</span> <span className="name">Carmelo Presicce</span>
</li> </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> <li>
<img src="//cdn.scratch.mit.edu/get_image/user/167_170x170.png" alt="Mitchel Avatar" /> <img src="//cdn.scratch.mit.edu/get_image/user/167_170x170.png" alt="Mitchel Avatar" />
<span className="name">Mitchel Resnick</span> <span className="name">Mitchel Resnick</span>
@ -152,18 +157,13 @@ var Credits = React.createClass({
</li> </li>
<li> <li>
<img src="//cdn.scratch.mit.edu/get_image/user/36977_170x170.png" alt="Connor Avatar" /> <img src="//cdn2.scratch.mit.edu/get_image/user/26249744_60x60.png" alt="Joel Avatar" />
<span className="name">Connor Hudson</span> <span className="name">Joel Gritter</span>
</li> </li>
<li> <li>
<img src="//cdn.scratch.mit.edu/get_image/user/14110644_170x170.png" alt="Lily Avatar" /> <img src="//cdn2.scratch.mit.edu/get_image/user/5311910_60x60.png" alt="Carolina Avatar" />
<span className="name">Lily Kim</span> <span className="name">Carolina Kaufman</span>
</li>
<li>
<img src="//cdn.scratch.mit.edu/get_image/user/13639421_170x170.png" alt="Tauntaun Avatar" />
<span className="name">Tauntaun Kim</span>
</li> </li>
<li> <li>
@ -171,11 +171,6 @@ var Credits = React.createClass({
<span className="name">Dalton Miner</span> <span className="name">Dalton Miner</span>
</li> </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> <li>
<img src="//cdn.scratch.mit.edu/get_image/user/159139_170x170.png" alt="Franchette Avatar" /> <img src="//cdn.scratch.mit.edu/get_image/user/159139_170x170.png" alt="Franchette Avatar" />
<span className="name">Franchette Viloria</span> <span className="name">Franchette Viloria</span>
@ -206,10 +201,13 @@ var Credits = React.createClass({
Dave Feinberg, Dave Feinberg,
Chris Graves, Chris Graves,
Megan Haddadi, Megan Haddadi,
Connor Hudson,
Christina Huang, Christina Huang,
Tony Hwang, Tony Hwang,
Abdulrahman Idlbi, Abdulrahman Idlbi,
Randy Jou, Randy Jou,
Lily Kim,
Tauntaun Kim,
Saskia Leggett, Saskia Leggett,
Tim Mickel, Tim Mickel,
Amon Millner, Amon Millner,
@ -218,6 +216,7 @@ var Credits = React.createClass({
Jay Silver, Jay Silver,
Tammy Stern, Tammy Stern,
Lis Sylvan, Lis Sylvan,
Hanako Tjia,
Claudia Urrea, Claudia Urrea,
Oren Zuckerman Oren Zuckerman
</p> </p>

View file

@ -40,7 +40,7 @@
"faq.viewUnsharedTitle":"Can the Scratch Team view unshared projects on my 'My Stuff' page?", "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.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.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.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.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?", "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.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.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.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.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.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.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.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.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.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.", "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.",
@ -110,7 +110,7 @@
"faq.chatRoomTitle":"Can I make chat rooms with cloud data?", "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.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.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.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.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?", "faq.newScratcherCloudTitle":"I am logged in, but I still cannot use projects with cloud data. What is going on?",

View file

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

View file

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

View file

@ -12,8 +12,7 @@ var Messages = React.createClass({
type: 'ConnectedMessages', type: 'ConnectedMessages',
getInitialState: function () { getInitialState: function () {
return { return {
filterValues: [], filter: ''
displayedMessages: []
}; };
}, },
getDefaultProps: function () { getDefaultProps: function () {
@ -26,14 +25,17 @@ var Messages = React.createClass({
}; };
}, },
componentDidUpdate: function (prevProps) { componentDidUpdate: function (prevProps) {
if (this.props.user != prevProps.user) { if (this.props.user.username !== prevProps.user.username) {
if (this.props.user.token) { if (this.props.user.token) {
this.props.dispatch( this.props.dispatch(
messageActions.getMessages( messageActions.getMessages(
this.props.user.username, this.props.user.username,
this.props.user.token, 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( this.props.dispatch(
@ -59,8 +61,11 @@ var Messages = React.createClass({
messageActions.getMessages( messageActions.getMessages(
this.props.user.username, this.props.user.username,
this.props.user.token, 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( this.props.dispatch(
@ -74,26 +79,19 @@ var Messages = React.createClass({
} }
}, },
handleFilterClick: function (field, choice) { handleFilterClick: function (field, choice) {
switch (choice) { if (this.props.user.token) {
case 'comments': this.props.dispatch(
return this.setState({filterValues: ['addcomment']}); messageActions.getMessages(
case 'projects': this.props.user.username,
return this.setState({filterValues: [ this.props.user.token,
'loveproject', {
'favoriteproject', filter: choice,
'remixproject' clearCount: false
]});
case 'studios':
return this.setState({filterValues: [
'curatorinvite',
'studioactivity',
'becomeownerstudio'
]});
case 'forums':
return this.setState({filterValues: ['forumpost']});
default:
return this.setState({filterValues: []});
} }
)
);
}
this.setState({filter: choice});
}, },
handleMessageDismiss: function (messageType, messageId) { handleMessageDismiss: function (messageType, messageId) {
var adminMessages = null; var adminMessages = null;
@ -111,68 +109,35 @@ var Messages = React.createClass({
messageActions.getMessages( messageActions.getMessages(
this.props.user.username, this.props.user.username,
this.props.user.token, 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, unreadCount) {
var filteredMessages = [];
if (typesAllowed.length > 0) {
for (var i in messages) {
// check to see if the position of the message in the list is earlier
// than the unread count. If it is, then the message is totally unread.
messages[i].unread = false;
if (i < unreadCount) messages[i].unread = true;
if (typesAllowed.indexOf(messages[i].type) > -1) {
filteredMessages.push(messages[i]);
}
}
} else {
filteredMessages = messages;
for (var j = 0; j < unreadCount; j++) {
if (typeof filteredMessages[j] !== 'undefined') {
filteredMessages[j].unread = true;
}
}
}
return filteredMessages;
},
render: function () { render: function () {
var loadMore = true; var loadMore = true;
if (this.props.messageOffset > this.props.messages.length && this.props.messageOffset > 0) { if (this.props.messageOffset > this.props.messages.length && this.props.messageOffset > 0) {
loadMore = false; loadMore = false;
} }
var adminMessagesLength = this.props.adminMessages.length;
if (Object.keys(this.props.invite).length > 0) {
adminMessagesLength = adminMessagesLength + 1;
}
var numNewSocialMessages = this.props.numNewMessages - adminMessagesLength;
if (numNewSocialMessages < 0) {
numNewSocialMessages = 0;
}
var messages = this.filterMessages(
this.props.messages,
this.state.filterValues,
numNewSocialMessages
);
return( return(
<MessagesPresentation <MessagesPresentation
sessionStatus={this.props.sessionStatus} sessionStatus={this.props.sessionStatus}
user={this.props.user} user={this.props.user}
messages={messages} messages={this.props.messages}
adminMessages={this.props.adminMessages} adminMessages={this.props.adminMessages}
scratcherInvite={this.props.invite} scratcherInvite={this.props.invite}
numNewMessages={numNewSocialMessages} numNewMessages={this.props.numNewMessages}
adminMessagesLength={adminMessagesLength}
handleFilterClick={this.handleFilterClick} handleFilterClick={this.handleFilterClick}
handleAdminDismiss={this.handleMessageDismiss} handleAdminDismiss={this.handleMessageDismiss}
loadMore={loadMore} loadMore={loadMore}
loadMoreMethod={this.handleLoadMoreMessages} loadMoreMethod={this.handleLoadMoreMessages}
requestStatus={this.props.requestStatus} requestStatus={this.props.requestStatus}
filter={this.props.filter}
/> />
); );
} }

View file

@ -26,5 +26,5 @@
"messages.studioCommentReply": "{profileLink} replied to your comment in {commentLink}", "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.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.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

@ -133,6 +133,7 @@ var CommentMessage = injectIntl(React.createClass({
var messageText = this.getMessageText(this.props.objectType, this.props.commentee); 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 commentorAvatar = 'https://cdn2.scratch.mit.edu/get_image/user/' + this.props.actorId + '_32x32.png';
var commentorAvatarAlt = this.props.actorUsername + '\'s avatar'; var commentorAvatarAlt = this.props.actorUsername + '\'s avatar';
var url = '/users/' + this.props.actorUsername;
var classes = classNames( var classes = classNames(
'mod-comment-message', 'mod-comment-message',
@ -147,11 +148,13 @@ var CommentMessage = injectIntl(React.createClass({
> >
<p className="comment-message-info">{messageText}</p> <p className="comment-message-info">{messageText}</p>
<FlexRow className="mod-comment-message"> <FlexRow className="mod-comment-message">
<a href={url}>
<img <img
className="comment-message-info-img" className="comment-message-info-img"
src={commentorAvatar} src={commentorAvatar}
alt={commentorAvatarAlt} alt={commentorAvatarAlt}
/> />
</a>
<Comment <Comment
comment={this.props.commentText} comment={this.props.commentText}
/> />

View file

@ -42,8 +42,8 @@ var SocialMessagesList = React.createClass({
numNewMessages: 0 numNewMessages: 0
}; };
}, },
getComponentForMessage: function (message) { getComponentForMessage: function (message, unread) {
var className = (message.unread === true) ? 'mod-unread' : ''; var className = (unread) ? 'mod-unread' : '';
var key = message.type + '_' + message.id; var key = message.type + '_' + message.id;
switch (message.type) { switch (message.type) {
@ -140,10 +140,14 @@ var SocialMessagesList = React.createClass({
/>; />;
} }
}, },
renderSocialMessages: function (messages) { renderSocialMessages: function (messages, unreadCount) {
var messageList = []; var messageList = [];
for (var i in messages) { for (var i in messages) {
messageList.push(this.getComponentForMessage(messages[i])); if (i <= unreadCount) {
messageList.push(this.getComponentForMessage(messages[i], true));
} else {
messageList.push(this.getComponentForMessage(messages[i], false));
}
} }
return messageList; return messageList;
}, },
@ -191,10 +195,10 @@ var SocialMessagesList = React.createClass({
</h4> </h4>
</div>, </div>,
<ul className="messages-social-list" key="messages-social-list"> <ul className="messages-social-list" key="messages-social-list">
{this.renderSocialMessages(this.props.messages)} {this.renderSocialMessages(this.props.messages, (this.props.numNewMessages - 1))}
</ul>, </ul>
this.renderLoadMore(this.props.loadMore)
] : []} ] : []}
{this.renderLoadMore(this.props.loadMore)}
</section> </section>
); );
} }
@ -209,21 +213,30 @@ var MessagesPresentation = injectIntl(React.createClass({
adminMessages: React.PropTypes.array.isRequired, adminMessages: React.PropTypes.array.isRequired,
scratcherInvite: React.PropTypes.object.isRequired, scratcherInvite: React.PropTypes.object.isRequired,
numNewMessages: React.PropTypes.number, numNewMessages: React.PropTypes.number,
adminMessagesLength: React.PropTypes.number,
handleFilterClick: React.PropTypes.func.isRequired, handleFilterClick: React.PropTypes.func.isRequired,
handleAdminDismiss: React.PropTypes.func.isRequired, handleAdminDismiss: React.PropTypes.func.isRequired,
loadMore: React.PropTypes.bool.isRequired, loadMore: React.PropTypes.bool.isRequired,
loadMoreMethod: React.PropTypes.func, loadMoreMethod: React.PropTypes.func,
requestStatus: React.PropTypes.object.isRequired requestStatus: React.PropTypes.object.isRequired,
filter: React.PropTypes.string
}, },
getDefaultProps: function () { getDefaultProps: function () {
return { return {
numNewMessages: 0, numNewMessages: 0,
adminMessagesLength: 0, filterOpen: false,
filterOpen: false filter: ''
}; };
}, },
render: function () { render: function () {
var adminMessageLength = this.props.adminMessages.length;
if (Object.keys(this.props.scratcherInvite).length > 0) {
adminMessageLength = adminMessageLength + 1;
}
var numNewSocialMessages = this.props.numNewMessages - adminMessageLength;
if (numNewSocialMessages < 0) {
numNewSocialMessages = 0;
}
return ( return (
<div className="messages"> <div className="messages">
<TitleBanner className="mod-messages"> <TitleBanner className="mod-messages">
@ -259,6 +272,7 @@ var MessagesPresentation = injectIntl(React.createClass({
value: 'forums' value: 'forums'
} }
]} ]}
value={this.props.filter}
/> />
</Form> </Form>
</div> </div>
@ -271,7 +285,7 @@ var MessagesPresentation = injectIntl(React.createClass({
<h4 className="messages-header"> <h4 className="messages-header">
<FormattedMessage id='messages.scratchTeamTitle' /> <FormattedMessage id='messages.scratchTeamTitle' />
<div className="messages-header-unread"> <div className="messages-header-unread">
<FormattedNumber value={this.props.adminMessagesLength} /> <FormattedNumber value={adminMessageLength} />
</div> </div>
</h4> </h4>
</div> </div>
@ -311,7 +325,7 @@ var MessagesPresentation = injectIntl(React.createClass({
<SocialMessagesList <SocialMessagesList
loadStatus={this.props.requestStatus.messages} loadStatus={this.props.requestStatus.messages}
messages={this.props.messages} messages={this.props.messages}
numNewMessages={this.props.numNewMessages} numNewMessages={numNewSocialMessages}
loadMore={this.props.loadMore} loadMore={this.props.loadMore}
loadMoreMethod={this.props.loadMoreMethod} loadMoreMethod={this.props.loadMoreMethod}
/> />

View file

@ -14,6 +14,7 @@
"intro.aboutScratch": "ABOUT SCRATCH", "intro.aboutScratch": "ABOUT SCRATCH",
"intro.forEducators": "FOR EDUCATORS", "intro.forEducators": "FOR EDUCATORS",
"intro.forParents": "FOR PARENTS", "intro.forParents": "FOR PARENTS",
"intro.itsFree": "it's free!",
"intro.joinScratch": "JOIN SCRATCH", "intro.joinScratch": "JOIN SCRATCH",
"intro.seeExamples": "SEE EXAMPLES", "intro.seeExamples": "SEE EXAMPLES",
"intro.tagLine": "Create stories, games, and animations<br /> Share with others around the world", "intro.tagLine": "Create stories, games, and animations<br /> Share with others around the world",

View file

@ -208,6 +208,7 @@ var SplashPresentation = injectIntl(React.createClass({
'intro.aboutScratch': formatMessage({id: 'intro.aboutScratch'}), 'intro.aboutScratch': formatMessage({id: 'intro.aboutScratch'}),
'intro.forEducators': formatMessage({id: 'intro.forEducators'}), 'intro.forEducators': formatMessage({id: 'intro.forEducators'}),
'intro.forParents': formatMessage({id: 'intro.forParents'}), 'intro.forParents': formatMessage({id: 'intro.forParents'}),
'intro.itsFree': formatMessage({id: 'intro.itsFree'}),
'intro.joinScratch': formatMessage({id: 'intro.joinScratch'}), 'intro.joinScratch': formatMessage({id: 'intro.joinScratch'}),
'intro.seeExamples': formatMessage({id: 'intro.seeExamples'}), 'intro.seeExamples': formatMessage({id: 'intro.seeExamples'}),
'intro.tagLine': formatHTMLMessage({id: 'intro.tagLine'}), 'intro.tagLine': formatHTMLMessage({id: 'intro.tagLine'}),

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.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.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.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.teacherGoogleBody": "Scratch Teacher accounts are not integrated with any classroom management services.",
"teacherfaq.teacherEdTitle": "Are Scratch Teacher accounts linked to ScratchEd accounts?", "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.", "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.commHiddenTitle": "Can I create a hidden class?",
"teacherfaq.commHiddenBody": "No. All content shared within your class will be accessible to the Scratch community.", "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.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.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>." "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 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 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 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 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. the Scratch Logo to refer to the Scratch website and programming language.
</p> </p>