GH-1361: Implement Notifications Page (#1487)
* start work on www page committing out of paranoia. including changing splash page endpoints * updates from feedback thanks @rschamp! This includes: 1. splitting out messages list into a separate component (for clarity) 2. some comment/formatting adjustments for the api calls 3. removal of an extraneous property in emoji-text * remove duplicate string declaration * use object.assign instead of defaults deep we don’t need deep defaults * fix react warnings
34
src/components/comment/comment.jsx
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
var classNames = require('classnames');
|
||||||
|
var FormattedRelative = require('react-intl').FormattedRelative;
|
||||||
|
var React = require('react');
|
||||||
|
|
||||||
|
var EmojiText = require('../emoji-text/emoji-text.jsx');
|
||||||
|
|
||||||
|
require('./comment.scss');
|
||||||
|
|
||||||
|
var CommentText = React.createClass({
|
||||||
|
type: 'CommentText',
|
||||||
|
propTypes: {
|
||||||
|
comment: React.PropTypes.string.isRequired,
|
||||||
|
datetimeCreated: React.PropTypes.string,
|
||||||
|
className: React.PropTypes.string
|
||||||
|
},
|
||||||
|
render: function () {
|
||||||
|
var classes = classNames(
|
||||||
|
'comment-text',
|
||||||
|
this.props.class
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<div className={classes}>
|
||||||
|
<EmojiText className="mod-comment" text={this.props.comment} />
|
||||||
|
{typeof this.props.datetimeCreated !== 'undefined' ? [
|
||||||
|
<p className="comment-text-timestamp">
|
||||||
|
<FormattedRelative value={new Date(this.props.datetimeCreated)} />
|
||||||
|
</p>
|
||||||
|
] : []}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = CommentText;
|
42
src/components/comment/comment.scss
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
@import "../../colors";
|
||||||
|
|
||||||
|
.comment-text {
|
||||||
|
position: relative;
|
||||||
|
border: 1px solid $ui-border;
|
||||||
|
border-radius: 0 5px 5px 5px;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-text:before {
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
top: -1px;
|
||||||
|
left: -13px;
|
||||||
|
border-top: 12px solid $ui-border;
|
||||||
|
border-left: 13px solid $ui-border;
|
||||||
|
border-radius: 0 0 0 13px;
|
||||||
|
width: 0;
|
||||||
|
content: "";
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-text:after {
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: -12px;
|
||||||
|
border-top: 10px solid $ui-white;
|
||||||
|
border-left: 12px solid $ui-white;
|
||||||
|
border-radius: 0 0 0 12px;
|
||||||
|
width: 0;
|
||||||
|
content: "";
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-text.mod-comment {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-text-timestamp {
|
||||||
|
margin: 1rem 0 0;
|
||||||
|
color: $ui-dark-gray;
|
||||||
|
font-size: .8rem;
|
||||||
|
}
|
33
src/components/emoji-text/emoji-text.jsx
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
var classNames = require('classnames');
|
||||||
|
var React = require('react');
|
||||||
|
|
||||||
|
require('./emoji-text.scss');
|
||||||
|
|
||||||
|
var EmojiText = React.createClass({
|
||||||
|
type: 'EmojiText',
|
||||||
|
propTyes: {
|
||||||
|
text: React.PropTypes.string.isRequired,
|
||||||
|
className: React.PropTypes.string
|
||||||
|
},
|
||||||
|
getDefaultProps: function () {
|
||||||
|
return {
|
||||||
|
as: 'p'
|
||||||
|
};
|
||||||
|
},
|
||||||
|
render: function () {
|
||||||
|
var classes = classNames(
|
||||||
|
'emoji-text',
|
||||||
|
this.props.className
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<this.props.as
|
||||||
|
className={classes}
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: this.props.text
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = EmojiText;
|
4
src/components/emoji-text/emoji-text.scss
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
.emoji {
|
||||||
|
max-width: 24px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ var ReactIntl = require('react-intl');
|
||||||
var FormattedMessage = ReactIntl.FormattedMessage;
|
var FormattedMessage = ReactIntl.FormattedMessage;
|
||||||
var injectIntl = ReactIntl.injectIntl;
|
var injectIntl = ReactIntl.injectIntl;
|
||||||
|
|
||||||
|
var messageCountActions = require('../../../redux/message-count.js');
|
||||||
var sessionActions = require('../../../redux/session.js');
|
var sessionActions = require('../../../redux/session.js');
|
||||||
|
|
||||||
var api = require('../../../lib/api');
|
var api = require('../../../lib/api');
|
||||||
|
@ -30,20 +31,21 @@ var Navigation = React.createClass({
|
||||||
loginOpen: false,
|
loginOpen: false,
|
||||||
loginError: null,
|
loginError: null,
|
||||||
registrationOpen: false,
|
registrationOpen: false,
|
||||||
unreadMessageCount: 0, // bubble number to display how many notifications someone has.
|
|
||||||
messageCountIntervalId: -1 // javascript method interval id for getting messsage count.
|
messageCountIntervalId: -1 // javascript method interval id for getting messsage count.
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
getDefaultProps: function () {
|
getDefaultProps: function () {
|
||||||
return {
|
return {
|
||||||
session: {},
|
session: {},
|
||||||
|
unreadMessageCount: 0, // bubble number to display how many notifications someone has.
|
||||||
searchTerm: ''
|
searchTerm: ''
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
componentDidMount: function () {
|
componentDidMount: function () {
|
||||||
if (this.props.session.session.user) {
|
if (this.props.session.session.user) {
|
||||||
this.getMessageCount();
|
var intervalId = setInterval(
|
||||||
var intervalId = setInterval(this.getMessageCount, 120000); // check for new messages every 2 mins.
|
this.props.dispatch(messageCountActions.getCount(this.props.session.session.user.username), 120000)
|
||||||
|
); // check for new messages every 2 mins.
|
||||||
this.setState({'messageCountIntervalId': intervalId});
|
this.setState({'messageCountIntervalId': intervalId});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -54,14 +56,15 @@ var Navigation = React.createClass({
|
||||||
'accountNavOpen': false
|
'accountNavOpen': false
|
||||||
});
|
});
|
||||||
if (this.props.session.session.user) {
|
if (this.props.session.session.user) {
|
||||||
this.getMessageCount();
|
var intervalId = setInterval(
|
||||||
var intervalId = setInterval(this.getMessageCount, 120000);
|
this.props.dispatch(messageCountActions.getCount(this.props.session.session.user.username), 120000)
|
||||||
|
); // check for new messages every 2 mins.
|
||||||
this.setState({'messageCountIntervalId': intervalId});
|
this.setState({'messageCountIntervalId': intervalId});
|
||||||
} else {
|
} else {
|
||||||
// clear message count check, and set to default id.
|
// clear message count check, and set to default id.
|
||||||
clearInterval(this.state.messageCountIntervalId);
|
clearInterval(this.state.messageCountIntervalId);
|
||||||
|
this.props.dispatch(messageCountActions.setCount(0));
|
||||||
this.setState({
|
this.setState({
|
||||||
'unreadMessageCount': 0,
|
|
||||||
'messageCountIntervalId': -1
|
'messageCountIntervalId': -1
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -71,8 +74,8 @@ var Navigation = React.createClass({
|
||||||
// clear message interval if it exists
|
// clear message interval if it exists
|
||||||
if (this.state.messageCountIntervalId != -1) {
|
if (this.state.messageCountIntervalId != -1) {
|
||||||
clearInterval(this.state.messageCountIntervalId);
|
clearInterval(this.state.messageCountIntervalId);
|
||||||
|
this.props.dispatch(messageCountActions.setCount(0));
|
||||||
this.setState({
|
this.setState({
|
||||||
'unreadMessageCount': 0,
|
|
||||||
'messageCountIntervalId': -1
|
'messageCountIntervalId': -1
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -81,18 +84,6 @@ var Navigation = React.createClass({
|
||||||
if (!this.props.session.session.user) return;
|
if (!this.props.session.session.user) return;
|
||||||
return '/users/' + this.props.session.session.user.username + '/';
|
return '/users/' + this.props.session.session.user.username + '/';
|
||||||
},
|
},
|
||||||
getMessageCount: function () {
|
|
||||||
api({
|
|
||||||
method: 'get',
|
|
||||||
uri: '/users/' + this.props.session.session.user.username + '/messages/count'
|
|
||||||
}, function (err, body) {
|
|
||||||
if (err) return this.setState({'unreadMessageCount': 0});
|
|
||||||
if (body) {
|
|
||||||
var count = parseInt(body.count, 10);
|
|
||||||
return this.setState({'unreadMessageCount': count});
|
|
||||||
}
|
|
||||||
}.bind(this));
|
|
||||||
},
|
|
||||||
handleJoinClick: function (e) {
|
handleJoinClick: function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.setState({'registrationOpen': true});
|
this.setState({'registrationOpen': true});
|
||||||
|
@ -179,7 +170,7 @@ var Navigation = React.createClass({
|
||||||
});
|
});
|
||||||
var messageClasses = classNames({
|
var messageClasses = classNames({
|
||||||
'message-count': true,
|
'message-count': true,
|
||||||
'show': this.state.unreadMessageCount > 0
|
'show': this.props.unreadMessageCount > 0
|
||||||
});
|
});
|
||||||
var dropdownClasses = classNames({
|
var dropdownClasses = classNames({
|
||||||
'user-info': true,
|
'user-info': true,
|
||||||
|
@ -230,7 +221,7 @@ var Navigation = React.createClass({
|
||||||
href="/messages/"
|
href="/messages/"
|
||||||
title={formatMessage({id: 'general.messages'})}>
|
title={formatMessage({id: 'general.messages'})}>
|
||||||
|
|
||||||
<span className={messageClasses}>{this.state.unreadMessageCount}</span>
|
<span className={messageClasses}>{this.props.unreadMessageCount}</span>
|
||||||
<FormattedMessage id="general.messages" />
|
<FormattedMessage id="general.messages" />
|
||||||
</a>
|
</a>
|
||||||
</li>,
|
</li>,
|
||||||
|
@ -343,6 +334,7 @@ var mapStateToProps = function (state) {
|
||||||
return {
|
return {
|
||||||
session: state.session,
|
session: state.session,
|
||||||
permissions: state.permissions,
|
permissions: state.permissions,
|
||||||
|
unreadMessageCount: state.messageCount.messageCount,
|
||||||
searchTerm: state.navigation
|
searchTerm: state.navigation
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
40
src/components/social-message/social-message.jsx
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
var classNames = require('classnames');
|
||||||
|
var FormattedRelative = require('react-intl').FormattedRelative;
|
||||||
|
var React = require('react');
|
||||||
|
|
||||||
|
var FlexRow = require('../flex-row/flex-row.jsx');
|
||||||
|
|
||||||
|
require('./social-message.scss');
|
||||||
|
|
||||||
|
var SocialMessage = React.createClass({
|
||||||
|
type: 'SocialMessage',
|
||||||
|
propTypes: {
|
||||||
|
as: React.PropTypes.string,
|
||||||
|
datetime: React.PropTypes.string.isRequired
|
||||||
|
},
|
||||||
|
getDefaultProps: function () {
|
||||||
|
return {
|
||||||
|
as: 'li'
|
||||||
|
};
|
||||||
|
},
|
||||||
|
render: function () {
|
||||||
|
var classes = classNames(
|
||||||
|
'social-message',
|
||||||
|
this.props.className
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<this.props.as className={classes}>
|
||||||
|
<FlexRow className="mod-social-message">
|
||||||
|
<div className="social-message-content">
|
||||||
|
{this.props.children}
|
||||||
|
</div>
|
||||||
|
<span className="social-message-date">
|
||||||
|
<FormattedRelative value={new Date(this.props.datetime)} />
|
||||||
|
</span>
|
||||||
|
</FlexRow>
|
||||||
|
</this.props.as>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = SocialMessage;
|
66
src/components/social-message/social-message.scss
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
@import "../../colors";
|
||||||
|
@import "../../frameless";
|
||||||
|
|
||||||
|
.social-message {
|
||||||
|
margin: 0;
|
||||||
|
border-bottom: 1px solid $ui-border;
|
||||||
|
padding: 1rem;
|
||||||
|
list-style-type: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-message.mod-unread {
|
||||||
|
background-color: $ui-gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-row.mod-social-message {
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-message-content {
|
||||||
|
max-width: 60%;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.social-messages-profile-link {
|
||||||
|
color: $type-gray;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: darken($type-gray, 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-row.mod-comment-message {
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-text {
|
||||||
|
margin-left: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: $mobile - 1) {
|
||||||
|
.social-message {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-message-date {
|
||||||
|
align-self: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-message-content {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (min-width: $mobile) and (max-width: $tablet - 1) {
|
||||||
|
.social-message {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-message-date {
|
||||||
|
align-self: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-message-content {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
}
|
|
@ -42,18 +42,19 @@ module.exports = function (opts, callback) {
|
||||||
|
|
||||||
var apiRequest = function (opts) {
|
var apiRequest = function (opts) {
|
||||||
if (opts.host !== '') {
|
if (opts.host !== '') {
|
||||||
// For IE < 10, we must use XDR for cross-domain requests. XDR does not support
|
if ('withCredentials' in new XMLHttpRequest()) {
|
||||||
// custom headers.
|
opts.useXDR = false;
|
||||||
defaults(opts, {useXDR: true});
|
} else {
|
||||||
if (opts.useXDR) {
|
// For IE < 10, we must use XDR for cross-domain requests. XDR does not support
|
||||||
|
// custom headers.
|
||||||
|
opts.useXDR = true;
|
||||||
delete opts.headers;
|
delete opts.headers;
|
||||||
}
|
if (opts.authentication) {
|
||||||
if (opts.authentication) {
|
var authenticationParams = ['x-token=' + opts.authentication];
|
||||||
var authenticationParams = ['x-token=' + opts.authentication];
|
var parts = opts.uri.split('?');
|
||||||
var parts = opts.uri.split('?');
|
var qs = (parts[1] || '').split('&').concat(authenticationParams).join('&');
|
||||||
var qs = (parts[1] || '').split('&').concat(authenticationParams).join('&');
|
opts.uri = parts[0] + '?' + qs;
|
||||||
opts.uri = parts[0] + '?' + qs;
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
xhr(opts, function (err, res, body) {
|
xhr(opts, function (err, res, body) {
|
||||||
|
|
|
@ -12,12 +12,7 @@ var reducer = require('../redux/reducer.js');
|
||||||
|
|
||||||
require('../main.scss');
|
require('../main.scss');
|
||||||
|
|
||||||
var store = redux.createStore(
|
var render = function (jsx, element, reducers) {
|
||||||
reducer,
|
|
||||||
redux.applyMiddleware(thunk)
|
|
||||||
);
|
|
||||||
|
|
||||||
var render = function (jsx, element) {
|
|
||||||
// Get locale and messages from global namespace (see "init.js")
|
// Get locale and messages from global namespace (see "init.js")
|
||||||
var locale = window._locale || 'en';
|
var locale = window._locale || 'en';
|
||||||
var messages = {};
|
var messages = {};
|
||||||
|
@ -33,6 +28,12 @@ var render = function (jsx, element) {
|
||||||
messages = window._messages[locale];
|
messages = window._messages[locale];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var allReducers = reducer(reducers);
|
||||||
|
var store = redux.createStore(
|
||||||
|
allReducers,
|
||||||
|
redux.applyMiddleware(thunk)
|
||||||
|
);
|
||||||
|
|
||||||
// Render view component
|
// Render view component
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<StoreProvider store={store}>
|
<StoreProvider store={store}>
|
||||||
|
|
70
src/redux/message-count.js
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
var keyMirror = require('keymirror');
|
||||||
|
var defaults = require('lodash.defaults');
|
||||||
|
|
||||||
|
var api = require('../lib/api');
|
||||||
|
|
||||||
|
var Types = keyMirror({
|
||||||
|
SET_MESSAGE_COUNT: null,
|
||||||
|
SET_MESSAGE_COUNT_ERROR: null,
|
||||||
|
SET_STATUS: null
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports.getInitialState = function (){
|
||||||
|
return {messageCount: 0};
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports.messageCountReducer = function (state, action) {
|
||||||
|
// Reducer for handling changes to session state
|
||||||
|
if (typeof state === 'undefined') {
|
||||||
|
state = module.exports.getInitialState();
|
||||||
|
}
|
||||||
|
switch (action.type) {
|
||||||
|
case Types.SET_MESSAGE_COUNT:
|
||||||
|
return defaults({messageCount: action.count}, state);
|
||||||
|
case Types.SET_STATUS:
|
||||||
|
return defaults({status: action.status}, state);
|
||||||
|
case Types.SET_SESSION_ERROR:
|
||||||
|
// TODO: do something with action.error
|
||||||
|
return state;
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports.setSessionError = function (error) {
|
||||||
|
return {
|
||||||
|
type: Types.SET_MESSAGE_COUNT_ERROR,
|
||||||
|
error: error
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports.setCount = function (count) {
|
||||||
|
return {
|
||||||
|
type: Types.SET_MESSAGE_COUNT,
|
||||||
|
count: count
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports.setStatus = function (status){
|
||||||
|
return {
|
||||||
|
type: Types.SET_STATUS,
|
||||||
|
status: status
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports.getCount = function (username) {
|
||||||
|
return function (dispatch) {
|
||||||
|
api({
|
||||||
|
method: 'get',
|
||||||
|
uri: '/users/' + username + '/messages/count'
|
||||||
|
}, function (err, body) {
|
||||||
|
if (err) {
|
||||||
|
dispatch(module.exports.setCount(0));
|
||||||
|
dispatch(module.exports.setSessionError(err));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var count = parseInt(body.count, 10);
|
||||||
|
dispatch(module.exports.setCount(count));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
245
src/redux/messages.js
Normal file
|
@ -0,0 +1,245 @@
|
||||||
|
var defaultsDeep = require('lodash.defaultsdeep');
|
||||||
|
var keyMirror = require('keymirror');
|
||||||
|
|
||||||
|
var api = require('../lib/api');
|
||||||
|
var log = require('../lib/log');
|
||||||
|
|
||||||
|
module.exports.Status = keyMirror({
|
||||||
|
FETCHED: null,
|
||||||
|
NOT_FETCHED: null,
|
||||||
|
FETCHING: null,
|
||||||
|
MESSAGES_ERROR: null,
|
||||||
|
ADMIN_ERROR: null,
|
||||||
|
INVITE_ERROR: null,
|
||||||
|
CLEAR_ERROR: null,
|
||||||
|
DELETE_ERROR: null
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports.getInitialState = function () {
|
||||||
|
return {
|
||||||
|
status: {
|
||||||
|
admin: module.exports.Status.NOT_FETCHED,
|
||||||
|
message: module.exports.Status.NOT_FETCHED,
|
||||||
|
clear: module.exports.Status.NOT_FETCHED,
|
||||||
|
delete: module.exports.Status.NOT_FETCHED
|
||||||
|
},
|
||||||
|
messages: {
|
||||||
|
admin: [],
|
||||||
|
social: [],
|
||||||
|
invite: {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports.messagesReducer = function (state, action) {
|
||||||
|
if (typeof state === 'undefined') {
|
||||||
|
state = module.exports.getInitialState();
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (action.type) {
|
||||||
|
case 'SET_MESSAGES':
|
||||||
|
return defaultsDeep({
|
||||||
|
messages: {social: action.messages}
|
||||||
|
}, state);
|
||||||
|
case 'SET_ADMIN_MESSAGES':
|
||||||
|
return defaultsDeep({
|
||||||
|
messages: {admin: action.messages}
|
||||||
|
}, state);
|
||||||
|
case 'SET_MESSAGES_OFFSET':
|
||||||
|
return defaultsDeep({
|
||||||
|
messages: {socialOffset: action.offset}
|
||||||
|
}, state);
|
||||||
|
case 'SET_SCRATCHER_INVITE':
|
||||||
|
return defaultsDeep({
|
||||||
|
messages: {invite: action.invite}
|
||||||
|
}, state);
|
||||||
|
case 'ADMIN_STATUS':
|
||||||
|
return defaultsDeep({status: {admin: action.status}}, state);
|
||||||
|
case 'MESSAGE_STATUS':
|
||||||
|
return defaultsDeep({status: {message: action.status}}, state);
|
||||||
|
case 'CLEAR_STATUS':
|
||||||
|
return defaultsDeep({status: {clear: action.status}}, state);
|
||||||
|
case 'DELETE_STATUS':
|
||||||
|
return defaultsDeep({status: {delete: action.status}}, state);
|
||||||
|
case 'ERROR':
|
||||||
|
log.error(action.error);
|
||||||
|
return state;
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports.setMessagesError = function (error) {
|
||||||
|
return {
|
||||||
|
type: 'ERROR',
|
||||||
|
error: error
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports.setMessages = function (messages) {
|
||||||
|
return {
|
||||||
|
type: 'SET_MESSAGES',
|
||||||
|
messages: messages
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports.setMessagesOffset = function (offset) {
|
||||||
|
return {
|
||||||
|
type: 'SET_MESSAGES_OFFSET',
|
||||||
|
offset: offset
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports.setAdminMessages = function (messages) {
|
||||||
|
return {
|
||||||
|
type: 'SET_ADMIN_MESSAGES',
|
||||||
|
messages: messages
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports.setScratcherInvite = function (invite) {
|
||||||
|
return {
|
||||||
|
type: 'SET_SCRATCHER_INVITE',
|
||||||
|
invite: invite
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports.setStatus = function (type, status){
|
||||||
|
return {
|
||||||
|
type: type,
|
||||||
|
status: status
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports.clearMessageCount = function () {
|
||||||
|
return function (dispatch) {
|
||||||
|
dispatch(module.exports.setStatus('CLEAR_STATUS', module.exports.Status.FETCHING));
|
||||||
|
api({
|
||||||
|
host: '',
|
||||||
|
uri: '/site-api/messages/messages-clear/',
|
||||||
|
method: 'POST'
|
||||||
|
}, function (err, body) {
|
||||||
|
if (err) {
|
||||||
|
dispatch(module.exports.setStatus('CLEAR_STATUS', module.exports.Status.CLEAR_ERROR));
|
||||||
|
dispatch(module.exports.setMessagesError(err));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!body.success) {
|
||||||
|
dispatch(module.exports.setStatus('CLEAR_STATUS', module.exports.Status.CLEAR_ERROR));
|
||||||
|
dispatch(module.exports.setMessagesError('messages not cleared'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dispatch(module.exports.setStatus('CLEAR_STATUS', module.exports.Status.FETCHED));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports.clearAdminMessage = function (messageType, messageId, adminMessages) {
|
||||||
|
return function (dispatch) {
|
||||||
|
dispatch(module.exports.setStatus('CLEAR_STATUS', module.exports.Status.FETCHING));
|
||||||
|
api({
|
||||||
|
host: '',
|
||||||
|
uri: '/site-api/messages/messages-delete/',
|
||||||
|
method: 'POST',
|
||||||
|
body: {
|
||||||
|
alertType: messageType,
|
||||||
|
alertId: messageId
|
||||||
|
}
|
||||||
|
}, function (err, body) {
|
||||||
|
if (err) {
|
||||||
|
dispatch(module.exports.setStatus('DELETE_STATUS', module.exports.Status.DELETE_ERROR));
|
||||||
|
dispatch(module.exports.setMessagesError(err));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!body.success) {
|
||||||
|
dispatch(module.exports.setStatus('DELETE_STATUS', module.exports.Status.DELETE_ERROR));
|
||||||
|
dispatch(module.exports.setMessagesError('messages not cleared'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (messageType === 'invite') {
|
||||||
|
// invite cleared, so set the invite prop to an empty object
|
||||||
|
dispatch(module.exports.setScratcherInvite({}));
|
||||||
|
} else {
|
||||||
|
// find the admin message and remove it
|
||||||
|
var toRemove = -1;
|
||||||
|
for (var i in adminMessages) {
|
||||||
|
if (adminMessages[i].id === messageId) {
|
||||||
|
toRemove = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
adminMessages.splice(toRemove, 1);
|
||||||
|
dispatch(module.exports.setAdminMessages(adminMessages));
|
||||||
|
}
|
||||||
|
dispatch(module.exports.setStatus('DELETE_STATUS', module.exports.Status.FETCHED));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports.getMessages = function (username, token, messages, offset) {
|
||||||
|
return function (dispatch) {
|
||||||
|
dispatch(module.exports.setStatus('MESSAGE_STATUS', module.exports.Status.FETCHING));
|
||||||
|
api({
|
||||||
|
uri: '/users/' + username + '/messages?limit=40&offset=' + offset,
|
||||||
|
authentication: token
|
||||||
|
}, function (err, body) {
|
||||||
|
if (err) {
|
||||||
|
dispatch(module.exports.setStatus('MESSAGE_STATUS', module.exports.Status.MESSAGES_ERROR));
|
||||||
|
dispatch(module.exports.setMessagesError(err));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof body === 'undefined') {
|
||||||
|
dispatch(module.exports.setStatus('MESSAGE_STATUS', module.exports.Status.MESSAGES_ERROR));
|
||||||
|
dispatch(module.exports.setMessagesError('No session content'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dispatch(module.exports.setStatus('MESSAGE_STATUS', module.exports.Status.FETCHED));
|
||||||
|
dispatch(module.exports.setMessages(messages.concat(body)));
|
||||||
|
dispatch(module.exports.setMessagesOffset(offset + 40));
|
||||||
|
dispatch(module.exports.clearMessageCount(token)); // clear count once messages loaded
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports.getAdminMessages = function (username, token) {
|
||||||
|
return function (dispatch) {
|
||||||
|
dispatch(module.exports.setStatus('ADMIN_STATUS', module.exports.Status.FETCHING));
|
||||||
|
api({
|
||||||
|
uri: '/users/' + username + '/messages/admin',
|
||||||
|
authentication: token
|
||||||
|
}, function (err, body) {
|
||||||
|
if (err) {
|
||||||
|
dispatch(module.exports.setStatus('ADMIN_STATUS', module.exports.Status.ADMIN_ERROR));
|
||||||
|
dispatch(module.exports.setMessagesError(err));
|
||||||
|
dispatch(module.exports.setAdminMessages([]));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof body === 'undefined') {
|
||||||
|
dispatch(module.exports.setStatus('ADMIN_STATUS', module.exports.Status.ADMIN_ERROR));
|
||||||
|
dispatch(module.exports.setMessagesError('No session content'));
|
||||||
|
dispatch(module.exports.setAdminMessages([]));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dispatch(module.exports.setAdminMessages(body));
|
||||||
|
dispatch(module.exports.setStatus('ADMIN_STATUS', module.exports.Status.FETCHED));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports.getScratcherInvite = function (username, token) {
|
||||||
|
return function (dispatch) {
|
||||||
|
api({
|
||||||
|
uri: '/users/' + username + '/invites',
|
||||||
|
authentication: token
|
||||||
|
}, function (err, body) {
|
||||||
|
if (err) {
|
||||||
|
dispatch(module.exports.setStatus('ADMIN_STATUS', module.exports.Status.INVITE_ERROR));
|
||||||
|
dispatch(module.exports.setMessagesError(err));
|
||||||
|
dispatch(module.exports.setScratcherInvite({}));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof body === 'undefined') return dispatch(module.exports.setMessagesError('No session content'));
|
||||||
|
dispatch(module.exports.setScratcherInvite(body));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
|
@ -1,17 +1,24 @@
|
||||||
var combineReducers = require('redux').combineReducers;
|
var combineReducers = require('redux').combineReducers;
|
||||||
|
|
||||||
var scheduleReducer = require('./conference-schedule.js').scheduleReducer;
|
var messageCountReducer = require('./message-count.js').messageCountReducer;
|
||||||
var detailsReducer = require('./conference-details.js').detailsReducer;
|
|
||||||
var permissionsReducer = require('./permissions.js').permissionsReducer;
|
var permissionsReducer = require('./permissions.js').permissionsReducer;
|
||||||
var sessionReducer = require('./session.js').sessionReducer;
|
var sessionReducer = require('./session.js').sessionReducer;
|
||||||
var navigationReducer = require('./navigation.js').navigationReducer;
|
|
||||||
|
|
||||||
var appReducer = combineReducers({
|
/**
|
||||||
session: sessionReducer,
|
* Returns a combined reducer to be used for a page in `render.jsx`.
|
||||||
permissions: permissionsReducer,
|
* The reducers used globally are applied here - session and permissions
|
||||||
conferenceSchedule: scheduleReducer,
|
* - and any reducers specific to the page should be passed into
|
||||||
conferenceDetails: detailsReducer,
|
* `render()` as an object (which will then be passed to the function
|
||||||
navigation: navigationReducer
|
* below).
|
||||||
});
|
* @param {Object} opts key/value where the key is the name of the
|
||||||
|
* redux state, value is the reducer function.
|
||||||
module.exports = appReducer;
|
* @return {Object} combined reducer to be used in the redux store
|
||||||
|
*/
|
||||||
|
module.exports = function (opts) {
|
||||||
|
opts = opts || {};
|
||||||
|
return combineReducers(Object.assign(opts, {
|
||||||
|
session: sessionReducer,
|
||||||
|
permissions: permissionsReducer,
|
||||||
|
messageCount: messageCountReducer
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
|
@ -2,6 +2,7 @@ var keyMirror = require('keymirror');
|
||||||
var defaults = require('lodash.defaults');
|
var defaults = require('lodash.defaults');
|
||||||
|
|
||||||
var api = require('../lib/api');
|
var api = require('../lib/api');
|
||||||
|
var messageCountActions = require('./message-count.js');
|
||||||
var permissionsActions = require('./permissions.js');
|
var permissionsActions = require('./permissions.js');
|
||||||
|
|
||||||
var Types = keyMirror({
|
var Types = keyMirror({
|
||||||
|
@ -96,6 +97,7 @@ module.exports.refreshSession = function () {
|
||||||
|
|
||||||
// get the permissions from the updated session
|
// get the permissions from the updated session
|
||||||
dispatch(permissionsActions.storePermissions(body.permissions));
|
dispatch(permissionsActions.storePermissions(body.permissions));
|
||||||
|
dispatch(messageCountActions.getCount(body.user.username));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -141,6 +141,13 @@
|
||||||
"view": "jobs/moderator/moderator",
|
"view": "jobs/moderator/moderator",
|
||||||
"title": "Community Moderator"
|
"title": "Community Moderator"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "messages",
|
||||||
|
"pattern": "^/messages/?$",
|
||||||
|
"routeAlias": "/messages",
|
||||||
|
"view": "messages/container",
|
||||||
|
"title": "Messages"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "microworld-art",
|
"name": "microworld-art",
|
||||||
"pattern": "^/microworlds/art",
|
"pattern": "^/microworlds/art",
|
||||||
|
|
|
@ -91,4 +91,8 @@ var mapStateToProps = function (state) {
|
||||||
|
|
||||||
var ConnectedDetails = connect(mapStateToProps)(ConferenceDetails);
|
var ConnectedDetails = connect(mapStateToProps)(ConferenceDetails);
|
||||||
|
|
||||||
render(<Page><ConnectedDetails /></Page>, document.getElementById('app'));
|
render(
|
||||||
|
<Page><ConnectedDetails /></Page>,
|
||||||
|
document.getElementById('app'),
|
||||||
|
{conferenceDetails: detailsActions.detailsReducer}
|
||||||
|
);
|
||||||
|
|
|
@ -128,4 +128,8 @@ var mapStateToProps = function (state) {
|
||||||
|
|
||||||
var ConnectedSchedule = connect(mapStateToProps)(ConferenceSchedule);
|
var ConnectedSchedule = connect(mapStateToProps)(ConferenceSchedule);
|
||||||
|
|
||||||
render(<Page><ConnectedSchedule /></Page>, document.getElementById('app'));
|
render(
|
||||||
|
<Page><ConnectedSchedule /></Page>,
|
||||||
|
document.getElementById('app'),
|
||||||
|
{conferenceSchedule: scheduleActions.scheduleReducer}
|
||||||
|
);
|
||||||
|
|
168
src/views/messages/container.jsx
Normal file
|
@ -0,0 +1,168 @@
|
||||||
|
var connect = require('react-redux').connect;
|
||||||
|
var React = require('react');
|
||||||
|
|
||||||
|
var messageActions = require('../../redux/messages.js');
|
||||||
|
var render = require('../../lib/render.jsx');
|
||||||
|
var sessionActions = require('../../redux/session.js');
|
||||||
|
|
||||||
|
var Page = require('../../components/page/www/page.jsx');
|
||||||
|
var MessagesPresentation = require('./presentation.jsx');
|
||||||
|
|
||||||
|
var Messages = React.createClass({
|
||||||
|
type: 'ConnectedMessages',
|
||||||
|
getInitialState: function () {
|
||||||
|
return {
|
||||||
|
filterValues: [],
|
||||||
|
displayedMessages: []
|
||||||
|
};
|
||||||
|
},
|
||||||
|
getDefaultProps: function () {
|
||||||
|
return {
|
||||||
|
sessionStatus: sessionActions.Status.NOT_FETCHED,
|
||||||
|
user: {},
|
||||||
|
flags: {},
|
||||||
|
messageOffset: 0,
|
||||||
|
numNewMessages: 0
|
||||||
|
};
|
||||||
|
},
|
||||||
|
componentDidUpdate: function (prevProps) {
|
||||||
|
if (this.props.user != prevProps.user) {
|
||||||
|
if (this.props.user.token) {
|
||||||
|
this.props.dispatch(
|
||||||
|
messageActions.getMessages(
|
||||||
|
this.props.user.username,
|
||||||
|
this.props.user.token,
|
||||||
|
this.props.messages,
|
||||||
|
this.props.messageOffset
|
||||||
|
)
|
||||||
|
);
|
||||||
|
this.props.dispatch(
|
||||||
|
messageActions.getAdminMessages(
|
||||||
|
this.props.user.username, this.props.user.token, this.props.messageOffset
|
||||||
|
)
|
||||||
|
);
|
||||||
|
this.props.dispatch(
|
||||||
|
messageActions.getScratcherInvite(this.props.user.username, this.props.user.token)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
componentDidMount: function () {
|
||||||
|
if (this.props.user.token) {
|
||||||
|
this.props.dispatch(
|
||||||
|
messageActions.getMessages(
|
||||||
|
this.props.user.username,
|
||||||
|
this.props.user.token,
|
||||||
|
this.props.messages,
|
||||||
|
this.props.messageOffset
|
||||||
|
)
|
||||||
|
);
|
||||||
|
this.props.dispatch(
|
||||||
|
messageActions.getAdminMessages(
|
||||||
|
this.props.user.username, this.props.user.token, this.props.messageOffset
|
||||||
|
)
|
||||||
|
);
|
||||||
|
this.props.dispatch(
|
||||||
|
messageActions.getScractherInvite(this.props.user.username, this.props.user.token)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleFilterClick: function (field, choice) {
|
||||||
|
switch (choice) {
|
||||||
|
case 'comments':
|
||||||
|
return this.setState({filterValues: ['addcomment']});
|
||||||
|
case 'projects':
|
||||||
|
return this.setState({filterValues: [
|
||||||
|
'loveproject',
|
||||||
|
'favoriteproject',
|
||||||
|
'remixproject'
|
||||||
|
]});
|
||||||
|
case 'studios':
|
||||||
|
return this.setState({filterValues: [
|
||||||
|
'curatorinvite',
|
||||||
|
'studioactivity',
|
||||||
|
'becomeownerstudio'
|
||||||
|
]});
|
||||||
|
case 'forums':
|
||||||
|
return this.setState({filterValues: ['forumpost']});
|
||||||
|
default:
|
||||||
|
return this.setState({filterValues: []});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleMessageDismiss: function (messageType, messageId) {
|
||||||
|
var adminMessages = null;
|
||||||
|
if (messageType === 'notification') {
|
||||||
|
adminMessages = this.props.adminMessages;
|
||||||
|
}
|
||||||
|
this.props.dispatch(
|
||||||
|
messageActions.clearAdminMessage(messageType, messageId, adminMessages)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
handleLoadMoreMessages: function () {
|
||||||
|
this.props.dispatch(
|
||||||
|
messageActions.getMessages(
|
||||||
|
this.props.user.username,
|
||||||
|
this.props.user.token,
|
||||||
|
this.props.messages,
|
||||||
|
this.props.messageOffset
|
||||||
|
)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
filterMessages: function (messages, typesAllowed) {
|
||||||
|
var filteredMessages = [];
|
||||||
|
for (var i in messages) {
|
||||||
|
if (typesAllowed.indexOf(messages[i].type) > -1) {
|
||||||
|
filteredMessages.push(messages[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return filteredMessages;
|
||||||
|
},
|
||||||
|
render: function () {
|
||||||
|
var loadMore = true;
|
||||||
|
if (this.props.messageOffset > this.props.messages.length && this.props.messageOffset > 0) {
|
||||||
|
loadMore = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var messages = this.props.messages;
|
||||||
|
if (this.state.filterValues.length > 0) {
|
||||||
|
messages = this.filterMessages(messages, this.state.filterValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
return(
|
||||||
|
<MessagesPresentation
|
||||||
|
sessionStatus={this.props.sessionStatus}
|
||||||
|
user={this.props.user}
|
||||||
|
messages={messages}
|
||||||
|
adminMessages={this.props.adminMessages}
|
||||||
|
scratcherInvite={this.props.invite}
|
||||||
|
numNewMessages={this.props.numNewMessages}
|
||||||
|
handleFilterClick={this.handleFilterClick}
|
||||||
|
handleAdminDismiss={this.handleMessageDismiss}
|
||||||
|
loadMore={loadMore}
|
||||||
|
loadMoreMethod={this.handleLoadMoreMessages}
|
||||||
|
requestStatus={this.props.requestStatus}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var mapStateToProps = function (state) {
|
||||||
|
return {
|
||||||
|
sessionStatus: state.session.status,
|
||||||
|
user: state.session.session.user,
|
||||||
|
flags: state.session.session.flags,
|
||||||
|
numNewMessages: state.messageCount.messageCount,
|
||||||
|
messages: state.messages.messages.social,
|
||||||
|
adminMessages: state.messages.messages.admin,
|
||||||
|
invite: state.messages.messages.invite,
|
||||||
|
messageOffset: state.messages.messages.socialOffset,
|
||||||
|
requestStatus: state.messages.status
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
var ConnectedMessages = connect(mapStateToProps)(Messages);
|
||||||
|
render(
|
||||||
|
<Page><ConnectedMessages /></Page>,
|
||||||
|
document.getElementById('app'),
|
||||||
|
{messages: messageActions.messagesReducer}
|
||||||
|
);
|
30
src/views/messages/l10n.json
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
{
|
||||||
|
"messages.activityAll": "All Activity",
|
||||||
|
"messages.activityComments": "Comment Activity",
|
||||||
|
"messages.activityProjects": "Project Activity",
|
||||||
|
"messages.activityStudios": "Studio Activity",
|
||||||
|
"messages.activityForums": "Forum Activity",
|
||||||
|
"messages.becomeManagerText": "{username} promoted you to manager for the studio {studio}",
|
||||||
|
"messages.curatorInviteText": "{actorLink} invited you to curate the studio {studioLink}. Visit the {tabLink} on the studio to accept the invitation",
|
||||||
|
"messages.curatorTabText": "curator tab",
|
||||||
|
"messages.favoriteText": "{profileLink} favorited your project {projectLink}",
|
||||||
|
"messages.filterBy": "Filter by",
|
||||||
|
"messages.followText": "{profileLink} is now following you",
|
||||||
|
"messages.forumPostText": "There are new posts in the forum thread: {topicLink}",
|
||||||
|
"messages.learnMore": "Click here to learn more",
|
||||||
|
"messages.loveText": "{profileLink} loved your project {projectLink}",
|
||||||
|
"messages.messageTitle": "Messages",
|
||||||
|
"messages.profileComment": "{profileLink} commented on {commentLink}",
|
||||||
|
"messages.commentReply": "{profileLink} replied to your comment on {commentLink}",
|
||||||
|
"messages.profileOther": "{username}'s reply",
|
||||||
|
"messages.profileSelf": "your profile",
|
||||||
|
"messages.projectComment": "{profileLink} commented on your project {commentLink}",
|
||||||
|
"messages.remixText": "{profileLink} remixed your project {remixedProjectLink} as {projectLink}",
|
||||||
|
"messages.scratcherInvite": "You are invited to become a Scratcher! {learnMore}!",
|
||||||
|
"messages.scratchTeamTitle": "Messages from the Scratch Team",
|
||||||
|
"messages.studioActivityText": "There was new activity in {studioLink} today",
|
||||||
|
"messages.studioCommentReply": "{profileLink} replied to your comment in {commentLink}",
|
||||||
|
"messages.userJoinText": "Welcome to Scratch! After you make projects and comments, you'll get messages about them here. Go {exploreLink} or {makeProjectLink}.",
|
||||||
|
"messages.userJoinMakeProject": "make a project",
|
||||||
|
"messages.requestError": "oops! Looks like there was a problem getting some of your messages. Please try to reload this page"
|
||||||
|
}
|
42
src/views/messages/message-rows/admin-message.jsx
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
var FormattedDate = require('react-intl').FormattedDate;
|
||||||
|
var React = require('react');
|
||||||
|
|
||||||
|
var Button = require('../../../components/forms/button.jsx');
|
||||||
|
var FlexRow = require('../../../components/flex-row/flex-row.jsx');
|
||||||
|
|
||||||
|
var AdminMessage = React.createClass({
|
||||||
|
type: 'AdminMessage',
|
||||||
|
propTypes: {
|
||||||
|
id: React.PropTypes.number.isRequired,
|
||||||
|
message: React.PropTypes.string.isRequired,
|
||||||
|
datetimeCreated: React.PropTypes.string.isRequired,
|
||||||
|
onDismiss: React.PropTypes.func.isRequired
|
||||||
|
},
|
||||||
|
render: function () {
|
||||||
|
return (
|
||||||
|
<li className="admin-message">
|
||||||
|
<FlexRow className="admin-message-header">
|
||||||
|
<span className="admin-message-date">
|
||||||
|
<FormattedDate value={new Date(this.props.datetimeCreated)} />
|
||||||
|
</span>
|
||||||
|
<Button
|
||||||
|
className="mod-admin-message-dismiss"
|
||||||
|
onClick={this.props.onDismiss.bind(this, 'notification', this.props.id)}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
className="mod-admin-message-icon"
|
||||||
|
src="/svgs/modal/close-x.svg"
|
||||||
|
alt="close-icon"
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
</FlexRow>
|
||||||
|
<p
|
||||||
|
className="admin-message-content"
|
||||||
|
dangerouslySetInnerHTML={{__html: this.props.message}}
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = AdminMessage;
|
45
src/views/messages/message-rows/become-manager.jsx
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
var classNames = require('classnames');
|
||||||
|
var FormattedMessage = require('react-intl').FormattedMessage;
|
||||||
|
var React = require('react');
|
||||||
|
|
||||||
|
var SocialMessage = require('../../../components/social-message/social-message.jsx');
|
||||||
|
|
||||||
|
var BecomeManagerMessage = React.createClass({
|
||||||
|
type: 'BecomeManagerMessage',
|
||||||
|
propTypes: {
|
||||||
|
actorUsername: React.PropTypes.string.isRequired,
|
||||||
|
studioId: React.PropTypes.number.isRequired,
|
||||||
|
studioTitle: React.PropTypes.string.isRequired,
|
||||||
|
datetimePromoted: React.PropTypes.string.isRequired
|
||||||
|
},
|
||||||
|
render: function () {
|
||||||
|
var actorUri = '/users/' + this.props.actorUsername + '/';
|
||||||
|
var studioUri = '/studios/' + this.props.studioId + '/';
|
||||||
|
|
||||||
|
var classes = classNames(
|
||||||
|
'mod-become-manager',
|
||||||
|
this.props.className
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<SocialMessage
|
||||||
|
className={classes}
|
||||||
|
datetime={this.props.datetimePromoted}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id='messages.becomeManagerText'
|
||||||
|
values={{
|
||||||
|
username: <a
|
||||||
|
href={actorUri}
|
||||||
|
className="social-messages-profile-link"
|
||||||
|
>
|
||||||
|
{this.props.actorUsername}
|
||||||
|
</a>,
|
||||||
|
studio: <a href={studioUri}>{this.props.studioTitle}</a>
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</SocialMessage>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = BecomeManagerMessage;
|
161
src/views/messages/message-rows/comment-message.jsx
Normal file
|
@ -0,0 +1,161 @@
|
||||||
|
var classNames = require('classnames');
|
||||||
|
var FormattedMessage = require('react-intl').FormattedMessage;
|
||||||
|
var injectIntl = require('react-intl').injectIntl;
|
||||||
|
var React = require('react');
|
||||||
|
|
||||||
|
var Comment = require('../../../components/comment/comment.jsx');
|
||||||
|
var FlexRow = require('../../../components/flex-row/flex-row.jsx');
|
||||||
|
var SocialMessage = require('../../../components/social-message/social-message.jsx');
|
||||||
|
|
||||||
|
var CommentMessage = injectIntl(React.createClass({
|
||||||
|
type: 'CommentMessage',
|
||||||
|
propTypes: {
|
||||||
|
actorUsername: React.PropTypes.string.isRequired,
|
||||||
|
actorId: React.PropTypes.number.isRequired,
|
||||||
|
objectType: React.PropTypes.oneOf([0, 1, 2]).isRequired,
|
||||||
|
objectId: React.PropTypes.number.isRequired,
|
||||||
|
commentId: React.PropTypes.number.isRequired,
|
||||||
|
commentText: React.PropTypes.string.isRequired,
|
||||||
|
commentDateTime: React.PropTypes.string.isRequired,
|
||||||
|
objectTitle: React.PropTypes.string,
|
||||||
|
commentee: React.PropTypes.string
|
||||||
|
},
|
||||||
|
getObjectLink: function (objectType, commentId, objectId) {
|
||||||
|
switch (objectType) {
|
||||||
|
case 0:
|
||||||
|
return '/projects/' + objectId + '/#comments-' + commentId;
|
||||||
|
case 1:
|
||||||
|
return '/users/' + objectId + '/#comments-' + commentId;
|
||||||
|
case 2:
|
||||||
|
return '/studios/' + objectId + '/comments/#comments-' + commentId;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getMessageText: function (objectType, commentee) {
|
||||||
|
var actorLink = '/users/' + this.props.actorUsername + '/';
|
||||||
|
if (objectType === 2) {
|
||||||
|
// studio comment notifications only occur for direct replies
|
||||||
|
if (typeof commentee !== 'undefined' && commentee === this.props.user.username) {
|
||||||
|
var commentLink = '/studios/' + this.props.objectId + '/comments/#comments-' + this.props.commentId;
|
||||||
|
return <FormattedMessage
|
||||||
|
id='messages.studioCommentReply'
|
||||||
|
values={{
|
||||||
|
profileLink: <a
|
||||||
|
href={actorLink}
|
||||||
|
className="social-messages-profile-link"
|
||||||
|
>
|
||||||
|
{this.props.actorUsername}
|
||||||
|
</a>,
|
||||||
|
commentLink: <a href={commentLink}>{this.props.objectTitle}</a>
|
||||||
|
}}
|
||||||
|
/>;
|
||||||
|
}
|
||||||
|
} else if (objectType === 1) {
|
||||||
|
var profileLink = '/users/' + this.props.objectId + '/#comments-' + this.props.commentId;
|
||||||
|
var linkText = '';
|
||||||
|
if (typeof commentee !== 'undefined' && commentee === this.props.user.username) {
|
||||||
|
// is a profile comment, and is a reply
|
||||||
|
if (this.props.objectTitle === this.props.user.username) {
|
||||||
|
linkText = this.props.intl.formatMessage({
|
||||||
|
id: 'messages.profileSelf'
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
linkText = this.props.intl.formatMessage({
|
||||||
|
id: 'messages.profileOther',
|
||||||
|
values: {
|
||||||
|
username: this.props.objectId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return <FormattedMessage
|
||||||
|
id='messages.commentReply'
|
||||||
|
values={{
|
||||||
|
profileLink: <a
|
||||||
|
href={actorLink}
|
||||||
|
className="social-messages-profile-link"
|
||||||
|
>
|
||||||
|
{this.props.actorUsername}
|
||||||
|
</a>,
|
||||||
|
commentLink: <a href={profileLink}>{linkText}</a>
|
||||||
|
}}
|
||||||
|
/>;
|
||||||
|
} else {
|
||||||
|
// is a profile comment and not a reply, must be own profile
|
||||||
|
linkText = this.props.intl.formatMessage({
|
||||||
|
id: 'messages.profileSelf'
|
||||||
|
});
|
||||||
|
return <FormattedMessage
|
||||||
|
id='messages.profileComment'
|
||||||
|
values={{
|
||||||
|
profileLink: <a
|
||||||
|
href={actorLink}
|
||||||
|
className="social-messages-profile-link"
|
||||||
|
>
|
||||||
|
{this.props.actorUsername}
|
||||||
|
</a>,
|
||||||
|
commentLink: <a href={profileLink}>{linkText}</a>
|
||||||
|
}}
|
||||||
|
/>;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var projectLink = '/projects/' + this.props.objectId + '/#comments-' + this.props.commentId;
|
||||||
|
// must be a project comment, since it's not the other two, and the strict prop type reqs
|
||||||
|
if (typeof commentee !== 'undefined' && commentee === this.props.user.username) {
|
||||||
|
return <FormattedMessage
|
||||||
|
id='messages.commentReply'
|
||||||
|
values={{
|
||||||
|
profileLink: <a
|
||||||
|
href={actorLink}
|
||||||
|
className="social-messages-profile-link"
|
||||||
|
>
|
||||||
|
{this.props.actorUsername}
|
||||||
|
</a>,
|
||||||
|
commentLink: <a href={projectLink}>{this.props.objectTitle}</a>
|
||||||
|
}}
|
||||||
|
/>;
|
||||||
|
} else {
|
||||||
|
return <FormattedMessage
|
||||||
|
id='messages.projectComment'
|
||||||
|
values={{
|
||||||
|
profileLink: <a
|
||||||
|
href={actorLink}
|
||||||
|
className="social-messages-profile-link"
|
||||||
|
>
|
||||||
|
{this.props.actorUsername}
|
||||||
|
</a>,
|
||||||
|
commentLink: <a href={projectLink}>{this.props.objectTitle}</a>
|
||||||
|
}}
|
||||||
|
/>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
render: function () {
|
||||||
|
var messageText = this.getMessageText(this.props.objectType, this.props.commentee);
|
||||||
|
var commentorAvatar = 'https://cdn2.scratch.mit.edu/get_image/user/' + this.props.actorId + '_32x32.png';
|
||||||
|
var commentorAvatarAlt = this.props.actorUsername + '\'s avatar';
|
||||||
|
|
||||||
|
var classes = classNames(
|
||||||
|
'mod-comment-message',
|
||||||
|
this.props.className
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<SocialMessage
|
||||||
|
className={classes}
|
||||||
|
datetime={this.props.commentDateTime}
|
||||||
|
>
|
||||||
|
<p className="comment-message-info">{messageText}</p>
|
||||||
|
<FlexRow className="mod-comment-message">
|
||||||
|
<img
|
||||||
|
className="comment-message-info-img"
|
||||||
|
src={commentorAvatar}
|
||||||
|
alt={commentorAvatarAlt}
|
||||||
|
/>
|
||||||
|
<Comment
|
||||||
|
comment={this.props.commentText}
|
||||||
|
/>
|
||||||
|
</FlexRow>
|
||||||
|
</SocialMessage>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
module.exports = CommentMessage;
|
48
src/views/messages/message-rows/curator-invite.jsx
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
var classNames = require('classnames');
|
||||||
|
var FormattedMessage = require('react-intl').FormattedMessage;
|
||||||
|
var injectIntl = require('react-intl').injectIntl;
|
||||||
|
var React = require('react');
|
||||||
|
|
||||||
|
var SocialMessage = require('../../../components/social-message/social-message.jsx');
|
||||||
|
|
||||||
|
var CuratorInviteMessage = injectIntl(React.createClass({
|
||||||
|
type: 'CuratorInviteMessage',
|
||||||
|
propTypes: {
|
||||||
|
actorUsername: React.PropTypes.string.isRequired,
|
||||||
|
studioId: React.PropTypes.number.isRequired,
|
||||||
|
studioTitle: React.PropTypes.string.isRequired,
|
||||||
|
datetimePromoted: React.PropTypes.string.isRequired
|
||||||
|
},
|
||||||
|
render: function () {
|
||||||
|
var studioLink = '/studios/' + this.props.studioId + '/';
|
||||||
|
var actorLink = '/users/' + this.props.actorUsername + '/';
|
||||||
|
var tabText = this.props.intl.formatMessage({id: 'messages.curatorTabText'});
|
||||||
|
|
||||||
|
var classes = classNames(
|
||||||
|
'mod-curator-invite',
|
||||||
|
this.props.className
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<SocialMessage
|
||||||
|
className={classes}
|
||||||
|
datetime={this.props.datetimePromoted}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id='messages.curatorInviteText'
|
||||||
|
values={{
|
||||||
|
actorLink: <a
|
||||||
|
href={actorLink}
|
||||||
|
className="social-messages-profile-link"
|
||||||
|
>
|
||||||
|
{this.props.actorUsername}
|
||||||
|
</a>,
|
||||||
|
studioLink: <a href={studioLink}>{this.props.studioTitle}</a>,
|
||||||
|
tabLink: <a href={studioLink}>{tabText}</a>
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</SocialMessage>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
module.exports = CuratorInviteMessage;
|
45
src/views/messages/message-rows/favorite-project.jsx
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
var classNames = require('classnames');
|
||||||
|
var FormattedMessage = require('react-intl').FormattedMessage;
|
||||||
|
var React = require('react');
|
||||||
|
|
||||||
|
var SocialMessage = require('../../../components/social-message/social-message.jsx');
|
||||||
|
|
||||||
|
var FavoriteProjectMessage = React.createClass({
|
||||||
|
type: 'FavoriteProjectMessage',
|
||||||
|
propTypes: {
|
||||||
|
actorUsername: React.PropTypes.string.isRequired,
|
||||||
|
projectId: React.PropTypes.number.isRequired,
|
||||||
|
projectTitle: React.PropTypes.string.isRequired,
|
||||||
|
favoriteDateTime: React.PropTypes.string.isRequired
|
||||||
|
},
|
||||||
|
render: function () {
|
||||||
|
var projectLink = '/projects/' + this.props.projectId;
|
||||||
|
var profileLink = '/users/' + this.props.actorUsername;
|
||||||
|
|
||||||
|
var classes = classNames(
|
||||||
|
'mod-love-favorite',
|
||||||
|
this.props.className
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<SocialMessage
|
||||||
|
className={classes}
|
||||||
|
datetime={this.props.favoriteDateTime}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id='messages.favoriteText'
|
||||||
|
values={{
|
||||||
|
profileLink: <a
|
||||||
|
href={profileLink}
|
||||||
|
className="social-messages-profile-link"
|
||||||
|
>
|
||||||
|
{this.props.actorUsername}
|
||||||
|
</a>,
|
||||||
|
projectLink: <a href={projectLink}>{this.props.projectTitle}</a>
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</SocialMessage>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = FavoriteProjectMessage;
|
41
src/views/messages/message-rows/follow-user.jsx
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
var classNames = require('classnames');
|
||||||
|
var FormattedMessage = require('react-intl').FormattedMessage;
|
||||||
|
var React = require('react');
|
||||||
|
|
||||||
|
var SocialMessage = require('../../../components/social-message/social-message.jsx');
|
||||||
|
|
||||||
|
var FollowUserMessage = React.createClass({
|
||||||
|
type: 'FollowUserMessage',
|
||||||
|
propTypes: {
|
||||||
|
followerUsername: React.PropTypes.string.isRequired,
|
||||||
|
followDateTime: React.PropTypes.string.isRequired
|
||||||
|
},
|
||||||
|
render: function () {
|
||||||
|
var profileLink = '/users/' + this.props.followerUsername; + '/';
|
||||||
|
|
||||||
|
var classes = classNames(
|
||||||
|
'mod-follow-user',
|
||||||
|
this.props.className
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<SocialMessage
|
||||||
|
className={classes}
|
||||||
|
datetime={this.props.followDateTime}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id='messages.followText'
|
||||||
|
values={{
|
||||||
|
profileLink: <a
|
||||||
|
href={profileLink}
|
||||||
|
className="social-messages-profile-link"
|
||||||
|
>
|
||||||
|
{this.props.followerUsername}
|
||||||
|
</a>
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</SocialMessage>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = FollowUserMessage;
|
38
src/views/messages/message-rows/forum-topic-post.jsx
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
var classNames = require('classnames');
|
||||||
|
var FormattedMessage = require('react-intl').FormattedMessage;
|
||||||
|
var React = require('react');
|
||||||
|
|
||||||
|
var SocialMessage = require('../../../components/social-message/social-message.jsx');
|
||||||
|
|
||||||
|
var ForumPostMessage = React.createClass({
|
||||||
|
type: 'ForumPostMessage',
|
||||||
|
propTypes: {
|
||||||
|
actorUsername: React.PropTypes.string.isRequired,
|
||||||
|
topicId: React.PropTypes.number.isRequired,
|
||||||
|
topicTitle: React.PropTypes.string.isRequired,
|
||||||
|
datetimeCreated: React.PropTypes.string.isRequired
|
||||||
|
},
|
||||||
|
render: function () {
|
||||||
|
var topicLink = '/discuss/topic/' + this.props.topicId + '/unread/';
|
||||||
|
|
||||||
|
var classes = classNames(
|
||||||
|
'mod-studio-activity',
|
||||||
|
this.props.className
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<SocialMessage
|
||||||
|
className={classes}
|
||||||
|
datetime={this.props.datetimeCreated}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id='messages.forumPostText'
|
||||||
|
values={{
|
||||||
|
topicLink: <a href={topicLink}>{this.props.topicTitle}</a>
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</SocialMessage>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = ForumPostMessage;
|
45
src/views/messages/message-rows/love-project.jsx
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
var classNames = require('classnames');
|
||||||
|
var FormattedMessage = require('react-intl').FormattedMessage;
|
||||||
|
var React = require('react');
|
||||||
|
|
||||||
|
var SocialMessage = require('../../../components/social-message/social-message.jsx');
|
||||||
|
|
||||||
|
var LoveProjectMessage = React.createClass({
|
||||||
|
type: 'LoveProjectMessage',
|
||||||
|
propTypes: {
|
||||||
|
actorUsername: React.PropTypes.string.isRequired,
|
||||||
|
projectId: React.PropTypes.number.isRequired,
|
||||||
|
projectTitle: React.PropTypes.string.isRequired,
|
||||||
|
loveDateTime: React.PropTypes.string.isRequired
|
||||||
|
},
|
||||||
|
render: function () {
|
||||||
|
var projectLink = '/projects/' + this.props.projectId;
|
||||||
|
var profileLink = '/users/' + this.props.actorUsername;
|
||||||
|
|
||||||
|
var classes = classNames(
|
||||||
|
'mod-love-project',
|
||||||
|
this.props.className
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<SocialMessage
|
||||||
|
className={classes}
|
||||||
|
datetime={this.props.loveDateTime}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id='messages.loveText'
|
||||||
|
values={{
|
||||||
|
profileLink: <a
|
||||||
|
href={profileLink}
|
||||||
|
className="social-messages-profile-link"
|
||||||
|
>
|
||||||
|
{this.props.actorUsername}
|
||||||
|
</a>,
|
||||||
|
projectLink: <a href={projectLink}>{this.props.projectTitle}</a>
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</SocialMessage>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = LoveProjectMessage;
|
49
src/views/messages/message-rows/remix-project.jsx
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
var classNames = require('classnames');
|
||||||
|
var FormattedMessage = require('react-intl').FormattedMessage;
|
||||||
|
var React = require('react');
|
||||||
|
|
||||||
|
var SocialMessage = require('../../../components/social-message/social-message.jsx');
|
||||||
|
|
||||||
|
var RemixProjectMessage = React.createClass({
|
||||||
|
type: 'RemixProjectMessage',
|
||||||
|
propTypes: {
|
||||||
|
actorUsername: React.PropTypes.string.isRequired,
|
||||||
|
projectId: React.PropTypes.number.isRequired,
|
||||||
|
projectTitle: React.PropTypes.string.isRequired,
|
||||||
|
parentId: React.PropTypes.number.isRequired,
|
||||||
|
parentTitle: React.PropTypes.string.isRequired,
|
||||||
|
remixDate: React.PropTypes.string.isRequired
|
||||||
|
},
|
||||||
|
render: function () {
|
||||||
|
var projectLink = '/projects/' + this.props.projectId;
|
||||||
|
var profileLink = '/users/' + this.props.actorUsername;
|
||||||
|
var remixedProjectLink = '/projects/' + this.props.parentId;
|
||||||
|
|
||||||
|
var classes = classNames(
|
||||||
|
'mod-remix-project',
|
||||||
|
this.props.className
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<SocialMessage
|
||||||
|
className={classes}
|
||||||
|
datetime={this.props.remixDate}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id='messages.remixText'
|
||||||
|
values={{
|
||||||
|
profileLink: <a
|
||||||
|
href={profileLink}
|
||||||
|
className="social-messages-profile-link"
|
||||||
|
>
|
||||||
|
{this.props.actorUsername}
|
||||||
|
</a>,
|
||||||
|
projectLink: <a href={projectLink}>{this.props.projectTitle}</a>,
|
||||||
|
remixedProjectLink: <a href={remixedProjectLink}>{this.props.parentTitle}</a>
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</SocialMessage>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = RemixProjectMessage;
|
50
src/views/messages/message-rows/scratcher-invite.jsx
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
var FormattedDate = require('react-intl').FormattedDate;
|
||||||
|
var FormattedMessage = require('react-intl').FormattedMessage;
|
||||||
|
var injectIntl = require('react-intl').injectIntl;
|
||||||
|
var React = require('react');
|
||||||
|
|
||||||
|
var Button = require('../../../components/forms/button.jsx');
|
||||||
|
var FlexRow = require('../../../components/flex-row/flex-row.jsx');
|
||||||
|
|
||||||
|
var AdminMessage = injectIntl(React.createClass({
|
||||||
|
type: 'AdminMessage',
|
||||||
|
propTypes: {
|
||||||
|
id: React.PropTypes.number.isRequired,
|
||||||
|
username: React.PropTypes.string.isRequired,
|
||||||
|
datetimeCreated: React.PropTypes.string.isRequired,
|
||||||
|
onDismiss: React.PropTypes.func.isRequired
|
||||||
|
},
|
||||||
|
render: function () {
|
||||||
|
var learnMoreLink = '/users/' + this.props.username + '#promote';
|
||||||
|
var learnMoreMessage = this.props.intl.formatMessage({id: 'messages.learnMore'});
|
||||||
|
return (
|
||||||
|
<li className="admin-message">
|
||||||
|
<FlexRow className="admin-message-header">
|
||||||
|
<span className="admin-message-date">
|
||||||
|
<FormattedDate value={new Date(this.props.datetimeCreated)} />
|
||||||
|
</span>
|
||||||
|
<Button
|
||||||
|
className="mod-scratcher-invite-dismiss"
|
||||||
|
onClick={this.props.onDismiss.bind(this, 'invite', this.props.id)}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
className="mod-scratcher-invite-icon"
|
||||||
|
src="/svgs/modal/close-x.svg"
|
||||||
|
alt="close-icon"
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
</FlexRow>
|
||||||
|
<p className="admin-message-content">
|
||||||
|
<FormattedMessage
|
||||||
|
id='messages.scratcherInvite'
|
||||||
|
values={{
|
||||||
|
learnMore: <a href={learnMoreLink}>{learnMoreMessage}</a>
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
module.exports = AdminMessage;
|
37
src/views/messages/message-rows/studio-activity.jsx
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
var classNames = require('classnames');
|
||||||
|
var FormattedMessage = require('react-intl').FormattedMessage;
|
||||||
|
var React = require('react');
|
||||||
|
|
||||||
|
var SocialMessage = require('../../../components/social-message/social-message.jsx');
|
||||||
|
|
||||||
|
var StudioActivityMessage = React.createClass({
|
||||||
|
type: 'StudioActivityMessage',
|
||||||
|
propTypes: {
|
||||||
|
studioId: React.PropTypes.number.isRequired,
|
||||||
|
studioTitle: React.PropTypes.string.isRequired,
|
||||||
|
datetimeCreated: React.PropTypes.string.isRequired
|
||||||
|
},
|
||||||
|
render: function () {
|
||||||
|
var studioLink = '/studios/' + this.props.studioId;
|
||||||
|
|
||||||
|
var classes = classNames(
|
||||||
|
'mod-studio-activity',
|
||||||
|
this.props.className
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<SocialMessage
|
||||||
|
className={classes}
|
||||||
|
datetime={this.props.datetimeCreated}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id='messages.studioActivityText'
|
||||||
|
values={{
|
||||||
|
studioLink: <a href={studioLink}>{this.props.studioTitle}</a>
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</SocialMessage>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = StudioActivityMessage;
|
38
src/views/messages/message-rows/user-join.jsx
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
var classNames = require('classnames');
|
||||||
|
var FormattedMessage = require('react-intl').FormattedMessage;
|
||||||
|
var injectIntl = require('react-intl').injectIntl;
|
||||||
|
var React = require('react');
|
||||||
|
|
||||||
|
var SocialMessage = require('../../../components/social-message/social-message.jsx');
|
||||||
|
|
||||||
|
var UserJoinMessage = injectIntl(React.createClass({
|
||||||
|
type: 'UserJoinMessage',
|
||||||
|
propTypes: {
|
||||||
|
datetimeJoined: React.PropTypes.string.isRequired
|
||||||
|
},
|
||||||
|
render: function () {
|
||||||
|
var exploreText = this.props.intl.formatMessage({id: 'general.explore'});
|
||||||
|
var projectText = this.props.intl.formatMessage({id: 'messages.userJoinMakeProject'});
|
||||||
|
|
||||||
|
var classes = classNames(
|
||||||
|
'mod-user-join',
|
||||||
|
this.props.className
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<SocialMessage
|
||||||
|
className={classes}
|
||||||
|
datetime={this.props.datetimeJoined}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id='messages.userJoinText'
|
||||||
|
values={{
|
||||||
|
exploreLink: <a href="/explore">{exploreText}</a>,
|
||||||
|
makeProjectLink: <a href="/projects/editor/?tip_bar=getStarted">{projectText}</a>
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</SocialMessage>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
module.exports = UserJoinMessage;
|
127
src/views/messages/messages.scss
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
@import "../../colors";
|
||||||
|
@import "../../frameless";
|
||||||
|
|
||||||
|
#view {
|
||||||
|
background-color: $ui-light-gray;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-banner.mod-messages {
|
||||||
|
background-color: $ui-blue;
|
||||||
|
color: $type-white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-row.mod-messages-title {
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-banner-h1.mod-messages {
|
||||||
|
margin: 0;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control {
|
||||||
|
color: $type-gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-block {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.messages-admin,
|
||||||
|
.messages-social {
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.messages-admin-list,
|
||||||
|
.messages-social-list {
|
||||||
|
padding: 0;
|
||||||
|
list-style-type: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-message {
|
||||||
|
border: 1px solid darken($ui-gray, 10);
|
||||||
|
border-radius: 5px;
|
||||||
|
background-color: lighten($ui-blue, 40);
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-message-header {
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-message-content {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-message-date {
|
||||||
|
font-size: .8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button.mod-scratcher-invite-dismiss,
|
||||||
|
.button.mod-admin-message-dismiss {
|
||||||
|
box-shadow: none;
|
||||||
|
padding: .25rem;
|
||||||
|
line-height: .8rem;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.messages-social-title-unread {
|
||||||
|
margin-left: 1rem;
|
||||||
|
border-radius: 1rem;
|
||||||
|
background-color: $ui-orange;
|
||||||
|
padding: .25rem .5rem;
|
||||||
|
color: $type-white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.messages-social-list {
|
||||||
|
border: 1px solid $ui-border;
|
||||||
|
border-bottom-width: 0;
|
||||||
|
border-radius: 5px;
|
||||||
|
background-color: $ui-white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-message-info {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-text {
|
||||||
|
background-color: $ui-white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-row.mod-comment-message {
|
||||||
|
margin-bottom: .25rem;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-message-info-img {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.messages-social-loadmore {
|
||||||
|
display: block;
|
||||||
|
margin: 1rem auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: $mobile - 1) {
|
||||||
|
.flex-row.admin-message-header,
|
||||||
|
.flex-row.mod-comment-message {
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-text {
|
||||||
|
max-width: 60%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (min-width: $mobile) and (max-width: $tablet - 1) {
|
||||||
|
.flex-row.admin-message-header,
|
||||||
|
.flex-row.mod-comment-message {
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
}
|
321
src/views/messages/presentation.jsx
Normal file
|
@ -0,0 +1,321 @@
|
||||||
|
var FormattedMessage = require('react-intl').FormattedMessage;
|
||||||
|
var FormattedNumber = require('react-intl').FormattedNumber;
|
||||||
|
var injectIntl = require('react-intl').injectIntl;
|
||||||
|
var React = require('react');
|
||||||
|
|
||||||
|
var Button = require('../../components/forms/button.jsx');
|
||||||
|
var FlexRow = require('../../components/flex-row/flex-row.jsx');
|
||||||
|
var Form = require('../../components/forms/form.jsx');
|
||||||
|
var Select = require('../../components/forms/select.jsx');
|
||||||
|
var TitleBanner = require('../../components/title-banner/title-banner.jsx');
|
||||||
|
|
||||||
|
var messageStatuses = require('../../redux/messages').Status;
|
||||||
|
|
||||||
|
// Message Components
|
||||||
|
var AdminMessage = require('./message-rows/admin-message.jsx');
|
||||||
|
var BecomeManagerMessage = require('./message-rows/become-manager.jsx');
|
||||||
|
var CommentMessage = require('./message-rows/comment-message.jsx');
|
||||||
|
var CuratorInviteMessage = require('./message-rows/curator-invite.jsx');
|
||||||
|
var FavoriteProjectMessage = require('./message-rows/favorite-project.jsx');
|
||||||
|
var FollowUserMessage = require('./message-rows/follow-user.jsx');
|
||||||
|
var ForumPostMessage= require('./message-rows/forum-topic-post.jsx');
|
||||||
|
var LoveProjectMessage = require('./message-rows/love-project.jsx');
|
||||||
|
var RemixProjectMessage = require('./message-rows/remix-project.jsx');
|
||||||
|
var ScratcherInvite = require('./message-rows/scratcher-invite.jsx');
|
||||||
|
var StudioActivityMessage = require('./message-rows/studio-activity.jsx');
|
||||||
|
var UserJoinMessage = require('./message-rows/user-join.jsx');
|
||||||
|
|
||||||
|
require('./messages.scss');
|
||||||
|
|
||||||
|
var SocialMessagesList = React.createClass({
|
||||||
|
type: 'SocialMessagesList',
|
||||||
|
propTypes: {
|
||||||
|
loadStatus: React.PropTypes.string,
|
||||||
|
messages: React.PropTypes.array.isRequired,
|
||||||
|
numNewMessages: React.PropTypes.number,
|
||||||
|
loadMore: React.PropTypes.bool.isRequired,
|
||||||
|
loadMoreMethod: React.PropTypes.func
|
||||||
|
},
|
||||||
|
getDefaultProps: function () {
|
||||||
|
return {
|
||||||
|
loadStatus: messageStatuses.FETCHING,
|
||||||
|
numNewMessages: 0
|
||||||
|
};
|
||||||
|
},
|
||||||
|
getComponentForMessage: function (message, unread) {
|
||||||
|
var className = (unread) ? 'mod-unread' : '';
|
||||||
|
var key = message.type + '_' + message.id;
|
||||||
|
|
||||||
|
switch (message.type) {
|
||||||
|
case 'followuser':
|
||||||
|
return <FollowUserMessage
|
||||||
|
key={key}
|
||||||
|
className={className}
|
||||||
|
followerUsername={message.actor_username}
|
||||||
|
followDateTime={message.datetime_created}
|
||||||
|
/>;
|
||||||
|
case 'loveproject':
|
||||||
|
return <LoveProjectMessage
|
||||||
|
key={key}
|
||||||
|
className={className}
|
||||||
|
actorUsername={message.actor_username}
|
||||||
|
projectId={message.project_id}
|
||||||
|
projectTitle={message.title}
|
||||||
|
loveDateTime={message.datetime_created}
|
||||||
|
/>;
|
||||||
|
case 'favoriteproject':
|
||||||
|
return <FavoriteProjectMessage
|
||||||
|
key={key}
|
||||||
|
className={className}
|
||||||
|
actorUsername={message.actor_username}
|
||||||
|
projectId={message.project_id}
|
||||||
|
projectTitle={message.project_title}
|
||||||
|
favoriteDateTime={message.datetime_created}
|
||||||
|
/>;
|
||||||
|
case 'addcomment':
|
||||||
|
return <CommentMessage
|
||||||
|
key={key}
|
||||||
|
className={className}
|
||||||
|
actorUsername={message.actor_username}
|
||||||
|
actorId={message.actor_id}
|
||||||
|
objectType={message.comment_type}
|
||||||
|
objectId={message.comment_obj_id}
|
||||||
|
commentId={message.comment_id}
|
||||||
|
commentText={message.comment_fragment}
|
||||||
|
commentDateTime={message.datetime_created}
|
||||||
|
objectTitle={message.comment_obj_title}
|
||||||
|
commentee={message.commentee}
|
||||||
|
/>;
|
||||||
|
case 'curatorinvite':
|
||||||
|
return <CuratorInviteMessage
|
||||||
|
key={key}
|
||||||
|
className={className}
|
||||||
|
actorUsername={message.actor_username}
|
||||||
|
studioId={message.gallery_id}
|
||||||
|
studioTitle={message.title}
|
||||||
|
datetimePromoted={message.datetime_created}
|
||||||
|
/>;
|
||||||
|
case 'remixproject':
|
||||||
|
return <RemixProjectMessage
|
||||||
|
key={key}
|
||||||
|
className={className}
|
||||||
|
actorUsername={message.actor_username}
|
||||||
|
projectId={message.project_id}
|
||||||
|
projectTitle={message.title}
|
||||||
|
parentId={message.parent_id}
|
||||||
|
parentTitle={message.parent_title}
|
||||||
|
remixDate={message.datetime_created}
|
||||||
|
/>;
|
||||||
|
case 'studioactivity':
|
||||||
|
return <StudioActivityMessage
|
||||||
|
key={key}
|
||||||
|
className={className}
|
||||||
|
studioId={message.gallery_id}
|
||||||
|
studioTitle={message.title}
|
||||||
|
datetimeCreated={message.datetime_created}
|
||||||
|
/>;
|
||||||
|
case 'forumpost':
|
||||||
|
return <ForumPostMessage
|
||||||
|
key={key}
|
||||||
|
className={className}
|
||||||
|
actorUsername={message.actor_username}
|
||||||
|
topicId={message.topic_id}
|
||||||
|
topicTitle={message.topic_title}
|
||||||
|
datetimeCreated={message.datetime_created}
|
||||||
|
/>;
|
||||||
|
case 'becomeownerstudio':
|
||||||
|
return <BecomeManagerMessage
|
||||||
|
key={key}
|
||||||
|
className={className}
|
||||||
|
actorUsername={message.actor_username}
|
||||||
|
studioId={message.gallery_id}
|
||||||
|
studioTitle={message.gallery_title}
|
||||||
|
datetimePromoted={message.datetime_created}
|
||||||
|
/>;
|
||||||
|
case 'userjoin':
|
||||||
|
return <UserJoinMessage
|
||||||
|
key={key}
|
||||||
|
className={className}
|
||||||
|
datetimeJoined={message.datetime_created}
|
||||||
|
/>;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
renderSocialMessages: function (messages, unreadCount) {
|
||||||
|
var messageList = [];
|
||||||
|
for (var i in messages) {
|
||||||
|
if (i <= unreadCount) {
|
||||||
|
messageList.push(this.getComponentForMessage(messages[i], true));
|
||||||
|
} else {
|
||||||
|
messageList.push(this.getComponentForMessage(messages[i], false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return messageList;
|
||||||
|
},
|
||||||
|
renderLoadMore: function (loadMore) {
|
||||||
|
if (loadMore) {
|
||||||
|
return <Button
|
||||||
|
onClick={this.props.loadMoreMethod}
|
||||||
|
className="messages-social-loadmore white"
|
||||||
|
key="load-more"
|
||||||
|
>
|
||||||
|
<FormattedMessage id='general.loadMore' />
|
||||||
|
</Button>;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
render: function () {
|
||||||
|
if (this.props.loadStatus === messageStatuses.MESSAGES_ERROR) {
|
||||||
|
return (
|
||||||
|
<section className="messages-social">
|
||||||
|
<div className="messages-social-title">
|
||||||
|
<h4>
|
||||||
|
<FormattedMessage id='messages.messageTitle' />
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
<p><FormattedMessage id='messages.requestError' /></p>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="messages-social">
|
||||||
|
{this.props.messages.length > 0 ? [
|
||||||
|
<div className="messages-social-title" key="messages-social-title">
|
||||||
|
<h4>
|
||||||
|
<FormattedMessage id='messages.messageTitle' />
|
||||||
|
<span className="messages-social-title-unread">
|
||||||
|
<FormattedNumber value={this.props.numNewMessages} />
|
||||||
|
</span>
|
||||||
|
</h4>
|
||||||
|
</div>,
|
||||||
|
<ul className="messages-social-list" key="messages-social-list">
|
||||||
|
{this.renderSocialMessages(this.props.messages, (this.props.numNewMessages - 1))}
|
||||||
|
</ul>,
|
||||||
|
this.renderLoadMore(this.props.loadMore)
|
||||||
|
] : []}
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var MessagesPresentation = injectIntl(React.createClass({
|
||||||
|
type: 'MessagesPresentation',
|
||||||
|
propTypes: {
|
||||||
|
sessionStatus: React.PropTypes.string.isRequired,
|
||||||
|
user: React.PropTypes.object.isRequired,
|
||||||
|
messages: React.PropTypes.array.isRequired,
|
||||||
|
adminMessages: React.PropTypes.array.isRequired,
|
||||||
|
scratcherInvite: React.PropTypes.object.isRequired,
|
||||||
|
numNewMessages: React.PropTypes.number,
|
||||||
|
handleFilterClick: React.PropTypes.func.isRequired,
|
||||||
|
handleAdminDismiss: React.PropTypes.func.isRequired,
|
||||||
|
loadMore: React.PropTypes.bool.isRequired,
|
||||||
|
loadMoreMethod: React.PropTypes.func,
|
||||||
|
requestStatus: React.PropTypes.object.isRequired
|
||||||
|
},
|
||||||
|
getDefaultProps: function () {
|
||||||
|
return {
|
||||||
|
numNewMessages: 0,
|
||||||
|
filterOpen: false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
render: function () {
|
||||||
|
var adminMessageLength = this.props.adminMessages.length;
|
||||||
|
if (Object.keys(this.props.scratcherInvite).length > 0) {
|
||||||
|
adminMessageLength = adminMessageLength + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="messages">
|
||||||
|
<TitleBanner className="mod-messages">
|
||||||
|
<FlexRow className="inner mod-messages-title">
|
||||||
|
<h1 className="title-banner-h1 mod-messages">
|
||||||
|
<FormattedMessage id='messages.messageTitle' />
|
||||||
|
</h1>
|
||||||
|
<div className="messages-title-filter">
|
||||||
|
<Form>
|
||||||
|
<Select
|
||||||
|
label={this.props.intl.formatMessage({id: 'messages.filterBy'})}
|
||||||
|
name="messages.filter"
|
||||||
|
onChange={this.props.handleFilterClick}
|
||||||
|
options={[
|
||||||
|
{
|
||||||
|
label: this.props.intl.formatMessage({id: 'messages.activityAll'}),
|
||||||
|
value: ''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: this.props.intl.formatMessage({id: 'messages.activityComments'}),
|
||||||
|
value: 'comments'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: this.props.intl.formatMessage({id: 'messages.activityProjects'}),
|
||||||
|
value: 'projects'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: this.props.intl.formatMessage({id: 'messages.activityStudios'}),
|
||||||
|
value: 'studios'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: this.props.intl.formatMessage({id: 'messages.activityForums'}),
|
||||||
|
value: 'forums'
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
</FlexRow>
|
||||||
|
</TitleBanner>
|
||||||
|
<div className="messages-details inner">
|
||||||
|
{this.props.adminMessages.length > 0 || Object.keys(this.props.scratcherInvite).length > 0 ? [
|
||||||
|
<section className="messages-admin">
|
||||||
|
<div className="messages-admin-title">
|
||||||
|
<h4>
|
||||||
|
<FormattedMessage id='messages.scratchTeamTitle' />
|
||||||
|
<span className="messages-social-title-unread">
|
||||||
|
<FormattedNumber value={adminMessageLength} />
|
||||||
|
</span>
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
<ul className="messages-admin-list">
|
||||||
|
{Object.keys(this.props.scratcherInvite).length > 0 ? [
|
||||||
|
<ScratcherInvite
|
||||||
|
id={this.props.scratcherInvite.id}
|
||||||
|
username={this.props.user.username}
|
||||||
|
datetimeCreated={this.props.scratcherInvite.datetime_created}
|
||||||
|
onDismiss={this.props.handleAdminDismiss}
|
||||||
|
/>
|
||||||
|
] : []}
|
||||||
|
{this.props.adminMessages.map(function (item) {
|
||||||
|
return <AdminMessage
|
||||||
|
key={'adminmessage' + item.id}
|
||||||
|
id={item.id}
|
||||||
|
message={item.message}
|
||||||
|
datetimeCreated={item.datetime_created}
|
||||||
|
onDismiss={this.props.handleAdminDismiss.bind('notification', item.id)}
|
||||||
|
/>;
|
||||||
|
}.bind(this))}
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
] : []}
|
||||||
|
{this.props.requestStatus.admin === messageStatuses.ADMIN_ERROR ? [
|
||||||
|
<section className="messages-admin">
|
||||||
|
<h4>
|
||||||
|
<FormattedMessage id='messages.scratchTeamTitle' />
|
||||||
|
</h4>
|
||||||
|
<p><FormattedMessage id='messages.requestError' /></p>
|
||||||
|
</section>
|
||||||
|
] : []}
|
||||||
|
<SocialMessagesList
|
||||||
|
loadStatus={this.props.requestStatus.messages}
|
||||||
|
messages={this.props.messages}
|
||||||
|
numNewMessages={this.props.numNewMessages}
|
||||||
|
loadMore={this.props.loadMore}
|
||||||
|
loadMoreMethod={this.props.loadMoreMethod}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
module.exports = MessagesPresentation;
|
|
@ -135,4 +135,8 @@ var mapStateToProps = function (state) {
|
||||||
|
|
||||||
var ConnectedSearch = connect(mapStateToProps)(Search);
|
var ConnectedSearch = connect(mapStateToProps)(Search);
|
||||||
|
|
||||||
render(<Page><ConnectedSearch /></Page>, document.getElementById('app'));
|
render(
|
||||||
|
<Page><ConnectedSearch /></Page>,
|
||||||
|
document.getElementById('app'),
|
||||||
|
{navigation: navigationActions.navigationReducer}
|
||||||
|
);
|
||||||
|
|
|
@ -38,9 +38,9 @@ var Splash = injectIntl(React.createClass({
|
||||||
if (this.props.user != prevProps.user) {
|
if (this.props.user != prevProps.user) {
|
||||||
if (this.props.user.username) {
|
if (this.props.user.username) {
|
||||||
this.getActivity(this.props.user.username);
|
this.getActivity(this.props.user.username);
|
||||||
this.getSharedByFollowing(this.props.user.token);
|
this.getSharedByFollowing(this.props.user.username, this.props.user.token);
|
||||||
this.getInStudiosFollowing(this.props.user.token);
|
this.getInStudiosFollowing(this.props.user.username, this.props.user.token);
|
||||||
this.getLovedByFollowing(this.props.user.token);
|
this.getLovedByFollowing(this.props.user.username, this.props.user.token);
|
||||||
this.getNews();
|
this.getNews();
|
||||||
} else {
|
} else {
|
||||||
this.setState({sharedByFollowing: []});
|
this.setState({sharedByFollowing: []});
|
||||||
|
@ -61,9 +61,9 @@ var Splash = injectIntl(React.createClass({
|
||||||
this.getFeaturedGlobal();
|
this.getFeaturedGlobal();
|
||||||
if (this.props.user.username) {
|
if (this.props.user.username) {
|
||||||
this.getActivity(this.props.user.username);
|
this.getActivity(this.props.user.username);
|
||||||
this.getSharedByFollowing(this.props.user.token);
|
this.getSharedByFollowing(this.props.user.username, this.props.user.token);
|
||||||
this.getInStudiosFollowing(this.props.user.token);
|
this.getInStudiosFollowing(this.props.user.username, this.props.user.token);
|
||||||
this.getLovedByFollowing(this.props.user.token);
|
this.getLovedByFollowing(this.props.user.username, this.props.user.token);
|
||||||
this.getNews();
|
this.getNews();
|
||||||
} else {
|
} else {
|
||||||
this.getProjectCount();
|
this.getProjectCount();
|
||||||
|
@ -85,27 +85,27 @@ var Splash = injectIntl(React.createClass({
|
||||||
if (!err) return this.setState({featuredGlobal: body});
|
if (!err) return this.setState({featuredGlobal: body});
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
},
|
},
|
||||||
getSharedByFollowing: function (token) {
|
getSharedByFollowing: function (username, token) {
|
||||||
api({
|
api({
|
||||||
uri: '/projects/following/users',
|
uri: '/users/' + username + '/following/users/projects',
|
||||||
authentication: token
|
authentication: token
|
||||||
}, function (err, body) {
|
}, function (err, body) {
|
||||||
if (!body) return log.error('No response body');
|
if (!body) return log.error('No response body');
|
||||||
if (!err) return this.setState({sharedByFollowing: body});
|
if (!err) return this.setState({sharedByFollowing: body});
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
},
|
},
|
||||||
getInStudiosFollowing: function (token) {
|
getInStudiosFollowing: function (username, token) {
|
||||||
api({
|
api({
|
||||||
uri: '/projects/following/studios',
|
uri: '/users/' + username + '/following/studios/projects',
|
||||||
authentication: token
|
authentication: token
|
||||||
}, function (err, body) {
|
}, function (err, body) {
|
||||||
if (!body) return log.error('No response body');
|
if (!body) return log.error('No response body');
|
||||||
if (!err) return this.setState({inStudiosFollowing: body});
|
if (!err) return this.setState({inStudiosFollowing: body});
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
},
|
},
|
||||||
getLovedByFollowing: function (token) {
|
getLovedByFollowing: function (username, token) {
|
||||||
api({
|
api({
|
||||||
uri: '/projects/following/loves',
|
uri: '/users/' + username + '/following/users/loves',
|
||||||
authentication: token
|
authentication: token
|
||||||
}, function (err, body) {
|
}, function (err, body) {
|
||||||
if (!body) return log.error('No response body');
|
if (!body) return log.error('No response body');
|
||||||
|
|
BIN
static/images/emoji/10mil.png
Normal file
After Width: | Height: | Size: 716 B |
BIN
static/images/emoji/apple.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
static/images/emoji/aww-cat.png
Normal file
After Width: | Height: | Size: 4.4 KiB |
BIN
static/images/emoji/binoculars.png
Normal file
After Width: | Height: | Size: 910 B |
BIN
static/images/emoji/broccoli.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
static/images/emoji/camera.png
Normal file
After Width: | Height: | Size: 959 B |
BIN
static/images/emoji/candycorn.png
Normal file
After Width: | Height: | Size: 613 B |
BIN
static/images/emoji/cat.png
Normal file
After Width: | Height: | Size: 4.1 KiB |
BIN
static/images/emoji/compass.png
Normal file
After Width: | Height: | Size: 827 B |
BIN
static/images/emoji/cool-cat.png
Normal file
After Width: | Height: | Size: 3.7 KiB |
BIN
static/images/emoji/cupcake.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
static/images/emoji/eggplant.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
static/images/emoji/fav-it-cat.png
Normal file
After Width: | Height: | Size: 4.6 KiB |
BIN
static/images/emoji/gobo.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
static/images/emoji/huh-cat.png
Normal file
After Width: | Height: | Size: 4.2 KiB |
BIN
static/images/emoji/lol-cat.png
Normal file
After Width: | Height: | Size: 4.2 KiB |
BIN
static/images/emoji/love-it-cat.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
BIN
static/images/emoji/map.png
Normal file
After Width: | Height: | Size: 937 B |
BIN
static/images/emoji/meow.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
static/images/emoji/pizza-cat.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
BIN
static/images/emoji/pizza.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
static/images/emoji/rainbow-cat.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
BIN
static/images/emoji/suitcase.png
Normal file
After Width: | Height: | Size: 545 B |
BIN
static/images/emoji/sushi.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
static/images/emoji/taco.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
static/images/emoji/tongue-out-cat.png
Normal file
After Width: | Height: | Size: 4.1 KiB |
BIN
static/images/emoji/upside-down-cat.png
Normal file
After Width: | Height: | Size: 4 KiB |
BIN
static/images/emoji/waffle.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
static/images/emoji/wink-cat.png
Normal file
After Width: | Height: | Size: 4.5 KiB |