assemblymade/meta

View on GitHub
app/assets/javascripts/components/news_feed/news_feed_item.js.jsx

Summary

Maintainability
F
3 days
Test Coverage
'use strict';

const ActivityFeedComment = require('../activity_feed_comment.js.jsx');
const AppIcon = require('../app_icon.js.jsx');
const ArchivedNewsFeedItemsStore = require('../../stores/archived_news_feed_items_store');
const Avatar = require('../ui/avatar.js.jsx');
const Bounty = require('../bounty.js.jsx')
const Comment = require('../comment.js.jsx');
const Discussion = require('../ui/discussion.js.jsx')
const Heart = require('../heart.js.jsx');
const Introduction = require('../introduction.js.jsx')
const Lightbox = require('../lightbox.js.jsx')
const Markdown = require('../markdown.js.jsx');
const moment = require('moment');
const NewsFeedItemActionCreators = require('../../actions/news_feed_item_action_creators');
const NewsFeedItemBounty = require('./news_feed_item_bounty.js.jsx');
const NewsFeedItemIntroduction = require('./news_feed_item_introduction.js.jsx');
const NewsFeedItemModal = require('./news_feed_item_modal.js.jsx');
const NewsFeedItemPost = require('./news_feed_item_post.js.jsx');
const ProductStore = require('../../stores/product_store');
const SubscriptionsStore = require('../../stores/subscriptions_store');
const SvgIcon = require('../ui/svg_icon.js.jsx')
const Tag = require('../tag.js.jsx');
const Tile = require('../ui/tile.js.jsx');
const Update = require('../update.js.jsx')
const UserStore = require('../../stores/user_store');

const ONE_DAY = 24 * 60 * 60 * 1000;

let NewsFeedItem = React.createClass({
  propTypes: {
    commentable: React.PropTypes.bool,
    enableModal: React.PropTypes.bool,
    productPage: React.PropTypes.bool,
    product: React.PropTypes.object.isRequired,
    showAllComments: React.PropTypes.bool,
    target: React.PropTypes.object,
    user: React.PropTypes.object.isRequired
  },

  typeMap: {
    task: 'bounty',
    team_membership: 'team member',
    news_feed_item_post: 'update'
  },

  componentDidMount: function() {
    ArchivedNewsFeedItemsStore.addChangeListener(this.getStateFromStore);
    SubscriptionsStore.addChangeListener(this.getStateFromStore);
  },

  componentWillUnmount: function() {
    ArchivedNewsFeedItemsStore.removeChangeListener(this.getStateFromStore);
    SubscriptionsStore.removeChangeListener(this.getStateFromStore);
  },

  getDefaultProps: function() {
    return {
      enableModal: true,
      productPage: false
    };
  },

  getInitialState: function() {
    let id = this.props.id;

    return {
      isArchived: ArchivedNewsFeedItemsStore.isArchived(id),
      isSubscribed: SubscriptionsStore.isSubscribed(id),
      modalShown: false
    };
  },

  getStateFromStore: function() {
    let target = this.props.target;

    if (target && target.type === 'post') {
      this.setState({
        isArchived: ArchivedNewsFeedItemsStore.isArchived(this.props.id),
        isSubscribed: SubscriptionsStore.isSubscribed(this.props.id)
      });
    }
  },

  handleArchive: function() {
    let productSlug = this.props.product.slug;
    let itemId = this.props.id;

    if (this.state.isArchived) {
      NewsFeedItemActionCreators.unarchive(productSlug, itemId);
    } else {
      NewsFeedItemActionCreators.archive(productSlug, itemId);
    }
  },

  handleSubscribe: function() {
    let productSlug = this.props.product.slug;
    let itemId = this.props.id;

    if (this.state.isSubscribed) {
      NewsFeedItemActionCreators.unsubscribe(productSlug, itemId);
    } else {
      NewsFeedItemActionCreators.subscribe(productSlug, itemId);
    }
  },

  onModalHidden: function() {
    this.setState({
      modalShown: false
    });
  },

  render: function() {
    return (
      <Tile>
        {this.props.productPage ? null : this.renderSource()}
        {this.renderTarget()}
        {this.props.enableModal && this.state.modalShown ? this.renderModal() : null}
        {this.renderTags()}
        {this.renderDetails()}
        {this.renderFooter()}
        {this.props.showAllComments ? null : this.renderLastComment()}
      </Tile>
    );
  },

  renderArchiveButton: function() {
    if (!ProductStore.isCoreTeam(UserStore.getUser())) {
      return;
    }

    // only turn on for posts
    if (this.props.target && this.props.target.type === 'post') {
      let text = 'Archive';
      if (this.state.isArchived) {
        text = 'Unarchive';
      }

      return (
        <li>
          <a href="javascript:void(0);" onClick={this.handleArchive}>{text}</a>
        </li>
      );
    }
  },

  renderLastComment: function() {
    let comment = this.props.last_comment

    if (comment) {
      return (
        <div className="px3 border-top">
          <ActivityFeedComment author={comment.user} body={comment.markdown_body} heartable={false} />
        </div>
      )
    }
  },

  renderEditButton: function() {
    if (ProductStore.isCoreTeam(UserStore.getUser()) || this.props.user.id === UserStore.getId()) {
      let target = this.props.target;

      // only turn on for posts
      if (target && target.type === 'post') {
        return (
          <li>
            <a href={target.url + '/edit'}>Edit</a>
          </li>
        );
      }
    }
  },

  renderTags: function() {
    let target = this.props.target
    let tags = target && (target.tags || target.marks);
    let tagItems;

    if (tags && tags.length) {
      tagItems = _.map(tags, function(tag) {
        return (
          <div className="inline-block" key={tag.id}>
            <Tag tag={tag} />
          </div>
        )
      })

      return (
        <div className="px3 pb3">
          {tagItems}
        </div>
      );
    }
  },

  renderDetails: function() {
    return (
      <div className="border-top clearfix">
        {this.renderBountyValue()}

        <div className="right gray-2 h6 mt0 mb0" style={{ lineHeight: '24px' }}>
          {this.renderCommentsCount()}
          {this.renderShare()}
          {this.renderHeart()}
        </div>
      </div>
    )
  },

  renderBountyValue: function() {
    let target = this.props.target;

    if (target && target.type == 'task') {
      return (
        <div className="px3 left h4 mt0 mb0" style={{ paddingTop: '1.5rem', paddingBottom: '1.5rem' }}>
          <AppCoins n={target.earnable_cents} />
        </div>
      )
    }
  },

  renderCommentsCount: function() {
    let _commentsCount = function() {
      let commentsCount = this.props.comments_count
      let target = this.props.target;

      if (this.props.target && this.props.target.url) {
        return (
          <a href={this.props.target.url} className="gray-1">
            {commentsCount} {commentsCount === 1 ? 'Comment' : 'Comments'}
          </a>
        );
      }

      return (
        <span>
          {commentsCount} {commentsCount === 1 ? 'Comment' : 'Comments'}
        </span>
      );
    }.bind(this);

    return (
      <div className="px3 inline-block" style={{ paddingTop: '1.5rem', paddingBottom: '1.5rem', fill: '#C2C7D0' }}>
        <SvgIcon type="comment" />
        <span className="ml1">
          {_commentsCount()}
        </span>
      </div>
    )
  },

  // TODO
  renderShare: function() {
  },

  renderHeart: function() {
    return (
      <div className="px3 inline-block border-left" style={{ paddingTop: '1.5rem', paddingBottom: '1.5rem', fill: '#C2C7D0' }}>
        <Heart size="medium" heartable_id={this.props.id} heartable_type="NewsFeedItem" />
      </div>
    )
  },

  renderFooter: function() {
    if (this.props.showAllComments) {
      return (
        <div className="card-footer px3 py2 clearfix">
          <ul className="list-inline mt0 mb0 py1 right">
            {this.renderArchiveButton()}
            {this.renderSubscribeButton()}
            {this.renderEditButton()}
          </ul>
        </div>
      );
    }
  },

  renderModal: function() {
    let item = this.props
    let product = this.props.product
    let target = this.props.target

    let modalTarget;

    target.product = product

    switch (target.type) {
    case 'task':
      modalTarget = <Bounty
        key={target.id}
        bounty={target}
        noInvites={true}
        item={item}
        editCoins={false} />
      break
    case 'post':
      modalTarget = <Update update={target}
                            item={item}
                            newsFeedItem={item}
                            productSlug={product.slug} />
      break
    case 'team_membership':
      item.bio = target.bio;
      modalTarget = <Introduction introduction={item} />
      break
    default:
      modalTarget = <NewsFeedItem {...item}
                                  commentable={false}
                                  enableModal={false}
                                  productPage={true}
                                  showAllComments={false} />
      break
    }

    return (
      <Lightbox onHidden={this.onModalHidden} showControlledOuside={true}>
        <Discussion newsFeedItem={item}>{modalTarget}</Discussion>
      </Lightbox>
    );
  },

  renderSource: function() {
    let product = this.props.product

    if (!product) {
      return;
    }

    return (
      <a href={product.url} className="block clearfix border-bottom px3 py2">
        <div className="left mr2">
          <AppIcon app={product} size={18} />
        </div>
        <div className="overflow-hidden">
          <h6 className="black mt0 mb0">{product.name}</h6>
        </div>
      </a>
    );
  },

  renderSubscribeButton: function() {
    let user = UserStore.getUser();

    if (!user) {
      return (
        <li>
          <a href="/signup">Sign up</a>
        </li>
      );
    }

    // only turn on for posts
    if (this.props.target && this.props.target.type === 'post') {
      let text = 'Subscribe';
      if (this.state.isSubscribed) {
        text = 'Unsubscribe';
      }

      return (
        <li>
          <a href="javascript:void(0);" onClick={this.handleSubscribe}>{text}</a>
        </li>
      );
    }
  },

  renderTarget: function() {
    let product = this.props.product;
    let target = this.props.target;
    let triggerModal = this.triggerModal;

    if (target) {
      switch (target.type) {
      case 'task':
        return <NewsFeedItemBounty
            {...this.props}
            item={this.props}
            triggerModal={triggerModal} />;

      case 'team_membership':
        return <NewsFeedItemIntroduction
            {...this.props}
            triggerModal={triggerModal} />;

      case 'discussion':
        return <NewsFeedItemPost
            {...this.props}
            body={target.markdown_body || target.description_html}
            url={target.url}
            title={target.title}
            triggerModal={triggerModal} />;

      case 'post':
        return <NewsFeedItemPost
            {...this.props}
            body={this.props.enableModal ? target.short_body : target.markdown_body}
            url={target.url}
            title={target.title}
            triggerModal={triggerModal} />;

      default:
        return <NewsFeedItemPost
            {...this.props}
            title={target.name || target.title}
            body={target.description ||
                  target.markdown_body ||
                  target.description_html ||
                  target.body}
            url={target.url}
            triggerModal={triggerModal} />;
      }
    }
  },

  targetNoun: function(type) {
    let typeMap = this.typeMap;

    if (typeMap[type]) {
      return typeMap[type];
    }

    return type;
  },

  triggerModal: function() {
    this.setState({
      modalShown: true
    });
  }
});

module.exports = window.NewsFeedItem = NewsFeedItem;