diff --git a/src/components/ScrollToAnchors.jsx b/src/components/ScrollToAnchors.jsx index 19058226c..90ba4db22 100644 --- a/src/components/ScrollToAnchors.jsx +++ b/src/components/ScrollToAnchors.jsx @@ -43,7 +43,7 @@ export function scrollToAnchors(Component) { componentDidMount() { const { hash } = window.location - if (hash !== '') { + if (!this.props.disableAutoScrolling && hash !== '') { // Push onto callback queue so it runs after the DOM is updated, // this is required when navigating from a different page so that // the element is rendered on the page before trying to getElementById. diff --git a/src/projects/actions/projectDashboard.js b/src/projects/actions/projectDashboard.js index d20494ed1..131e3706b 100644 --- a/src/projects/actions/projectDashboard.js +++ b/src/projects/actions/projectDashboard.js @@ -1,15 +1,9 @@ import _ from 'lodash' import { loadMembers } from '../../actions/members' -import { loadProject, loadProjectInvite, loadDirectProjectData, loadProjectPhasesWithProducts } from './project' +import { loadProject, loadProjectInvite, loadDirectProjectData } from './project' +import { loadProjectPlan } from './projectPlan' import { loadProjectsMetadata } from '../../actions/templates' -import { loadProductTimelineWithMilestones } from './productsTimelines' -import { loadFeedsForPhases } from './phasesTopics' -import { LOAD_PROJECT_DASHBOARD, - LOAD_ADDITIONAL_PROJECT_DATA, - DISCOURSE_BOT_USERID, - CODER_BOT_USERID, - TC_SYSTEM_USERID -} from '../../config/constants' +import { LOAD_PROJECT_DASHBOARD, LOAD_ADDITIONAL_PROJECT_DATA } from '../../config/constants' /** * Load all project data to paint the dashboard @@ -44,28 +38,7 @@ const getDashboardData = (dispatch, getState, projectId, isOnlyLoadProjectInfo) // for new projects load phases, products, project template and product templates if (project.version === 'v3') { promises.push( - dispatch(loadProjectPhasesWithProducts(projectId)) - .then(({ value: phases }) => { - loadFeedsForPhases(projectId, phases, dispatch) - .then((phaseFeeds) => { - let phaseUserIds = [] - _.forEach(phaseFeeds, phaseFeed => { - phaseUserIds = _.union(phaseUserIds, _.map(phaseFeed.topics, 'userId')) - _.forEach(phaseFeed.topics, topic => { - phaseUserIds = _.union(phaseUserIds, _.map(topic.posts, 'userId')) - }) - // this is to remove any nulls from the list (dev had some bad data) - _.remove(phaseUserIds, i => !i || [DISCOURSE_BOT_USERID, CODER_BOT_USERID, TC_SYSTEM_USERID].indexOf(i) > -1) - }) - // take difference of userIds identified from project members - phaseUserIds = _.difference(phaseUserIds, userIds) - - dispatch(loadMembers(phaseUserIds)) - }) - // load timelines for phase products here together with all dashboard data - // as we need to know timeline data not only inside timeline container - loadTimelinesForPhasesProducts(phases, dispatch) - }) + dispatch(loadProjectPlan(projectId, userIds)) ) } @@ -96,26 +69,6 @@ const getData = (dispatch, getState, projectId, isOnlyLoadProjectInfo) => { .catch(() => getDashboardData(dispatch, getState, projectId, isOnlyLoadProjectInfo)) } -/** - * Load timelines for phase's products - * - * @param {Array} phases list of phases - * @param {Function} dispatch dispatch function - */ -function loadTimelinesForPhasesProducts(phases, dispatch) { - const products = [] - - phases.forEach((phase) => { - phase.products.forEach((product) => { - products.push(product) - }) - }) - - return Promise.all( - products.map((product) => dispatch(loadProductTimelineWithMilestones(product.id))) - ) -} - export function loadProjectDashboard(projectId, isOnlyLoadProjectInfo = false) { return (dispatch, getState) => { return dispatch({ diff --git a/src/projects/actions/projectPlan.js b/src/projects/actions/projectPlan.js new file mode 100644 index 000000000..71707f9da --- /dev/null +++ b/src/projects/actions/projectPlan.js @@ -0,0 +1,58 @@ +import _ from 'lodash' +import { loadMembers } from '../../actions/members' +import { loadProjectPhasesWithProducts } from './project' +import { loadFeedsForPhases } from './phasesTopics' +import { loadProductTimelineWithMilestones } from './productsTimelines' +import { + DISCOURSE_BOT_USERID, + CODER_BOT_USERID, + TC_SYSTEM_USERID +} from '../../config/constants' + + +/** + * Load timelines for phase's products + * + * @param {Array} phases list of phases + * @param {Function} dispatch dispatch function + */ +function loadTimelinesForPhasesProducts(phases, dispatch) { + const products = [] + + phases.forEach((phase) => { + phase.products.forEach((product) => { + products.push(product) + }) + }) + + return Promise.all( + products.map((product) => dispatch(loadProductTimelineWithMilestones(product.id))) + ) +} + +export function loadProjectPlan(projectId, existingUserIds) { + return (dispatch) => { + return dispatch(loadProjectPhasesWithProducts(projectId)) + .then(({ value: phases }) => { + loadFeedsForPhases(projectId, phases, dispatch) + .then((phaseFeeds) => { + let phaseUserIds = [] + _.forEach(phaseFeeds, phaseFeed => { + phaseUserIds = _.union(phaseUserIds, _.map(phaseFeed.topics, 'userId')) + _.forEach(phaseFeed.topics, topic => { + phaseUserIds = _.union(phaseUserIds, _.map(topic.posts, 'userId')) + }) + // this is to remove any nulls from the list (dev had some bad data) + _.remove(phaseUserIds, i => !i || [DISCOURSE_BOT_USERID, CODER_BOT_USERID, TC_SYSTEM_USERID].indexOf(i) > -1) + }) + // take difference of existingUserIds identified from project members + phaseUserIds = _.difference(phaseUserIds, existingUserIds) + + dispatch(loadMembers(phaseUserIds)) + }) + // load timelines for phase products here together with all dashboard data + // as we need to know timeline data not only inside timeline container + loadTimelinesForPhasesProducts(phases, dispatch) + }) + } +} diff --git a/src/projects/detail/ProjectDetail.jsx b/src/projects/detail/ProjectDetail.jsx index 1191dc1f6..ae164f9eb 100644 --- a/src/projects/detail/ProjectDetail.jsx +++ b/src/projects/detail/ProjectDetail.jsx @@ -64,9 +64,6 @@ const ProjectDetailView = (props) => { if (!currentMemberRole && props.currentUserRoles && props.currentUserRoles.length > 0) { currentMemberRole = props.currentUserRoles[0] } - const regex = /#(feed-([0-9]+)|comment-([0-9]+))/ - const match = props.location.hash.match(regex) - const ids = match ? { feedId: match[2], commentId: match[3] } : {} const { component: Component } = props const componentProps = { @@ -79,7 +76,7 @@ const ProjectDetailView = (props) => { isProcessing: props.isProcessing, allProductTemplates: props.allProductTemplates, productsTimelines: props.productsTimelines, - ...ids + location: props.location, } return } diff --git a/src/projects/detail/components/EditProjectForm/EditProjectForm.jsx b/src/projects/detail/components/EditProjectForm/EditProjectForm.jsx index 9f6bb7eeb..584045f7b 100644 --- a/src/projects/detail/components/EditProjectForm/EditProjectForm.jsx +++ b/src/projects/detail/components/EditProjectForm/EditProjectForm.jsx @@ -268,7 +268,7 @@ class EditProjectForm extends Component { render() { - const { isEdittable, showHidden, productTemplates, productCategories } = this.props + const { isEdittable, showHidden, productTemplates, productCategories, disableAutoScrolling } = this.props const { template } = this.state const { project, dirtyProject } = this.state const onLeaveMessage = this.onLeave() || '' @@ -287,6 +287,7 @@ class EditProjectForm extends Component { sectionNumber={idx + 1} resetFeatures={this.onFeaturesSaveAttachedClick} showFeaturesDialog={this.showFeaturesDialog} + disableAutoScrolling={disableAutoScrolling} // TODO we shoudl not update the props (section is coming from props) validate={(isInvalid) => section.isInvalid = isInvalid} showHidden={showHidden} @@ -353,7 +354,8 @@ class EditProjectForm extends Component { } EditProjectForm.defaultProps = { - shouldUpdateTemplate: false + shouldUpdateTemplate: false, + disableAutoScrolling: false, } EditProjectForm.propTypes = { @@ -371,6 +373,7 @@ EditProjectForm.propTypes = { updateAttachment: PropTypes.func.isRequired, removeAttachment: PropTypes.func.isRequired, shouldUpdateTemplate: PropTypes.bool, + disableAutoScrolling: PropTypes.bool, } export default EditProjectForm diff --git a/src/projects/detail/components/PhaseFeed/PhaseFeed.jsx b/src/projects/detail/components/PhaseFeed/PhaseFeed.jsx index 1fbeb0bc7..0269568ff 100644 --- a/src/projects/detail/components/PhaseFeed/PhaseFeed.jsx +++ b/src/projects/detail/components/PhaseFeed/PhaseFeed.jsx @@ -5,26 +5,49 @@ */ import React from 'react' import _ from 'lodash' +import { withRouter } from 'react-router-dom' import ScrollableFeed from '../../../../components/Feed/ScrollableFeed' import spinnerWhileLoading from '../../../../components/LoadingSpinner' +import { scrollToHash } from '../../../../components/ScrollToAnchors' import './PhaseFeed.scss' -const PhaseFeedView = (props) => ( -
- -
-) +class PhaseFeedView extends React.Component { + constructor(props) { + super(props) + } + + componentDidMount() { + !_.isEmpty(this.props.location.hash) && this.handleUrlHash(this.props) + } + + // when the phase feed is actually loaded/rendered scroll to the appropriate post depending on url hash + handleUrlHash(props) { + const hashParts = _.split(location.hash.substring(1), '-') + const phaseId = hashParts[0] === 'phase' ? parseInt(hashParts[1], 10) : null + if (phaseId === props.phaseId) { + setTimeout(() => scrollToHash(props.location.hash), 100) + } + } + + render() { + return ( +
+ +
+ ) + } +} const enhance = spinnerWhileLoading(props => !props.isLoading) -const EnhancedPhaseFeedView = enhance(PhaseFeedView) +const EnhancedPhaseFeedView = enhance(withRouter(PhaseFeedView)) export default EnhancedPhaseFeedView diff --git a/src/projects/detail/components/ProjectStage.jsx b/src/projects/detail/components/ProjectStage.jsx index fea286765..96df9bfb3 100644 --- a/src/projects/detail/components/ProjectStage.jsx +++ b/src/projects/detail/components/ProjectStage.jsx @@ -5,6 +5,7 @@ import React from 'react' import PT from 'prop-types' import _ from 'lodash' import uncontrollable from 'uncontrollable' +import { withRouter } from 'react-router-dom' import { formatNumberWithCommas } from '../../../helpers/format' import { getPhaseActualData } from '../../../helpers/projectHelper' @@ -22,7 +23,6 @@ import ProductTimelineContainer from '../containers/ProductTimelineContainer' import NotificationsReader from '../../../components/NotificationsReader' import { phaseFeedHOC } from '../containers/PhaseFeedHOC' import spinnerWhileLoading from '../../../components/LoadingSpinner' -import { scrollToHash } from '../../../components/ScrollToAnchors' const enhance = spinnerWhileLoading(props => !props.processing) const EnhancedEditProjectForm = enhance(EditProjectForm) @@ -130,23 +130,28 @@ class ProjectStage extends React.Component{ expandProjectPhase(phase.id, tab) } - componentDidUpdate() { - const { phaseState } = this.props - if (_.get(phaseState, 'isExpanded')) { - const scrollTo = window.location.hash ? window.location.hash.substring(1) : null - if (scrollTo) { - scrollToHash(scrollTo) - } - } + componentDidMount() { + !_.isEmpty(this.props.location.hash) && this.handleUrlHash(this.props) } - componentWillReceiveProps(nextProps) { - const { feedId, commentId, phase, phaseState, expandProjectPhase } = this.props - const { feed } = nextProps - if (!_.get(phaseState, 'isExpanded') && feed && (feed.id === parseInt(feedId) || feed.postIds.includes(parseInt(commentId)))){ - expandProjectPhase(phase.id, 'posts') + componentDidUpdate(prevProps) { + const { location } = this.props + if (!_.isEmpty(location.hash) && location.hash !== prevProps.location.hash) { + this.handleUrlHash(this.props) } + } + + // expand a phase if necessary depending on the url hash + handleUrlHash(props) { + const { expandProjectPhase, phase, location } = props + const hashParts = _.split(location.hash.substring(1), '-') + const phaseId = hashParts[0] === 'phase' ? parseInt(hashParts[1], 10) : null + + if (phaseId && phase.id === phaseId) { + const tab = hashParts[2] + expandProjectPhase(phaseId, tab) + } } render() { @@ -169,6 +174,7 @@ class ProjectStage extends React.Component{ collapseProjectPhase, expandProjectPhase, commentAnchorPrefix, + isLoading, // comes from phaseFeedHOC currentUser, @@ -253,6 +259,8 @@ class ProjectStage extends React.Component{ projectMembers={projectMembers} onSaveMessage={onSaveMessage} commentAnchorPrefix={commentAnchorPrefix} + phaseId={phase.id} + isLoading={isLoading} /> )} @@ -278,6 +286,7 @@ class ProjectStage extends React.Component{ removeAttachment={this.removeProductAttachment} attachmentsStorePath={attachmentsStorePath} canManageAttachments={!!currentMemberRole} + disableAutoScrolling /> } @@ -311,7 +320,7 @@ ProjectStage.propTypes = { commentAnchorPrefix: PT.string, } -const ProjectStageUncontrollable = uncontrollable(ProjectStage, { +const ProjectStageUncontrollable = uncontrollable(withRouter(ProjectStage), { activeTab: 'onTabClick', }) diff --git a/src/projects/detail/components/ProjectStages.jsx b/src/projects/detail/components/ProjectStages.jsx index 744a368f9..595cf0aa3 100644 --- a/src/projects/detail/components/ProjectStages.jsx +++ b/src/projects/detail/components/ProjectStages.jsx @@ -10,6 +10,7 @@ import { withRouter } from 'react-router-dom' import { formatNumberWithCommas } from '../../../helpers/format' import { getPhaseActualData } from '../../../helpers/projectHelper' +import spinnerWhileLoading from '../../../components/LoadingSpinner' import Section from '../components/Section' import ProjectStage from '../components/ProjectStage' import PhaseCardListHeader from '../components/PhaseCardListHeader' @@ -93,6 +94,7 @@ const ProjectStages = ({ collapseProjectPhase, feedId, commentId, + location }) => (
@@ -100,6 +102,7 @@ const ProjectStages = ({ { phases.map((phase, index) => ( !props.isLoadingPhases) +const EnhancedProjectStages = enhance(ProjectStages) + +export default withRouter(EnhancedProjectStages) diff --git a/src/projects/detail/components/SpecSection.jsx b/src/projects/detail/components/SpecSection.jsx index da9efb25e..ed278e4b5 100644 --- a/src/projects/detail/components/SpecSection.jsx +++ b/src/projects/detail/components/SpecSection.jsx @@ -425,6 +425,7 @@ SpecSection.propTypes = { updateAttachment: PropTypes.func, removeAttachment: PropTypes.func, productCategories: PropTypes.array.isRequired, + disableAutoScrolling: PropTypes.bool, } export default scrollToAnchors(SpecSection) diff --git a/src/projects/detail/containers/DashboardContainer.jsx b/src/projects/detail/containers/DashboardContainer.jsx index a0ffb55df..3405f36d7 100644 --- a/src/projects/detail/containers/DashboardContainer.jsx +++ b/src/projects/detail/containers/DashboardContainer.jsx @@ -112,6 +112,7 @@ class DashboardContainer extends React.Component { phasesTopics, expandProjectPhase, collapseProjectPhase, + location, } = this.props // system notifications @@ -125,6 +126,7 @@ class DashboardContainer extends React.Component { const leftArea = ( -1, resetNewComment, prevProps) }).filter(item => item) - }, () => { - if (prevProps) { - // only scroll at first time - return - } - const scrollTo = window.location.hash ? window.location.hash.substring(1) : null - if (scrollTo) { - scrollToHash(scrollTo) - } }) } diff --git a/src/projects/detail/containers/PhaseFeedHOC.jsx b/src/projects/detail/containers/PhaseFeedHOC.jsx index 74e1127d0..d7f957d65 100644 --- a/src/projects/detail/containers/PhaseFeedHOC.jsx +++ b/src/projects/detail/containers/PhaseFeedHOC.jsx @@ -43,9 +43,9 @@ const phaseFeedHOC = (Component) => { } componentWillMount() { - const { isLoading, loadPhaseFeed, phase, project } = this.props + const { isLoading, topic, loadPhaseFeed, phase, project } = this.props - if (!isLoading) { + if (!isLoading && !topic) { loadPhaseFeed(project.id, phase.id) } } diff --git a/src/projects/detail/containers/ProjectInfoContainer.js b/src/projects/detail/containers/ProjectInfoContainer.js index 73bd4f3a9..bca064cf8 100644 --- a/src/projects/detail/containers/ProjectInfoContainer.js +++ b/src/projects/detail/containers/ProjectInfoContainer.js @@ -1,6 +1,7 @@ import React from 'react' import PropTypes from 'prop-types' import { connect } from 'react-redux' +import { withRouter } from 'react-router-dom' import update from 'react-addons-update' import _ from 'lodash' import LinksMenu from '../../../components/LinksMenu/LinksMenu' @@ -9,6 +10,7 @@ import TeamManagementContainer from './TeamManagementContainer' import { updateProject, deleteProject } from '../../actions/project' import { loadDashboardFeeds, loadProjectMessages } from '../../actions/projectTopics' import { loadPhaseFeed } from '../../actions/phasesTopics' +import { loadProjectPlan } from '../../actions/projectPlan' import { setDuration } from '../../../helpers/projectHelper' import { PROJECT_ROLE_OWNER, PROJECT_ROLE_COPILOT, PROJECT_ROLE_MANAGER, DIRECT_PROJECT_URL, SALESFORCE_PROJECT_LEAD_LINK, PROJECT_STATUS_CANCELLED, PROJECT_ATTACHMENTS_FOLDER, @@ -68,16 +70,14 @@ class ProjectInfoContainer extends React.Component { } componentWillMount() { - const { project, isFeedsLoading, feeds, loadDashboardFeeds, - loadProjectMessages, phases, phasesTopics, loadPhaseFeed, canAccessPrivatePosts } = this.props + const { project, isFeedsLoading, feeds, phases, phasesTopics, loadPhaseFeed, location } = this.props this.setDuration(project) // load feeds from dashboard if they are not currently loading or loaded yet // also it will load feeds, if we already loaded them, but it was 0 feeds before if (!isFeedsLoading && feeds.length < 1) { - loadDashboardFeeds(project.id) - canAccessPrivatePosts && loadProjectMessages(project.id) + this.loadAllFeeds() } // load phases feeds if they are not loaded yet @@ -87,10 +87,80 @@ class ProjectInfoContainer extends React.Component { loadPhaseFeed(project.id, phase.id) } }) + + // handle url hash + if (!_.isEmpty(location.hash)) { + this.handleUrlHash(this.props) + } } - componentWillReceiveProps({project}) { + componentWillReceiveProps(props) { + const { project, location } = props + this.setDuration(project) + + if (!_.isEmpty(location.hash) && location.hash !== this.props.location.hash) { + this.handleUrlHash(props) + } + } + + // this is just to see if the comment/feed/post/phase the url hash is attempting to scroll to is loaded or not + // if its not loaded then we load the appropriate item + handleUrlHash(props) { + const { project, isFeedsLoading, phases, phasesTopics, feeds, loadProjectPlan, loadPhaseFeed, location } = props + const hashParts = _.split(location.hash.substring(1), '-') + const hashPrimaryId = parseInt(hashParts[1], 10) + + switch (hashParts[0]) { + case 'comment': { + if (!isFeedsLoading && !this.foundCommentInFeeds(feeds, hashPrimaryId)) { + this.loadAllFeeds() + } + break + } + + case 'feed': { + if (!isFeedsLoading && !_.some(feeds, { id: hashPrimaryId})) { + this.loadAllFeeds() + } + break + } + + case 'phase': { + const postId = parseInt(hashParts[3], 10) + + if (phases && phasesTopics) { + if (!_.some(phases, { id: hashPrimaryId})) { + let existingUserIds = _.map(project.members, 'userId') + existingUserIds= _.union(existingUserIds, _.map(project.invites, 'userId')) + loadProjectPlan(project.id, existingUserIds) + } else if(postId && !(phasesTopics[hashPrimaryId].topic && phasesTopics[hashPrimaryId].topic.postIds.includes(postId))) { + loadPhaseFeed(project.id, hashPrimaryId) + } + } + break + } + } + } + + loadAllFeeds() { + const { canAccessPrivatePosts, loadDashboardFeeds, loadProjectMessages, project } = this.props + + loadDashboardFeeds(project.id) + canAccessPrivatePosts && loadProjectMessages(project.id) + } + + foundCommentInFeeds(feeds, commentId) { + let commentFound = false + + _.forEach(feeds, feed => _.forEach(feed.posts, post => { + if (post.id === commentId) { + commentFound = true + return false + } + })) + + return commentFound } onChangeStatus(projectId, status, reason) { @@ -537,6 +607,6 @@ const mapStateToProps = ({ templates, projectState, members, loadUser }) => { const mapDispatchToProps = { updateProject, deleteProject, addProjectAttachment, updateProjectAttachment, loadProjectMessages, discardAttachments, uploadProjectAttachments, loadDashboardFeeds, loadPhaseFeed, changeAttachmentPermission, - removeProjectAttachment, saveFeedComment } + removeProjectAttachment, loadProjectPlan, saveFeedComment } -export default connect(mapStateToProps, mapDispatchToProps)(ProjectInfoContainer) +export default connect(mapStateToProps, mapDispatchToProps)(withRouter(ProjectInfoContainer)) diff --git a/src/projects/detail/containers/ProjectPlanContainer.jsx b/src/projects/detail/containers/ProjectPlanContainer.jsx index 460530cbd..f9bbb0147 100644 --- a/src/projects/detail/containers/ProjectPlanContainer.jsx +++ b/src/projects/detail/containers/ProjectPlanContainer.jsx @@ -7,6 +7,7 @@ import React from 'react' import PT from 'prop-types' import { connect } from 'react-redux' +import { withRouter } from 'react-router-dom' import _ from 'lodash' import { @@ -63,27 +64,16 @@ class ProjectPlanContainer extends React.Component { } componentDidMount() { - const { expandProjectPhase } = this.props - const scrollTo = window.location.hash ? window.location.hash.substring(1) : null - if (scrollTo) { - const hashParts = _.split(scrollTo, '-') - const phaseId = hashParts[0] === 'phase' ? parseInt(hashParts[1], 10) : null - if (phaseId) { - let tab = hashParts[2] - tab = tab === scrollTo ? 'timeline' : tab - // we just open tab, while smooth scrolling has to be caused by URL hash - expandProjectPhase(phaseId, tab) - } - } else { - // if the user is a customer and its not a direct link to a particular phase - // then by default expand all phases which are active - if (this.props.isCustomerUser) { - _.forEach(this.props.phases, phase => { - if (phase.status === PHASE_STATUS_ACTIVE) { - expandProjectPhase(phase.id) - } - }) - } + const { expandProjectPhase, location } = this.props + + // if the user is a customer and its not a direct link to a particular phase + // then by default expand all phases which are active + if (_.isEmpty(location.hash) && this.props.isCustomerUser) { + _.forEach(this.props.phases, phase => { + if (phase.status === PHASE_STATUS_ACTIVE) { + expandProjectPhase(phase.id) + } + }) } } @@ -106,6 +96,8 @@ class ProjectPlanContainer extends React.Component { productsTimelines, phasesTopics, isProcessing, + isLoadingPhases, + location } = this.props // manager user sees all phases @@ -123,6 +115,7 @@ class ProjectPlanContainer extends React.Component { const leftArea = ( )} - {isProjectLive && checkPermission(PERMISSIONS.EDIT_PROJECT_PLAN, project, phases) && (
+ {isProjectLive && checkPermission(PERMISSIONS.EDIT_PROJECT_PLAN, project, phases) && !isLoadingPhases && (
Add New Phase
)} @@ -208,6 +201,7 @@ const mapStateToProps = ({ projectState, projectTopics, phasesTopics, templates isFeedsLoading: projectTopics.isLoading, phasesTopics, phasesStates: projectState.phasesStates, + isLoadingPhases: projectState.isLoadingPhases, } } @@ -224,4 +218,4 @@ const mapDispatchToProps = { collapseAllProjectPhases, } -export default connect(mapStateToProps, mapDispatchToProps)(ProjectPlanContainer) +export default connect(mapStateToProps, mapDispatchToProps)(withRouter(ProjectPlanContainer)) diff --git a/src/projects/detail/containers/SingleFeedContainer.jsx b/src/projects/detail/containers/SingleFeedContainer.jsx index 844b9671c..84f4687fa 100644 --- a/src/projects/detail/containers/SingleFeedContainer.jsx +++ b/src/projects/detail/containers/SingleFeedContainer.jsx @@ -14,6 +14,7 @@ import React from 'react' import _ from 'lodash' import ScrollableFeed from '../../../components/Feed/ScrollableFeed' +import { scrollToHash } from '../../../components/ScrollToAnchors' const bindMethods = [ 'onNewCommentChange', @@ -43,6 +44,22 @@ class SingleFeedContainer extends React.Component { }) } + componentDidMount() { + // we use this to just scroll to a feed block or comment in a feed block, + // only if there is a url hash and the feed id and comment id match this feed + const scrollTo = window.location.hash ? window.location.hash.substring(1) : null + if (scrollTo) { + const hashParts = _.split(scrollTo, '-') + const hashId = parseInt(hashParts[1], 10) + if ( + (hashParts[0] === 'feed' && hashId === this.props.id) || + (hashParts[0] === 'comment' && this.props.comments && _.some(this.props.comments, { id: hashId })) + ) { + setTimeout(() => scrollToHash(scrollTo), 100) + } + } + } + render() { const nonBindProps = _.omit(this.props, bindMethods) const bindProps = _.zipObject(bindMethods, bindMethods.map((method) => this[method]))