diff --git a/src/components/Feed/NewPost.jsx b/src/components/Feed/NewPost.jsx
index 653e16c2e..37d5982ce 100644
--- a/src/components/Feed/NewPost.jsx
+++ b/src/components/Feed/NewPost.jsx
@@ -41,12 +41,13 @@ class NewPost extends React.Component {
constructor(props) {
super(props)
this.state = {editorState: EditorState.createEmpty(), expandedEditor: false, canSubmit: false}
+ this.onTitleChange = this.onTitleChange.bind(this)
this.onEditorChange = this.onEditorChange.bind(this)
this.handleKeyCommand = this.handleKeyCommand.bind(this)
this.toggleBlockType = this.toggleBlockType.bind(this)
this.toggleInlineStyle = this.toggleInlineStyle.bind(this)
this.onClickOutside = this.onClickOutside.bind(this)
- this.onNewPostChange = this.onNewPostChange.bind(this)
+ this.validateSubmitState = this.validateSubmitState.bind(this)
}
componentDidMount() {
@@ -59,11 +60,11 @@ class NewPost extends React.Component {
}
componentWillReceiveProps(nextProps) {
- if (!(nextProps.isCreating || nextProps.hasError && !nextProps.isCreating)) {
+ if (nextProps.isCreating !== this.props.isCreating && !nextProps.isCreating && !nextProps.hasError) {
this.setState({editorState: EditorState.createEmpty()})
this.refs.title.value = ''
}
- this.onNewPostChange()
+ this.validateSubmitState()
}
onClickOutside(evt) {
@@ -125,15 +126,29 @@ class NewPost extends React.Component {
onEditorChange(editorState) {
this.setState({editorState})
- this.onNewPostChange()
+ this.validateSubmitState()
+ if (this.props.onNewPostChange) {
+ // NOTE: uses getPlainText method to avoid newline character for empty content
+ this.props.onNewPostChange(this.refs.title.value, editorState.getCurrentContent().getPlainText())
+ }
}
- onNewPostChange() {
+ validateSubmitState() {
+ const { editorState } = this.state
this.setState({
- canSubmit: this.refs.title && !!this.refs.title.value.trim().length && this.state.editorState.getCurrentContent().hasText()
+ canSubmit: this.refs.title && !!this.refs.title.value.trim().length && editorState.getCurrentContent().hasText()
})
}
+ onTitleChange() {
+ const { editorState } = this.state
+ this.validateSubmitState()
+ if (this.props.onNewPostChange) {
+ // NOTE: uses getPlainText method to avoid newline character for empty content
+ this.props.onNewPostChange(this.refs.title.value, editorState.getCurrentContent().getPlainText())
+ }
+ }
+
render() {
const {currentUser, titlePlaceholder, isCreating} = this.props
const {editorState, canSubmit} = this.state
@@ -191,7 +206,7 @@ class NewPost extends React.Component {
ref="title"
className="new-post-title"
type="text"
- onChange={this.onNewPostChange}
+ onChange={this.onTitleChange}
placeholder={ titlePlaceholder || 'Title of the post'}
/>
diff --git a/src/projects/detail/Dashboard.jsx b/src/projects/detail/Dashboard.jsx
index 45d89c546..1279d4015 100644
--- a/src/projects/detail/Dashboard.jsx
+++ b/src/projects/detail/Dashboard.jsx
@@ -5,7 +5,7 @@ import Sticky from 'react-stickynode'
require('./Dashboard.scss')
-const Dashboard = ({project, currentMemberRole}) => (
+const Dashboard = ({project, currentMemberRole, route}) => (
@@ -16,7 +16,7 @@ const Dashboard = ({project, currentMemberRole}) => (
-
+
diff --git a/src/projects/detail/Messages.jsx b/src/projects/detail/Messages.jsx
index 3086400fa..a1e9f5a2a 100644
--- a/src/projects/detail/Messages.jsx
+++ b/src/projects/detail/Messages.jsx
@@ -4,7 +4,12 @@ import MessagesContainer from './containers/MessagesContainer'
require('./Messages.scss')
-const Messages = ({ location, project, currentMemberRole }) => (
-
+const Messages = ({ location, project, currentMemberRole, route }) => (
+
)
export default Messages
diff --git a/src/projects/detail/containers/FeedContainer.js b/src/projects/detail/containers/FeedContainer.js
index e14298aaf..5a1e21bab 100644
--- a/src/projects/detail/containers/FeedContainer.js
+++ b/src/projects/detail/containers/FeedContainer.js
@@ -1,4 +1,5 @@
import React, { PropTypes } from 'react'
+import { withRouter } from 'react-router'
import _ from 'lodash'
import {
THREAD_MESSAGES_PAGE_SIZE,
@@ -36,7 +37,16 @@ class FeedView extends React.Component {
this.onNewCommentChange = this.onNewCommentChange.bind(this)
this.onShowAllComments = this.onShowAllComments.bind(this)
this.onAddNewComment = this.onAddNewComment.bind(this)
- this.state = { feeds : [], showAll: [] }
+ this.onLeave = this.onLeave.bind(this)
+ this.isChanged = this.isChanged.bind(this)
+ this.onNewPostChange = this.onNewPostChange.bind(this)
+ this.state = { feeds : [], showAll: [], newPost: {} }
+ }
+
+ componentDidMount() {
+ const routeLeaveHook = this.props.router.setRouteLeaveHook(this.props.route, this.onLeave)
+ window.addEventListener('beforeunload', this.onLeave)
+ this.setState({ routeLeaveHook })
}
componentWillMount() {
@@ -47,6 +57,27 @@ class FeedView extends React.Component {
this.init(nextProps)
}
+ componentWillUnmount() {
+ if (this.state.routeLeaveHook) {
+ this.state.routeLeaveHook()
+ }
+ window.removeEventListener('beforeunload', this.onLeave)
+ }
+
+ // Notify user if they navigate away while the form is modified.
+ onLeave(e) {
+ if (this.isChanged()) {
+ return e.returnValue = 'You have uposted content. Are you sure you want to leave?'
+ }
+ }
+
+ isChanged() {
+ const { newPost } = this.state
+ const hasComment = !_.isUndefined(_.find(this.state.feeds, (feed) => feed.newComment && feed.newComment.length))
+ const hasThread = (newPost.title && !!newPost.title.trim().length) || ( newPost.content && !!newPost.content.trim().length)
+ return hasThread || hasComment
+ }
+
mapFeed(feed, showAll = false) {
const { allMembers } = this.props
const item = _.pick(feed, ['id', 'date', 'read', 'tag', 'title', 'totalPosts', 'userId', 'reference', 'referenceId', 'postIds', 'isAddingComment', 'isLoadingComments', 'error'])
@@ -99,6 +130,12 @@ class FeedView extends React.Component {
})
}
+ onNewPostChange(title, content) {
+ this.setState({
+ newPost: {title, content}
+ })
+ }
+
onNewPost({title, content}) {
const { project } = this.props
const newFeed = {
@@ -194,6 +231,7 @@ class FeedView extends React.Component {
isCreating={ isCreatingFeed }
hasError={ error }
heading="NEW STATUS POST"
+ onNewPostChange={this.onNewPostChange}
titlePlaceholder="Share the latest project updates with the team"
/>
}
@@ -203,7 +241,7 @@ class FeedView extends React.Component {
}
}
const enhance = spinnerWhileLoading(props => !props.isLoading)
-const EnhancedFeedView = enhance(FeedView)
+const EnhancedFeedView = withRouter(enhance(FeedView))
class FeedContainer extends React.Component {
diff --git a/src/projects/detail/containers/MessagesContainer.js b/src/projects/detail/containers/MessagesContainer.js
index 83afdb8f6..fc81d6c45 100644
--- a/src/projects/detail/containers/MessagesContainer.js
+++ b/src/projects/detail/containers/MessagesContainer.js
@@ -1,5 +1,6 @@
import _ from 'lodash'
import React from 'react'
+import { withRouter } from 'react-router'
import { connect } from 'react-redux'
import update from 'react-addons-update'
import MessageList from '../../../components/MessageList/MessageList'
@@ -30,12 +31,30 @@ class MessagesView extends React.Component {
constructor(props) {
super(props)
- this.state = { threads : [], activeThreadId : null, showEmptyState : true, showAll: []}
+ this.state = {
+ threads : [],
+ activeThreadId : null,
+ showEmptyState : true,
+ showAll: [],
+ newPost: {}
+ }
this.onThreadSelect = this.onThreadSelect.bind(this)
this.onShowAllComments = this.onShowAllComments.bind(this)
this.onAddNewMessage = this.onAddNewMessage.bind(this)
this.onNewMessageChange = this.onNewMessageChange.bind(this)
this.onNewThread = this.onNewThread.bind(this)
+ this.onLeave = this.onLeave.bind(this)
+ this.isChanged = this.isChanged.bind(this)
+ this.onNewPostChange = this.onNewPostChange.bind(this)
+ this.changeThread = this.changeThread.bind(this)
+ this.onNewThreadClick = this.onNewThreadClick.bind(this)
+ this.showNewThreadForm = this.showNewThreadForm.bind(this)
+ }
+
+ componentDidMount() {
+ const routeLeaveHook = this.props.router.setRouteLeaveHook(this.props.route, this.onLeave)
+ window.addEventListener('beforeunload', this.onLeave)
+ this.setState({ routeLeaveHook })
}
componentWillMount() {
@@ -46,6 +65,27 @@ class MessagesView extends React.Component {
this.init(nextProps)
}
+ componentWillUnmount() {
+ window.removeEventListener('beforeunload', this.onLeave)
+ if (this.state.routeLeaveHook) {
+ this.state.routeLeaveHook()
+ }
+ }
+
+ // Notify user if they navigate away while the form is modified.
+ onLeave(e) {
+ if (this.isChanged()) {
+ return e.returnValue = 'You have uposted content. Are you sure you want to leave?'
+ }
+ }
+
+ isChanged() {
+ const { newPost } = this.state
+ const hasMessage = !_.isUndefined(_.find(this.state.threads, (thread) => thread.newMessage && thread.newMessage.length))
+ const hasThread = (newPost.title && !!newPost.title.trim().length) || ( newPost.content && !!newPost.content.trim().length)
+ return hasThread || hasMessage
+ }
+
mapFeed(feed, isActive, showAll = false) {
const { allMembers } = this.props
const item = _.pick(feed, ['id', 'date', 'read', 'tag', 'title', 'totalPosts', 'userId', 'reference', 'referenceId', 'postIds', 'isAddingComment', 'isLoadingComments', 'error'])
@@ -137,15 +177,28 @@ class MessagesView extends React.Component {
}
onThreadSelect(thread) {
+ const unsavedContentMsg = this.onLeave({})
+ if (unsavedContentMsg) {
+ const changeConfirmed = confirm(unsavedContentMsg)
+ if (changeConfirmed) {
+ this.changeThread(thread)
+ }
+ } else {
+ this.changeThread(thread)
+ }
+ }
+
+ changeThread(thread) {
this.setState({
isCreateNewMessage: false,
+ newPost: {},
activeThreadId: thread.id,
threads: this.state.threads.map((item) => {
if (item.isActive) {
if (item.id === thread.id) {
return item
}
- return {...item, isActive: false, messages: item.messages.map((msg) => ({...msg, unread: false}))}
+ return {...item, isActive: false, newMessage: '', messages: item.messages.map((msg) => ({...msg, unread: false}))}
}
if (item.id === thread.id) {
return {...item, isActive: true, unreadCount: 0}
@@ -155,6 +208,36 @@ class MessagesView extends React.Component {
})
}
+ onNewPostChange(title, content) {
+ this.setState({
+ newPost: {title, content}
+ })
+ }
+
+ onNewThreadClick() {
+ const unsavedContentMsg = this.onLeave({})
+ if (unsavedContentMsg) {
+ const changeConfirmed = confirm(unsavedContentMsg)
+ if (changeConfirmed) {
+ this.showNewThreadForm()
+ }
+ } else {
+ this.showNewThreadForm()
+ }
+ }
+
+ showNewThreadForm() {
+ this.setState({
+ isCreateNewMessage: true,
+ threads: this.state.threads.map((item) => {
+ if (item.isActive) {
+ return {...item, newMessage: ''}
+ }
+ return item
+ })
+ })
+ }
+
onNewMessageChange(content) {
this.setState({
threads: this.state.threads.map((item) => {
@@ -200,6 +283,7 @@ class MessagesView extends React.Component {
this.setState({isCreateNewMessage: true})}
+ onAdd={ this.onNewThreadClick }
threads={threads}
onSelect={this.onThreadSelect}
showAddButton={ !!currentMemberRole }
@@ -251,7 +335,7 @@ class MessagesView extends React.Component {
}
const enhance = spinnerWhileLoading(props => !props.isLoading)
-const EnhancedMessagesView = enhance(MessagesView)
+const EnhancedMessagesView = withRouter(enhance(MessagesView))
class MessagesContainer extends React.Component {
constructor(props) {