From 86551baa943241331fefbcf72a53bd299c4b3c89 Mon Sep 17 00:00:00 2001 From: Robert Chen Date: Thu, 20 Sep 2018 22:15:10 -0700 Subject: [PATCH 001/137] Fixed Issue 1930 --- src/components/registration/steps.jsx | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/components/registration/steps.jsx b/src/components/registration/steps.jsx index a1d752f99..f9c0b3b4c 100644 --- a/src/components/registration/steps.jsx +++ b/src/components/registration/steps.jsx @@ -444,18 +444,20 @@ class DemographicsStep extends React.Component { handleChooseGender (name, gender) { this.setState({otherDisabled: gender !== 'other'}); } - handleValidSubmit (formData, reset, invalidate) { + handleValidSubmit (formData) { + return this.props.onNextStep(formData); + } + isValidBirthdate (year, month) { const birthdate = new Date( - formData.user.birth.year, - formData.user.birth.month - 1, + year, + month - 1, 1 ); - if (((Date.now() - birthdate) / (24 * 3600 * 1000 * 365.25)) < this.props.birthOffset) { - return invalidate({ - 'user.birth.year': this.props.intl.formatMessage({id: 'teacherRegistration.validationAge'}) - }); - } - return this.props.onNextStep(formData); + return (((Date.now() - birthdate) / (24 * 3600 * 1000 * 365.25)) >= this.props.birthOffset); + } + birthDateValidator (values) { + const isValid = this.isValidBirthdate(values['user.birth.year'], values['user.birth.month']); + return isValid ? true : this.props.intl.formatMessage({id: 'teacherRegistration.validationAge'}); } render () { const countryOptions = getCountryOptions(this.props.intl, DEFAULT_COUNTRY); @@ -485,6 +487,9 @@ class DemographicsStep extends React.Component { } name="user.birth.month" options={this.getMonthOptions()} + validations={{ + birthDateVal: values => this.birthDateValidator(values) + }} /> + + + + ) : null} - {isLoggedIn && - - } + {projectInfo.comments_allowed ? ( + isLoggedIn ? ( + + ) : ( + /* TODO add box for signing in to leave a comment */ + null + ) + ) : ( +
+ +
+ )}
@@ -399,6 +423,7 @@ PreviewPresentation.propTypes = { onReportClose: PropTypes.func.isRequired, onReportSubmit: PropTypes.func.isRequired, onSeeInside: PropTypes.func, + onToggleComments: PropTypes.func, onToggleStudio: PropTypes.func, onUpdate: PropTypes.func, originalInfo: projectShape, diff --git a/src/views/preview/preview.jsx b/src/views/preview/preview.jsx index 76ed08b89..def3de3bd 100644 --- a/src/views/preview/preview.jsx +++ b/src/views/preview/preview.jsx @@ -48,6 +48,7 @@ class Preview extends React.Component { 'handleSeeInside', 'handleUpdateProjectTitle', 'handleUpdate', + 'handleToggleComments', 'initCounts', 'pushHistory', 'renderLogin', @@ -167,6 +168,14 @@ class Preview extends React.Component { }); }); } + handleToggleComments () { + this.props.updateProject( + this.props.projectInfo.id, + {comments_allowed: !this.props.projectInfo.comments_allowed}, + this.props.user.username, + this.props.user.token + ); + } handleAddComment (comment, topLevelCommentId) { this.props.handleAddComment(comment, topLevelCommentId); } @@ -356,6 +365,7 @@ class Preview extends React.Component { onReportClose={this.handleReportClose} onReportSubmit={this.handleReportSubmit} onSeeInside={this.handleSeeInside} + onToggleComments={this.handleToggleComments} onToggleStudio={this.handleToggleStudio} onUpdate={this.handleUpdate} /> diff --git a/src/views/preview/preview.scss b/src/views/preview/preview.scss index 5111857e6..d62711e39 100644 --- a/src/views/preview/preview.scss +++ b/src/views/preview/preview.scss @@ -189,6 +189,19 @@ $stage-width: 480px; .comment-bubble { text-align: left; } + + .comments-turned-off { + border: 1px solid $ui-blue-25percent; + border-radius: .5rem; + padding: 1.5rem 0; + background: $ui-blue-10percent; + width: 100%; + text-align: center; + } + + .comments-allowed-input { + margin-right: 3px; + } } .remix-button, From 57408e46af1b6d0245d88d142d4d89e7d37c924d Mon Sep 17 00:00:00 2001 From: Paul Kaplan Date: Wed, 10 Oct 2018 15:29:30 -0400 Subject: [PATCH 068/137] Fix scss name order --- src/views/preview/preview.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/preview/preview.scss b/src/views/preview/preview.scss index d62711e39..8a7d37707 100644 --- a/src/views/preview/preview.scss +++ b/src/views/preview/preview.scss @@ -193,8 +193,8 @@ $stage-width: 480px; .comments-turned-off { border: 1px solid $ui-blue-25percent; border-radius: .5rem; - padding: 1.5rem 0; background: $ui-blue-10percent; + padding: 1.5rem 0; width: 100%; text-align: center; } From 600ba2993391f61ea948d30e4749235744b41385 Mon Sep 17 00:00:00 2001 From: Paul Kaplan Date: Thu, 11 Oct 2018 11:54:58 -0400 Subject: [PATCH 069/137] Prevent replying when comments are turned off --- src/views/preview/presentation.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/preview/presentation.jsx b/src/views/preview/presentation.jsx index 43019d79b..965221b39 100644 --- a/src/views/preview/presentation.jsx +++ b/src/views/preview/presentation.jsx @@ -358,7 +358,7 @@ const PreviewPresentation = ({ {comments.map(comment => ( Date: Thu, 11 Oct 2018 16:58:30 -0400 Subject: [PATCH 070/137] fix ReactModal bodyOpenClassName error so that it is entirely omitted, rather than passing null value (#2176) --- src/components/modal/base/modal.jsx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/components/modal/base/modal.jsx b/src/components/modal/base/modal.jsx index 98d4a9977..156d435ce 100644 --- a/src/components/modal/base/modal.jsx +++ b/src/components/modal/base/modal.jsx @@ -23,14 +23,15 @@ class Modal extends React.Component { return this.modal.portal.requestClose(); } render () { + // bodyOpenClassName prop cannot be blank string or null here; both cause + // an error, because ReactModal does not correctly handle them. + // If we're not setting it to a class name, we must omit the prop entirely. + const bodyOpenClassNameProp = this.props.useStandardSizes ? + {bodyOpenClassName: classNames('overflow-hidden')} : {}; return ( Date: Fri, 12 Oct 2018 09:16:27 -0400 Subject: [PATCH 071/137] Internationalize strings in compose-comment --- src/l10n.json | 18 +++++++- src/views/preview/comment/compose-comment.jsx | 45 +++++-------------- .../preview/comment/top-level-comment.jsx | 10 ++++- src/views/preview/l10n.json | 1 + src/views/preview/presentation.jsx | 2 +- 5 files changed, 40 insertions(+), 36 deletions(-) diff --git a/src/l10n.json b/src/l10n.json index 422f8017d..f4195433e 100644 --- a/src/l10n.json +++ b/src/l10n.json @@ -211,5 +211,21 @@ "comments.reportModal.prompt": "Are you sure you want to report this comment?", "comments.deleteModal.title": "Delete Comment", "comments.deleteModal.body": "Delete this comment? If the comment is mean or disrespectful, please click Report instead to let the Scratch Team know about it.", - "comments.reply": "reply" + "comments.reply": "reply", + "comments.isEmpty": "You can't post an empty comment", + "comments.isFlood": "Woah, seems like you're commenting really quickly. Please wait longer between posts.", + "comments.isBad": "Hmm...the bad word detector thinks there is a problem with your comment. Please change it and remember to be respectful.", + "comments.hasChatSite": "Uh oh! The comment contains a link to a website with unmoderated chat. For safety reasons, please do not link to these sites!", + "comments.isSpam": "Hmm, seems like you've posted the same comment a bunch of times. Please don't spam.", + "comments.isMuted": "Hmm, the filterbot is pretty sure your recent comments weren't ok for Scratch, so your account has been muted for the rest of the day. :/", + "comments.isUnconstructive": "Hmm, the filterbot thinks your comment may be mean or disrespectful. Remember, most projects on Scratch are made by people who are just learning how to program.", + "comments.isDisallowed": "Hmm, it looks like comments have been turned off for this page. :/", + "comments.isIPMuted": "Sorry, the Scratch Team had to prevent your network from sharing comments or projects because it was used to break our community guidelines too many times. You can still share comments and projects from another network.", + "comments.isTooLong": "That comment is too long! Please find a way to shorten your text.", + "comments.error": "Oops! Something went wrong posting your comment", + "comments.posting": "Posting...", + "comments.post": "Post", + "comments.cancel": "Cancel", + "comments.lengthWarning": "{remainingCharacters, plural, one {1 character left} other {{remainingCharacters} characters left}}", + "comments.seeMoreReplies": "{repliesCount, plural, one {See 1 more reply} other {See all {repliesCount} replies}}" } diff --git a/src/views/preview/comment/compose-comment.jsx b/src/views/preview/comment/compose-comment.jsx index 73f07733c..5f8dd37e7 100644 --- a/src/views/preview/comment/compose-comment.jsx +++ b/src/views/preview/comment/compose-comment.jsx @@ -3,6 +3,7 @@ const PropTypes = require('prop-types'); const bindAll = require('lodash.bindall'); const classNames = require('classnames'); const keyMirror = require('keymirror'); +const FormattedMessage = require('react-intl').FormattedMessage; const FlexRow = require('../../../components/flex-row/flex-row.jsx'); const Avatar = require('../../../components/avatar/avatar.jsx'); @@ -25,29 +26,6 @@ const ComposeStatus = keyMirror({ REJECTED: null }); -/* TODO translations */ -const CommentErrorMessages = { - isEmpty: "You can't post an empty comment", - isFlood: "Woah, seems like you're commenting really quickly. Please wait longer between posts.", - isBad: 'Hmm...the bad word detector thinks there is a problem with your comment. ' + - 'Please change it and remember to be respectful.', - hasChatSite: 'Uh oh! This comment contains a link to a website with unmoderated chat.' + - 'For safety reasons, please do not link to these sites!', - isSpam: "Hmm, seems like you've posted the same comment a bunch of times. Please don't spam.", - isMuted: "Hmm, the filterbot is pretty sure your recent comments weren't ok for Scratch, " + - 'so your account has been muted for the rest of the day. :/', - isUnconstructive: 'Hmm, the filterbot thinks your comment may be mean or disrespectful. ' + - 'Remember, most projects on Scratch are made by people who are just learning how to program.', - isDisallowed: 'Hmm, it looks like comments have been turned off for this page. :/', - // TODO implement the special modal for ip mute bans that includes links to appeals - // this is just a stub of the actual message - isIPMuted: 'Sorry, the Scratch Team had to prevent your network from sharing comments or ' + - 'projects because it was used to break our community guidelines too many times.' + - 'You can still share comments and projects from another network.', - isTooLong: "That's too long! Please find a way to shorten your text.", - error: 'Oops! Something went wrong' -}; - class ComposeComment extends React.Component { constructor (props) { super(props); @@ -91,10 +69,7 @@ class ComposeComment extends React.Component { // Note: does not reset the message state this.setState({ status: ComposeStatus.REJECTED, - // If there is a special error message for the rejected reason, - // use it. Otherwise, use the generic 'error' ("Oops!...") - error: CommentErrorMessages[body.rejected] ? - body.rejected : 'error' + error: body.rejected }); return; } @@ -132,7 +107,7 @@ class ComposeComment extends React.Component { {this.state.error ? (
- {CommentErrorMessages[this.state.error] || 'Unknown error'} +
) : null} @@ -152,24 +127,28 @@ class ComposeComment extends React.Component { onClick={this.handlePost} > {this.state.status === ComposeStatus.SUBMITTING ? ( - 'Posting...' /* TODO internationalize */ + ) : ( - 'Post' /* TODO internationalize */ + )} = 0 ? 'compose-valid' : 'compose-invalid')} > - {/* TODO internationalize */} - {MAX_COMMENT_LENGTH - this.state.message.length} characters left +
diff --git a/src/views/preview/comment/top-level-comment.jsx b/src/views/preview/comment/top-level-comment.jsx index 62be1a13d..a5d9af899 100644 --- a/src/views/preview/comment/top-level-comment.jsx +++ b/src/views/preview/comment/top-level-comment.jsx @@ -2,6 +2,7 @@ const React = require('react'); const PropTypes = require('prop-types'); const bindAll = require('lodash.bindall'); const classNames = require('classnames'); +const FormattedMessage = require('react-intl').FormattedMessage; const FlexRow = require('../../../components/flex-row/flex-row.jsx'); const Comment = require('./comment.jsx'); @@ -110,7 +111,14 @@ class TopLevelComment extends React.Component { See all {replies.length} replies + > + + } ); diff --git a/src/views/preview/l10n.json b/src/views/preview/l10n.json index e60902193..fa64531e9 100644 --- a/src/views/preview/l10n.json +++ b/src/views/preview/l10n.json @@ -7,6 +7,7 @@ "preview.speechExtensionChip": "Google Speech", "preview.translateExtensionChip": "Google Translate", "preview.videoMotionChip": "Video Motion", + "preview.comments.header": "Comments", "preview.comments.turnOff": "Turn off commenting", "preview.comments.turnedOff": "Sorry, comment posting has been turned off for this project." } diff --git a/src/views/preview/presentation.jsx b/src/views/preview/presentation.jsx index 43019d79b..ef46e3208 100644 --- a/src/views/preview/presentation.jsx +++ b/src/views/preview/presentation.jsx @@ -320,7 +320,7 @@ const PreviewPresentation = ({
-

Comments

+

{userOwnsProject ? (
- {(props.isLoggedIn && props.userOwnsProject) && + {props.canAddToStudio && - {(props.isLoggedIn && !props.userOwnsProject) && + {(props.canReport) &&
@@ -24,6 +28,7 @@ const ShareBanner = props => { }; ShareBanner.propTypes = { + onShare: PropTypes.func, shared: PropTypes.bool.isRequired }; From 9ce79deac65a311da0f91b229a83e765ee561402 Mon Sep 17 00:00:00 2001 From: Paul Kaplan Date: Tue, 16 Oct 2018 13:02:22 -0400 Subject: [PATCH 081/137] Control visibility externally and fix css --- src/views/preview/presentation.jsx | 7 +++--- src/views/preview/share-banner.jsx | 38 ++++++++++++----------------- src/views/preview/share-banner.scss | 10 +++++--- 3 files changed, 26 insertions(+), 29 deletions(-) diff --git a/src/views/preview/presentation.jsx b/src/views/preview/presentation.jsx index 980e3c093..ba9b0c618 100644 --- a/src/views/preview/presentation.jsx +++ b/src/views/preview/presentation.jsx @@ -87,10 +87,9 @@ const PreviewPresentation = ({ const shareDate = ((projectInfo.history && projectInfo.history.shared)) ? projectInfo.history.shared : ''; return (
- + {!isShared && ( + + )} { projectInfo && projectInfo.author && projectInfo.author.id && (
diff --git a/src/views/preview/share-banner.jsx b/src/views/preview/share-banner.jsx index a156c31ef..e3b29b82e 100644 --- a/src/views/preview/share-banner.jsx +++ b/src/views/preview/share-banner.jsx @@ -6,30 +6,24 @@ const Button = require('../../components/forms/button.jsx'); require('./share-banner.scss'); -const ShareBanner = props => { - if (props.shared) return null; - return ( -
-
- - - - - - -
-
- ); -}; +const ShareBanner = ({onShare}) => ( +
+ + + + + + +
+); ShareBanner.propTypes = { - onShare: PropTypes.func, - shared: PropTypes.bool.isRequired + onShare: PropTypes.func }; module.exports = ShareBanner; diff --git a/src/views/preview/share-banner.scss b/src/views/preview/share-banner.scss index 8c5140ccb..f43d4e5fd 100644 --- a/src/views/preview/share-banner.scss +++ b/src/views/preview/share-banner.scss @@ -2,19 +2,23 @@ $navigation-height: 50px; -.shareBanner { +.share-banner-outer { background-color: $ui-orange-25percent; width: 100%; overflow: hidden; color: $ui-orange; } +.share-banner { + align-items: center; + justify-content: space-between; +} + .share-button { - margin-top: 0; background-color: $ui-orange; font-size: .875rem; font-weight: normal; - + &:before { display: inline-block; margin-right: .5rem; From 6d8235c7e8085e04abc960ea63d9b648d837789b Mon Sep 17 00:00:00 2001 From: sjgllghr Date: Tue, 16 Oct 2018 10:28:41 -0700 Subject: [PATCH 082/137] Fix gh-2156: Use variables for media queries for consistency (#2182) * use variables for media queries * add medium-and-intermediate media query variable --- src/_frameless.scss | 10 ++++++---- src/components/box/box.scss | 10 +++++----- src/components/card/card.scss | 8 ++++---- src/components/flex-row/flex-row.scss | 2 +- src/components/footer/conference/footer.scss | 6 +++--- src/components/grid/grid.scss | 8 ++++---- src/components/masonrygrid/masonrygrid.scss | 8 ++++---- src/components/modal/ttt/modal.scss | 6 +++--- .../navigation/conference/2016/navigation.scss | 4 ++-- .../navigation/conference/2018/navigation.scss | 6 +++--- src/components/navigation/www/accountnav.scss | 6 +++--- src/components/navigation/www/navigation.scss | 6 +++--- src/components/page/conference/page.scss | 14 +++++++------- src/components/registration/registration.scss | 2 +- src/components/registration/steps.scss | 4 ++-- src/components/slide/slide.scss | 2 +- src/components/social-message/social-message.scss | 4 ++-- src/components/tooltip/tooltip.scss | 2 +- src/views/conference/2016/details/details.scss | 6 +++--- src/views/conference/2016/expect/expect.scss | 12 ++++++------ src/views/conference/2016/index/index.scss | 8 ++++---- src/views/conference/2016/plan/plan.scss | 14 +++++++------- src/views/conference/2016/schedule/schedule.scss | 4 ++-- src/views/conference/2017/index/index.scss | 6 +++--- src/views/conference/2018/details/details.scss | 6 +++--- src/views/conference/2018/expect/expect.scss | 12 ++++++------ src/views/conference/2018/index/index.scss | 8 ++++---- src/views/conference/2018/plan/plan.scss | 14 +++++++------- src/views/conference/2018/schedule/schedule.scss | 4 ++-- src/views/developers/developers.scss | 6 +++--- src/views/download/download.scss | 4 ++-- src/views/explore/explore.scss | 6 +++--- src/views/guidelines/guidelines.scss | 2 +- src/views/messages/messages.scss | 6 +++--- src/views/search/search.scss | 6 +++--- src/views/splash/beta/middle-banner.scss | 2 +- src/views/splash/splash.scss | 6 +++--- src/views/teachers/landing/landing.scss | 8 ++++---- src/views/tips/tips.scss | 8 ++++---- src/views/wedo2-legacy/wedo2.scss | 6 +++--- 40 files changed, 132 insertions(+), 130 deletions(-) diff --git a/src/_frameless.scss b/src/_frameless.scss index d0ff4626b..0881a3ec6 100644 --- a/src/_frameless.scss +++ b/src/_frameless.scss @@ -55,6 +55,8 @@ $big: "only screen and (min-width : #{$desktop})"; $medium-and-smaller: "only screen and (max-width : #{$tablet}-1)"; $intermediate-and-smaller: "only screen and (max-width : #{$desktop}-1)"; +$medium-and-intermediate: "only screen and (min-width : #{$mobile}) and (max-width : #{$desktop}-1)"; + /* Height */ $small-height: "only screen and (max-height : #{$mobile} - 1)"; @@ -71,7 +73,7 @@ $medium-height: "only screen and (min-height : #{$mobile}) and (max-height : #{$ //4 columns @mixin submobile ($parent-selector, $child-selector) { - @media only screen and (max-width: $mobile - 1) { + @media #{$small} { #{$parent-selector} { text-align: center; } @@ -87,7 +89,7 @@ $medium-height: "only screen and (min-height : #{$mobile}) and (max-height : #{$ //6 columns @mixin mobile ($parent-selector, $child-selector) { - @media only screen and (min-width: $mobile) and (max-width: $tablet - 1) { + @media #{$medium} { #{$parent-selector} { text-align: center; } @@ -103,7 +105,7 @@ $medium-height: "only screen and (min-height : #{$mobile}) and (max-height : #{$ //8 columns @mixin tablet ($parent-selector, $child-selector) { - @media only screen and (min-width: $tablet) and (max-width: $desktop - 1) { + @media #{$intermediate} { #{$parent-selector} { text-align: center; } @@ -117,7 +119,7 @@ $medium-height: "only screen and (min-height : #{$mobile}) and (max-height : #{$ //12 columns @mixin desktop ($parent-selector, $child-selector) { - @media only screen and (min-width: $desktop) { + @media #{$big} { #{$child-selector} { margin: 0 auto; width: $desktop; diff --git a/src/components/box/box.scss b/src/components/box/box.scss index 4b9392771..bc981dd94 100644 --- a/src/components/box/box.scss +++ b/src/components/box/box.scss @@ -7,9 +7,9 @@ $base-bg: $ui-white; display: inline-block; border: 1px solid $ui-border; border-radius: 10px 10px 0 0; - + //4 columns - @media only screen and (max-width: $mobile - 1) { + @media #{$small} { width: $cols4; .box-header { @@ -22,7 +22,7 @@ $base-bg: $ui-white; } //6 columns - @media only screen and (min-width: $mobile) and (max-width: $tablet - 1) { + @media #{$medium} { width: $cols6; .box-header { @@ -35,7 +35,7 @@ $base-bg: $ui-white; } //8 columns - @media only screen and (min-width: $tablet) and (max-width: $desktop - 1) { + @media #{$intermediate} { width: $cols8; .box-header { @@ -48,7 +48,7 @@ $base-bg: $ui-white; } //12 columns - @media only screen and (min-width: $desktop) { + @media #{$big} { width: $cols12; .box-header { diff --git a/src/components/card/card.scss b/src/components/card/card.scss index b2b70c22c..d23f5f2e3 100644 --- a/src/components/card/card.scss +++ b/src/components/card/card.scss @@ -65,7 +65,7 @@ margin: 0 0 -3rem -4rem; } - .row { + .row { margin-bottom: 1.2rem; &.has-error { @@ -81,7 +81,7 @@ } } -@media only screen and (max-width: $mobile - 1) { +@media #{$small} { .card { width: 22.5rem; @@ -95,7 +95,7 @@ } } -@media only screen and (max-width: $tablet - 1) { +@media #{$medium-and-smaller} { .card { .input { width: 90%; @@ -103,7 +103,7 @@ } } -@media only screen and (max-width: $desktop - 1) { +@media #{$intermediate-and-smaller} { .card { .validation-message { position: relative; diff --git a/src/components/flex-row/flex-row.scss b/src/components/flex-row/flex-row.scss index 4b0f28069..eff8370e3 100644 --- a/src/components/flex-row/flex-row.scss +++ b/src/components/flex-row/flex-row.scss @@ -25,7 +25,7 @@ } } - @media only screen and (max-width: $tablet - 1) { + @media #{$medium-and-smaller} { flex-direction: column; &.uneven { diff --git a/src/components/footer/conference/footer.scss b/src/components/footer/conference/footer.scss index 3694c2d87..3eafcc4cb 100644 --- a/src/components/footer/conference/footer.scss +++ b/src/components/footer/conference/footer.scss @@ -51,7 +51,7 @@ justify-content: space-between; align-items: flex-start; - @media only screen and (max-width: $tablet - 1) { + @media #{$medium-and-smaller} { align-items: center; } } @@ -103,7 +103,7 @@ } } - @media only screen and (max-width: $tablet - 1) { + @media #{$medium-and-smaller} { text-align: center; .family { @@ -122,7 +122,7 @@ } } - @media only screen and (min-width: $tablet) and (max-width: $desktop - 1) { + @media #{$intermediate} { ul { li { margin-left: 0; diff --git a/src/components/grid/grid.scss b/src/components/grid/grid.scss index 253210619..3cd926af2 100644 --- a/src/components/grid/grid.scss +++ b/src/components/grid/grid.scss @@ -7,7 +7,7 @@ $thumbnail-width: 220px; $thumbnail-inner-width: 204px; - + $project-height: 208px; $gallery-height: 164px; @@ -94,21 +94,21 @@ } //4 columns - @media only screen and (max-width: $mobile - 1) { + @media #{$small} { .flex-row { width: $cols4; } } //6 columns - @media only screen and (min-width: $mobile) and (max-width: $tablet - 1) { + @media #{$medium} { .flex-row { width: $cols6; } } // 8 columns - @media only screen and (min-width: $tablet) and (max-width: $desktop - 1) { + @media #{$intermediate} { .flex-row { width: $cols9; } diff --git a/src/components/masonrygrid/masonrygrid.scss b/src/components/masonrygrid/masonrygrid.scss index 867f33350..98b75c95f 100644 --- a/src/components/masonrygrid/masonrygrid.scss +++ b/src/components/masonrygrid/masonrygrid.scss @@ -9,14 +9,14 @@ // column-count required for Firefox, IE and Edge //4 columns -@media only screen and (max-width: $mobile - 1) { +@media #{$small} { .masonry { column-count: 1; } } //6 columns -@media only screen and (min-width: $mobile) and (max-width: $tablet - 1) { +@media #{$medium} { .masonry { column-count: 1; } @@ -24,14 +24,14 @@ //8 columns -@media only screen and (min-width: $tablet) and (max-width: $desktop - 1) { +@media #{$intermediate} { .masonry { column-count: 2; } } // 12 columns -@media only screen and (min-width: $desktop) { +@media #{$big} { .masonry { column-count: 3; } diff --git a/src/components/modal/ttt/modal.scss b/src/components/modal/ttt/modal.scss index 1604eca7b..b152e9025 100644 --- a/src/components/modal/ttt/modal.scss +++ b/src/components/modal/ttt/modal.scss @@ -62,7 +62,7 @@ box-shadow: none; } -@media only screen and (max-width: $mobile - 1) { +@media #{$small} { .modal-content.mod-ttt { overflow: scroll; } @@ -76,7 +76,7 @@ } } -@media only screen and (min-width: $mobile) and (max-width: $tablet - 1) { +@media #{$medium} { .modal-content.mod-ttt { overflow: scroll; } @@ -90,7 +90,7 @@ } } -@media only screen and (min-width: $tablet) and (max-width: $desktop - 1) { +@media #{$intermediate} { .modal-content.mod-ttt { overflow: scroll; } diff --git a/src/components/navigation/conference/2016/navigation.scss b/src/components/navigation/conference/2016/navigation.scss index bc1536c16..13c74c539 100644 --- a/src/components/navigation/conference/2016/navigation.scss +++ b/src/components/navigation/conference/2016/navigation.scss @@ -40,7 +40,7 @@ font-weight: bold; } - @media only screen and (max-width: $mobile - 1) { + @media #{$small} { .li-right-ul.mod-2016 { flex-flow: row nowrap; } @@ -55,7 +55,7 @@ } } - @media only screen and (max-width: $tablet - 1) { + @media #{$medium-and-smaller} { height: 100px; .ul.mod-2016 { diff --git a/src/components/navigation/conference/2018/navigation.scss b/src/components/navigation/conference/2018/navigation.scss index 7651d846f..6e36aaab6 100644 --- a/src/components/navigation/conference/2018/navigation.scss +++ b/src/components/navigation/conference/2018/navigation.scss @@ -11,7 +11,7 @@ align-items: center; list-style-type: none; } - + .li-left-ul.mod-2018 { padding-left: 0; } @@ -45,7 +45,7 @@ font-weight: bold; } - @media only screen and (max-width: $mobile - 1) { + @media #{$small} { .li-right-ul.mod-2018 { flex-flow: row nowrap; } @@ -60,7 +60,7 @@ } } - @media only screen and (max-width: $tablet - 1) { + @media #{$medium-and-smaller} { height: 100px; .ul.mod-2018 { diff --git a/src/components/navigation/www/accountnav.scss b/src/components/navigation/www/accountnav.scss index ebee28122..e4bc29710 100644 --- a/src/components/navigation/www/accountnav.scss +++ b/src/components/navigation/www/accountnav.scss @@ -56,7 +56,7 @@ } //4 columns -@media only screen and (max-width: $mobile - 1) { +@media #{$small} { .account-nav { margin-left: 0; @@ -74,7 +74,7 @@ //6 columns -@media only screen and (min-width: $mobile) and (max-width: $tablet - 1) { +@media #{$medium} { .account-nav { margin-left: 0; @@ -91,7 +91,7 @@ } //8 columns -@media only screen and (min-width: $tablet) and (max-width: $desktop - 1) { +@media #{$intermediate} { .account-nav { margin-left: 0; } diff --git a/src/components/navigation/www/navigation.scss b/src/components/navigation/www/navigation.scss index 62429789a..b07300010 100644 --- a/src/components/navigation/www/navigation.scss +++ b/src/components/navigation/www/navigation.scss @@ -166,7 +166,7 @@ } //4 columns -@media only screen and (max-width: $mobile - 1) { +@media #{$small} { #navigation .inner { width: $cols4; @@ -190,7 +190,7 @@ //6 columns -@media only screen and (min-width: $mobile) and (max-width: $tablet - 1) { +@media #{$medium} { #navigation .inner { width: $cols6; @@ -212,7 +212,7 @@ } //8 columns -@media only screen and (min-width: $tablet) and (max-width: $desktop - 1) { +@media #{$intermediate} { #navigation .inner { width: $cols8; diff --git a/src/components/page/conference/page.scss b/src/components/page/conference/page.scss index 5f0f2fbf2..e164d2eb2 100644 --- a/src/components/page/conference/page.scss +++ b/src/components/page/conference/page.scss @@ -6,19 +6,19 @@ font-size: 4.5rem; } - @media only screen and (max-width: $mobile - 1) { + @media #{$small} { h1 { font-size: 2.5rem; } } - @media only screen and (min-width: $mobile) and (max-width: $tablet - 1) { + @media #{$medium} { h1 { font-size: 3rem; } } - @media only screen and (min-width: $tablet) and (max-width: $desktop - 1) { + @media #{$intermediate} { h1 { font-size: 3.5rem; } @@ -52,7 +52,7 @@ } } - @media only screen and (max-width: $tablet - 1) { + @media #{$medium-and-smaller} { margin-top: 50px; } } @@ -79,7 +79,7 @@ font-size: 4rem; } - @media only screen and (max-width: $tablet - 1) { + @media #{$medium-and-smaller} { h1, .title-banner-h1.mod-2017 { font-size: 2.5rem; @@ -96,7 +96,7 @@ width: 125px; } - @media only screen and (max-width: $tablet - 1) { + @media #{$medium-and-smaller} { img { transform: translate(0, 5px); width: 85px; @@ -108,7 +108,7 @@ section { padding: 64px 0; } -@media only screen and (max-width: $mobile - 1) { +@media #{$small} { #view { width: 100%; min-width: 100%; diff --git a/src/components/registration/registration.scss b/src/components/registration/registration.scss index acd5c2d8c..3b33ba0a1 100644 --- a/src/components/registration/registration.scss +++ b/src/components/registration/registration.scss @@ -10,7 +10,7 @@ min-height: 27.375rem; } -@media only screen and (max-width: $desktop - 1) { +@media #{$intermediate-and-smaller} { .modal-content.mod-registration { width: 100%; overflow: scroll; diff --git a/src/components/registration/steps.scss b/src/components/registration/steps.scss index b5ce409d5..f40dc4060 100644 --- a/src/components/registration/steps.scss +++ b/src/components/registration/steps.scss @@ -155,7 +155,7 @@ } } -@media only screen and (max-width: $mobile - 1) { +@media #{$small} { .registration-step { &.demographics-step { .radio { @@ -174,7 +174,7 @@ } } -@media only screen and (max-width: $desktop - 1) { +@media #{$intermediate-and-smaller} { .registration-step { .form { text-align: left; diff --git a/src/components/slide/slide.scss b/src/components/slide/slide.scss index 1fbf439ba..45928df61 100644 --- a/src/components/slide/slide.scss +++ b/src/components/slide/slide.scss @@ -25,7 +25,7 @@ } } -@media only screen and (max-width: $tablet - 1) { +@media #{$medium-and-smaller} { .slide { padding: 0; } diff --git a/src/components/social-message/social-message.scss b/src/components/social-message/social-message.scss index 258fa713e..a7afbb18c 100644 --- a/src/components/social-message/social-message.scss +++ b/src/components/social-message/social-message.scss @@ -53,7 +53,7 @@ a.social-messages-profile-link { margin-left: 1.5rem; } -@media only screen and (max-width: $mobile - 1) { +@media #{$small} { .social-message { text-align: left; } @@ -67,7 +67,7 @@ a.social-messages-profile-link { } } -@media only screen and (min-width: $mobile) and (max-width: $tablet - 1) { +@media #{$medium} { .social-message { text-align: left; } diff --git a/src/components/tooltip/tooltip.scss b/src/components/tooltip/tooltip.scss index b4286c3c5..76dc2ad81 100644 --- a/src/components/tooltip/tooltip.scss +++ b/src/components/tooltip/tooltip.scss @@ -58,7 +58,7 @@ } } -@media only screen and (max-width: $desktop - 1) { +@media #{$intermediate-and-smaller} { .tooltip { display: block; diff --git a/src/views/conference/2016/details/details.scss b/src/views/conference/2016/details/details.scss index 68ab5db56..57ec59495 100644 --- a/src/views/conference/2016/details/details.scss +++ b/src/views/conference/2016/details/details.scss @@ -1,11 +1,11 @@ @import "../../../../frameless"; #view { - @media only screen and (max-width: $tablet - 1) { + @media #{$medium-and-smaller} { margin-top: 100px; } - @media only screen and (max-width: $desktop - 1) { + @media #{$intermediate-and-smaller} { text-align: left; } } @@ -49,7 +49,7 @@ } //8 columns -@media only screen and (max-width: $desktop - 1) { +@media #{$intermediate-and-smaller} { .details { width: 100%; } diff --git a/src/views/conference/2016/expect/expect.scss b/src/views/conference/2016/expect/expect.scss index cfc1a3236..95338b11a 100644 --- a/src/views/conference/2016/expect/expect.scss +++ b/src/views/conference/2016/expect/expect.scss @@ -25,7 +25,7 @@ margin-top: 1.2rem; } - @media only screen and (max-width: $tablet - 1) { + @media #{$medium-and-smaller} { img { width: 50%; } @@ -37,7 +37,7 @@ } } - @media only screen and (max-width: $desktop - 1) { + @media #{$intermediate-and-smaller} { .uneven { flex-direction: column; align-items: center; @@ -80,7 +80,7 @@ margin: 15px 0; } - @media only screen and (max-width: $desktop - 1) { + @media #{$intermediate-and-smaller} { .flex-row { flex-direction: column; align-items: center; @@ -156,7 +156,7 @@ } } - @media only screen and (max-width: $mobile - 1) { + @media #{$small} { .flex-row { table { width: 100%; @@ -164,7 +164,7 @@ } } - @media only screen and (min-width: $mobile) and (max-width: $desktop - 1) { + @media #{$medium-and-intermediate} { .flex-row { table { width: $cols6; @@ -172,7 +172,7 @@ } } - @media only screen and (max-width: $desktop - 1) { + @media #{$intermediate-and-smaller} { .flex-row { flex-direction: column; align-items: center; diff --git a/src/views/conference/2016/index/index.scss b/src/views/conference/2016/index/index.scss index 12a3a9214..a2d4b16fa 100644 --- a/src/views/conference/2016/index/index.scss +++ b/src/views/conference/2016/index/index.scss @@ -48,7 +48,7 @@ } } - @media only screen and (max-width: $mobile - 1) { + @media #{$small} { h3 { display: none; margin-top: 0; @@ -60,7 +60,7 @@ } - @media only screen and (max-width: $desktop - 1) { + @media #{$intermediate-and-smaller} { h1 { font-size: 2.5rem; } @@ -85,7 +85,7 @@ max-width: 125px; } - @media only screen and (max-width: $tablet - 1) { + @media #{$medium-and-smaller} { margin: .5rem; width: 125px; } @@ -93,7 +93,7 @@ } } -@media only screen and (max-width: $tablet - 1) { +@media #{$medium-and-smaller} { .index { .flex-row { align-items: center; diff --git a/src/views/conference/2016/plan/plan.scss b/src/views/conference/2016/plan/plan.scss index f1e4948ac..1f219b5d9 100644 --- a/src/views/conference/2016/plan/plan.scss +++ b/src/views/conference/2016/plan/plan.scss @@ -19,13 +19,13 @@ width: 100%; } - @media only screen and (max-width: $tablet - 1) { + @media #{$medium-and-smaller} { img { width: 30%; } } - @media only screen and (min-width: $tablet) and (max-width: $desktop - 1) { + @media #{$intermediate} { img { width: 70%; } @@ -36,7 +36,7 @@ .lodging { text-align: left; - @media only screen and (max-width: $desktop - 1) { + @media #{$intermediate-and-smaller} { .uneven { .short { display: none; @@ -50,7 +50,7 @@ align-items: center; } - @media only screen and (max-width: $desktop - 1) { + @media #{$intermediate-and-smaller} { .flex-row { flex-direction: column-reverse; } @@ -69,13 +69,13 @@ justify-content: flex-start; } - @media only screen and (max-width: $tablet - 1) { + @media #{$medium-and-smaller} { ul { max-height: 100%; } } - @media only screen and (max-width: $desktop - 1) { + @media #{$intermediate-and-smaller} { div { text-align: left; } @@ -105,7 +105,7 @@ margin: 0; } - @media only screen and (max-width: $tablet - 1) { + @media #{$medium-and-smaller} { margin-top: 0; } } diff --git a/src/views/conference/2016/schedule/schedule.scss b/src/views/conference/2016/schedule/schedule.scss index d65a921d0..568a30375 100644 --- a/src/views/conference/2016/schedule/schedule.scss +++ b/src/views/conference/2016/schedule/schedule.scss @@ -102,7 +102,7 @@ } } - @media only screen and (max-width: $mobile - 1) { + @media #{$small} { .sub-nav { flex-wrap: nowrap; } @@ -124,7 +124,7 @@ } } - @media only screen and (max-width: $tablet - 1) { + @media #{$medium-and-smaller} { .inner { h2 { &.breaking-title { diff --git a/src/views/conference/2017/index/index.scss b/src/views/conference/2017/index/index.scss index 6c571b5fd..96a722dbe 100644 --- a/src/views/conference/2017/index/index.scss +++ b/src/views/conference/2017/index/index.scss @@ -79,7 +79,7 @@ td { color: $type-white; } -@media only screen and (max-width: $mobile - 1) { +@media #{$small} { .index.mod-2017 { text-align: left; } @@ -123,7 +123,7 @@ td { } } -@media only screen and (min-width: $mobile) and (max-width: $tablet - 1) { +@media #{$medium} { .index.mod-2017 { text-align: left; } @@ -159,7 +159,7 @@ td { } } -@media only screen and (min-width: $tablet) and (max-width: $desktop - 1) { +@media #{$intermediate} { .index.mod-2017 { text-align: left; } diff --git a/src/views/conference/2018/details/details.scss b/src/views/conference/2018/details/details.scss index 68ab5db56..57ec59495 100644 --- a/src/views/conference/2018/details/details.scss +++ b/src/views/conference/2018/details/details.scss @@ -1,11 +1,11 @@ @import "../../../../frameless"; #view { - @media only screen and (max-width: $tablet - 1) { + @media #{$medium-and-smaller} { margin-top: 100px; } - @media only screen and (max-width: $desktop - 1) { + @media #{$intermediate-and-smaller} { text-align: left; } } @@ -49,7 +49,7 @@ } //8 columns -@media only screen and (max-width: $desktop - 1) { +@media #{$intermediate-and-smaller} { .details { width: 100%; } diff --git a/src/views/conference/2018/expect/expect.scss b/src/views/conference/2018/expect/expect.scss index b2d685798..f1cb84919 100644 --- a/src/views/conference/2018/expect/expect.scss +++ b/src/views/conference/2018/expect/expect.scss @@ -25,7 +25,7 @@ margin-top: 1.2rem; } - @media only screen and (max-width: $tablet - 1) { + @media #{$medium-and-smaller} { img { width: 50%; } @@ -37,7 +37,7 @@ } } - @media only screen and (max-width: $desktop - 1) { + @media #{$intermediate-and-smaller} { .uneven { flex-direction: column; align-items: center; @@ -85,7 +85,7 @@ margin: 15px 0; } - @media only screen and (max-width: $desktop - 1) { + @media #{$intermediate-and-smaller} { .flex-row { flex-direction: column; align-items: center; @@ -163,7 +163,7 @@ } } - @media only screen and (max-width: $mobile - 1) { + @media #{$small} { .flex-row { table { width: 100%; @@ -171,7 +171,7 @@ } } - @media only screen and (min-width: $mobile) and (max-width: $desktop - 1) { + @media #{$medium-and-intermediate} { .flex-row { table { width: $cols6; @@ -179,7 +179,7 @@ } } - @media only screen and (max-width: $desktop - 1) { + @media #{$intermediate-and-smaller} { .flex-row { flex-direction: column; align-items: center; diff --git a/src/views/conference/2018/index/index.scss b/src/views/conference/2018/index/index.scss index 705775faf..2fb59c052 100644 --- a/src/views/conference/2018/index/index.scss +++ b/src/views/conference/2018/index/index.scss @@ -60,7 +60,7 @@ } } - @media only screen and (max-width: $mobile - 1) { + @media #{$small} { h3 { display: none; margin-top: 0; @@ -72,7 +72,7 @@ } - @media only screen and (max-width: $desktop - 1) { + @media #{$intermediate-and-smaller} { h1 { font-size: 2.5rem; } @@ -97,7 +97,7 @@ max-width: 125px; } - @media only screen and (max-width: $tablet - 1) { + @media #{$medium-and-smaller} { margin: .5rem; width: 125px; } @@ -105,7 +105,7 @@ } } -@media only screen and (max-width: $tablet - 1) { +@media #{$medium-and-smaller} { .index { .flex-row { align-items: center; diff --git a/src/views/conference/2018/plan/plan.scss b/src/views/conference/2018/plan/plan.scss index f1e4948ac..1f219b5d9 100644 --- a/src/views/conference/2018/plan/plan.scss +++ b/src/views/conference/2018/plan/plan.scss @@ -19,13 +19,13 @@ width: 100%; } - @media only screen and (max-width: $tablet - 1) { + @media #{$medium-and-smaller} { img { width: 30%; } } - @media only screen and (min-width: $tablet) and (max-width: $desktop - 1) { + @media #{$intermediate} { img { width: 70%; } @@ -36,7 +36,7 @@ .lodging { text-align: left; - @media only screen and (max-width: $desktop - 1) { + @media #{$intermediate-and-smaller} { .uneven { .short { display: none; @@ -50,7 +50,7 @@ align-items: center; } - @media only screen and (max-width: $desktop - 1) { + @media #{$intermediate-and-smaller} { .flex-row { flex-direction: column-reverse; } @@ -69,13 +69,13 @@ justify-content: flex-start; } - @media only screen and (max-width: $tablet - 1) { + @media #{$medium-and-smaller} { ul { max-height: 100%; } } - @media only screen and (max-width: $desktop - 1) { + @media #{$intermediate-and-smaller} { div { text-align: left; } @@ -105,7 +105,7 @@ margin: 0; } - @media only screen and (max-width: $tablet - 1) { + @media #{$medium-and-smaller} { margin-top: 0; } } diff --git a/src/views/conference/2018/schedule/schedule.scss b/src/views/conference/2018/schedule/schedule.scss index 27b4625a8..18edbd60b 100644 --- a/src/views/conference/2018/schedule/schedule.scss +++ b/src/views/conference/2018/schedule/schedule.scss @@ -102,7 +102,7 @@ } } - @media only screen and (max-width: $mobile - 1) { + @media #{$small} { .sub-nav { flex-wrap: nowrap; } @@ -124,7 +124,7 @@ } } - @media only screen and (max-width: $tablet - 1) { + @media #{$medium-and-smaller} { .inner { h2 { &.breaking-title { diff --git a/src/views/developers/developers.scss b/src/views/developers/developers.scss index ea706de08..b598c8501 100644 --- a/src/views/developers/developers.scss +++ b/src/views/developers/developers.scss @@ -170,7 +170,7 @@ $developer-spot: $ui-aqua; } //4 columns -@media only screen and (max-width: $mobile - 1) { +@media #{$small} { #view { text-align: left; } @@ -196,7 +196,7 @@ $developer-spot: $ui-aqua; } //6 columns -@media only screen and (min-width: $mobile) and (max-width: $tablet - 1) { +@media #{$medium} { #view { text-align: left; } @@ -216,7 +216,7 @@ $developer-spot: $ui-aqua; } //8 columns -@media only screen and (min-width: $tablet) and (max-width: $desktop - 1) { +@media #{$intermediate} { #view { text-align: left; } diff --git a/src/views/download/download.scss b/src/views/download/download.scss index 60a32e3ab..146508a6f 100644 --- a/src/views/download/download.scss +++ b/src/views/download/download.scss @@ -111,7 +111,7 @@ color: $ui-white; } - @media only screen and (max-width: $mobile - 1) { + @media #{$small} { .inner { .installation-column { max-width: 100%; @@ -119,7 +119,7 @@ } } - @media only screen and (max-width: $desktop - 1) { + @media #{$intermediate-and-smaller} { .three-col-row { flex-direction: column; align-items: center; diff --git a/src/views/explore/explore.scss b/src/views/explore/explore.scss index c4159fcd5..1252422e3 100644 --- a/src/views/explore/explore.scss +++ b/src/views/explore/explore.scss @@ -120,7 +120,7 @@ $base-bg: $ui-white; } //4 columns -@media only screen and (max-width: $mobile - 1) { +@media #{$small} { .outer { .tabs { width: $cols4; @@ -139,7 +139,7 @@ $base-bg: $ui-white; } //6 columns -@media only screen and (min-width: $mobile) and (max-width: $tablet - 1) { +@media #{$medium} { .outer { .tabs { width: $cols6; @@ -158,7 +158,7 @@ $base-bg: $ui-white; } // 8 columns -@media only screen and (min-width: $tablet) and (max-width: $desktop - 1) { +@media #{$intermediate} { .outer { .tabs { width: $cols8; diff --git a/src/views/guidelines/guidelines.scss b/src/views/guidelines/guidelines.scss index 9084c6fb3..92566445d 100644 --- a/src/views/guidelines/guidelines.scss +++ b/src/views/guidelines/guidelines.scss @@ -14,7 +14,7 @@ } } -@media only screen and (max-width: $tablet - 1){ +@media #{$medium-and-smaller}{ .guidelines-footer { img { display: none; diff --git a/src/views/messages/messages.scss b/src/views/messages/messages.scss index f91c679ed..3932c30f2 100644 --- a/src/views/messages/messages.scss +++ b/src/views/messages/messages.scss @@ -129,7 +129,7 @@ word-wrap: break-word; } -@media only screen and (max-width: $mobile - 1) { +@media #{$small} { .flex-row.admin-message-header, .flex-row.mod-comment-message { flex-direction: row; @@ -144,7 +144,7 @@ } } -@media only screen and (min-width: $mobile) and (max-width: $tablet - 1) { +@media #{$medium} { .flex-row.admin-message-header, .flex-row.mod-comment-message { flex-direction: row; @@ -159,7 +159,7 @@ } } -@media only screen and (min-width: $tablet) and (max-width: $desktop - 1) { +@media #{$intermediate} { .comment-text { max-width: 23.75rem; } diff --git a/src/views/search/search.scss b/src/views/search/search.scss index 93e3c3fc0..07bf1f911 100644 --- a/src/views/search/search.scss +++ b/src/views/search/search.scss @@ -169,7 +169,7 @@ $base-bg: $ui-white; } //4 columns -@media only screen and (max-width: $mobile - 1) { +@media #{$small} { .outer { .search { width: $cols4; @@ -197,7 +197,7 @@ $base-bg: $ui-white; //6 columns -@media only screen and (min-width: $mobile) and (max-width: $tablet - 1) { +@media #{$medium} { .outer { .tabs { width: $cols6; @@ -216,7 +216,7 @@ $base-bg: $ui-white; } // 8 columns -@media only screen and (min-width: $tablet) and (max-width: $desktop - 1) { +@media #{$intermediate} { .outer { .tabs { width: $cols8; diff --git a/src/views/splash/beta/middle-banner.scss b/src/views/splash/beta/middle-banner.scss index 8578b51d1..ca0e759f4 100644 --- a/src/views/splash/beta/middle-banner.scss +++ b/src/views/splash/beta/middle-banner.scss @@ -65,7 +65,7 @@ } } -@media only screen and (min-width: $tablet) and (max-width: $desktop - 1) { +@media #{$intermediate} { .beta-middle-banner { margin: 20px auto 40px auto; width: $cols8; diff --git a/src/views/splash/splash.scss b/src/views/splash/splash.scss index 4fd6f225a..cf6ab16e3 100644 --- a/src/views/splash/splash.scss +++ b/src/views/splash/splash.scss @@ -96,7 +96,7 @@ } //4 columns -@media only screen and (max-width: $mobile - 1) { +@media #{$small} { .splash { .splash-header { flex-wrap: wrap; @@ -119,7 +119,7 @@ } //6 columns -@media only screen and (min-width: $mobile) and (max-width: $tablet - 1) { +@media #{$medium} { .splash { .splash-header { flex-wrap: wrap; @@ -143,7 +143,7 @@ } //6 columns -@media only screen and (min-width: $tablet) and (max-width: $desktop - 1) { +@media #{$intermediate} { .splash { .splash-header { margin: 0 auto; diff --git a/src/views/teachers/landing/landing.scss b/src/views/teachers/landing/landing.scss index 8863ab74b..324f36c3a 100644 --- a/src/views/teachers/landing/landing.scss +++ b/src/views/teachers/landing/landing.scss @@ -247,7 +247,7 @@ $story-width: $cols3; } //4 columns -@media only screen and (max-width: $mobile - 1) { +@media #{$small} { $story-width: $cols4; .stories { @@ -311,7 +311,7 @@ $story-width: $cols3; } //6 columns -@media only screen and (min-width: $mobile) and (max-width: $tablet - 1) { +@media #{$medium} { $story-width: $cols3; .stories { @@ -366,7 +366,7 @@ $story-width: $cols3; //8 columns -@media only screen and (min-width: $tablet) and (max-width: $desktop - 1) { +@media #{$intermediate} { $story-width: $cols4; .stories { @@ -500,7 +500,7 @@ $story-width: $cols3; } // 12 columns -@media only screen and (min-width: $desktop) { +@media #{$big} { $story-width: $cols3; .stories { diff --git a/src/views/tips/tips.scss b/src/views/tips/tips.scss index f9b4c62aa..26adec97c 100644 --- a/src/views/tips/tips.scss +++ b/src/views/tips/tips.scss @@ -83,7 +83,7 @@ img.tips-icon { height: 1.75rem; } //4 columns -@media only screen and (max-width: $mobile - 1) { +@media #{$small} { .title-banner { &.masthead { @@ -121,7 +121,7 @@ img.tips-icon { } //6 columns -@media only screen and (min-width: $mobile) and (max-width: $tablet - 1) { +@media #{$medium} { .title-banner { &.masthead { @@ -152,7 +152,7 @@ img.tips-icon { //8 columns -@media only screen and (min-width: $tablet) and (max-width: $desktop - 1) { +@media #{$intermediate} { .title-banner { &.masthead { padding-bottom: 2rem; @@ -189,7 +189,7 @@ img.tips-icon { } // 12 columns -@media only screen and (min-width: $desktop) { +@media #{$big} { .title-banner { &.masthead { padding-bottom: 1.25rem; diff --git a/src/views/wedo2-legacy/wedo2.scss b/src/views/wedo2-legacy/wedo2.scss index c661e2e32..56c80034d 100644 --- a/src/views/wedo2-legacy/wedo2.scss +++ b/src/views/wedo2-legacy/wedo2.scss @@ -156,7 +156,7 @@ // Responsive Behavior //4 columns -@media only screen and (max-width: $mobile - 1) { +@media #{$small} { .wedo { .inner { margin: 0 auto; @@ -186,7 +186,7 @@ } //6 columns -@media only screen and (min-width: $mobile) and (max-width: $tablet - 1) { +@media #{$medium} { .wedo { .project-list, .columns3 { @@ -210,7 +210,7 @@ } //8 columns -@media only screen and (min-width: $tablet) and (max-width: $desktop - 1) { +@media #{$intermediate} { #view { text-align: center; } From b7b7b079b9e89c36b7dfcc86df7c1e6e684d6e9e Mon Sep 17 00:00:00 2001 From: Paul Kaplan Date: Tue, 16 Oct 2018 09:10:29 -0400 Subject: [PATCH 083/137] Combine comment reported and deleted flags into visibility string To prepare for server including visibility --- src/l10n.json | 15 +++++- src/redux/preview.js | 4 +- src/views/preview/comment/comment.jsx | 50 ++++++++++++------- src/views/preview/comment/comment.scss | 19 +++---- .../preview/comment/top-level-comment.jsx | 14 ++---- src/views/preview/presentation.jsx | 3 +- 6 files changed, 59 insertions(+), 46 deletions(-) diff --git a/src/l10n.json b/src/l10n.json index f4195433e..9bafe33c2 100644 --- a/src/l10n.json +++ b/src/l10n.json @@ -227,5 +227,18 @@ "comments.post": "Post", "comments.cancel": "Cancel", "comments.lengthWarning": "{remainingCharacters, plural, one {1 character left} other {{remainingCharacters} characters left}}", - "comments.seeMoreReplies": "{repliesCount, plural, one {See 1 more reply} other {See all {repliesCount} replies}}" + "comments.seeMoreReplies": "{repliesCount, plural, one {See 1 more reply} other {See all {repliesCount} replies}}", + "comments.status.delbyusr": "Deleted by project owner", + "comments.status.censbyfilter": "Censored by filter", + "comments.status.delbyparentcomment": "Parent comment deleted", + "comments.status.censbyadmin": "Censored by admin", + "comments.status.delbyadmin": "Deleted by admin", + "comments.status.parentcommentcensored": "Parent comment censored", + "comments.status.delbyclass": "Deleted by class", + "comments.status.hiddenduetourl": "Hidden due to URL", + "comments.status.markedbyfilter": "Marked by filter", + "comments.status.suspended": "Suspended", + "comments.status.acctdel": "Account deleted", + "comments.status.deleted": "Deleted", + "comments.status.reported": "Reported" } diff --git a/src/redux/preview.js b/src/redux/preview.js index 7ead89094..48497bdf7 100644 --- a/src/redux/preview.js +++ b/src/redux/preview.js @@ -233,7 +233,7 @@ module.exports.setCommentDeleted = (commentId, topLevelCommentId) => ({ commentId: commentId, topLevelCommentId: topLevelCommentId, comment: { - deleted: true + visibility: 'deleted' } }); @@ -242,7 +242,7 @@ module.exports.setCommentReported = (commentId, topLevelCommentId) => ({ commentId: commentId, topLevelCommentId: topLevelCommentId, comment: { - reported: true + visibility: 'reported' } }); diff --git a/src/views/preview/comment/comment.jsx b/src/views/preview/comment/comment.jsx index 067e8aabd..385594af4 100644 --- a/src/views/preview/comment/comment.jsx +++ b/src/views/preview/comment/comment.jsx @@ -13,6 +13,10 @@ const ReportCommentModal = require('../../../components/modal/comments/report-co require('./comment.scss'); +const CommentVisibility = { + VISIBLE: 'visible' // Has to match the server response for visibility +}; + class Comment extends React.Component { constructor (props) { super(props); @@ -81,15 +85,16 @@ class Comment extends React.Component { const { author, deletable, - deleted, canReply, content, datetimeCreated, id, projectId, - reported + visibility } = this.props; + const visible = visibility === CommentVisibility.VISIBLE; + return (
{author.username}
- {deletable ? ( - - + {visible ? ( + + {deletable ? ( + + + + ) : null} + + + + + ) : ( + + + {/* TODO restore action will go here */} - ) : null} - - - + )}
{/* TODO: at the moment, comment content does not properly display @@ -193,13 +206,12 @@ Comment.propTypes = { content: PropTypes.string, datetimeCreated: PropTypes.string, deletable: PropTypes.bool, - deleted: PropTypes.bool, id: PropTypes.number, onAddComment: PropTypes.func, onDelete: PropTypes.func, onReport: PropTypes.func, projectId: PropTypes.string, - reported: PropTypes.bool + visibility: PropTypes.string }; module.exports = Comment; diff --git a/src/views/preview/comment/comment.scss b/src/views/preview/comment/comment.scss index 6214a2212..b2e840ac0 100644 --- a/src/views/preview/comment/comment.scss +++ b/src/views/preview/comment/comment.scss @@ -82,6 +82,7 @@ opacity: .5; font-size: .75rem; font-weight: 500; + cursor: pointer; &:before { display: inline-block; @@ -93,6 +94,11 @@ } } + .comment-visibility { + opacity: .5; + font-size: .75rem; + } + .comment-delete { margin-right: 1rem; @@ -150,19 +156,6 @@ content: ""; } - &.comment-bubble-deleted { - $deleted-outline: $active-gray; - $deleted-background: rgb(215, 222, 234); - - border-color: $deleted-outline; - background-color: $deleted-background; - - &:before { - border-color: $deleted-outline transparent $deleted-outline $deleted-outline; - background: $deleted-background; - } - } - &.comment-bubble-reported { $reported-outline: #ff6680; $reported-background: rgb(236, 206, 223); diff --git a/src/views/preview/comment/top-level-comment.jsx b/src/views/preview/comment/top-level-comment.jsx index a5d9af899..020d4fc2c 100644 --- a/src/views/preview/comment/top-level-comment.jsx +++ b/src/views/preview/comment/top-level-comment.jsx @@ -52,13 +52,12 @@ class TopLevelComment extends React.Component { content, datetimeCreated, deletable, - deleted, id, onDelete, onReport, replies, - reported, - projectId + projectId, + visibility } = this.props; return ( @@ -71,12 +70,11 @@ class TopLevelComment extends React.Component { content, datetimeCreated, deletable, - deleted, canReply, id, onDelete, onReport, - reported + visibility }} /> {replies.length > 0 && @@ -95,11 +93,10 @@ class TopLevelComment extends React.Component { content={reply.content} datetimeCreated={reply.datetime_created} deletable={deletable} - deleted={reply.deleted} id={reply.id} key={reply.id} projectId={projectId} - reported={reply.reported} + visibility={reply.visibility} onAddComment={this.handleAddComment} onDelete={this.handleDeleteReply} onReport={this.handleReportReply} @@ -135,7 +132,6 @@ TopLevelComment.propTypes = { content: PropTypes.string, datetimeCreated: PropTypes.string, deletable: PropTypes.bool, - deleted: PropTypes.bool, id: PropTypes.number, onAddComment: PropTypes.func, onDelete: PropTypes.func, @@ -143,7 +139,7 @@ TopLevelComment.propTypes = { parentId: PropTypes.number, projectId: PropTypes.string, replies: PropTypes.arrayOf(PropTypes.object), - reported: PropTypes.bool + visibility: PropTypes.string }; module.exports = TopLevelComment; diff --git a/src/views/preview/presentation.jsx b/src/views/preview/presentation.jsx index 9fb6820bb..026cab5f2 100644 --- a/src/views/preview/presentation.jsx +++ b/src/views/preview/presentation.jsx @@ -366,13 +366,12 @@ const PreviewPresentation = ({ content={comment.content} datetimeCreated={comment.datetime_created} deletable={userOwnsProject} - deleted={comment.deleted} id={comment.id} key={comment.id} parentId={comment.parent_id} projectId={projectId} replies={replies && replies[comment.id] ? replies[comment.id] : []} - reported={comment.reported} + visibility={comment.visibility} onAddComment={onAddComment} onDelete={onDeleteComment} onReport={onReportComment} From b0ac4018eec4844ab432cc639709184fe1936e5a Mon Sep 17 00:00:00 2001 From: Paul Kaplan Date: Tue, 16 Oct 2018 09:13:39 -0400 Subject: [PATCH 084/137] Use admin routes to include all comments when loading page --- src/redux/preview.js | 12 +++++++----- src/views/preview/preview.jsx | 13 +++++++++---- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/redux/preview.js b/src/redux/preview.js index 48497bdf7..c2c5e6cc1 100644 --- a/src/redux/preview.js +++ b/src/redux/preview.js @@ -337,10 +337,11 @@ module.exports.getFavedStatus = (id, username, token) => (dispatch => { }); }); -module.exports.getTopLevelComments = (id, offset) => (dispatch => { +module.exports.getTopLevelComments = (id, offset, isAdmin, token) => (dispatch => { dispatch(module.exports.setFetchStatus('comments', module.exports.Status.FETCHING)); api({ - uri: `/comments/project/${id}`, + uri: `${isAdmin ? '/admin' : ''}/comments/project/${id}`, + authentication: isAdmin ? token : null, params: {offset: offset || 0} }, (err, body) => { if (err) { @@ -355,16 +356,17 @@ module.exports.getTopLevelComments = (id, offset) => (dispatch => { } dispatch(module.exports.setFetchStatus('comments', module.exports.Status.FETCHED)); dispatch(module.exports.setComments(body)); - dispatch(module.exports.getReplies(id, body.map(comment => comment.id))); + dispatch(module.exports.getReplies(id, body.map(comment => comment.id), isAdmin, token)); }); }); -module.exports.getReplies = (projectId, commentIds) => (dispatch => { +module.exports.getReplies = (projectId, commentIds, isAdmin, token) => (dispatch => { dispatch(module.exports.setFetchStatus('replies', module.exports.Status.FETCHING)); const fetchedReplies = {}; async.eachLimit(commentIds, 10, (parentId, callback) => { api({ - uri: `/comments/project/${projectId}/${parentId}` + uri: `${isAdmin ? '/admin' : ''}/comments/project/${projectId}/${parentId}`, + authentication: isAdmin ? token : null }, (err, body) => { if (err) { return callback(`Error fetching comment replies: ${err}`); diff --git a/src/views/preview/preview.jsx b/src/views/preview/preview.jsx index 1b4704603..8c29ee900 100644 --- a/src/views/preview/preview.jsx +++ b/src/views/preview/preview.jsx @@ -81,7 +81,8 @@ class Preview extends React.Component { if (this.props.user) { const username = this.props.user.username; const token = this.props.user.token; - this.props.getTopLevelComments(this.state.projectId, this.props.comments.length); + this.props.getTopLevelComments(this.state.projectId, this.props.comments.length, + this.props.isAdmin, token); this.props.getProjectInfo(this.state.projectId, token); this.props.getRemixes(this.state.projectId, token); this.props.getProjectStudios(this.state.projectId, token); @@ -269,7 +270,8 @@ class Preview extends React.Component { } } handleLoadMore () { - this.props.getTopLevelComments(this.state.projectId, this.props.comments.length); + this.props.getTopLevelComments(this.state.projectId, this.props.comments.length, + this.props.isAdmin, this.props.user && this.props.user.token); } handleLoveToggle () { this.props.setLovedStatus( @@ -447,6 +449,7 @@ Preview.propTypes = { handleOpenRegistration: PropTypes.func, handleReportComment: PropTypes.func, handleToggleLoginOpen: PropTypes.func, + isAdmin: PropTypes.bool, isEditable: PropTypes.bool, isLoggedIn: PropTypes.bool, isShared: PropTypes.bool, @@ -532,6 +535,7 @@ const mapStateToProps = state => { Object.keys(state.session.session.user).length > 0; const isLoggedIn = state.session.status === sessionActions.Status.FETCHED && userPresent; + const isAdmin = isLoggedIn && state.session.session.permissions.admin; const authorPresent = projectInfoPresent && state.preview.projectInfo.author && Object.keys(state.preview.projectInfo.author).length > 0; const userOwnsProject = isLoggedIn && authorPresent && @@ -554,6 +558,7 @@ const mapStateToProps = state => { ((authorPresent && state.preview.projectInfo.author.username === state.session.session.user.username) || state.permissions.admin === true), isLoggedIn: isLoggedIn, + isAdmin: isAdmin, // if we don't have projectInfo, assume it's shared until we know otherwise isShared: !projectInfoPresent || state.preview.projectInfo.is_published, loved: state.preview.loved, @@ -623,8 +628,8 @@ const mapDispatchToProps = dispatch => ({ dispatch(previewActions.leaveStudio(studioId, id, token)); } }, - getTopLevelComments: (id, offset) => { - dispatch(previewActions.getTopLevelComments(id, offset)); + getTopLevelComments: (id, offset, isAdmin, token) => { + dispatch(previewActions.getTopLevelComments(id, offset, isAdmin, token)); }, getFavedStatus: (id, username, token) => { dispatch(previewActions.getFavedStatus(id, username, token)); From 592c0e57033a9dca614b0cb3390a6c70728dbec5 Mon Sep 17 00:00:00 2001 From: Paul Kaplan Date: Tue, 16 Oct 2018 10:00:38 -0400 Subject: [PATCH 085/137] Add restore functionality to comments for admins --- src/l10n.json | 1 + src/redux/preview.js | 25 +++++++++++++++ src/views/preview/comment/comment.jsx | 31 +++++++++++++------ src/views/preview/comment/comment.scss | 15 ++++++++- .../preview/comment/top-level-comment.jsx | 19 +++++++++--- src/views/preview/presentation.jsx | 3 ++ src/views/preview/preview.jsx | 9 ++++++ 7 files changed, 87 insertions(+), 16 deletions(-) diff --git a/src/l10n.json b/src/l10n.json index 9bafe33c2..b78becdcc 100644 --- a/src/l10n.json +++ b/src/l10n.json @@ -206,6 +206,7 @@ "comments.report": "Report", "comments.delete": "Delete", + "comments.restore": "Restore", "comments.reportModal.title": "Report Comment", "comments.reportModal.reported": "The comment has been reported, and the Scratch Team has been notified.", "comments.reportModal.prompt": "Are you sure you want to report this comment?", diff --git a/src/redux/preview.js b/src/redux/preview.js index c2c5e6cc1..ab0fdcf6d 100644 --- a/src/redux/preview.js +++ b/src/redux/preview.js @@ -246,6 +246,15 @@ module.exports.setCommentReported = (commentId, topLevelCommentId) => ({ } }); +module.exports.setCommentRestored = (commentId, topLevelCommentId) => ({ + type: 'UPDATE_COMMENT', + commentId: commentId, + topLevelCommentId: topLevelCommentId, + comment: { + visibility: 'visible' + } +}); + module.exports.addNewComment = (comment, topLevelCommentId) => ({ type: 'ADD_NEW_COMMENT', comment: comment, @@ -659,6 +668,22 @@ module.exports.reportComment = (projectId, commentId, topLevelCommentId, token) }); }); +module.exports.restoreComment = (projectId, commentId, topLevelCommentId, token) => (dispatch => { + api({ + uri: `/proxy/admin/project/${projectId}/comment/${commentId}/undelete`, + authentication: token, + withCredentials: true, + method: 'PUT', + useCsrf: true + }, (err, body, res) => { + if (err || res.statusCode !== 200) { + log.error(err || res.body); + return; + } + dispatch(module.exports.setCommentRestored(commentId, topLevelCommentId)); + }); +}); + module.exports.reportProject = (id, jsonData, token) => (dispatch => { dispatch(module.exports.setFetchStatus('report', module.exports.Status.FETCHING)); // scratchr2 will fail if no thumbnail base64 string provided. We don't yet have diff --git a/src/views/preview/comment/comment.jsx b/src/views/preview/comment/comment.jsx index 385594af4..d30fff365 100644 --- a/src/views/preview/comment/comment.jsx +++ b/src/views/preview/comment/comment.jsx @@ -13,10 +13,6 @@ const ReportCommentModal = require('../../../components/modal/comments/report-co require('./comment.scss'); -const CommentVisibility = { - VISIBLE: 'visible' // Has to match the server response for visibility -}; - class Comment extends React.Component { constructor (props) { super(props); @@ -28,7 +24,8 @@ class Comment extends React.Component { 'handleConfirmReport', 'handleCancelReport', 'handlePostReply', - 'handleToggleReplying' + 'handleToggleReplying', + 'handleRestore' ]); this.state = { deleting: false, @@ -64,6 +61,10 @@ class Comment extends React.Component { this.setState({reporting: true}); } + handleRestore () { + this.props.onRestore(this.props.id); + } + handleConfirmReport () { this.setState({ reporting: false, @@ -93,7 +94,7 @@ class Comment extends React.Component { visibility } = this.props; - const visible = visibility === CommentVisibility.VISIBLE; + const visible = visibility === 'visible'; return (
) : ( - - - {/* TODO restore action will go here */} - + + + + + {this.props.onRestore && ( + + + + )} + )}
@@ -210,6 +220,7 @@ Comment.propTypes = { onAddComment: PropTypes.func, onDelete: PropTypes.func, onReport: PropTypes.func, + onRestore: PropTypes.func, projectId: PropTypes.string, visibility: PropTypes.string }; diff --git a/src/views/preview/comment/comment.scss b/src/views/preview/comment/comment.scss index b2e840ac0..d17eae8d1 100644 --- a/src/views/preview/comment/comment.scss +++ b/src/views/preview/comment/comment.scss @@ -78,7 +78,8 @@ } .comment-delete, - .comment-report { + .comment-report, + .comment-restore { opacity: .5; font-size: .75rem; font-weight: 500; @@ -119,6 +120,18 @@ vertical-align: -.125rem; } } + + .comment-restore { + margin-left: 1rem; + + &:before { + margin-right: .25rem; + background-image: url("/svgs/project/restore-gray.svg"); + width: .75rem; + height: .75rem; + vertical-align: -.125rem; + } + } } .avatar { diff --git a/src/views/preview/comment/top-level-comment.jsx b/src/views/preview/comment/top-level-comment.jsx index 020d4fc2c..8fa7c6d22 100644 --- a/src/views/preview/comment/top-level-comment.jsx +++ b/src/views/preview/comment/top-level-comment.jsx @@ -16,7 +16,8 @@ class TopLevelComment extends React.Component { 'handleExpandThread', 'handleAddComment', 'handleDeleteReply', - 'handleReportReply' + 'handleReportReply', + 'handleRestoreReply' ]); this.state = { expanded: false @@ -29,16 +30,20 @@ class TopLevelComment extends React.Component { }); } - handleDeleteReply (commentId) { + handleDeleteReply (replyId) { // Only apply topLevelCommentId for deleting replies // The top level comment itself just gets passed onDelete directly - this.props.onDelete(commentId, this.props.id); + this.props.onDelete(replyId, this.props.id); } - handleReportReply (commentId) { + handleReportReply (replyId) { // Only apply topLevelCommentId for reporting replies // The top level comment itself just gets passed onReport directly - this.props.onReport(commentId, this.props.id); + this.props.onReport(replyId, this.props.id); + } + + handleRestoreReply (replyId) { + this.props.onRestore(replyId, this.props.id); } handleAddComment (comment) { @@ -55,6 +60,7 @@ class TopLevelComment extends React.Component { id, onDelete, onReport, + onRestore, replies, projectId, visibility @@ -74,6 +80,7 @@ class TopLevelComment extends React.Component { id, onDelete, onReport, + onRestore, visibility }} /> @@ -100,6 +107,7 @@ class TopLevelComment extends React.Component { onAddComment={this.handleAddComment} onDelete={this.handleDeleteReply} onReport={this.handleReportReply} + onRestore={this.handleRestoreReply} /> ))} @@ -136,6 +144,7 @@ TopLevelComment.propTypes = { onAddComment: PropTypes.func, onDelete: PropTypes.func, onReport: PropTypes.func, + onRestore: PropTypes.func, parentId: PropTypes.number, projectId: PropTypes.string, replies: PropTypes.arrayOf(PropTypes.object), diff --git a/src/views/preview/presentation.jsx b/src/views/preview/presentation.jsx index 026cab5f2..3988e270d 100644 --- a/src/views/preview/presentation.jsx +++ b/src/views/preview/presentation.jsx @@ -78,6 +78,7 @@ const PreviewPresentation = ({ onReportClose, onReportComment, onReportSubmit, + onRestoreComment, onAddToStudioClicked, onAddToStudioClosed, onToggleStudio, @@ -375,6 +376,7 @@ const PreviewPresentation = ({ onAddComment={onAddComment} onDelete={onDeleteComment} onReport={onReportComment} + onRestore={onRestoreComment} /> ))} {comments.length < projectInfo.stats.comments && @@ -431,6 +433,7 @@ PreviewPresentation.propTypes = { onReportClose: PropTypes.func.isRequired, onReportComment: PropTypes.func.isRequired, onReportSubmit: PropTypes.func.isRequired, + onRestoreComment: PropTypes.func, onSeeInside: PropTypes.func, onShare: PropTypes.func, onToggleComments: PropTypes.func, diff --git a/src/views/preview/preview.jsx b/src/views/preview/preview.jsx index 8c29ee900..511cb3d9d 100644 --- a/src/views/preview/preview.jsx +++ b/src/views/preview/preview.jsx @@ -44,6 +44,7 @@ class Preview extends React.Component { 'handleReportClose', 'handleReportComment', 'handleReportSubmit', + 'handleRestoreComment', 'handleAddToStudioClick', 'handleAddToStudioClose', 'handleSeeInside', @@ -188,6 +189,9 @@ class Preview extends React.Component { handleReportComment (id, topLevelCommentId) { this.props.handleReportComment(this.state.projectId, id, topLevelCommentId, this.props.user.token); } + handleRestoreComment (id, topLevelCommentId) { + this.props.handleRestoreComment(this.state.projectId, id, topLevelCommentId, this.props.user.token); + } handleReportClick () { this.setState({reportOpen: true}); } @@ -379,6 +383,7 @@ class Preview extends React.Component { onReportClose={this.handleReportClose} onReportComment={this.handleReportComment} onReportSubmit={this.handleReportSubmit} + onRestoreComment={this.props.isAdmin && this.handleRestoreComment} onSeeInside={this.handleSeeInside} onShare={this.handleShare} onToggleComments={this.handleToggleComments} @@ -448,6 +453,7 @@ Preview.propTypes = { handleLogOut: PropTypes.func, handleOpenRegistration: PropTypes.func, handleReportComment: PropTypes.func, + handleRestoreComment: PropTypes.func, handleToggleLoginOpen: PropTypes.func, isAdmin: PropTypes.bool, isEditable: PropTypes.bool, @@ -588,6 +594,9 @@ const mapDispatchToProps = dispatch => ({ handleReportComment: (projectId, commentId, topLevelCommentId, token) => { dispatch(previewActions.reportComment(projectId, commentId, topLevelCommentId, token)); }, + handleRestoreComment: (projectId, commentId, topLevelCommentId, token) => { + dispatch(previewActions.restoreComment(projectId, commentId, topLevelCommentId, token)); + }, handleOpenRegistration: event => { event.preventDefault(); dispatch(navigationActions.setRegistrationOpen(true)); From 7772e197c75e7b16335a0b7e7082fe4b49547575 Mon Sep 17 00:00:00 2001 From: Paul Kaplan Date: Tue, 16 Oct 2018 10:25:47 -0400 Subject: [PATCH 086/137] Make comment deleting and restoring correctly update replies --- src/redux/preview.js | 30 +++++++++++++++++++ src/views/preview/comment/comment.jsx | 2 +- .../preview/comment/top-level-comment.jsx | 4 ++- 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/redux/preview.js b/src/redux/preview.js index ab0fdcf6d..237897855 100644 --- a/src/redux/preview.js +++ b/src/redux/preview.js @@ -123,6 +123,14 @@ module.exports.previewReducer = (state, action) => { comments: [action.comment, ...state.comments], replies: Object.assign({}, state.replies, {[action.comment.id]: []}) }); + case 'UPDATE_ALL_REPLIES': + return Object.assign({}, state, { + replies: Object.assign({}, state.replies, { + [action.commentId]: state.replies[action.commentId].map(reply => + Object.assign({}, reply, action.comment) + ) + }) + }); case 'SET_REPLIES': return Object.assign({}, state, { replies: merge({}, state.replies, action.replies) @@ -237,6 +245,14 @@ module.exports.setCommentDeleted = (commentId, topLevelCommentId) => ({ } }); +module.exports.setRepliesDeleted = commentId => ({ + type: 'UPDATE_ALL_REPLIES', + commentId: commentId, + comment: { + visibility: 'deleted' + } +}); + module.exports.setCommentReported = (commentId, topLevelCommentId) => ({ type: 'UPDATE_COMMENT', commentId: commentId, @@ -255,6 +271,14 @@ module.exports.setCommentRestored = (commentId, topLevelCommentId) => ({ } }); +module.exports.setRepliesRestored = commentId => ({ + type: 'UPDATE_ALL_REPLIES', + commentId: commentId, + comment: { + visibility: 'visible' + } +}); + module.exports.addNewComment = (comment, topLevelCommentId) => ({ type: 'ADD_NEW_COMMENT', comment: comment, @@ -648,6 +672,9 @@ module.exports.deleteComment = (projectId, commentId, topLevelCommentId, token) return; } dispatch(module.exports.setCommentDeleted(commentId, topLevelCommentId)); + if (!topLevelCommentId) { + dispatch(module.exports.setRepliesDeleted(commentId)); + } }); }); @@ -681,6 +708,9 @@ module.exports.restoreComment = (projectId, commentId, topLevelCommentId, token) return; } dispatch(module.exports.setCommentRestored(commentId, topLevelCommentId)); + if (!topLevelCommentId) { + dispatch(module.exports.setRepliesRestored(commentId)); + } }); }); diff --git a/src/views/preview/comment/comment.jsx b/src/views/preview/comment/comment.jsx index d30fff365..85a3b2350 100644 --- a/src/views/preview/comment/comment.jsx +++ b/src/views/preview/comment/comment.jsx @@ -161,7 +161,7 @@ class Comment extends React.Component { - {canReply ? ( + {(canReply && visible) ? ( ))} From 1965a1223d7e97d43673a16ddfcf5544298a31f9 Mon Sep 17 00:00:00 2001 From: Paul Kaplan Date: Tue, 16 Oct 2018 10:37:32 -0400 Subject: [PATCH 087/137] Fix sass lint --- src/views/preview/comment/comment.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/preview/comment/comment.scss b/src/views/preview/comment/comment.scss index d17eae8d1..d3181f28a 100644 --- a/src/views/preview/comment/comment.scss +++ b/src/views/preview/comment/comment.scss @@ -81,9 +81,9 @@ .comment-report, .comment-restore { opacity: .5; + cursor: pointer; font-size: .75rem; font-weight: 500; - cursor: pointer; &:before { display: inline-block; From 2ac90315eaf0bfa6c71fed1bf5c3d193293e27ca Mon Sep 17 00:00:00 2001 From: Paul Kaplan Date: Tue, 16 Oct 2018 14:02:40 -0400 Subject: [PATCH 088/137] Use can prefix instead of undefined callback --- src/views/preview/comment/comment.jsx | 10 ++++++---- src/views/preview/comment/top-level-comment.jsx | 15 ++++++++++----- src/views/preview/presentation.jsx | 5 ++++- src/views/preview/preview.jsx | 3 ++- 4 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/views/preview/comment/comment.jsx b/src/views/preview/comment/comment.jsx index 85a3b2350..c40ff4c15 100644 --- a/src/views/preview/comment/comment.jsx +++ b/src/views/preview/comment/comment.jsx @@ -85,8 +85,9 @@ class Comment extends React.Component { render () { const { author, - deletable, + canDelete, canReply, + canRestore, content, datetimeCreated, id, @@ -113,7 +114,7 @@ class Comment extends React.Component {
{visible ? ( - {deletable ? ( + {canDelete ? ( - {this.props.onRestore && ( + {canRestore && ( @@ -77,8 +78,9 @@ class TopLevelComment extends React.Component { author, content, datetimeCreated, - deletable, + canDelete, canReply, + canRestore, id, onDelete, onReport, @@ -98,10 +100,11 @@ class TopLevelComment extends React.Component { {(this.state.expanded ? replies : replies.slice(0, 3)).map(reply => ( ))} @@ -138,7 +141,9 @@ TopLevelComment.propTypes = { image: PropTypes.string, username: PropTypes.string }), + canDelete: PropTypes.bool, canReply: PropTypes.bool, + canRestore: PropTypes.bool, content: PropTypes.string, datetimeCreated: PropTypes.string, deletable: PropTypes.bool, diff --git a/src/views/preview/presentation.jsx b/src/views/preview/presentation.jsx index 3988e270d..b6a3c0f94 100644 --- a/src/views/preview/presentation.jsx +++ b/src/views/preview/presentation.jsx @@ -46,6 +46,7 @@ const PreviewPresentation = ({ backpackOptions, canAddToStudio, canReport, + canRestoreComments, comments, editable, extensions, @@ -363,10 +364,11 @@ const PreviewPresentation = ({ {comments.map(comment => ( Date: Wed, 17 Oct 2018 11:20:26 -0400 Subject: [PATCH 089/137] Change conditional rendering to be consistent --- src/views/preview/comment/comment.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/views/preview/comment/comment.jsx b/src/views/preview/comment/comment.jsx index c40ff4c15..688f4b1cf 100644 --- a/src/views/preview/comment/comment.jsx +++ b/src/views/preview/comment/comment.jsx @@ -114,14 +114,14 @@ class Comment extends React.Component {
{visible ? ( - {canDelete ? ( + {canDelete && ( - ) : null} + )} Date: Wed, 17 Oct 2018 11:56:43 -0400 Subject: [PATCH 090/137] Add missing status message for comments --- src/l10n.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/l10n.json b/src/l10n.json index b78becdcc..917a0a6d5 100644 --- a/src/l10n.json +++ b/src/l10n.json @@ -238,6 +238,7 @@ "comments.status.delbyclass": "Deleted by class", "comments.status.hiddenduetourl": "Hidden due to URL", "comments.status.markedbyfilter": "Marked by filter", + "comments.status.censbyunconstructive": "Censored unconstructive", "comments.status.suspended": "Suspended", "comments.status.acctdel": "Account deleted", "comments.status.deleted": "Deleted", From 7b514344fa9daff38e9e479131822509998ee4cb Mon Sep 17 00:00:00 2001 From: Paul Kaplan Date: Wed, 17 Oct 2018 13:16:41 -0400 Subject: [PATCH 091/137] Allow admins access to comment delete --- src/views/preview/presentation.jsx | 4 +++- src/views/preview/preview.jsx | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/views/preview/presentation.jsx b/src/views/preview/presentation.jsx index b6a3c0f94..b6cb77917 100644 --- a/src/views/preview/presentation.jsx +++ b/src/views/preview/presentation.jsx @@ -45,6 +45,7 @@ const PreviewPresentation = ({ assetHost, backpackOptions, canAddToStudio, + canDeleteComments, canReport, canRestoreComments, comments, @@ -364,7 +365,7 @@ const PreviewPresentation = ({ {comments.map(comment => ( Date: Wed, 17 Oct 2018 13:19:46 -0400 Subject: [PATCH 092/137] Restrict reporting comments to logged in users --- src/views/preview/comment/comment.jsx | 16 ++++++++++------ src/views/preview/comment/top-level-comment.jsx | 4 ++++ src/views/preview/presentation.jsx | 1 + 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/views/preview/comment/comment.jsx b/src/views/preview/comment/comment.jsx index 688f4b1cf..bcd5a6e33 100644 --- a/src/views/preview/comment/comment.jsx +++ b/src/views/preview/comment/comment.jsx @@ -87,6 +87,7 @@ class Comment extends React.Component { author, canDelete, canReply, + canReport, canRestore, content, datetimeCreated, @@ -122,12 +123,14 @@ class Comment extends React.Component { )} - - - + {canReport && ( + + + + )} ) : ( @@ -215,6 +218,7 @@ Comment.propTypes = { }), canDelete: PropTypes.bool, canReply: PropTypes.bool, + canReport: PropTypes.bool, canRestore: PropTypes.bool, content: PropTypes.string, datetimeCreated: PropTypes.string, diff --git a/src/views/preview/comment/top-level-comment.jsx b/src/views/preview/comment/top-level-comment.jsx index 83d745632..ad09eabbe 100644 --- a/src/views/preview/comment/top-level-comment.jsx +++ b/src/views/preview/comment/top-level-comment.jsx @@ -55,6 +55,7 @@ class TopLevelComment extends React.Component { author, canDelete, canReply, + canReport, canRestore, content, datetimeCreated, @@ -80,6 +81,7 @@ class TopLevelComment extends React.Component { datetimeCreated, canDelete, canReply, + canReport, canRestore, id, onDelete, @@ -102,6 +104,7 @@ class TopLevelComment extends React.Component { author={reply.author} canDelete={canDelete} canReply={canReply} + canReport={canReport} canRestore={canRestore && parentVisible} content={reply.content} datetimeCreated={reply.datetime_created} @@ -143,6 +146,7 @@ TopLevelComment.propTypes = { }), canDelete: PropTypes.bool, canReply: PropTypes.bool, + canReport: PropTypes.bool, canRestore: PropTypes.bool, content: PropTypes.string, datetimeCreated: PropTypes.string, diff --git a/src/views/preview/presentation.jsx b/src/views/preview/presentation.jsx index b6cb77917..65a1db68b 100644 --- a/src/views/preview/presentation.jsx +++ b/src/views/preview/presentation.jsx @@ -367,6 +367,7 @@ const PreviewPresentation = ({ author={comment.author} canDelete={canDeleteComments} canReply={isLoggedIn && projectInfo.comments_allowed} + canReport={isLoggedIn} canRestore={canRestoreComments} content={comment.content} datetimeCreated={comment.datetime_created} From d982d2d723a21f475929159ba2d1ce2b97630317 Mon Sep 17 00:00:00 2001 From: Paul Kaplan Date: Thu, 18 Oct 2018 10:11:51 -0400 Subject: [PATCH 093/137] Show the username of the person being replied to in a comment on preview --- src/views/preview/comment/comment.jsx | 10 +++++++++- src/views/preview/comment/top-level-comment.jsx | 17 +++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/views/preview/comment/comment.jsx b/src/views/preview/comment/comment.jsx index bcd5a6e33..7d473a21b 100644 --- a/src/views/preview/comment/comment.jsx +++ b/src/views/preview/comment/comment.jsx @@ -93,6 +93,7 @@ class Comment extends React.Component { datetimeCreated, id, projectId, + replyUsername, visibility } = this.props; @@ -160,7 +161,13 @@ class Comment extends React.Component { * @user links in replies * links to scratch.mit.edu pages */} - {content} + + + {replyUsername && ( + @{replyUsername}  + )} + {content} + @@ -228,6 +235,7 @@ Comment.propTypes = { onReport: PropTypes.func, onRestore: PropTypes.func, projectId: PropTypes.string, + replyUsername: PropTypes.string, visibility: PropTypes.string }; diff --git a/src/views/preview/comment/top-level-comment.jsx b/src/views/preview/comment/top-level-comment.jsx index ad09eabbe..fceb24ddf 100644 --- a/src/views/preview/comment/top-level-comment.jsx +++ b/src/views/preview/comment/top-level-comment.jsx @@ -22,6 +22,9 @@ class TopLevelComment extends React.Component { this.state = { expanded: false }; + + // A cache of {commentId: username, ...} in order to show reply usernames + this.commentUsernameCache = {}; } handleExpandThread () { @@ -50,6 +53,19 @@ class TopLevelComment extends React.Component { this.props.onAddComment(comment, this.props.id); } + commentUsername (parentId) { + if (this.commentUsernameCache[parentId]) return this.commentUsernameCache[parentId]; + + // If the cache misses, rebuild it. Every reply has a parent id that is + // either a reply to this top level comment or to one of the replies. + this.commentUsernameCache[this.props.id] = this.props.author.username; + const replies = this.props.replies; + for (let i = 0; i < replies.length; i++) { + this.commentUsernameCache[replies[i].id] = replies[i].author.username; + } + return this.commentUsernameCache[parentId]; + } + render () { const { author, @@ -111,6 +127,7 @@ class TopLevelComment extends React.Component { id={reply.id} key={reply.id} projectId={projectId} + replyUsername={this.commentUsername(reply.parent_id)} visibility={reply.visibility} onAddComment={this.handleAddComment} onDelete={this.handleDeleteReply} From c257379846f2308c69c6546efa6319aecb4fc67f Mon Sep 17 00:00:00 2001 From: Paul Kaplan Date: Thu, 18 Oct 2018 10:14:42 -0400 Subject: [PATCH 094/137] Add * to comments from staff on preview This requires the API to include the `is_staff` flag, but it will not change functionality if it is not there, everyone would be considered not staff, which is the current behavior. --- src/views/preview/comment/comment.jsx | 5 ++++- src/views/preview/comment/top-level-comment.jsx | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/views/preview/comment/comment.jsx b/src/views/preview/comment/comment.jsx index bcd5a6e33..a4405d219 100644 --- a/src/views/preview/comment/comment.jsx +++ b/src/views/preview/comment/comment.jsx @@ -111,7 +111,9 @@ class Comment extends React.Component { {author.username} + > + {author.username}{author.is_staff ? '*' : ''} +
{visible ? ( @@ -214,6 +216,7 @@ Comment.propTypes = { author: PropTypes.shape({ id: PropTypes.number, image: PropTypes.string, + is_staff: PropTypes.bool, username: PropTypes.string }), canDelete: PropTypes.bool, diff --git a/src/views/preview/comment/top-level-comment.jsx b/src/views/preview/comment/top-level-comment.jsx index ad09eabbe..c21b1d37b 100644 --- a/src/views/preview/comment/top-level-comment.jsx +++ b/src/views/preview/comment/top-level-comment.jsx @@ -142,6 +142,7 @@ TopLevelComment.propTypes = { author: PropTypes.shape({ id: PropTypes.number, image: PropTypes.string, + is_staff: PropTypes.bool, username: PropTypes.string }), canDelete: PropTypes.bool, From 1d3c3ff4313ed5256d668b9b3ae7994d85017622 Mon Sep 17 00:00:00 2001 From: Paul Kaplan Date: Thu, 18 Oct 2018 10:18:00 -0400 Subject: [PATCH 095/137] Use EmojiText component to allow emoji display when API includes them --- src/views/preview/comment/comment.jsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/views/preview/comment/comment.jsx b/src/views/preview/comment/comment.jsx index 7d473a21b..a50c7e713 100644 --- a/src/views/preview/comment/comment.jsx +++ b/src/views/preview/comment/comment.jsx @@ -5,6 +5,7 @@ const classNames = require('classnames'); const FlexRow = require('../../../components/flex-row/flex-row.jsx'); const Avatar = require('../../../components/avatar/avatar.jsx'); +const EmojiText = require('../../../components/emoji-text/emoji-text.jsx'); const FormattedRelative = require('react-intl').FormattedRelative; const FormattedMessage = require('react-intl').FormattedMessage; const ComposeComment = require('./compose-comment.jsx'); @@ -166,7 +167,10 @@ class Comment extends React.Component { {replyUsername && ( @{replyUsername}  )} - {content} + From 066e5ff141318f52250830288deef3a7ec83aa60 Mon Sep 17 00:00:00 2001 From: Paul Kaplan Date: Thu, 18 Oct 2018 11:42:10 -0400 Subject: [PATCH 096/137] Use top level + replies count to control "Load More" comments button This fixes an issue where a project with one top-level comment and one reply would should the "Load more" button at the bottom, even though there were no more to load --- src/views/preview/presentation.jsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/views/preview/presentation.jsx b/src/views/preview/presentation.jsx index 65a1db68b..c00334f8b 100644 --- a/src/views/preview/presentation.jsx +++ b/src/views/preview/presentation.jsx @@ -90,6 +90,7 @@ const PreviewPresentation = ({ onUpdate }) => { const shareDate = ((projectInfo.history && projectInfo.history.shared)) ? projectInfo.history.shared : ''; + const loadedCommentCount = comments.length + Object.keys(replies).reduce((acc, id) => acc + replies[id].length, 0); return (
{!isShared && ( @@ -383,7 +384,7 @@ const PreviewPresentation = ({ onRestore={onRestoreComment} /> ))} - {comments.length < projectInfo.stats.comments && + {loadedCommentCount < projectInfo.stats.comments && , - + + {props.addToStudioOpen && ( + + )} } , - + + {props.reportOpen && ( + + )} } @@ -81,8 +84,7 @@ Subactions.propTypes = { onReportSubmit: PropTypes.func.isRequired, onToggleStudio: PropTypes.func, reportOpen: PropTypes.bool, - shareDate: PropTypes.string, - studios: PropTypes.arrayOf(PropTypes.object) + shareDate: PropTypes.string }; module.exports = Subactions; From a6409bbcce65d851761141efa3a14eb492acdc10 Mon Sep 17 00:00:00 2001 From: Benjamin Wheeler Date: Fri, 19 Oct 2018 16:02:59 -0400 Subject: [PATCH 100/137] Pass to and receive from GUI info about project creation lifecycle; handle url changes (#2197) * add canSaveNew prop to pass to GUI * pass to and receive from GUI info about project lifecycle * reset project data or fetch new project data depending on updates received from gui * removed canSaveNew * projectId always a string * moved handleUpdateProjectId calls that fetch or set project metadata into componentDidUpdate * changed page history object * removed comments * two small fixes to deal with edge cases * cleaning up getExtensions --- src/redux/preview.js | 6 ++ src/views/preview/preview.jsx | 173 ++++++++++++++++++++++------------ 2 files changed, 117 insertions(+), 62 deletions(-) diff --git a/src/redux/preview.js b/src/redux/preview.js index 237897855..2827cf58a 100644 --- a/src/redux/preview.js +++ b/src/redux/preview.js @@ -46,6 +46,8 @@ module.exports.previewReducer = (state, action) => { } switch (action.type) { + case 'RESET_TO_INTIAL_STATE': + return module.exports.getInitialState(); case 'SET_PROJECT_INFO': return Object.assign({}, state, { projectInfo: action.info @@ -164,6 +166,10 @@ module.exports.setError = error => ({ error: error }); +module.exports.resetProject = () => ({ + type: 'RESET_TO_INTIAL_STATE' +}); + module.exports.setProjectInfo = info => ({ type: 'SET_PROJECT_INFO', info: info diff --git a/src/views/preview/preview.jsx b/src/views/preview/preview.jsx index 2db3b6873..4afd260d7 100644 --- a/src/views/preview/preview.jsx +++ b/src/views/preview/preview.jsx @@ -33,6 +33,7 @@ class Preview extends React.Component { super(props); bindAll(this, [ 'addEventListeners', + 'fetchCommunityData', 'handleAddComment', 'handleDeleteComment', 'handleToggleStudio', @@ -49,6 +50,7 @@ class Preview extends React.Component { 'handleAddToStudioClose', 'handleSeeInside', 'handleShare', + 'handleUpdateProjectId', 'handleUpdateProjectTitle', 'handleUpdate', 'handleToggleComments', @@ -66,7 +68,7 @@ class Preview extends React.Component { extensions: [], favoriteCount: 0, loveCount: 0, - projectId: parts[1] === 'editor' ? 0 : parts[1], + projectId: parts[1] === 'editor' ? '0' : parts[1], addToStudioOpen: false, reportOpen: false }; @@ -75,38 +77,30 @@ class Preview extends React.Component { /* In the beginning, if user is on mobile and landscape, go to fullscreen */ this.setScreenFromOrientation(); } - componentDidUpdate (prevProps) { - if (this.props.sessionStatus !== prevProps.sessionStatus && - this.props.sessionStatus === sessionActions.Status.FETCHED && - this.state.projectId) { - if (this.props.user) { - const username = this.props.user.username; - const token = this.props.user.token; - this.props.getTopLevelComments(this.state.projectId, this.props.comments.length, - this.props.isAdmin, token); - this.props.getProjectInfo(this.state.projectId, token); - this.props.getRemixes(this.state.projectId, token); - this.props.getProjectStudios(this.state.projectId, token); - this.props.getCuratedStudios(username); - this.props.getFavedStatus(this.state.projectId, username, token); - this.props.getLovedStatus(this.state.projectId, username, token); - } else { - this.props.getTopLevelComments(this.state.projectId, this.props.comments.length); - this.props.getProjectInfo(this.state.projectId); - this.props.getRemixes(this.state.projectId); - this.props.getProjectStudios(this.state.projectId); - } + componentDidUpdate (prevProps, prevState) { + if (this.state.projectId > 0 && + ((this.props.sessionStatus !== prevProps.sessionStatus && + this.props.sessionStatus === sessionActions.Status.FETCHED) || + (this.state.projectId !== prevState.projectId))) { + this.fetchCommunityData(); + } + if (this.state.projectId === '0' && this.state.projectId !== prevState.projectId) { + this.props.resetProject(); } if (this.props.projectInfo.id !== prevProps.projectInfo.id) { this.getExtensions(this.state.projectId); - this.initCounts(this.props.projectInfo.stats.favorites, this.props.projectInfo.stats.loves); - if (this.props.projectInfo.remix.parent !== null) { - this.props.getParentInfo(this.props.projectInfo.remix.parent); - } - if (this.props.projectInfo.remix.root !== null && - this.props.projectInfo.remix.root !== this.props.projectInfo.remix.parent - ) { - this.props.getOriginalInfo(this.props.projectInfo.remix.root); + if (typeof this.props.projectInfo.id === 'undefined') { + this.initCounts(0, 0); + } else { + this.initCounts(this.props.projectInfo.stats.favorites, this.props.projectInfo.stats.loves); + if (this.props.projectInfo.remix.parent !== null) { + this.props.getParentInfo(this.props.projectInfo.remix.parent); + } + if (this.props.projectInfo.remix.root !== null && + this.props.projectInfo.remix.root !== this.props.projectInfo.remix.parent + ) { + this.props.getOriginalInfo(this.props.projectInfo.remix.root); + } } } if (this.props.playerMode !== prevProps.playerMode || this.props.fullScreen !== prevProps.fullScreen) { @@ -124,6 +118,25 @@ class Preview extends React.Component { window.removeEventListener('popstate', this.handlePopState); window.removeEventListener('orientationchange', this.setScreenFromOrientation); } + fetchCommunityData () { + if (this.props.userPresent) { + const username = this.props.user.username; + const token = this.props.user.token; + this.props.getTopLevelComments(this.state.projectId, this.props.comments.length, + this.props.isAdmin, token); + this.props.getProjectInfo(this.state.projectId, token); + this.props.getRemixes(this.state.projectId, token); + this.props.getProjectStudios(this.state.projectId, token); + this.props.getCuratedStudios(username); + this.props.getFavedStatus(this.state.projectId, username, token); + this.props.getLovedStatus(this.state.projectId, username, token); + } else { + this.props.getTopLevelComments(this.state.projectId, this.props.comments.length); + this.props.getProjectInfo(this.state.projectId); + this.props.getRemixes(this.state.projectId); + this.props.getProjectStudios(this.state.projectId); + } + } setScreenFromOrientation () { /* * If the user is on a mobile device, switching to @@ -141,36 +154,42 @@ class Preview extends React.Component { } } getExtensions (projectId) { - storage - .load(storage.AssetType.Project, projectId, storage.DataFormat.JSON) - .then(projectAsset => { // NOTE: this is turning up null, breaking the line below. - let input = projectAsset.data; - if (typeof input === 'object' && !(input instanceof ArrayBuffer) && - !ArrayBuffer.isView(input)) { // taken from scratch-vm - // If the input is an object and not any ArrayBuffer - // or an ArrayBuffer view (this includes all typed arrays and DataViews) - // turn the object into a JSON string, because we suspect - // this is a project.json as an object - // validate expects a string or buffer as input - // TODO not sure if we need to check that it also isn't a data view - input = JSON.stringify(input); - } - parser(projectAsset.data, false, (err, projectData) => { - if (err) { - log.error(`Unhandled project parsing error: ${err}`); - return; + if (projectId > 0) { + storage + .load(storage.AssetType.Project, projectId, storage.DataFormat.JSON) + .then(projectAsset => { // NOTE: this is turning up null, breaking the line below. + let input = projectAsset.data; + if (typeof input === 'object' && !(input instanceof ArrayBuffer) && + !ArrayBuffer.isView(input)) { // taken from scratch-vm + // If the input is an object and not any ArrayBuffer + // or an ArrayBuffer view (this includes all typed arrays and DataViews) + // turn the object into a JSON string, because we suspect + // this is a project.json as an object + // validate expects a string or buffer as input + // TODO not sure if we need to check that it also isn't a data view + input = JSON.stringify(input); } - const extensionSet = new Set(); - if (projectData[0].extensions) { - projectData[0].extensions.forEach(extension => { - extensionSet.add(EXTENSION_INFO[extension]); + parser(projectAsset.data, false, (err, projectData) => { + if (err) { + log.error(`Unhandled project parsing error: ${err}`); + return; + } + const extensionSet = new Set(); + if (projectData[0].extensions) { + projectData[0].extensions.forEach(extension => { + extensionSet.add(EXTENSION_INFO[extension]); + }); + } + this.setState({ + extensions: Array.from(extensionSet) }); - } - this.setState({ - extensions: Array.from(extensionSet) }); }); + } else { // projectId is default or invalid; empty the extensions array + this.setState({ + extensions: [] }); + } } handleToggleComments () { this.props.updateProject( @@ -313,6 +332,25 @@ class Preview extends React.Component { title: title }); } + handleUpdateProjectId (projectId, callback) { + this.setState({projectId: projectId}, () => { + const parts = window.location.pathname.toLowerCase() + .split('/') + .filter(Boolean); + let newUrl; + if (projectId === '0') { + newUrl = `/${parts[0]}/editor`; + } else { + newUrl = `/${parts[0]}/${projectId}/editor`; + } + history.pushState( + {projectId: projectId}, + {projectId: projectId}, + newUrl + ); + if (callback) callback(); + }); + } initCounts (favorites, loves) { this.setState({ favoriteCount: favorites, @@ -389,7 +427,6 @@ class Preview extends React.Component { : @@ -432,6 +471,7 @@ Preview.propTypes = { canSaveAsCopy: PropTypes.bool, canShare: PropTypes.bool, comments: PropTypes.arrayOf(PropTypes.object), + enableCommunity: PropTypes.bool, faved: PropTypes.bool, fullScreen: PropTypes.bool, getCuratedStudios: PropTypes.func.isRequired, @@ -465,6 +505,7 @@ Preview.propTypes = { remixes: PropTypes.arrayOf(PropTypes.object), replies: PropTypes.objectOf(PropTypes.array), reportProject: PropTypes.func, + resetProject: PropTypes.func, sessionStatus: PropTypes.string, setFavedStatus: PropTypes.func.isRequired, setFullScreen: PropTypes.func.isRequired, @@ -482,7 +523,8 @@ Preview.propTypes = { email: PropTypes.string, classroomId: PropTypes.string }), - userOwnsProject: PropTypes.bool + userOwnsProject: PropTypes.bool, + userPresent: PropTypes.bool }; Preview.defaultProps = { @@ -493,12 +535,14 @@ Preview.defaultProps = { }, projectHost: process.env.PROJECT_HOST, sessionStatus: sessionActions.Status.NOT_FETCHED, - user: {} + user: {}, + userPresent: false }; const mapStateToProps = state => { const projectInfoPresent = Object.keys(state.preview.projectInfo).length > 0; - const userPresent = state.session.session.user && + const userPresent = state.session.session.user !== null && + typeof state.session.session.user !== 'undefined' && Object.keys(state.session.session.user).length > 0; const isLoggedIn = state.session.status === sessionActions.Status.FETCHED && userPresent; @@ -510,13 +554,14 @@ const mapStateToProps = state => { return { canAddToStudio: isLoggedIn && userOwnsProject, - canCreateNew: false, + canCreateNew: true, canRemix: false, canReport: isLoggedIn && !userOwnsProject, - canSave: userOwnsProject, + canSave: isLoggedIn && (userOwnsProject || !state.preview.projectInfo.id), canSaveAsCopy: false, canShare: userOwnsProject && state.permissions.social, comments: state.preview.comments, + enableCommunity: state.preview.projectInfo && state.preview.projectInfo.id > 0, faved: state.preview.faved, fullScreen: state.scratchGui.mode.isFullScreen, // project is editable iff logged in user is the author of the project, or @@ -538,7 +583,8 @@ const mapStateToProps = state => { replies: state.preview.replies, sessionStatus: state.session.status, // check if used user: state.session.session.user, - userOwnsProject: userOwnsProject + userOwnsProject: userOwnsProject, + userPresent: userPresent }; }; @@ -613,6 +659,9 @@ const mapDispatchToProps = dispatch => ({ reportProject: (id, formData, token) => { dispatch(previewActions.reportProject(id, formData, token)); }, + resetProject: () => { + dispatch(previewActions.resetProject()); + }, setOriginalInfo: info => { dispatch(previewActions.setOriginalInfo(info)); }, From c289ce72d1d1264fa188f156211784db57f5c18d Mon Sep 17 00:00:00 2001 From: Paul Kaplan Date: Mon, 22 Oct 2018 12:06:58 -0400 Subject: [PATCH 101/137] Use parent_id and commentee_id the right way when posting comments. The parent_id is the top-level-parent, the commentee_id is the user to whom the comment is directed , the one that is mentioned in @ reply. --- src/views/preview/comment/comment.jsx | 5 ++++- src/views/preview/comment/compose-comment.jsx | 4 ++-- .../preview/comment/top-level-comment.jsx | 19 +++++++++++-------- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/views/preview/comment/comment.jsx b/src/views/preview/comment/comment.jsx index bcf70f031..c6785e99d 100644 --- a/src/views/preview/comment/comment.jsx +++ b/src/views/preview/comment/comment.jsx @@ -93,6 +93,7 @@ class Comment extends React.Component { content, datetimeCreated, id, + parentId, projectId, replyUsername, visibility @@ -192,7 +193,8 @@ class Comment extends React.Component { {this.state.replying ? ( { if (err || res.statusCode !== 200) { @@ -158,7 +158,7 @@ class ComposeComment extends React.Component { } ComposeComment.propTypes = { - comenteeId: PropTypes.number, + commenteeId: PropTypes.number, onAddComment: PropTypes.func, onCancel: PropTypes.func, parentId: PropTypes.number, diff --git a/src/views/preview/comment/top-level-comment.jsx b/src/views/preview/comment/top-level-comment.jsx index d75a69779..deef7d4c6 100644 --- a/src/views/preview/comment/top-level-comment.jsx +++ b/src/views/preview/comment/top-level-comment.jsx @@ -23,8 +23,8 @@ class TopLevelComment extends React.Component { expanded: false }; - // A cache of {commentId: username, ...} in order to show reply usernames - this.commentUsernameCache = {}; + // A cache of {userId: username, ...} in order to show reply usernames + this.authorUsernameCache = {}; } handleExpandThread () { @@ -53,17 +53,19 @@ class TopLevelComment extends React.Component { this.props.onAddComment(comment, this.props.id); } - commentUsername (parentId) { - if (this.commentUsernameCache[parentId]) return this.commentUsernameCache[parentId]; + authorUsername (authorId) { + if (this.authorUsernameCache[authorId]) return this.authorUsernameCache[authorId]; // If the cache misses, rebuild it. Every reply has a parent id that is // either a reply to this top level comment or to one of the replies. - this.commentUsernameCache[this.props.id] = this.props.author.username; + this.authorUsernameCache[this.props.author.id] = this.props.author.username; const replies = this.props.replies; for (let i = 0; i < replies.length; i++) { - this.commentUsernameCache[replies[i].id] = replies[i].author.username; + this.authorUsernameCache[replies[i].author.id] = replies[i].author.username; } - return this.commentUsernameCache[parentId]; + // Default to top level author if no author is found from authorId + // This can happen if there is no commentee_id stored with the comment + return this.authorUsernameCache[authorId] || this.props.author.username; } render () { @@ -126,8 +128,9 @@ class TopLevelComment extends React.Component { datetimeCreated={reply.datetime_created} id={reply.id} key={reply.id} + parentId={id} projectId={projectId} - replyUsername={this.commentUsername(reply.parent_id)} + replyUsername={this.authorUsername(reply.commentee_id)} visibility={reply.visibility} onAddComment={this.handleAddComment} onDelete={this.handleDeleteReply} From 3fcc13a8cdf013957e9f51a509b7bf84f3880edd Mon Sep 17 00:00:00 2001 From: chrisgarrity Date: Fri, 19 Oct 2018 09:34:16 -0400 Subject: [PATCH 102/137] Integrate www-gui localization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Initialize `locales` in redux using methods exported by GUI. * pass GUI method to set the `scratchlanguage` cookie NOTE: It does not reload the page, so switching back to the project page will not reflect language changes made in gui until the next page reload. Reloading the page will lose project state so we don’t want to do that. --- src/views/preview/preview.jsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/views/preview/preview.jsx b/src/views/preview/preview.jsx index 4afd260d7..e8d5dc6e2 100644 --- a/src/views/preview/preview.jsx +++ b/src/views/preview/preview.jsx @@ -12,6 +12,7 @@ const render = require('../../lib/render.jsx'); const storage = require('../../lib/storage.js').default; const log = require('../../lib/log'); const EXTENSION_INFO = require('../../lib/extensions.js').default; +const jar = require('../../lib/jar.js'); const PreviewPresentation = require('./presentation.jsx'); const projectShape = require('./projectshape.jsx').projectShape; @@ -332,6 +333,9 @@ class Preview extends React.Component { title: title }); } + handleSetLanguage (locale) { + jar.set('scratchlanguage', locale); + } handleUpdateProjectId (projectId, callback) { this.setState({projectId: projectId}, () => { const parts = window.location.pathname.toLowerCase() @@ -444,6 +448,7 @@ class Preview extends React.Component { renderLogin={this.renderLogin} onLogOut={this.props.handleLogOut} onOpenRegistration={this.props.handleOpenRegistration} + onSetLanguage={this.handleSetLanguage} onShare={this.handleShare} onToggleLoginOpen={this.props.handleToggleLoginOpen} onUpdateProjectId={this.handleUpdateProjectId} @@ -707,6 +712,9 @@ render( preview: previewActions.previewReducer, ...GUI.guiReducers }, - {scratchGui: initGuiState(GUI.guiInitialState)}, + { + locales: GUI.initLocale(GUI.localesInitialState, window._locale), + scratchGui: initGuiState(GUI.guiInitialState) + }, GUI.guiMiddleware ); From 733c3d1bff6616e114ebcc87b17a81e74a429f00 Mon Sep 17 00:00:00 2001 From: Vibhor Sehgal Date: Tue, 23 Oct 2018 11:29:44 +0800 Subject: [PATCH 103/137] Responsive about page. --- src/views/about/about.scss | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/views/about/about.scss b/src/views/about/about.scss index 1ae2e7ce6..83f13330c 100644 --- a/src/views/about/about.scss +++ b/src/views/about/about.scss @@ -2,14 +2,14 @@ .about { .masthead { - display: flex; - flex-wrap: no-wrap; + display: grid; + grid-template-columns: repeat(auto-fit, minmax(461px, 1fr));; justify-content: space-between; align-items: stretch; div { display: inline-block; - width: calc(50% - 10px); + width: calc(100% - 10px); ul { margin: 0; From f4887008a5a8d94c123c3971d141fbe490091363 Mon Sep 17 00:00:00 2001 From: Paul Kaplan Date: Tue, 23 Oct 2018 11:37:59 -0400 Subject: [PATCH 104/137] Extract strings for localizing the preview page --- .../modal/comments/delete-comment.jsx | 2 +- .../modal/comments/report-comment.jsx | 2 +- src/l10n.json | 3 +- src/views/preview/extension-chip.jsx | 2 +- src/views/preview/l10n.json | 11 ++++++- src/views/preview/presentation.jsx | 10 +++---- src/views/preview/remix-credit.jsx | 29 ++++++++++++------- src/views/preview/remix-list.jsx | 5 ++-- src/views/preview/studio-list.jsx | 5 ++-- src/views/preview/subactions.jsx | 7 +++-- 10 files changed, 49 insertions(+), 27 deletions(-) diff --git a/src/components/modal/comments/delete-comment.jsx b/src/components/modal/comments/delete-comment.jsx index 0d2264854..5ddccee4a 100644 --- a/src/components/modal/comments/delete-comment.jsx +++ b/src/components/modal/comments/delete-comment.jsx @@ -55,7 +55,7 @@ const DeleteModal = ({ type="button" onClick={onReport} > - + )} diff --git a/src/l10n.json b/src/l10n.json index 917a0a6d5..ba8197c04 100644 --- a/src/l10n.json +++ b/src/l10n.json @@ -84,6 +84,8 @@ "general.website": "Website", "general.whatsHappening": "What's Happening?", "general.wiki": "Scratch Wiki", + "general.copyLink": "Copy Link", + "general.report": "Report", "general.all": "All", "general.animations": "Animations", @@ -204,7 +206,6 @@ "report.sending": "Sending...", "report.textMissing": "Please tell us why you are reporting this project", - "comments.report": "Report", "comments.delete": "Delete", "comments.restore": "Restore", "comments.reportModal.title": "Report Comment", diff --git a/src/views/preview/extension-chip.jsx b/src/views/preview/extension-chip.jsx index 5213ab6f1..aecdc2724 100644 --- a/src/views/preview/extension-chip.jsx +++ b/src/views/preview/extension-chip.jsx @@ -19,7 +19,7 @@ const ExtensionChip = props => ( } {props.hasStatus && (
- Needs Connection +
)}
diff --git a/src/views/preview/l10n.json b/src/views/preview/l10n.json index 07782dbe3..2cfa687f4 100644 --- a/src/views/preview/l10n.json +++ b/src/views/preview/l10n.json @@ -1,15 +1,24 @@ { "addToStudio.title": "Add to Studio", "addToStudio.finishing": "Finishing up...", + "addToStudio.inviteUser": "Invite user to add to studio", "preview.titleMaxLength": "Title is too long", "preview.musicExtensionChip": "Music", "preview.penExtensionChip": "Pen", "preview.speechExtensionChip": "Google Speech", "preview.translateExtensionChip": "Google Translate", "preview.videoMotionChip": "Video Motion", + "preview.needsConnection": "Needs Connection", "preview.comments.header": "Comments", "preview.comments.turnOff": "Turn off commenting", "preview.comments.turnedOff": "Sorry, comment posting has been turned off for this project.", "preview.share.notShared": "This project is not shared — so only you can see it. Click share to let everyone see it!", - "preview.share.shareButton": "Share" + "preview.share.shareButton": "Share", + "preview.seeInsideButton": "See inside", + "preview.remixButton": "Remix", + "preview.remixes": "Remixes", + "preview.inviteToRemix": "Invite user to remix", + "preview.instructionsLabel": "Instructions", + "preview.notesAndCreditsLabel": "Notes and Credits", + "preview.credit": "Thanks to {userLink} for the original project {projectLink}." } diff --git a/src/views/preview/presentation.jsx b/src/views/preview/presentation.jsx index de421d13e..28fe9877c 100644 --- a/src/views/preview/presentation.jsx +++ b/src/views/preview/presentation.jsx @@ -140,14 +140,14 @@ const PreviewPresentation = ({ {/* TODO: Hide Remix button for now until implemented */} {(!userOwnsProject && false) && }
@@ -213,7 +213,7 @@ const PreviewPresentation = ({
- Instructions +
{editable ?
- Notes and Credits +
{editable ? - Load More + }
diff --git a/src/views/preview/remix-credit.jsx b/src/views/preview/remix-credit.jsx index a3557d454..b11359f6f 100644 --- a/src/views/preview/remix-credit.jsx +++ b/src/views/preview/remix-credit.jsx @@ -1,4 +1,5 @@ const React = require('react'); +const FormattedMessage = require('react-intl').FormattedMessage; const FlexRow = require('../../components/flex-row/flex-row.jsx'); const Avatar = require('../../components/avatar/avatar.jsx'); const projectShape = require('./projectshape.jsx').projectShape; @@ -13,16 +14,24 @@ const RemixCredit = props => { src={`https://cdn2.scratch.mit.edu/get_image/user/${projectInfo.author.id}_48x48.png`} />
- Thanks to - {projectInfo.author.username} - for the original project - {projectInfo.title} - . + + {projectInfo.author.username} + + ), + projectLink: ( + + {projectInfo.title} + + ) + }} + />
); diff --git a/src/views/preview/remix-list.jsx b/src/views/preview/remix-list.jsx index f9fb4d7c3..684f489f0 100644 --- a/src/views/preview/remix-list.jsx +++ b/src/views/preview/remix-list.jsx @@ -1,5 +1,6 @@ const React = require('react'); const PropTypes = require('prop-types'); +const FormattedMessage = require('react-intl').FormattedMessage; const FlexRow = require('../../components/flex-row/flex-row.jsx'); const ThumbnailColumn = require('../../components/thumbnailcolumn/thumbnailcolumn.jsx'); const projectShape = require('./projectshape.jsx').projectShape; @@ -10,11 +11,11 @@ const RemixList = props => { return (
- Remixes +
{remixes.length === 0 ? ( // TODO: style remix invitation - Invite user to remix + ) : ( { return (
- Studios +
{studios.length === 0 ? ( // TODO: style remix invitation - Invite user to add to studio + ) : ( ( key="add-to-studio-button" onClick={props.onAddToStudioClicked} > - Add to Studio + {props.addToStudioOpen && ( (
} {(props.canReport) && @@ -56,7 +57,7 @@ const Subactions = props => ( key="report-button" onClick={props.onReportClicked} > - Report + {props.reportOpen && ( Date: Mon, 22 Oct 2018 06:59:58 -0400 Subject: [PATCH 105/137] set canRemix and canSaveAsCopy --- src/views/preview/preview.jsx | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/views/preview/preview.jsx b/src/views/preview/preview.jsx index e8d5dc6e2..985963941 100644 --- a/src/views/preview/preview.jsx +++ b/src/views/preview/preview.jsx @@ -545,7 +545,8 @@ Preview.defaultProps = { }; const mapStateToProps = state => { - const projectInfoPresent = Object.keys(state.preview.projectInfo).length > 0; + const projectInfoPresent = state.preview.projectInfo && + Object.keys(state.preview.projectInfo).length > 0 && state.preview.projectInfo.id > 0; const userPresent = state.session.session.user !== null && typeof state.session.session.user !== 'undefined' && Object.keys(state.session.session.user).length > 0; @@ -558,15 +559,15 @@ const mapStateToProps = state => { state.session.session.user.id === state.preview.projectInfo.author.id; return { - canAddToStudio: isLoggedIn && userOwnsProject, + canAddToStudio: userOwnsProject, canCreateNew: true, - canRemix: false, + canRemix: isLoggedIn && projectInfoPresent && !userOwnsProject, canReport: isLoggedIn && !userOwnsProject, - canSave: isLoggedIn && (userOwnsProject || !state.preview.projectInfo.id), - canSaveAsCopy: false, + canSave: isLoggedIn && (userOwnsProject || !projectInfoPresent), // can save a new project + canSaveAsCopy: userOwnsProject && projectInfoPresent, canShare: userOwnsProject && state.permissions.social, comments: state.preview.comments, - enableCommunity: state.preview.projectInfo && state.preview.projectInfo.id > 0, + enableCommunity: projectInfoPresent, faved: state.preview.faved, fullScreen: state.scratchGui.mode.isFullScreen, // project is editable iff logged in user is the author of the project, or From e03f83466e92367cb00b2e3b201baa334ecf62fe Mon Sep 17 00:00:00 2001 From: Ben Wheeler Date: Mon, 22 Oct 2018 23:12:11 -0400 Subject: [PATCH 106/137] better logic around canSave when project is new --- src/views/preview/preview.jsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/views/preview/preview.jsx b/src/views/preview/preview.jsx index 985963941..ec63ae6eb 100644 --- a/src/views/preview/preview.jsx +++ b/src/views/preview/preview.jsx @@ -547,6 +547,7 @@ Preview.defaultProps = { const mapStateToProps = state => { const projectInfoPresent = state.preview.projectInfo && Object.keys(state.preview.projectInfo).length > 0 && state.preview.projectInfo.id > 0; + const projectIsDefault = (state.projectId === '0'); const userPresent = state.session.session.user !== null && typeof state.session.session.user !== 'undefined' && Object.keys(state.session.session.user).length > 0; @@ -563,7 +564,7 @@ const mapStateToProps = state => { canCreateNew: true, canRemix: isLoggedIn && projectInfoPresent && !userOwnsProject, canReport: isLoggedIn && !userOwnsProject, - canSave: isLoggedIn && (userOwnsProject || !projectInfoPresent), // can save a new project + canSave: isLoggedIn && (userOwnsProject || projectIsDefault), // can save a new project canSaveAsCopy: userOwnsProject && projectInfoPresent, canShare: userOwnsProject && state.permissions.social, comments: state.preview.comments, From d713249beeb3e92820cd5a26b7acd2d2d67209e2 Mon Sep 17 00:00:00 2001 From: Ben Wheeler Date: Tue, 23 Oct 2018 00:38:23 -0400 Subject: [PATCH 107/137] changed the meaning of canCreateNew to true creation --- src/views/preview/preview.jsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/views/preview/preview.jsx b/src/views/preview/preview.jsx index ec63ae6eb..4c5e5734f 100644 --- a/src/views/preview/preview.jsx +++ b/src/views/preview/preview.jsx @@ -547,7 +547,6 @@ Preview.defaultProps = { const mapStateToProps = state => { const projectInfoPresent = state.preview.projectInfo && Object.keys(state.preview.projectInfo).length > 0 && state.preview.projectInfo.id > 0; - const projectIsDefault = (state.projectId === '0'); const userPresent = state.session.session.user !== null && typeof state.session.session.user !== 'undefined' && Object.keys(state.session.session.user).length > 0; @@ -561,10 +560,10 @@ const mapStateToProps = state => { return { canAddToStudio: userOwnsProject, - canCreateNew: true, + canCreateNew: isLoggedIn, canRemix: isLoggedIn && projectInfoPresent && !userOwnsProject, canReport: isLoggedIn && !userOwnsProject, - canSave: isLoggedIn && (userOwnsProject || projectIsDefault), // can save a new project + canSave: isLoggedIn && userOwnsProject, canSaveAsCopy: userOwnsProject && projectInfoPresent, canShare: userOwnsProject && state.permissions.social, comments: state.preview.comments, From 3d964911a1d36dd5201cdbc230acc17fefa0b825 Mon Sep 17 00:00:00 2001 From: Ben Wheeler Date: Tue, 23 Oct 2018 12:30:41 -0400 Subject: [PATCH 108/137] renamed canSaveAsCopy to canCreateCopy --- src/views/preview/preview.jsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/views/preview/preview.jsx b/src/views/preview/preview.jsx index 4c5e5734f..ec3f1e8dc 100644 --- a/src/views/preview/preview.jsx +++ b/src/views/preview/preview.jsx @@ -435,10 +435,10 @@ class Preview extends React.Component { assetHost={this.props.assetHost} backpackOptions={this.props.backpackOptions} basePath="/" + canCreateCopy={this.props.canCreateCopy} canCreateNew={this.props.canCreateNew} canRemix={this.props.canRemix} canSave={this.props.canSave} - canSaveAsCopy={this.props.canSaveAsCopy} canShare={this.props.canShare} className="gui" enableCommunity={this.props.enableCommunity} @@ -469,11 +469,11 @@ Preview.propTypes = { visible: PropTypes.bool }), canAddToStudio: PropTypes.bool, + canCreateCopy: PropTypes.bool, canCreateNew: PropTypes.bool, canRemix: PropTypes.bool, canReport: PropTypes.bool, canSave: PropTypes.bool, - canSaveAsCopy: PropTypes.bool, canShare: PropTypes.bool, comments: PropTypes.arrayOf(PropTypes.object), enableCommunity: PropTypes.bool, @@ -560,11 +560,11 @@ const mapStateToProps = state => { return { canAddToStudio: userOwnsProject, + canCreateCopy: userOwnsProject && projectInfoPresent, canCreateNew: isLoggedIn, canRemix: isLoggedIn && projectInfoPresent && !userOwnsProject, canReport: isLoggedIn && !userOwnsProject, canSave: isLoggedIn && userOwnsProject, - canSaveAsCopy: userOwnsProject && projectInfoPresent, canShare: userOwnsProject && state.permissions.social, comments: state.preview.comments, enableCommunity: projectInfoPresent, From efb1a380da0c8b4d221ec599d45d110aed66a69b Mon Sep 17 00:00:00 2001 From: Paul Kaplan Date: Wed, 24 Oct 2018 08:55:36 -0400 Subject: [PATCH 109/137] Update the comment routes --- src/redux/preview.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/redux/preview.js b/src/redux/preview.js index 2827cf58a..5492b51d3 100644 --- a/src/redux/preview.js +++ b/src/redux/preview.js @@ -379,7 +379,7 @@ module.exports.getFavedStatus = (id, username, token) => (dispatch => { module.exports.getTopLevelComments = (id, offset, isAdmin, token) => (dispatch => { dispatch(module.exports.setFetchStatus('comments', module.exports.Status.FETCHING)); api({ - uri: `${isAdmin ? '/admin' : ''}/comments/project/${id}`, + uri: `${isAdmin ? '/admin' : ''}/projects/${id}/comments`, authentication: isAdmin ? token : null, params: {offset: offset || 0} }, (err, body) => { @@ -404,7 +404,7 @@ module.exports.getReplies = (projectId, commentIds, isAdmin, token) => (dispatch const fetchedReplies = {}; async.eachLimit(commentIds, 10, (parentId, callback) => { api({ - uri: `${isAdmin ? '/admin' : ''}/comments/project/${projectId}/${parentId}`, + uri: `${isAdmin ? '/admin' : ''}/projects/${projectId}/comments/${parentId}/replies`, authentication: isAdmin ? token : null }, (err, body) => { if (err) { From ae626d52442376f31566726d274ae0f5448a1cce Mon Sep 17 00:00:00 2001 From: Paul Kaplan Date: Wed, 24 Oct 2018 09:18:45 -0400 Subject: [PATCH 110/137] Do not rely on total comment number for load more functionality Show the load more comments button any time the last comment page was filled to the requested limit. As noted in the comment, this heuristic will be wrong at most 5% of the time but the failure mode (showing load more which, when clicked doesn't load any more, just goes away) is very mild, and for the overwhelming majority of project views that happen on projects with many, many comments, this is very unlikely to ever be noticed. It obviously isn't a perfect solution, but I cannot think of another that does not need the server to do another query to find out the total number of visible comments, or to find out if there are more comments after the requested offset+limit. --- src/redux/preview.js | 22 ++++++++++++++++++++-- src/views/preview/presentation.jsx | 5 +++-- src/views/preview/preview.jsx | 3 +++ 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/redux/preview.js b/src/redux/preview.js index 2827cf58a..31a3e3836 100644 --- a/src/redux/preview.js +++ b/src/redux/preview.js @@ -37,7 +37,8 @@ module.exports.getInitialState = () => ({ parent: {}, projectStudios: [], curatedStudios: [], - currentStudioIds: [] + currentStudioIds: [], + moreCommentsToLoad: false }); module.exports.previewReducer = (state, action) => { @@ -153,6 +154,10 @@ module.exports.previewReducer = (state, action) => { state = JSON.parse(JSON.stringify(state)); state.status.studioRequests[action.studioId] = action.status; return state; + case 'SET_MORE_COMMENTS_TO_LOAD': + return Object.assign({}, state, { + moreCommentsToLoad: action.moreCommentsToLoad + }); case 'ERROR': log.error(action.error); return state; @@ -291,6 +296,11 @@ module.exports.addNewComment = (comment, topLevelCommentId) => ({ topLevelCommentId: topLevelCommentId }); +module.exports.setMoreCommentsToLoad = moreCommentsToLoad => ({ + type: 'SET_MORE_COMMENTS_TO_LOAD', + moreCommentsToLoad: moreCommentsToLoad +}); + module.exports.getProjectInfo = (id, token) => (dispatch => { const opts = { uri: `/projects/${id}` @@ -377,11 +387,12 @@ module.exports.getFavedStatus = (id, username, token) => (dispatch => { }); module.exports.getTopLevelComments = (id, offset, isAdmin, token) => (dispatch => { + const COMMENT_LIMIT = 20; dispatch(module.exports.setFetchStatus('comments', module.exports.Status.FETCHING)); api({ uri: `${isAdmin ? '/admin' : ''}/comments/project/${id}`, authentication: isAdmin ? token : null, - params: {offset: offset || 0} + params: {offset: offset || 0, limit: COMMENT_LIMIT} }, (err, body) => { if (err) { dispatch(module.exports.setFetchStatus('comments', module.exports.Status.ERROR)); @@ -396,6 +407,13 @@ module.exports.getTopLevelComments = (id, offset, isAdmin, token) => (dispatch = dispatch(module.exports.setFetchStatus('comments', module.exports.Status.FETCHED)); dispatch(module.exports.setComments(body)); dispatch(module.exports.getReplies(id, body.map(comment => comment.id), isAdmin, token)); + + // If we loaded a full page of comments, assume there are more to load. + // This will be wrong (1 / COMMENT_LIMIT) of the time, but does not require + // any more server query complexity, so seems worth it. In the case of a project with + // number of comments divisible by the COMMENT_LIMIT, the load more button will be + // clickable, but upon clicking it will go away. + dispatch(module.exports.setMoreCommentsToLoad(body.length === COMMENT_LIMIT)); }); }); diff --git a/src/views/preview/presentation.jsx b/src/views/preview/presentation.jsx index de421d13e..ad13768fe 100644 --- a/src/views/preview/presentation.jsx +++ b/src/views/preview/presentation.jsx @@ -59,6 +59,7 @@ const PreviewPresentation = ({ isShared, loved, loveCount, + moreCommentsToLoad, originalInfo, parentInfo, projectHost, @@ -89,7 +90,6 @@ const PreviewPresentation = ({ onUpdate }) => { const shareDate = ((projectInfo.history && projectInfo.history.shared)) ? projectInfo.history.shared : ''; - const loadedCommentCount = comments.length + Object.keys(replies).reduce((acc, id) => acc + replies[id].length, 0); return (
{!isShared && ( @@ -381,7 +381,7 @@ const PreviewPresentation = ({ onRestore={onRestoreComment} /> ))} - {loadedCommentCount < projectInfo.stats.comments && + {moreCommentsToLoad &&
diff --git a/src/views/preview/l10n.json b/src/views/preview/l10n.json index 2cfa687f4..0fe42f293 100644 --- a/src/views/preview/l10n.json +++ b/src/views/preview/l10n.json @@ -2,23 +2,23 @@ "addToStudio.title": "Add to Studio", "addToStudio.finishing": "Finishing up...", "addToStudio.inviteUser": "Invite user to add to studio", - "preview.titleMaxLength": "Title is too long", - "preview.musicExtensionChip": "Music", - "preview.penExtensionChip": "Pen", - "preview.speechExtensionChip": "Google Speech", - "preview.translateExtensionChip": "Google Translate", - "preview.videoMotionChip": "Video Motion", - "preview.needsConnection": "Needs Connection", - "preview.comments.header": "Comments", - "preview.comments.turnOff": "Turn off commenting", - "preview.comments.turnedOff": "Sorry, comment posting has been turned off for this project.", - "preview.share.notShared": "This project is not shared — so only you can see it. Click share to let everyone see it!", - "preview.share.shareButton": "Share", - "preview.seeInsideButton": "See inside", - "preview.remixButton": "Remix", - "preview.remixes": "Remixes", - "preview.inviteToRemix": "Invite user to remix", - "preview.instructionsLabel": "Instructions", - "preview.notesAndCreditsLabel": "Notes and Credits", - "preview.credit": "Thanks to {userLink} for the original project {projectLink}." + "project.titleMaxLength": "Title is too long", + "project.musicExtensionChip": "Music", + "project.penExtensionChip": "Pen", + "project.speechExtensionChip": "Google Speech", + "project.translateExtensionChip": "Google Translate", + "project.videoMotionChip": "Video Motion", + "project.needsConnection": "Needs Connection", + "project.comments.header": "Comments", + "project.comments.turnOff": "Turn off commenting", + "project.comments.turnedOff": "Sorry, comment posting has been turned off for this project.", + "project.share.notShared": "This project is not shared — so only you can see it. Click share to let everyone see it!", + "project.share.shareButton": "Share", + "project.seeInsideButton": "See inside", + "project.remixButton": "Remix", + "project.remixes": "Remixes", + "project.inviteToRemix": "Invite user to remix", + "project.instructionsLabel": "Instructions", + "project.notesAndCreditsLabel": "Notes and Credits", + "project.credit": "Thanks to {userLink} for the original project {projectLink}." } diff --git a/src/views/preview/presentation.jsx b/src/views/preview/presentation.jsx index 28fe9877c..f55e438e3 100644 --- a/src/views/preview/presentation.jsx +++ b/src/views/preview/presentation.jsx @@ -114,7 +114,7 @@ const PreviewPresentation = ({ name="title" validationErrors={{ maxLength: intl.formatMessage({ - id: 'preview.titleMaxLength' + id: 'project.titleMaxLength' }) }} validations={{ @@ -140,14 +140,14 @@ const PreviewPresentation = ({ {/* TODO: Hide Remix button for now until implemented */} {(!userOwnsProject && false) && }
@@ -213,7 +213,7 @@ const PreviewPresentation = ({
- +
{editable ?
- +
{editable ?
-

+

{userOwnsProject ? (
) : null} @@ -354,7 +354,7 @@ const PreviewPresentation = ({ ) ) : (
- +
)}
diff --git a/src/views/preview/remix-credit.jsx b/src/views/preview/remix-credit.jsx index b11359f6f..52a7744ef 100644 --- a/src/views/preview/remix-credit.jsx +++ b/src/views/preview/remix-credit.jsx @@ -15,7 +15,7 @@ const RemixCredit = props => { />
diff --git a/src/views/preview/remix-list.jsx b/src/views/preview/remix-list.jsx index 684f489f0..485557afa 100644 --- a/src/views/preview/remix-list.jsx +++ b/src/views/preview/remix-list.jsx @@ -11,11 +11,11 @@ const RemixList = props => { return (
- +
{remixes.length === 0 ? ( // TODO: style remix invitation - + ) : ( (
- +
From 520018ee5063eee46ac3c1ebf9d7d17e8263b9db Mon Sep 17 00:00:00 2001 From: Paul Kaplan Date: Wed, 24 Oct 2018 10:23:28 -0400 Subject: [PATCH 112/137] View single thread of comments by URL hash --- src/redux/preview.js | 29 +++++++++++++++++++ src/views/preview/comment/comment.jsx | 21 ++++++++++++-- src/views/preview/comment/comment.scss | 11 +++++++ .../preview/comment/top-level-comment.jsx | 11 ++++++- src/views/preview/presentation.jsx | 4 +++ src/views/preview/preview.jsx | 27 +++++++++++++++-- 6 files changed, 96 insertions(+), 7 deletions(-) diff --git a/src/redux/preview.js b/src/redux/preview.js index 5492b51d3..18d4f7712 100644 --- a/src/redux/preview.js +++ b/src/redux/preview.js @@ -399,6 +399,35 @@ module.exports.getTopLevelComments = (id, offset, isAdmin, token) => (dispatch = }); }); +module.exports.getCommentById = (projectId, commentId, isAdmin, token) => (dispatch => { + dispatch(module.exports.setFetchStatus('comments', module.exports.Status.FETCHING)); + api({ + uri: `${isAdmin ? '/admin' : ''}/projects/comments/${commentId}`, + authentication: isAdmin ? token : null + }, (err, body) => { + if (err) { + dispatch(module.exports.setFetchStatus('comments', module.exports.Status.ERROR)); + dispatch(module.exports.setError(err)); + return; + } + if (!body) { + dispatch(module.exports.setFetchStatus('comments', module.exports.Status.ERROR)); + dispatch(module.exports.setError('No comment info')); + return; + } + + if (body.parent_id) { + // If the comment is a reply, load the parent + return dispatch(module.exports.getCommentById(projectId, body.parent_id, isAdmin, token)); + } + + // If the comment is not a reply, show it as top level and load replies + dispatch(module.exports.setFetchStatus('comments', module.exports.Status.FETCHED)); + dispatch(module.exports.setComments([body])); + dispatch(module.exports.getReplies(projectId, [body.id], isAdmin, token)); + }); +}); + module.exports.getReplies = (projectId, commentIds, isAdmin, token) => (dispatch => { dispatch(module.exports.setFetchStatus('replies', module.exports.Status.FETCHING)); const fetchedReplies = {}; diff --git a/src/views/preview/comment/comment.jsx b/src/views/preview/comment/comment.jsx index c6785e99d..3d4c75fb8 100644 --- a/src/views/preview/comment/comment.jsx +++ b/src/views/preview/comment/comment.jsx @@ -26,7 +26,8 @@ class Comment extends React.Component { 'handleCancelReport', 'handlePostReply', 'handleToggleReplying', - 'handleRestore' + 'handleRestore', + 'setRef' ]); this.state = { deleting: false, @@ -36,6 +37,12 @@ class Comment extends React.Component { }; } + componentDidMount () { + if (this.props.highlighted) { + this.ref.scrollIntoView({behavior: 'smooth'}); + } + } + handlePostReply (comment) { this.setState({replying: false}); this.props.onAddComment(comment); @@ -82,6 +89,9 @@ class Comment extends React.Component { reportConfirmed: false }); } + setRef (ref) { + this.ref = ref; + } render () { const { @@ -92,6 +102,7 @@ class Comment extends React.Component { canRestore, content, datetimeCreated, + highlighted, id, parentId, projectId, @@ -103,8 +114,11 @@ class Comment extends React.Component { return (
@@ -238,6 +252,7 @@ Comment.propTypes = { canRestore: PropTypes.bool, content: PropTypes.string, datetimeCreated: PropTypes.string, + highlighted: PropTypes.bool, id: PropTypes.number, onAddComment: PropTypes.func, onDelete: PropTypes.func, diff --git a/src/views/preview/comment/comment.scss b/src/views/preview/comment/comment.scss index d3181f28a..8e3bdff40 100644 --- a/src/views/preview/comment/comment.scss +++ b/src/views/preview/comment/comment.scss @@ -69,6 +69,17 @@ justify-content: space-between; align-items: flex-start; + &.highlighted-comment:before { + position: absolute; + top: -0.5rem; + left: -0.5rem; + border-radius: 0.5rem; + background: $ui-blue-10percent; + width: calc(100% + 1rem); + height: 100%; + content: ""; + } + .comment-top-row { margin-bottom: 8px; width: 100%; diff --git a/src/views/preview/comment/top-level-comment.jsx b/src/views/preview/comment/top-level-comment.jsx index deef7d4c6..22f732b12 100644 --- a/src/views/preview/comment/top-level-comment.jsx +++ b/src/views/preview/comment/top-level-comment.jsx @@ -20,7 +20,7 @@ class TopLevelComment extends React.Component { 'handleRestoreReply' ]); this.state = { - expanded: false + expanded: this.props.defaultExpanded }; // A cache of {userId: username, ...} in order to show reply usernames @@ -77,6 +77,7 @@ class TopLevelComment extends React.Component { canRestore, content, datetimeCreated, + highlightedCommentId, id, onDelete, onReport, @@ -91,6 +92,7 @@ class TopLevelComment extends React.Component { return ( ({ getTopLevelComments: (id, offset, isAdmin, token) => { dispatch(previewActions.getTopLevelComments(id, offset, isAdmin, token)); }, + getCommentById: (projectId, commentId, isAdmin, token) => { + dispatch(previewActions.getCommentById(projectId, commentId, isAdmin, token)); + }, getFavedStatus: (id, username, token) => { dispatch(previewActions.getFavedStatus(id, username, token)); }, From faf3f49c503585b4e10c2acd8faaf368876cf158 Mon Sep 17 00:00:00 2001 From: Paul Kaplan Date: Wed, 24 Oct 2018 10:43:21 -0400 Subject: [PATCH 113/137] Fix proptypes --- src/views/preview/comment/top-level-comment.jsx | 2 +- src/views/preview/presentation.jsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/views/preview/comment/top-level-comment.jsx b/src/views/preview/comment/top-level-comment.jsx index 22f732b12..188150d7b 100644 --- a/src/views/preview/comment/top-level-comment.jsx +++ b/src/views/preview/comment/top-level-comment.jsx @@ -176,7 +176,7 @@ TopLevelComment.propTypes = { datetimeCreated: PropTypes.string, defaultExpanded: PropTypes.bool, deletable: PropTypes.bool, - highlightedCommentId: PropTypes.number, + highlightedCommentId: PropTypes.oneOfType([PropTypes.number, PropTypes.bool]), id: PropTypes.number, onAddComment: PropTypes.func, onDelete: PropTypes.func, diff --git a/src/views/preview/presentation.jsx b/src/views/preview/presentation.jsx index 65ea8bdef..4df88a523 100644 --- a/src/views/preview/presentation.jsx +++ b/src/views/preview/presentation.jsx @@ -455,7 +455,7 @@ PreviewPresentation.propTypes = { remixes: PropTypes.arrayOf(PropTypes.object), replies: PropTypes.objectOf(PropTypes.array), reportOpen: PropTypes.bool, - singleCommentId: PropTypes.number, + singleCommentId: PropTypes.oneOfType([PropTypes.number, PropTypes.bool]), userOwnsProject: PropTypes.bool }; From 9066686c2bb613a4ad071d73af0d7cd20a2e1495 Mon Sep 17 00:00:00 2001 From: Paul Kaplan Date: Wed, 24 Oct 2018 11:25:11 -0400 Subject: [PATCH 114/137] Decorate comment text to add @username links and scratch-domain links --- src/lib/decorate-text.jsx | 87 ++++++++++++++++++++------- src/views/preview/comment/comment.jsx | 29 ++++++--- src/views/preview/presentation.jsx | 12 +++- 3 files changed, 98 insertions(+), 30 deletions(-) diff --git a/src/lib/decorate-text.jsx b/src/lib/decorate-text.jsx index 4bd2583c9..8d92bad75 100644 --- a/src/lib/decorate-text.jsx +++ b/src/lib/decorate-text.jsx @@ -3,28 +3,73 @@ const reactStringReplace = require('react-string-replace'); /** * Helper method that replaces @mentions and #hashtags in plain text - * - * @param {string} text string to convert - * @return {string} string with links for @mentions and #hashtags + * + * @param {string} text string to convert + * @param {?object} opts options object of boolean flags, defaults to all true + * @property {boolean} opts.hashtag If #hashtags should be converted to search links + * @property {boolean} opts.usernames If @usernames should be converted to /users/username links + * @property {boolean} opts.scratchLinks If scratch-domain links should be converted to links + * @return {Array} Array with strings and react components for links */ -module.exports = text => { - let replacedText; - +module.exports = (text, opts) => { + opts = opts || { + usernames: true, + hashtags: true, + scratchLinks: true + }; + + let replacedText = [text]; + // Match @-mentions (username is alphanumeric, underscore and dash) - replacedText = reactStringReplace(text, /@([\w-]+)/g, (match, i) => ( - @{match} - )); - - // Match hashtags - replacedText = reactStringReplace(replacedText, /(#[\w-]+)/g, (match, i) => ( - {match} - )); - + if (opts.usernames) { + replacedText = reactStringReplace(replacedText, /@([\w-]+)/g, (match, i) => ( + @{match} + )); + } + + // Match hashtags + if (opts.hashtags) { + replacedText = reactStringReplace(replacedText, /(#[\w-]+)/g, (match, i) => ( + {match} + )); + } + + // Match scratch links + /* + Ported from the python... + "Oh boy a giant regex!" Said nobody ever. + (^|\s)(https?://(?:[\w-]+\.)*scratch\.mit\.edu(?:/(?:\S*[\w:/#[\]@\$&\'()*+=])?)?(?![^?!,:;\w\s]\S)) + (^|\s) + Only begin capturing after a space, or at the beginning of a word + https? + URLs beginning with http or https + ://(?:[\w-]+\.)*scratch\.mit\.edu + allow *.scratch.mit.edu urls + (?:/...)? + optionally followed by a slash + (?:\S*[\w:/#[\]@\$&\'()*+=])? + optionally that slash is followed by anything that's not a space, until + that string is followed by URL-valid characters that aren't punctuation + (?![^?!,:;\w\s]\S)) + Don't capture if this string is embedded in another string (e.g., the + beginning of a non-scratch URL), but allow punctuation + */ + if (opts.scratchLinks) { + // eslint-disable-next-line max-len + const linkRegexp = /((?:^|\s)https?:\/\/(?:[\w-]+\.)*(?:scratch\.mit\.edu|scratch-wiki\.info)(?:\/(?:\S*[\w:/#[\]@$&'()*+=])?)?(?![^?!,:;\w\s]\S))/g; + replacedText = reactStringReplace(replacedText, linkRegexp, (match, i) => ( + {match} + )); + } + return replacedText; }; diff --git a/src/views/preview/comment/comment.jsx b/src/views/preview/comment/comment.jsx index c6785e99d..1e7a70d1b 100644 --- a/src/views/preview/comment/comment.jsx +++ b/src/views/preview/comment/comment.jsx @@ -11,6 +11,7 @@ const FormattedMessage = require('react-intl').FormattedMessage; const ComposeComment = require('./compose-comment.jsx'); const DeleteCommentModal = require('../../../components/modal/comments/delete-comment.jsx'); const ReportCommentModal = require('../../../components/modal/comments/report-comment.jsx'); +const decorateText = require('../../../lib/decorate-text.jsx'); require('./comment.scss'); @@ -101,6 +102,16 @@ class Comment extends React.Component { const visible = visibility === 'visible'; + let commentText = content; + if (replyUsername) { + commentText = `@${replyUsername} ${commentText}`; + } + commentText = decorateText(commentText, { + scratchLinks: true, + usernames: true, + hashtags: false + }); + return (
- {replyUsername && ( - @{replyUsername}  - )} - + {commentText.map(fragment => { + if (typeof fragment === 'string') { + return ( + + ); + } + return fragment; + })} diff --git a/src/views/preview/presentation.jsx b/src/views/preview/presentation.jsx index de421d13e..4d67968ee 100644 --- a/src/views/preview/presentation.jsx +++ b/src/views/preview/presentation.jsx @@ -238,7 +238,11 @@ const PreviewPresentation = ({ value={projectInfo.instructions} /> :
- {decorateText(projectInfo.instructions)} + {decorateText(projectInfo.instructions, { + usernames: true, + hashtags: true, + scratchLinks: false + })}
}
@@ -270,7 +274,11 @@ const PreviewPresentation = ({ value={projectInfo.description} /> :
- {decorateText(projectInfo.description)} + {decorateText(projectInfo.description, { + usernames: true, + hashtags: true, + scratchLinks: false + })}
} From 2c5efbda502a64c64d21341c5b00a80f5058cd48 Mon Sep 17 00:00:00 2001 From: Paul Kaplan Date: Wed, 24 Oct 2018 11:45:17 -0400 Subject: [PATCH 115/137] Fix scss linting --- src/views/preview/comment/comment.scss | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/views/preview/comment/comment.scss b/src/views/preview/comment/comment.scss index 8e3bdff40..7e242a684 100644 --- a/src/views/preview/comment/comment.scss +++ b/src/views/preview/comment/comment.scss @@ -71,9 +71,9 @@ &.highlighted-comment:before { position: absolute; - top: -0.5rem; - left: -0.5rem; - border-radius: 0.5rem; + top: -.5rem; + left: -.5rem; + border-radius: .5rem; background: $ui-blue-10percent; width: calc(100% + 1rem); height: 100%; From dd07e46c6a21caec4588574b41f83de80cfd49da Mon Sep 17 00:00:00 2001 From: Ben Wheeler Date: Wed, 24 Oct 2018 15:38:18 -0400 Subject: [PATCH 116/137] moved getExtensions out of constructor, and out of projectInfo response --- src/views/preview/preview.jsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/views/preview/preview.jsx b/src/views/preview/preview.jsx index 4740ef3d5..b709d9e76 100644 --- a/src/views/preview/preview.jsx +++ b/src/views/preview/preview.jsx @@ -73,7 +73,6 @@ class Preview extends React.Component { addToStudioOpen: false, reportOpen: false }; - this.getExtensions(this.state.projectId); this.addEventListeners(); /* In the beginning, if user is on mobile and landscape, go to fullscreen */ this.setScreenFromOrientation(); @@ -84,12 +83,12 @@ class Preview extends React.Component { this.props.sessionStatus === sessionActions.Status.FETCHED) || (this.state.projectId !== prevState.projectId))) { this.fetchCommunityData(); + this.getExtensions(this.state.projectId); } if (this.state.projectId === '0' && this.state.projectId !== prevState.projectId) { this.props.resetProject(); } if (this.props.projectInfo.id !== prevProps.projectInfo.id) { - this.getExtensions(this.state.projectId); if (typeof this.props.projectInfo.id === 'undefined') { this.initCounts(0, 0); } else { From c4de63d93a8b83002f0a312ae95e961ceb4f7ce9 Mon Sep 17 00:00:00 2001 From: Ben Wheeler Date: Wed, 24 Oct 2018 18:13:57 -0400 Subject: [PATCH 117/137] pass isShared to gui --- src/views/preview/preview.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/views/preview/preview.jsx b/src/views/preview/preview.jsx index 4740ef3d5..df3c6aca9 100644 --- a/src/views/preview/preview.jsx +++ b/src/views/preview/preview.jsx @@ -443,6 +443,7 @@ class Preview extends React.Component { canShare={this.props.canShare} className="gui" enableCommunity={this.props.enableCommunity} + isShared={this.props.isShared} projectHost={this.props.projectHost} projectId={this.state.projectId} projectTitle={this.props.projectInfo.title} From eef2e1a8146a2976d1b86bad94274df5554fbabe Mon Sep 17 00:00:00 2001 From: julescubtree Date: Wed, 24 Oct 2018 15:39:17 -0700 Subject: [PATCH 118/137] Update README.md correct `ASSETS_HOST` to `ASSET_HOST` --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 25e52a316..72fd997e7 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ To stop the process that is making the site available to your web browser (creat | Variable | Default | Description | | --------------- | ---------------------------------- | ---------------------------------------------- | | `API_HOST` | `https://api.scratch.mit.edu` | Hostname for API requests | -| `ASSETS_HOST` | `https://assets.scratch.mit.edu` | Hostname for asset requests | +| `ASSET_HOST` | `https://assets.scratch.mit.edu` | Hostname for asset requests | | `BACKPACK_HOST` | `https://backpack.scratch.mit.edu` | Hostname for backpack requests | | `PROJECTS_HOST` | `https://projects.scratch.mit.edu` | Hostname for project requests | | `SENTRY_DSN` | `''` | DSN for Sentry | From 04adcb50638c164162684a688749d3e101eeb2a3 Mon Sep 17 00:00:00 2001 From: Vibhor Sehgal Date: Thu, 25 Oct 2018 09:53:28 +0800 Subject: [PATCH 119/137] Retaining Flex Layout --- src/views/about/about.scss | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/views/about/about.scss b/src/views/about/about.scss index 83f13330c..d7cb5b809 100644 --- a/src/views/about/about.scss +++ b/src/views/about/about.scss @@ -2,14 +2,15 @@ .about { .masthead { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(461px, 1fr));; + display: flex; + flex-wrap: no-wrap; justify-content: space-between; align-items: stretch; div { display: inline-block; - width: calc(100% - 10px); + width: calc(50% - 10px); + min-width: calc(50% - 10px); ul { margin: 0; @@ -30,8 +31,9 @@ iframe { border: 1px solid $ui-gray; - width: 460px; + width: 100%; height: 290px; + display: block; } } } From 209c8da9f7aeb54d9eb2b8583ff5a88fed7b47a2 Mon Sep 17 00:00:00 2001 From: Paul Kaplan Date: Thu, 25 Oct 2018 10:27:54 -0400 Subject: [PATCH 120/137] Keep each input in its own formsy wrapper to improve performance Having Formsy around the entire page was causing re-renders to happen when you typed into an input, when really just that component needed to re-render. --- src/views/preview/comment/comment.scss | 4 + src/views/preview/comment/compose-comment.jsx | 80 +++++------ src/views/preview/presentation.jsx | 124 +++++++++--------- 3 files changed, 111 insertions(+), 97 deletions(-) diff --git a/src/views/preview/comment/comment.scss b/src/views/preview/comment/comment.scss index 7e242a684..53644e822 100644 --- a/src/views/preview/comment/comment.scss +++ b/src/views/preview/comment/comment.scss @@ -21,6 +21,10 @@ } } + .full-width-form { + width: 100%; + } + .textarea-row { width: 100%; diff --git a/src/views/preview/comment/compose-comment.jsx b/src/views/preview/comment/compose-comment.jsx index 9f94563f2..bc97fb66d 100644 --- a/src/views/preview/comment/compose-comment.jsx +++ b/src/views/preview/comment/compose-comment.jsx @@ -5,6 +5,7 @@ const classNames = require('classnames'); const keyMirror = require('keymirror'); const FormattedMessage = require('react-intl').FormattedMessage; +const Formsy = require('formsy-react').default; const FlexRow = require('../../../components/flex-row/flex-row.jsx'); const Avatar = require('../../../components/avatar/avatar.jsx'); const InplaceInput = require('../../../components/forms/inplace-input.jsx'); @@ -111,46 +112,49 @@ class ComposeComment extends React.Component {
) : null} - = 0 ? 'compose-valid' : 'compose-invalid')} - handleUpdate={onUpdate} - name="compose-comment" - type="textarea" - value={this.state.message} - onInput={this.handleInput} - /> - - - - + = 0 ? 'compose-valid' : 'compose-invalid')} - > - - - + handleUpdate={onUpdate} + name="compose-comment" + type="textarea" + value={this.state.message} + onInput={this.handleInput} + /> + + + + = 0 ? + 'compose-valid' : 'compose-invalid')} + > + + + +
); diff --git a/src/views/preview/presentation.jsx b/src/views/preview/presentation.jsx index 610b8eb10..31c268de1 100644 --- a/src/views/preview/presentation.jsx +++ b/src/views/preview/presentation.jsx @@ -97,7 +97,7 @@ const PreviewPresentation = ({ )} { projectInfo && projectInfo.author && projectInfo.author.id && ( - +
@@ -109,20 +109,22 @@ const PreviewPresentation = ({
{editable ? - : + + + :
{editable ? - : + + + :
{decorateText(projectInfo.instructions)}
@@ -248,28 +252,30 @@ const PreviewPresentation = ({
{editable ? - : + + + :
{decorateText(projectInfo.description)}
@@ -401,7 +407,7 @@ const PreviewPresentation = ({
- + )}
); From f133f29353b703970eecf6d7932886b17f8b580b Mon Sep 17 00:00:00 2001 From: Paul Kaplan Date: Thu, 25 Oct 2018 11:38:27 -0400 Subject: [PATCH 121/137] Fix form styling for project edit fields --- src/views/preview/presentation.jsx | 10 ++++++++-- src/views/preview/preview.scss | 6 ++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/views/preview/presentation.jsx b/src/views/preview/presentation.jsx index 31c268de1..76a65911e 100644 --- a/src/views/preview/presentation.jsx +++ b/src/views/preview/presentation.jsx @@ -219,7 +219,10 @@ const PreviewPresentation = ({
{editable ? - +
{editable ? - + Date: Thu, 25 Oct 2018 15:53:11 -0400 Subject: [PATCH 122/137] Use #comments-id instead of #comment-id for fetching single comments --- src/views/preview/comment/comment.jsx | 2 +- src/views/preview/preview.jsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/views/preview/comment/comment.jsx b/src/views/preview/comment/comment.jsx index 3d4c75fb8..7bc695656 100644 --- a/src/views/preview/comment/comment.jsx +++ b/src/views/preview/comment/comment.jsx @@ -117,7 +117,7 @@ class Comment extends React.Component { className={classNames('flex-row', 'comment', { 'highlighted-comment': highlighted })} - id={`comment-${id}`} + id={`comments-${id}`} ref={this.setRef} > diff --git a/src/views/preview/preview.jsx b/src/views/preview/preview.jsx index 6d72e7871..7c185b7b5 100644 --- a/src/views/preview/preview.jsx +++ b/src/views/preview/preview.jsx @@ -66,8 +66,8 @@ class Preview extends React.Component { // parts[1]: either :id or 'editor' // parts[2]: undefined if no :id, otherwise either 'editor' or 'fullscreen' - // Get single-comment id from url hash, using the #comment-{id} scheme from scratch2 - const commentHashPrefix = '#comment-'; + // Get single-comment id from url hash, using the #comments-{id} scheme from scratch2 + const commentHashPrefix = '#comments-'; const singleCommentId = window.location.hash.indexOf(commentHashPrefix) !== -1 && parseInt(window.location.hash.replace(commentHashPrefix, ''), 10); From 792f1addb3141071f227441f6f3c5ec57b285868 Mon Sep 17 00:00:00 2001 From: Ben Wheeler Date: Fri, 26 Oct 2018 13:57:47 -0400 Subject: [PATCH 123/137] use latest scratch-gui, not develop, temporarily --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5817bc436..3f52c53fc 100644 --- a/package.json +++ b/package.json @@ -100,7 +100,7 @@ "redux-thunk": "2.0.1", "sass-lint": "1.5.1", "sass-loader": "6.0.6", - "scratch-gui": "develop", + "scratch-gui": "latest", "scratchr2_translations": "git://github.com/LLK/scratchr2_translations.git#master", "slick-carousel": "1.6.0", "source-map-support": "0.3.2", From 9dd13dac74c88a66f10d5e9427a5079daf010c25 Mon Sep 17 00:00:00 2001 From: chrisgarrity Date: Fri, 26 Oct 2018 15:11:46 -0400 Subject: [PATCH 124/137] remove obsolete code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Preview doesn’t need to set a z-index (the first version was trying to overlay gui on top of the project view). --- src/views/preview/preview.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/src/views/preview/preview.scss b/src/views/preview/preview.scss index 0ec87a8bf..e81b4c122 100644 --- a/src/views/preview/preview.scss +++ b/src/views/preview/preview.scss @@ -15,7 +15,6 @@ $stage-width: 480px; .gui { position: absolute; top: 0; - z-index: 11; margin: 0; width: 100%; height: 100%; From 93d847271d2aa94507574e392ddbfed16967e054 Mon Sep 17 00:00:00 2001 From: Vibhor Sehgal Date: Mon, 29 Oct 2018 08:08:13 +0530 Subject: [PATCH 125/137] Travis Fix --- src/views/about/about.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/about/about.scss b/src/views/about/about.scss index d7cb5b809..12e84bb6a 100644 --- a/src/views/about/about.scss +++ b/src/views/about/about.scss @@ -30,10 +30,10 @@ } iframe { + display: block; border: 1px solid $ui-gray; width: 100%; height: 290px; - display: block; } } } From e771e91164060787a8da834ffa6dca8640e2328d Mon Sep 17 00:00:00 2001 From: Karishma Chadha Date: Mon, 29 Oct 2018 01:05:36 -0400 Subject: [PATCH 126/137] Configure cloud host and pass it as a prop to the gui. --- .travis.yml | 5 +++++ src/views/preview/presentation.jsx | 3 +++ src/views/preview/preview.jsx | 4 ++++ webpack.config.js | 1 + 4 files changed, 13 insertions(+) diff --git a/.travis.yml b/.travis.yml index 479e987fd..c9fb8efb4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,6 +28,11 @@ env: - BACKPACK_HOST_VAR=BACKPACK_HOST_$TRAVIS_BRANCH - BACKPACK_HOST=${!BACKPACK_HOST_VAR} - BACKPACK_HOST=${BACKPACK_HOST:-$BACKPACK_HOST_STAGING} + - CLOUDDATA_HOST_master=clouddata.scratch.mit.edu + - CLOUDDATA_HOST_STAGING=varserver2.scratch.ly + - CLOUDDATA_HOST_VAR=CLOUDDATA_HOST_$TRAVIS_BRANCH + - CLOUDDATA_HOST=${!CLOUDDATA_HOST_VAR} + - CLOUDDATA_HOST=${CLOUDDATA_HOST:-$CLOUDDATA_HOST_STAGING} - ROOT_URL_master=https://scratch.mit.edu - ROOT_URL_STAGING=https://scratch.ly - ROOT_URL_VAR=ROOT_URL_$TRAVIS_BRANCH diff --git a/src/views/preview/presentation.jsx b/src/views/preview/presentation.jsx index 4e9789ce6..ec08d692f 100644 --- a/src/views/preview/presentation.jsx +++ b/src/views/preview/presentation.jsx @@ -48,6 +48,7 @@ const PreviewPresentation = ({ canDeleteComments, canReport, canRestoreComments, + cloudHost, comments, editable, extensions, @@ -163,6 +164,7 @@ const PreviewPresentation = ({ backpackOptions={backpackOptions} basePath="/" className="guiPlayer" + cloudHost={cloudHost} isFullScreen={isFullScreen} previewInfoVisible="false" projectHost={projectHost} @@ -438,6 +440,7 @@ PreviewPresentation.propTypes = { canDeleteComments: PropTypes.bool, canReport: PropTypes.bool, canRestoreComments: PropTypes.bool, + cloudHost: PropTypes.string, comments: PropTypes.arrayOf(PropTypes.object), editable: PropTypes.bool, extensions: PropTypes.arrayOf(PropTypes.object), diff --git a/src/views/preview/preview.jsx b/src/views/preview/preview.jsx index 7239f5436..1b4769a5c 100644 --- a/src/views/preview/preview.jsx +++ b/src/views/preview/preview.jsx @@ -405,6 +405,7 @@ class Preview extends React.Component { canDeleteComments={this.props.isAdmin || this.props.userOwnsProject} canReport={this.props.canReport} canRestoreComments={this.props.isAdmin} + cloudHost={this.props.cloudHost} comments={this.props.comments} editable={this.props.isEditable} extensions={this.state.extensions} @@ -458,6 +459,7 @@ class Preview extends React.Component { canSave={this.props.canSave} canShare={this.props.canShare} className="gui" + cloudHost={this.props.cloudHost} enableCommunity={this.props.enableCommunity} isShared={this.props.isShared} projectHost={this.props.projectHost} @@ -493,6 +495,7 @@ Preview.propTypes = { canReport: PropTypes.bool, canSave: PropTypes.bool, canShare: PropTypes.bool, + cloudHost: PropTypes.string, comments: PropTypes.arrayOf(PropTypes.object), enableCommunity: PropTypes.bool, faved: PropTypes.bool, @@ -558,6 +561,7 @@ Preview.defaultProps = { host: process.env.BACKPACK_HOST, visible: true }, + cloudHost: process.env.CLOUDDATA_HOST, projectHost: process.env.PROJECT_HOST, sessionStatus: sessionActions.Status.NOT_FETCHED, user: {}, diff --git a/webpack.config.js b/webpack.config.js index ca7377562..f10a056d0 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -167,6 +167,7 @@ module.exports = { 'process.env.API_HOST': '"' + (process.env.API_HOST || 'https://api.scratch.mit.edu') + '"', 'process.env.ASSET_HOST': '"' + (process.env.ASSET_HOST || 'https://assets.scratch.mit.edu') + '"', 'process.env.BACKPACK_HOST': '"' + (process.env.BACKPACK_HOST || 'https://backpack.scratch.mit.edu') + '"', + 'process.env.CLOUDDATA_HOST': '"' + (process.env.CLOUDDATA_HOST || 'clouddata.scratch.mit.edu') + '"', 'process.env.PROJECT_HOST': '"' + (process.env.PROJECT_HOST || 'https://projects.scratch.mit.edu') + '"', 'process.env.SCRATCH_ENV': '"' + (process.env.SCRATCH_ENV || 'development') + '"' }), From 78f059e8b4fa581e48d04781e4104ce96c0bc54a Mon Sep 17 00:00:00 2001 From: Ben Wheeler Date: Mon, 29 Oct 2018 17:30:53 -0400 Subject: [PATCH 127/137] removed erroneous share button icon --- src/views/preview/share-banner.scss | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/views/preview/share-banner.scss b/src/views/preview/share-banner.scss index f43d4e5fd..0524699f4 100644 --- a/src/views/preview/share-banner.scss +++ b/src/views/preview/share-banner.scss @@ -19,16 +19,17 @@ $navigation-height: 50px; font-size: .875rem; font-weight: normal; - &:before { - display: inline-block; - margin-right: .5rem; - background-image: url("/svgs/project/share-white.svg"); - background-repeat: no-repeat; - background-position: center center; - background-size: contain; - width: 1.25rem; - height: 1.25rem; - vertical-align: middle; - content: ""; - } + // don't show an image in share button, for now. + // &:before { + // display: inline-block; + // margin-right: .5rem; + // background-image: url("/svgs/project/share-white.svg"); + // background-repeat: no-repeat; + // background-position: center center; + // background-size: contain; + // width: 1.25rem; + // height: 1.25rem; + // vertical-align: middle; + // content: ""; + // } } From 5ffe31beb45d083776d70e2672651f311cb5fe72 Mon Sep 17 00:00:00 2001 From: julescubtree Date: Mon, 29 Oct 2018 14:45:00 -0700 Subject: [PATCH 128/137] Update README.md correct `PROJECTS_HOST` to `PROJECT_HOST` --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 72fd997e7..57ab0aa11 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ To stop the process that is making the site available to your web browser (creat | `API_HOST` | `https://api.scratch.mit.edu` | Hostname for API requests | | `ASSET_HOST` | `https://assets.scratch.mit.edu` | Hostname for asset requests | | `BACKPACK_HOST` | `https://backpack.scratch.mit.edu` | Hostname for backpack requests | -| `PROJECTS_HOST` | `https://projects.scratch.mit.edu` | Hostname for project requests | +| `PROJECT_HOST` | `https://projects.scratch.mit.edu` | Hostname for project requests | | `SENTRY_DSN` | `''` | DSN for Sentry | | `FALLBACK` | `''` | Pass-through location for old site | | `GA_TRACKER` | `''` | Where to log Google Analytics data | From ab9004f901a9aa7dd6172b95c0c0b3ee748323ac Mon Sep 17 00:00:00 2001 From: DeleteThisAcount Date: Wed, 31 Oct 2018 17:52:16 +0100 Subject: [PATCH 129/137] fix small typo --- src/views/microbit/l10n.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/microbit/l10n.json b/src/views/microbit/l10n.json index 388db9497..b067a62d8 100644 --- a/src/views/microbit/l10n.json +++ b/src/views/microbit/l10n.json @@ -26,7 +26,7 @@ "microbit.closeScratchCopiesTitle": "Close other copies of Scratch", "microbit.closeScratchCopiesText": "Only one copy of Scratch can connect with the micro:bit at a time. If you have Scratch open in other browser tabs, close it and try again.", "microbit.otherComputerConnectedTitle": "Make sure no other computer is connected to your micro:bit", - "microbit.otherComputerConnectedText": "Only one computer can be connected to an micro:bit at a time. If you have another computer connected to your micro:bit, disconnect the micro:bit or close Scratch on that computer and try again.", + "microbit.otherComputerConnectedText": "Only one computer can be connected to a micro:bit at a time. If you have another computer connected to your micro:bit, disconnect the micro:bit or close Scratch on that computer and try again.", "microbit.resetButtonTitle": "Make sure you aren’t hitting the “reset” button", "microbit.resetButtonText": "Sometimes while using the micro:bit you can accidentally press the “reset” button on the back in-between the USB and power ports. Make sure you keep your fingers (and toes) away from it while using Scratch!", "microbit.imgAltMicrobitIllustration": "Illustration of the micro:bit circuit board.", From 7aae4219ec1694b363fa4c8d5e3b4601c42016d9 Mon Sep 17 00:00:00 2001 From: Ben Wheeler Date: Thu, 1 Nov 2018 14:19:28 -0400 Subject: [PATCH 130/137] make share banner appear only if user has the power to share this project --- src/views/preview/presentation.jsx | 4 +++- src/views/preview/preview.jsx | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/views/preview/presentation.jsx b/src/views/preview/presentation.jsx index ec08d692f..3196e2c69 100644 --- a/src/views/preview/presentation.jsx +++ b/src/views/preview/presentation.jsx @@ -48,6 +48,7 @@ const PreviewPresentation = ({ canDeleteComments, canReport, canRestoreComments, + canShare, cloudHost, comments, editable, @@ -94,7 +95,7 @@ const PreviewPresentation = ({ const shareDate = ((projectInfo.history && projectInfo.history.shared)) ? projectInfo.history.shared : ''; return (
- {!isShared && ( + {canShare && !isShared && ( )} { projectInfo && projectInfo.author && projectInfo.author.id && ( @@ -440,6 +441,7 @@ PreviewPresentation.propTypes = { canDeleteComments: PropTypes.bool, canReport: PropTypes.bool, canRestoreComments: PropTypes.bool, + canShare: PropTypes.bool, cloudHost: PropTypes.string, comments: PropTypes.arrayOf(PropTypes.object), editable: PropTypes.bool, diff --git a/src/views/preview/preview.jsx b/src/views/preview/preview.jsx index 1b4769a5c..2b057fe05 100644 --- a/src/views/preview/preview.jsx +++ b/src/views/preview/preview.jsx @@ -405,6 +405,7 @@ class Preview extends React.Component { canDeleteComments={this.props.isAdmin || this.props.userOwnsProject} canReport={this.props.canReport} canRestoreComments={this.props.isAdmin} + canShare={this.props.canShare} cloudHost={this.props.cloudHost} comments={this.props.comments} editable={this.props.isEditable} From f935374571d5b26693df9fc901d5416b2687412f Mon Sep 17 00:00:00 2001 From: Benjamin Wheeler Date: Thu, 1 Nov 2018 16:37:57 -0400 Subject: [PATCH 131/137] Revert "Track scratch-gui@latest " --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3f52c53fc..5817bc436 100644 --- a/package.json +++ b/package.json @@ -100,7 +100,7 @@ "redux-thunk": "2.0.1", "sass-lint": "1.5.1", "sass-loader": "6.0.6", - "scratch-gui": "latest", + "scratch-gui": "develop", "scratchr2_translations": "git://github.com/LLK/scratchr2_translations.git#master", "slick-carousel": "1.6.0", "source-map-support": "0.3.2", From 037078d623f574ecf7a8b1ca9a204763e68688f3 Mon Sep 17 00:00:00 2001 From: Ray Schamp Date: Mon, 5 Nov 2018 21:00:12 +0000 Subject: [PATCH 132/137] Avoid displaying an error when /news returns a 500 This situation probably occurs most frequently when running dev servers while offline, but could also happen if the API is having issues. 500 responses from the API should not take down the homepage so drastically, and are also sometimes unavoidable while working offline. --- src/views/splash/splash.jsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/views/splash/splash.jsx b/src/views/splash/splash.jsx index 9eac6b052..06655fd96 100644 --- a/src/views/splash/splash.jsx +++ b/src/views/splash/splash.jsx @@ -71,7 +71,10 @@ class Splash extends React.Component { getNews () { api({ uri: '/news?limit=3' - }, (err, body) => { + }, (err, body, resp) => { + if (resp.statusCode !== 200) { + return log.error(`Unexpected status code ${resp.statusCode} received from news request`); + } if (!body) return log.error('No response body'); if (!err) return this.setState({news: body}); }); From 9bb8b7db2812a9ee681cfba404da1e7e10fc5f10 Mon Sep 17 00:00:00 2001 From: BryceLTaylor Date: Tue, 6 Nov 2018 15:35:21 -0500 Subject: [PATCH 133/137] Update chromedriver so tests pass more reliably --- test/integration/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/package.json b/test/integration/package.json index 909e95e91..811acf4df 100644 --- a/test/integration/package.json +++ b/test/integration/package.json @@ -1,6 +1,6 @@ { "dependencies": { "selenium-webdriver": "3.6.0", - "chromedriver": "2.37.0" + "chromedriver": "2.43.1" } } From b5d551b7056fc3ae86bf7bdd204f943225d91cbc Mon Sep 17 00:00:00 2001 From: chrisgarrity Date: Tue, 6 Nov 2018 16:15:54 -0500 Subject: [PATCH 134/137] Add new strings in preparation for Hour of Code --- src/views/splash/l10n.json | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/views/splash/l10n.json b/src/views/splash/l10n.json index 8466e9c78..d1a2aaf11 100644 --- a/src/views/splash/l10n.json +++ b/src/views/splash/l10n.json @@ -43,5 +43,15 @@ "betabanner.title": "The Next Generation of Scratch", "betabanner.subtitle": "Scratch 3.0 is coming in January! Try the Beta version now.", - "betabanner.callToAction": "Try it!" + "betabanner.callToAction": "Try it!", + + "hocbanner.title": "Get Creative with Coding!", + "hocbanner.moreActivities": "See more activities", + "hocbanner.gettingStarted": "Getting Started", + "hocbanner.animationTalk": "Create Animations that Talk", + "hocbanner.adventureGame": "Animate an Adventure Game", + "hocbanner.name": "Animate a Name", + "hocbanner.fly": "Make it Fly", + "hocbanner.pong": "Pong Game" + } From 4105e88cfdefe2e08a87826ea997eb21439a746b Mon Sep 17 00:00:00 2001 From: Paul Kaplan Date: Wed, 7 Nov 2018 10:58:07 -0500 Subject: [PATCH 135/137] Add IP Mute message to project comment errors --- src/l10n.json | 2 +- src/views/preview/comment/compose-comment.jsx | 22 ++++++++++++++----- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/l10n.json b/src/l10n.json index ba8197c04..04ad73738 100644 --- a/src/l10n.json +++ b/src/l10n.json @@ -222,7 +222,7 @@ "comments.isMuted": "Hmm, the filterbot is pretty sure your recent comments weren't ok for Scratch, so your account has been muted for the rest of the day. :/", "comments.isUnconstructive": "Hmm, the filterbot thinks your comment may be mean or disrespectful. Remember, most projects on Scratch are made by people who are just learning how to program.", "comments.isDisallowed": "Hmm, it looks like comments have been turned off for this page. :/", - "comments.isIPMuted": "Sorry, the Scratch Team had to prevent your network from sharing comments or projects because it was used to break our community guidelines too many times. You can still share comments and projects from another network.", + "comments.isIPMuted": "Sorry, the Scratch Team had to prevent your network from sharing comments or projects because it was used to break our community guidelines too many times. You can still share comments and projects from another network. If you'd like to appeal this block, you can contact appeals@scratch.mit.edu and reference Case Number {appealId}.", "comments.isTooLong": "That comment is too long! Please find a way to shorten your text.", "comments.error": "Oops! Something went wrong posting your comment", "comments.posting": "Posting...", diff --git a/src/views/preview/comment/compose-comment.jsx b/src/views/preview/comment/compose-comment.jsx index bc97fb66d..a1d2e85db 100644 --- a/src/views/preview/comment/compose-comment.jsx +++ b/src/views/preview/comment/compose-comment.jsx @@ -38,14 +38,16 @@ class ComposeComment extends React.Component { this.state = { message: '', status: ComposeStatus.EDITING, - error: null + error: null, + appealId: null }; } handleInput (event) { this.setState({ message: event.target.value, status: ComposeStatus.EDITING, - error: null + error: null, + appealId: null }); } handlePost () { @@ -70,7 +72,8 @@ class ComposeComment extends React.Component { // Note: does not reset the message state this.setState({ status: ComposeStatus.REJECTED, - error: body.rejected + error: body.rejected, + appealId: body.appealId }); return; } @@ -79,7 +82,8 @@ class ComposeComment extends React.Component { this.setState({ message: '', status: ComposeStatus.EDITING, - error: null + error: null, + appealId: null }); // Add the username, which isn't included right now from scratch-api @@ -92,7 +96,8 @@ class ComposeComment extends React.Component { this.setState({ message: '', status: ComposeStatus.EDITING, - error: null + error: null, + appealId: null }); if (this.props.onCancel) this.props.onCancel(); } @@ -108,7 +113,12 @@ class ComposeComment extends React.Component { {this.state.error ? (
- +
) : null} From fc7979944b95964d6127315e4010ca0c0e3a10da Mon Sep 17 00:00:00 2001 From: BryceLTaylor Date: Wed, 7 Nov 2018 11:40:10 -0500 Subject: [PATCH 136/137] Update Chrome version in Saucelabs tests --- test/integration/selenium-helpers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/selenium-helpers.js b/test/integration/selenium-helpers.js index 5b6e6e392..42157b029 100644 --- a/test/integration/selenium-helpers.js +++ b/test/integration/selenium-helpers.js @@ -61,7 +61,7 @@ class SeleniumHelper { let driverConfig = { browserName: 'chrome', platform: 'macOS 10.13', - version: '67.0' + version: '70.0' }; var driver = new webdriver.Builder() .withCapabilities({ From c6654106e43e24b00fe1904a87372e9e7d55e19b Mon Sep 17 00:00:00 2001 From: Ben Wheeler Date: Thu, 8 Nov 2018 15:36:57 -0500 Subject: [PATCH 137/137] point to scratch-gui latest npm release, not develop --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5817bc436..3f52c53fc 100644 --- a/package.json +++ b/package.json @@ -100,7 +100,7 @@ "redux-thunk": "2.0.1", "sass-lint": "1.5.1", "sass-loader": "6.0.6", - "scratch-gui": "develop", + "scratch-gui": "latest", "scratchr2_translations": "git://github.com/LLK/scratchr2_translations.git#master", "slick-carousel": "1.6.0", "source-map-support": "0.3.2",