<commit_before>import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
import StatusPrepend from './status_prepend';
import StatusHeader from './status_header';
import StatusIcons from './status_icons';
import StatusContent from './status_content';
import StatusActionBar from './status_action_bar';
import AttachmentList from './attachment_list';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { MediaGallery, Video } from 'flavours/glitch/util/async-components';
import { HotKeys } from 'react-hotkeys';
import NotificationOverlayContainer from 'flavours/glitch/features/notifications/containers/overlay_container';
import classNames from 'classnames';

// We use the component (and not the container) since we do not want
// to use the progress bar to show download progress
import Bundle from '../features/ui/components/bundle';

export default class Status extends ImmutablePureComponent {

  static contextTypes = {
    router: PropTypes.object,
  };

  static propTypes = {
    containerId: PropTypes.string,
    id: PropTypes.string,
    status: ImmutablePropTypes.map,
    account: ImmutablePropTypes.map,
    onReply: PropTypes.func,
    onFavourite: PropTypes.func,
    onReblog: PropTypes.func,
    onDelete: PropTypes.func,
    onPin: PropTypes.func,
    onOpenMedia: PropTypes.func,
    onOpenVideo: PropTypes.func,
    onBlock: PropTypes.func,
    onEmbed: PropTypes.func,
    onHeightChange: PropTypes.func,
    muted: PropTypes.bool,
    collapse: PropTypes.bool,
    hidden: PropTypes.bool,
    prepend: PropTypes.string,
    withDismiss: PropTypes.bool,
    onMoveUp: PropTypes.func,
    onMoveDown: PropTypes.func,
    getScrollPosition: PropTypes.func,
    updateScrollBottom: PropTypes.func,
    expanded: PropTypes.bool,
  };

  state = {
    isExpanded: this.props.expanded,
    isCollapsed: false,
    autoCollapsed: false,
  }

  // Avoid checking props that are functions (and whose equality will always
  // evaluate to false. See react-immutable-pure-component for usage.
  updateOnProps = [
    'status',
    'account',
    'settings',
    'prepend',
    'boostModal',
    'favouriteModal',
    'muted',
    'collapse',
    'notification',
    'hidden',
    'expanded',
  ]

  updateOnStates = [
    'isExpanded',
    'isCollapsed',
  ]

  //  If our settings have changed to disable collapsed statuses, then we
  //  need to make sure that we uncollapse every one. We do that by watching
  //  for changes to `settings.collapsed.enabled` in
  //  `componentWillReceiveProps()`.

  //  We also need to watch for changes on the `collapse` prop---if this
  //  changes to anything other than `undefined`, then we need to collapse or
  //  uncollapse our status accordingly.
  componentWillReceiveProps (nextProps) {
    if (!nextProps.settings.getIn(['collapsed', 'enabled'])) {
      if (this.state.isCollapsed) {
        this.setCollapsed(false);
      }
    } else if (
      nextProps.collapse !== this.props.collapse &&
      nextProps.collapse !== undefined
    ) this.setCollapsed(nextProps.collapse);
    if (nextProps.expanded !== this.props.expanded &&
      nextProps.expanded !== undefined
    ) this.setExpansion(nextProps.expanded);
  }

  //  When mounting, we just check to see if our status should be collapsed,
  //  and collapse it if so. We don't need to worry about whether collapsing
  //  is enabled here, because `setCollapsed()` already takes that into
  //  account.

  //  The cases where a status should be collapsed are:
  //
  //   -  The `collapse` prop has been set to `true`
  //   -  The user has decided in local settings to collapse all statuses.
  //   -  The user has decided to collapse all notifications ('muted'
  //      statuses).
  //   -  The user has decided to collapse long statuses and the status is
  //      over 400px (without media, or 650px with).
  //   -  The status is a reply and the user has decided to collapse all
  //      replies.
  //   -  The status contains media and the user has decided to collapse all
  //      statuses with media.
  //   -  The status is a reblog the user has decided to collapse all
  //      statuses which are reblogs.
  componentDidMount () {
    const { node } = this;
    const {
      status,
      settings,
      collapse,
      muted,
      prepend,
    } = this.props;
    const autoCollapseSettings = settings.getIn(['collapsed', 'auto']);

    if (function () {
      switch (true) {
      case !!collapse:
      case !!autoCollapseSettings.get('all'):
      case autoCollapseSettings.get('notifications') && !!muted:
      case autoCollapseSettings.get('lengthy') && node.clientHeight > (
        status.get('media_attachments').size && !muted ? 650 : 400
      ):
      case autoCollapseSettings.get('reblogs') && prepend === 'reblogged_by':
      case autoCollapseSettings.get('replies') && status.get('in_reply_to_id', null) !== null:
      case autoCollapseSettings.get('media') && !(status.get('spoiler_text').length) && !!status.get('media_attachments').size:
        return true;
      default:
        return false;
      }
    }()) {
      this.setCollapsed(true);
      // Hack to fix timeline jumps on second rendering when auto-collapsing
      this.setState({ autoCollapsed: true });
    }
  }

  getSnapshotBeforeUpdate (prevProps, prevState) {
    if (this.props.getScrollPosition) {
      return this.props.getScrollPosition();
    } else {
      return null;
    }
  }

  //  Hack to fix timeline jumps on second rendering when auto-collapsing
  componentDidUpdate (prevProps, prevState, snapshot) {
    if (this.state.autoCollapsed) {
      this.setState({ autoCollapsed: false });
      if (snapshot !== null && this.props.updateScrollBottom) {
        if (this.node.offsetTop < snapshot.top) {
          this.props.updateScrollBottom(snapshot.height - snapshot.top);
        }
      }
    }
  }

  //  `setCollapsed()` sets the value of `isCollapsed` in our state, that is,
  //  whether the toot is collapsed or not.

  //  `setCollapsed()` automatically checks for us whether toot collapsing
  //  is enabled, so we don't have to.
  setCollapsed = (value) => {
    if (this.props.settings.getIn(['collapsed', 'enabled'])) {
      this.setState({ isCollapsed: value });
      if (value) {
        this.setExpansion(false);
      }
    } else {
      this.setState({ isCollapsed: false });
    }
  }

  setExpansion = (value) => {
    this.setState({ isExpanded: value });
    if (value) {
      this.setCollapsed(false);
    }
  }

  //  `parseClick()` takes a click event and responds appropriately.
  //  If our status is collapsed, then clicking on it should uncollapse it.
  //  If `Shift` is held, then clicking on it should collapse it.
  //  Otherwise, we open the url handed to us in `destination`, if
  //  applicable.
  parseClick = (e, destination) => {
    const { router } = this.context;
    const { status } = this.props;
    const { isCollapsed } = this.state;
    if (!router) return;
    if (destination === undefined) {
      destination = `/statuses/${
        status.getIn(['reblog', 'id'], status.get('id'))
      }`;
    }
    if (e.button === 0) {
      if (isCollapsed) this.setCollapsed(false);
      else if (e.shiftKey) {
        this.setCollapsed(true);
        document.getSelection().removeAllRanges();
      } else router.history.push(destination);
      e.preventDefault();
    }
  }

  handleAccountClick = (e) => {
    if (this.context.router && e.button === 0) {
      const id = e.currentTarget.getAttribute('data-id');
      e.preventDefault();
      this.context.router.history.push(`/accounts/${id}`);
    }
  }

  handleExpandedToggle = () => {
    if (this.props.status.get('spoiler_text')) {
      this.setExpansion(!this.state.isExpanded);
    }
  };

  handleOpenVideo = startTime => {
    this.props.onOpenVideo(this.props.status.getIn(['media_attachments', 0]), startTime);
  }

  handleHotkeyReply = e => {
    e.preventDefault();
    this.props.onReply(this.props.status, this.context.router.history);
  }

  handleHotkeyFavourite = (e) => {
    this.props.onFavourite(this.props.status, e);
  }

  handleHotkeyBoost = e => {
    this.props.onReblog(this.props.status, e);
  }

  handleHotkeyMention = e => {
    e.preventDefault();
    this.props.onMention(this.props.status.get('account'), this.context.router.history);
  }

  handleHotkeyOpen = () => {
    this.context.router.history.push(`/statuses/${this.props.status.get('id')}`);
  }

  handleHotkeyOpenProfile = () => {
    this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`);
  }

  handleHotkeyMoveUp = e => {
    this.props.onMoveUp(this.props.containerId || this.props.id, e.target.getAttribute('data-featured'));
  }

  handleHotkeyMoveDown = e => {
    this.props.onMoveDown(this.props.containerId || this.props.id, e.target.getAttribute('data-featured'));
  }

  handleRef = c => {
    this.node = c;
  }

  renderLoadingMediaGallery () {
    return
;
  }

  renderLoadingVideoPlayer () {
    return
;
  }

  render () {
    const {
      handleRef,
      parseClick,
      setExpansion,
      setCollapsed,
    } = this;
    const { router } = this.context;
    const {
      status,
      account,
      settings,
      collapsed,
      muted,
      prepend,
      intersectionObserverWrapper,
      onOpenVideo,
      onOpenMedia,
      notification,
      hidden,
      featured,
      ...other
    } = this.props;
    const { isExpanded, isCollapsed } = this.state;
    let background = null;
    let attachments = null;
    let media = null;
    let mediaIcon = null;

    if (status === null) {
      return null;
    }

    if (hidden) {
      return (
        <div
          ref={this.handleRef}
          data-id={status.get('id')}
          style={{
            height: `${this.height}px`,
            opacity: 0,
            overflow: 'hidden',
          }}
        >
          {status.getIn(['account', 'display_name']) || status.getIn(['account', 'username'])}
          {' '}
          {status.get('content')}

      );
    }

    //  If user backgrounds for collapsed statuses are enabled, then we
    //  initialize our background accordingly. This will only be rendered if
    //  the status is collapsed.
    if (settings.getIn(['collapsed', 'backgrounds', 'user_backgrounds'])) {
      background = status.getIn(['account', 'header']);
    }

    //  This handles our media attachments.
    //  If a media file is of unknwon type or if the status is muted
    //  (notification), we show a list of links instead of embedded media.

    //  After we have generated our appropriate media element and stored it in
    //  `media`, we snatch the thumbnail to use as our `background` if media
    //  backgrounds for collapsed statuses are enabled.
    attachments = status.get('media_attachments');
    if (attachments.size > 0) {
      if (muted || attachments.some(item => item.get('type') === 'unknown')) {
        media = (
          <AttachmentList
            compact
            media={status.get('media_attachments')}
          />
        );
      } else if (attachments.getIn([0, 'type']) === 'video') {  //  Media type is 'video'
        const video = status.getIn(['media_attachments', 0]);

        media = (
          <Bundle fetchComponent={Video} loading={this.renderLoadingVideoPlayer} >
            {Component => (<Component
              preview={video.get('preview_url')}
              src={video.get('url')}
              inline
              sensitive={status.get('sensitive')}
              letterbox={settings.getIn(['media', 'letterbox'])}
              fullwidth={settings.getIn(['media', 'fullwidth'])}
              onOpenVideo={this.handleOpenVideo}
            />)}
          </Bundle>
        );
        mediaIcon = 'video-camera';
      } else {  //  Media type is 'image' or 'gifv'
        media = (
          <Bundle fetchComponent={MediaGallery} loading={this.renderLoadingMediaGallery} >
            {Component => (
              <Component
                media={attachments}
                sensitive={status.get('sensitive')}
                letterbox={settings.getIn(['media', 'letterbox'])}
                fullwidth={settings.getIn(['media', 'fullwidth'])}
                onOpenMedia={this.props.onOpenMedia}
              />
            )}
          </Bundle>
        );
        mediaIcon = 'picture-o';
      }

      if (!status.get('sensitive') && !(status.get('spoiler_text').length > 0) && settings.getIn(['collapsed', 'backgrounds', 'preview_images'])) {
        background = attachments.getIn([0, 'preview_url']);
      }
    }

    //  Here we prepare extra data-* attributes for CSS selectors.
    //  Users can use those for theming, hiding avatars etc via UserStyle
    const selectorAttribs = {
      'data-status-by': `@${status.getIn(['account', 'acct'])}`,
    };

    if (prepend && account) {
      const notifKind = {
        favourite: 'favourited',
        reblog: 'boosted',
        reblogged_by: 'boosted',
      }[prepend];

      selectorAttribs[`data-${notifKind}-by`] = `@${account.get('acct')}`;
    }

    const handlers = {
      reply: this.handleHotkeyReply,
      favourite: this.handleHotkeyFavourite,
      boost: this.handleHotkeyBoost,
      mention: this.handleHotkeyMention,
      open: this.handleHotkeyOpen,
      openProfile: this.handleHotkeyOpenProfile,
      moveUp: this.handleHotkeyMoveUp,
      moveDown: this.handleHotkeyMoveDown,
      toggleSpoiler: this.handleExpandedToggle,
    };

    const computedClass = classNames('status', `status-${status.get('visibility')}`, {
      collapsed: isCollapsed,
      'has-background': isCollapsed && background,
      muted,
    }, 'focusable');

    return (
      <HotKeys handlers={handlers}>
        <div
          className={computedClass}
          style={isCollapsed && background ? { backgroundImage: `url(${background})` } : null}
          {...selectorAttribs}
          ref={handleRef}
          tabIndex='0'
          data-featured={featured ? 'true' : null}
        >

              {prepend && account ? (
                <StatusPrepend
                  type={prepend}
                  account={account}
                  parseClick={parseClick}
                  notificationId={this.props.notificationId}
                />
              ) : null}
              {!muted || !isCollapsed ? (
                <StatusHeader
                  status={status}
                  friend={account}
                  collapsed={isCollapsed}
                  parseClick={parseClick}
                />
              ) : null}

            <StatusIcons
              status={status}
              mediaIcon={mediaIcon}
              collapsible={settings.getIn(['collapsed', 'enabled'])}
              collapsed={isCollapsed}
              setCollapsed={setCollapsed}
            />

          <StatusContent
            status={status}
            media={media}
            mediaIcon={mediaIcon}
            expanded={isExpanded}
            onExpandedToggle={this.handleExpandedToggle}
            parseClick={parseClick}
            disabled={!router}
          />
          {!isCollapsed || !muted ? (
            <StatusActionBar
              {...other}
              status={status}
              account={status.get('account')}
            />
          ) : null}
          {notification ? (
            <NotificationOverlayContainer
              notification={notification}
            />
          ) : null}

      </HotKeys>
    );
  }

}
<commit_msg>Use new getDerivedStateFromProps instead of deprecated componentWillReceiveProps<commit_after>import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
import StatusPrepend from './status_prepend';
import StatusHeader from './status_header';
import StatusIcons from './status_icons';
import StatusContent from './status_content';
import StatusActionBar from './status_action_bar';
import AttachmentList from './attachment_list';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { MediaGallery, Video } from 'flavours/glitch/util/async-components';
import { HotKeys } from 'react-hotkeys';
import NotificationOverlayContainer from 'flavours/glitch/features/notifications/containers/overlay_container';
import classNames from 'classnames';

// We use the component (and not the container) since we do not want
// to use the progress bar to show download progress
import Bundle from '../features/ui/components/bundle';

export default class Status extends ImmutablePureComponent {

  static contextTypes = {
    router: PropTypes.object,
  };

  static propTypes = {
    containerId: PropTypes.string,
    id: PropTypes.string,
    status: ImmutablePropTypes.map,
    account: ImmutablePropTypes.map,
    onReply: PropTypes.func,
    onFavourite: PropTypes.func,
    onReblog: PropTypes.func,
    onDelete: PropTypes.func,
    onPin: PropTypes.func,
    onOpenMedia: PropTypes.func,
    onOpenVideo: PropTypes.func,
    onBlock: PropTypes.func,
    onEmbed: PropTypes.func,
    onHeightChange: PropTypes.func,
    muted: PropTypes.bool,
    collapse: PropTypes.bool,
    hidden: PropTypes.bool,
    prepend: PropTypes.string,
    withDismiss: PropTypes.bool,
    onMoveUp: PropTypes.func,
    onMoveDown: PropTypes.func,
    getScrollPosition: PropTypes.func,
    updateScrollBottom: PropTypes.func,
    expanded: PropTypes.bool,
  };

  state = {
    isCollapsed: false,
    autoCollapsed: false,
  }

  // Avoid checking props that are functions (and whose equality will always
  // evaluate to false. See react-immutable-pure-component for usage.
  updateOnProps = [
    'status',
    'account',
    'settings',
    'prepend',
    'boostModal',
    'favouriteModal',
    'muted',
    'collapse',
    'notification',
    'hidden',
    'expanded',
  ]

  updateOnStates = [
    'isExpanded',
    'isCollapsed',
  ]

  //  If our settings have changed to disable collapsed statuses, then we
  //  need to make sure that we uncollapse every one. We do that by watching
  //  for changes to `settings.collapsed.enabled` in
  //  `getderivedStateFromProps()`.

  //  We also need to watch for changes on the `collapse` prop---if this
  //  changes to anything other than `undefined`, then we need to collapse or
  //  uncollapse our status accordingly.
  static getDerivedStateFromProps(nextProps, prevState) {
    let update = {};
    let updated = false;

    // Make sure the state mirrors props we track…
    if (nextProps.collapse !== prevState.collapseProp) {
      update.collapseProp = nextProps.collapse;
      updated = true;
    }
    if (nextProps.expanded !== prevState.expandedProp) {
      update.expandedProp = nextProps.expanded;
      updated = true;
    }

    // Update state based on new props
    if (!nextProps.settings.getIn(['collapsed', 'enabled'])) {
      if (prevState.isCollapsed) {
        update.isCollapsed = false;
        updated = true;
      }
    } else if (
      nextProps.collapse !== prevState.collapseProp &&
      nextProps.collapse !== undefined
    ) {
      update.isCollapsed = nextProps.collapse;
      if (nextProps.collapse) update.isExpanded = false;
      updated = true;
    }
    if (nextProps.expanded !== prevState.expandedProp &&
      nextProps.expanded !== undefined
    ) {
      update.isExpanded = nextProps.expanded;
      if (nextProps.expanded) update.isCollapsed = false;
      updated = true;
    }

    return updated ? update : null;
  }

  //  When mounting, we just check to see if our status should be collapsed,
  //  and collapse it if so. We don't need to worry about whether collapsing
  //  is enabled here, because `setCollapsed()` already takes that into
  //  account.

  //  The cases where a status should be collapsed are:
  //
  //   -  The `collapse` prop has been set to `true`
  //   -  The user has decided in local settings to collapse all statuses.
  //   -  The user has decided to collapse all notifications ('muted'
  //      statuses).
  //   -  The user has decided to collapse long statuses and the status is
  //      over 400px (without media, or 650px with).
  //   -  The status is a reply and the user has decided to collapse all
  //      replies.
  //   -  The status contains media and the user has decided to collapse all
  //      statuses with media.
  //   -  The status is a reblog the user has decided to collapse all
  //      statuses which are reblogs.
  componentDidMount () {
    const { node } = this;
    const {
      status,
      settings,
      collapse,
      muted,
      prepend,
    } = this.props;
    const autoCollapseSettings = settings.getIn(['collapsed', 'auto']);

    if (function () {
      switch (true) {
      case !!collapse:
      case !!autoCollapseSettings.get('all'):
      case autoCollapseSettings.get('notifications') && !!muted:
      case autoCollapseSettings.get('lengthy') && node.clientHeight > (
        status.get('media_attachments').size && !muted ? 650 : 400
      ):
      case autoCollapseSettings.get('reblogs') && prepend === 'reblogged_by':
      case autoCollapseSettings.get('replies') && status.get('in_reply_to_id', null) !== null:
      case autoCollapseSettings.get('media') && !(status.get('spoiler_text').length) && !!status.get('media_attachments').size:
        return true;
      default:
        return false;
      }
    }()) {
      this.setCollapsed(true);
      // Hack to fix timeline jumps on second rendering when auto-collapsing
      this.setState({ autoCollapsed: true });
    }
  }

  getSnapshotBeforeUpdate (prevProps, prevState) {
    if (this.props.getScrollPosition) {
      return this.props.getScrollPosition();
    } else {
      return null;
    }
  }

  //  Hack to fix timeline jumps on second rendering when auto-collapsing
  componentDidUpdate (prevProps, prevState, snapshot) {
    if (this.state.autoCollapsed) {
      this.setState({ autoCollapsed: false });
      if (snapshot !== null && this.props.updateScrollBottom) {
        if (this.node.offsetTop < snapshot.top) {
          this.props.updateScrollBottom(snapshot.height - snapshot.top);
        }
      }
    }
  }

  //  `setCollapsed()` sets the value of `isCollapsed` in our state, that is,
  //  whether the toot is collapsed or not.

  //  `setCollapsed()` automatically checks for us whether toot collapsing
  //  is enabled, so we don't have to.
  setCollapsed = (value) => {
    if (this.props.settings.getIn(['collapsed', 'enabled'])) {
      this.setState({ isCollapsed: value });
      if (value) {
        this.setExpansion(false);
      }
    } else {
      this.setState({ isCollapsed: false });
    }
  }

  setExpansion = (value) => {
    this.setState({ isExpanded: value });
    if (value) {
      this.setCollapsed(false);
    }
  }

  //  `parseClick()` takes a click event and responds appropriately.
  //  If our status is collapsed, then clicking on it should uncollapse it.
  //  If `Shift` is held, then clicking on it should collapse it.
  //  Otherwise, we open the url handed to us in `destination`, if
  //  applicable.
  parseClick = (e, destination) => {
    const { router } = this.context;
    const { status } = this.props;
    const { isCollapsed } = this.state;
    if (!router) return;
    if (destination === undefined) {
      destination = `/statuses/${
        status.getIn(['reblog', 'id'], status.get('id'))
      }`;
    }
    if (e.button === 0) {
      if (isCollapsed) this.setCollapsed(false);
      else if (e.shiftKey) {
        this.setCollapsed(true);
        document.getSelection().removeAllRanges();
      } else router.history.push(destination);
      e.preventDefault();
    }
  }

  handleAccountClick = (e) => {
    if (this.context.router && e.button === 0) {
      const id = e.currentTarget.getAttribute('data-id');
      e.preventDefault();
      this.context.router.history.push(`/accounts/${id}`);
    }
  }

  handleExpandedToggle = () => {
    if (this.props.status.get('spoiler_text')) {
      this.setExpansion(!this.state.isExpanded);
    }
  };

  handleOpenVideo = startTime => {
    this.props.onOpenVideo(this.props.status.getIn(['media_attachments', 0]), startTime);
  }

  handleHotkeyReply = e => {
    e.preventDefault();
    this.props.onReply(this.props.status, this.context.router.history);
  }

  handleHotkeyFavourite = (e) => {
    this.props.onFavourite(this.props.status, e);
  }

  handleHotkeyBoost = e => {
    this.props.onReblog(this.props.status, e);
  }

  handleHotkeyMention = e => {
    e.preventDefault();
    this.props.onMention(this.props.status.get('account'), this.context.router.history);
  }

  handleHotkeyOpen = () => {
    this.context.router.history.push(`/statuses/${this.props.status.get('id')}`);
  }

  handleHotkeyOpenProfile = () => {
    this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`);
  }

  handleHotkeyMoveUp = e => {
    this.props.onMoveUp(this.props.containerId || this.props.id, e.target.getAttribute('data-featured'));
  }

  handleHotkeyMoveDown = e => {
    this.props.onMoveDown(this.props.containerId || this.props.id, e.target.getAttribute('data-featured'));
  }

  handleRef = c => {
    this.node = c;
  }

  renderLoadingMediaGallery () {
    return
;
  }

  renderLoadingVideoPlayer () {
    return
;
  }

  render () {
    const {
      handleRef,
      parseClick,
      setExpansion,
      setCollapsed,
    } = this;
    const { router } = this.context;
    const {
      status,
      account,
      settings,
      collapsed,
      muted,
      prepend,
      intersectionObserverWrapper,
      onOpenVideo,
      onOpenMedia,
      notification,
      hidden,
      featured,
      ...other
    } = this.props;
    const { isExpanded, isCollapsed } = this.state;
    let background = null;
    let attachments = null;
    let media = null;
    let mediaIcon = null;

    if (status === null) {
      return null;
    }

    if (hidden) {
      return (
        <div
          ref={this.handleRef}
          data-id={status.get('id')}
          style={{
            height: `${this.height}px`,
            opacity: 0,
            overflow: 'hidden',
          }}
        >
          {status.getIn(['account', 'display_name']) || status.getIn(['account', 'username'])}
          {' '}
          {status.get('content')}

      );
    }

    //  If user backgrounds for collapsed statuses are enabled, then we
    //  initialize our background accordingly. This will only be rendered if
    //  the status is collapsed.
    if (settings.getIn(['collapsed', 'backgrounds', 'user_backgrounds'])) {
      background = status.getIn(['account', 'header']);
    }

    //  This handles our media attachments.
    //  If a media file is of unknwon type or if the status is muted
    //  (notification), we show a list of links instead of embedded media.

    //  After we have generated our appropriate media element and stored it in
    //  `media`, we snatch the thumbnail to use as our `background` if media
    //  backgrounds for collapsed statuses are enabled.
    attachments = status.get('media_attachments');
    if (attachments.size > 0) {
      if (muted || attachments.some(item => item.get('type') === 'unknown')) {
        media = (
          <AttachmentList
            compact
            media={status.get('media_attachments')}
          />
        );
      } else if (attachments.getIn([0, 'type']) === 'video') {  //  Media type is 'video'
        const video = status.getIn(['media_attachments', 0]);

        media = (
          <Bundle fetchComponent={Video} loading={this.renderLoadingVideoPlayer} >
            {Component => (<Component
              preview={video.get('preview_url')}
              src={video.get('url')}
              inline
              sensitive={status.get('sensitive')}
              letterbox={settings.getIn(['media', 'letterbox'])}
              fullwidth={settings.getIn(['media', 'fullwidth'])}
              onOpenVideo={this.handleOpenVideo}
            />)}
          </Bundle>
        );
        mediaIcon = 'video-camera';
      } else {  //  Media type is 'image' or 'gifv'
        media = (
          <Bundle fetchComponent={MediaGallery} loading={this.renderLoadingMediaGallery} >
            {Component => (
              <Component
                media={attachments}
                sensitive={status.get('sensitive')}
                letterbox={settings.getIn(['media', 'letterbox'])}
                fullwidth={settings.getIn(['media', 'fullwidth'])}
                onOpenMedia={this.props.onOpenMedia}
              />
            )}
          </Bundle>
        );
        mediaIcon = 'picture-o';
      }

      if (!status.get('sensitive') && !(status.get('spoiler_text').length > 0) && settings.getIn(['collapsed', 'backgrounds', 'preview_images'])) {
        background = attachments.getIn([0, 'preview_url']);
      }
    }

    //  Here we prepare extra data-* attributes for CSS selectors.
    //  Users can use those for theming, hiding avatars etc via UserStyle
    const selectorAttribs = {
      'data-status-by': `@${status.getIn(['account', 'acct'])}`,
    };

    if (prepend && account) {
      const notifKind = {
        favourite: 'favourited',
        reblog: 'boosted',
        reblogged_by: 'boosted',
      }[prepend];

      selectorAttribs[`data-${notifKind}-by`] = `@${account.get('acct')}`;
    }

    const handlers = {
      reply: this.handleHotkeyReply,
      favourite: this.handleHotkeyFavourite,
      boost: this.handleHotkeyBoost,
      mention: this.handleHotkeyMention,
      open: this.handleHotkeyOpen,
      openProfile: this.handleHotkeyOpenProfile,
      moveUp: this.handleHotkeyMoveUp,
      moveDown: this.handleHotkeyMoveDown,
      toggleSpoiler: this.handleExpandedToggle,
    };

    const computedClass = classNames('status', `status-${status.get('visibility')}`, {
      collapsed: isCollapsed,
      'has-background': isCollapsed && background,
      muted,
    }, 'focusable');

    return (
      <HotKeys handlers={handlers}>
        <div
          className={computedClass}
          style={isCollapsed && background ? { backgroundImage: `url(${background})` } : null}
          {...selectorAttribs}
          ref={handleRef}
          tabIndex='0'
          data-featured={featured ? 'true' : null}
        >

              {prepend && account ? (
                <StatusPrepend
                  type={prepend}
                  account={account}
                  parseClick={parseClick}
                  notificationId={this.props.notificationId}
                />
              ) : null}
              {!muted || !isCollapsed ? (
                <StatusHeader
                  status={status}
                  friend={account}
                  collapsed={isCollapsed}
                  parseClick={parseClick}
                />
              ) : null}

            <StatusIcons
              status={status}
              mediaIcon={mediaIcon}
              collapsible={settings.getIn(['collapsed', 'enabled'])}
              collapsed={isCollapsed}
              setCollapsed={setCollapsed}
            />

          <StatusContent
            status={status}
            media={media}
            mediaIcon={mediaIcon}
            expanded={isExpanded}
            onExpandedToggle={this.handleExpandedToggle}
            parseClick={parseClick}
            disabled={!router}
          />
          {!isCollapsed || !muted ? (
            <StatusActionBar
              {...other}
              status={status}
              account={status.get('account')}
            />
          ) : null}
          {notification ? (
            <NotificationOverlayContainer
              notification={notification}
            />
          ) : null}

      </HotKeys>
    );
  }

}
<|endoftext|>


<commit_before>import {
  ACCOUNT_BLOCK_SUCCESS,
  ACCOUNT_MUTE_SUCCESS,
} from 'flavours/glitch/actions/accounts';
import { CONTEXT_FETCH_SUCCESS } from 'flavours/glitch/actions/statuses';
import { TIMELINE_DELETE, TIMELINE_CONTEXT_UPDATE } from 'flavours/glitch/actions/timelines';
import { Map as ImmutableMap, List as ImmutableList } from 'immutable';

const initialState = ImmutableMap({
  ancestors: ImmutableMap(),
  descendants: ImmutableMap(),
});

const normalizeContext = (state, id, ancestors, descendants) => {
  const ancestorsIds   = ImmutableList(ancestors.map(ancestor => ancestor.id));
  const descendantsIds = ImmutableList(descendants.map(descendant => descendant.id));

  return state.withMutations(map => {
    map.setIn(['ancestors', id], ancestorsIds);
    map.setIn(['descendants', id], descendantsIds);
  });
};

const deleteFromContexts = (state, id) => {
  state.getIn(['descendants', id], ImmutableList()).forEach(descendantId => {
    state = state.updateIn(['ancestors', descendantId], ImmutableList(), list => list.filterNot(itemId => itemId === id));
  });

  state.getIn(['ancestors', id], ImmutableList()).forEach(ancestorId => {
    state = state.updateIn(['descendants', ancestorId], ImmutableList(), list => list.filterNot(itemId => itemId === id));
  });

  state = state.deleteIn(['descendants', id]).deleteIn(['ancestors', id]);

  return state;
};

const filterContexts = (state, relationship) => {
  return state.map(
    statuses => statuses.filter(
      status => status.get('account') !== relationship.id));
};

const updateContext = (state, status, references) => {
  return state.update('descendants', map => {
    references.forEach(parentId => {
      map = map.update(parentId, ImmutableList(), list => {
        if (list.includes(status.id)) {
          return list;
        }

        return list.push(status.id);
      });
    });

    return map;
  });
};

export default function contexts(state = initialState, action) {
  switch(action.type) {
  case ACCOUNT_BLOCK_SUCCESS:
  case ACCOUNT_MUTE_SUCCESS:
    return filterContexts(state, action.relationship);
  case CONTEXT_FETCH_SUCCESS:
    return normalizeContext(state, action.id, action.ancestors, action.descendants);
  case TIMELINE_DELETE:
    return deleteFromContexts(state, action.id);
  case TIMELINE_CONTEXT_UPDATE:
    return updateContext(state, action.status, action.references);
  default:
    return state;
  }
};
<commit_msg>[Glitch] Fix status filtering in contexts reducer<commit_after>import {
  ACCOUNT_BLOCK_SUCCESS,
  ACCOUNT_MUTE_SUCCESS,
} from 'flavours/glitch/actions/accounts';
import { CONTEXT_FETCH_SUCCESS } from 'flavours/glitch/actions/statuses';
import { TIMELINE_DELETE, TIMELINE_CONTEXT_UPDATE } from 'flavours/glitch/actions/timelines';
import { Map as ImmutableMap, List as ImmutableList } from 'immutable';

const initialState = ImmutableMap({
  ancestors: ImmutableMap(),
  descendants: ImmutableMap(),
});

const normalizeContext = (state, id, ancestors, descendants) => {
  const ancestorsIds   = ImmutableList(ancestors.map(ancestor => ancestor.id));
  const descendantsIds = ImmutableList(descendants.map(descendant => descendant.id));

  return state.withMutations(map => {
    map.setIn(['ancestors', id], ancestorsIds);
    map.setIn(['descendants', id], descendantsIds);
  });
};

const deleteFromContexts = (immutableState, ids) => immutableState.withMutations(state => {
  state.update('ancestors', immutableAncestors => immutableAncestors.withMutations(ancestors => {
    state.update('descendants', immutableDescendants => immutableDescendants.withMutations(descendants => {
      ids.forEach(id => {
        descendants.get(id, ImmutableList()).forEach(descendantId => {
          ancestors.update(descendantId, ImmutableList(), list => list.filterNot(itemId => itemId === id));
        });

        ancestors.get(id, ImmutableList()).forEach(ancestorId => {
          descendants.update(ancestorId, ImmutableList(), list => list.filterNot(itemId => itemId === id));
        });

        descendants.delete(id);
        ancestors.delete(id);
      });
    }));
  }));
});

const filterContexts = (state, relationship, statuses) => {
  const ownedStatusIds = statuses.filter(status => status.get('account') === relationship.id)
                                 .map(status => status.get('id'));

  return deleteFromContexts(state, ownedStatusIds);
};

const updateContext = (state, status, references) => {
  return state.update('descendants', map => {
    references.forEach(parentId => {
      map = map.update(parentId, ImmutableList(), list => {
        if (list.includes(status.id)) {
          return list;
        }

        return list.push(status.id);
      });
    });

    return map;
  });
};

export default function contexts(state = initialState, action) {
  switch(action.type) {
  case ACCOUNT_BLOCK_SUCCESS:
  case ACCOUNT_MUTE_SUCCESS:
    return filterContexts(state, action.relationship, action.statuses);
  case CONTEXT_FETCH_SUCCESS:
    return normalizeContext(state, action.id, action.ancestors, action.descendants);
  case TIMELINE_DELETE:
    return deleteFromContexts(state, [action.id]);
  case TIMELINE_CONTEXT_UPDATE:
    return updateContext(state, action.status, action.references);
  default:
    return state;
  }
};
<|endoftext|>


<commit_before>const ES_KEYS = {
  companiesHouseAccount: 'dit:Accounts',
  companiesHouseCompany: 'dit:Company',
  hmrcExporter: 'dit:Export',
  interaction: 'dit:Interaction',
  serviceDelivery: 'dit:ServiceDelivery',
  investmentProject: 'dit:InvestmentProject',
  omis: 'dit:OMISOrder',
  type: 'object.type',
  attributedTo: 'object.attributedTo.id',
}

const ES_KEYS_GROUPED = {
  allActivity: [
    ES_KEYS.companiesHouseAccount,
    ES_KEYS.companiesHouseCompany,
    ES_KEYS.hmrcExporter,
    ES_KEYS.interaction, // Interaction
    ES_KEYS.serviceDelivery, // Interaction
    ES_KEYS.investmentProject,
    ES_KEYS.omis,
  ],

  externalActivity: [
    ES_KEYS.companiesHouseAccount,
    ES_KEYS.companiesHouseCompany,
    ES_KEYS.hmrcExporter,
  ],

  dataHubActivity: [
    ES_KEYS.interaction, // Interaction
    ES_KEYS.serviceDelivery, // Interaction
    ES_KEYS.investmentProject,
    ES_KEYS.omis,
  ],
}

const FILTER_KEYS = {
  allActivity: 'allActivity',
  myActivity: 'myActivity',
  externalActivity: 'externalActivity',
  dataHubActivity: 'dataHubActivity',
  ultimateHQSubsidiaries: 'ultimateHQSubsidiaries',
}

const FILTER_ITEMS = {
  allActivity: {
    label: 'All Data Hub & external activity',
    value: FILTER_KEYS.allActivity,
  },

  externalActivity: {
    label: 'All external activity',
    value: FILTER_KEYS.externalActivity,
  },

  myActivity: {
    label: 'My activity',
    value: FILTER_KEYS.myActivity,
  },

  dataHubActivity: {
    label: 'All Data Hub activity',
    value: FILTER_KEYS.dataHubActivity,
  },
}

module.exports = {
  ES_KEYS,
  FILTER_KEYS,
  FILTER_ITEMS,
  ES_KEYS_GROUPED,
}
<commit_msg>Add the referrals to the ES keys<commit_after>const ES_KEYS = {
  companiesHouseAccount: 'dit:Accounts',
  companiesHouseCompany: 'dit:Company',
  hmrcExporter: 'dit:Export',
  interaction: 'dit:Interaction',
  serviceDelivery: 'dit:ServiceDelivery',
  investmentProject: 'dit:InvestmentProject',
  omis: 'dit:OMISOrder',
  type: 'object.type',
  attributedTo: 'object.attributedTo.id',
  companyReferral: 'dit:CompanyReferral',
}

const ES_KEYS_GROUPED = {
  allActivity: [
    ES_KEYS.companiesHouseAccount,
    ES_KEYS.companiesHouseCompany,
    ES_KEYS.hmrcExporter,
    ES_KEYS.interaction, // Interaction
    ES_KEYS.serviceDelivery, // Interaction
    ES_KEYS.investmentProject,
    ES_KEYS.omis,
    ES_KEYS.companyReferral,
  ],

  externalActivity: [
    ES_KEYS.companiesHouseAccount,
    ES_KEYS.companiesHouseCompany,
    ES_KEYS.hmrcExporter,
  ],

  dataHubActivity: [
    ES_KEYS.interaction, // Interaction
    ES_KEYS.serviceDelivery, // Interaction
    ES_KEYS.investmentProject,
    ES_KEYS.omis,
    ES_KEYS.companyReferral,
  ],
}

const FILTER_KEYS = {
  allActivity: 'allActivity',
  myActivity: 'myActivity',
  externalActivity: 'externalActivity',
  dataHubActivity: 'dataHubActivity',
  ultimateHQSubsidiaries: 'ultimateHQSubsidiaries',
}

const FILTER_ITEMS = {
  allActivity: {
    label: 'All Data Hub & external activity',
    value: FILTER_KEYS.allActivity,
  },

  externalActivity: {
    label: 'All external activity',
    value: FILTER_KEYS.externalActivity,
  },

  myActivity: {
    label: 'My activity',
    value: FILTER_KEYS.myActivity,
  },

  dataHubActivity: {
    label: 'All Data Hub activity',
    value: FILTER_KEYS.dataHubActivity,
  },
}

module.exports = {
  ES_KEYS,
  FILTER_KEYS,
  FILTER_ITEMS,
  ES_KEYS_GROUPED,
}
<|endoftext|>


<commit_before>'use strict';

import { Meteor } from 'meteor/meteor';
import { Mongo } from 'meteor/mongo';
import { check } from 'meteor/check';

import { Accounts } from './accounts.js';

import { Position } from '../core/models/position.js';
import { Rate } from '../core/models/rate.js';

export const Positions = new Mongo.Collection('positions');

if (Meteor.isServer) {
    Meteor.publish(
        'positions',
        function positionsPublication() {
            return Positions.find({ owner: this.userId });
        });
}

Meteor.methods({
    'positions.insert' (accountId, buy, sell, quantity, exchange, takeProfit) {
        check(accountId, String);
        check(buy, Number);
        check(sell, Number);
        check(quantity, Number);
        check(exchange, Number);
        check(takeProfit, Number);
        if (!Meteor.userId()) {
            throw new Meteor.Error('not-authorized');
        }
        const account = Accounts.findOne(accountId);
        if (!account) {
            throw new Meteor.Error('unknown-account');
        }
        const rate = new Rate(account.pair, buy, sell);
        const pos = new Position(rate, quantity, exchange, takeProfit);
        Positions.insert({
            "body": position,
            "accountId": accountId,
            "owner": Meteor.userId()
        });
    },
    'positions.remove' (positionId) {
        check(positionId, String);
        if (!Meteor.userId()) {
            throw new Meteor.Error('not-authorized');
        }
        Positions.remove(positionId);
    },
});<commit_msg>Fixed crasing when add position.<commit_after>'use strict';

import { Meteor } from 'meteor/meteor';
import { Mongo } from 'meteor/mongo';
import { check } from 'meteor/check';

import { Accounts } from './accounts.js';

import { Position } from '../core/models/position.js';
import { Rate } from '../core/models/rate.js';

export const Positions = new Mongo.Collection('positions');

if (Meteor.isServer) {
    Meteor.publish(
        'positions',
        function positionsPublication() {
            return Positions.find({ owner: this.userId });
        });
}

Meteor.methods({
    'positions.insert' (accountId, buy, sell, quantity, exchange, takeProfit) {
        check(accountId, String);
        check(buy, Number);
        check(sell, Number);
        check(quantity, Number);
        check(exchange, Number);
        check(takeProfit, Number);
        if (!Meteor.userId()) {
            throw new Meteor.Error('not-authorized');
        }
        const account = Accounts.findOne(accountId);
        if (!account) {
            throw new Meteor.Error('unknown-account');
        }
        const rate = new Rate(account.pair, buy, sell);
        const position = new Position(rate, quantity, exchange, takeProfit);
        Positions.insert({
            "body": position,
            "accountId": accountId,
            "owner": Meteor.userId()
        });
    },
    'positions.remove' (positionId) {
        check(positionId, String);
        if (!Meteor.userId()) {
            throw new Meteor.Error('not-authorized');
        }
        Positions.remove(positionId);
    },
});<|endoftext|>


<commit_before>/*
* RetargetMouseScroll JavaScript Library v0.0.2
* 2009-07-30
* By Eli Grey, http://eligrey.com
*
* http://github.com/eligrey/RetargetMouseScroll/tree/master
*
* Licensed under the MIT License
*   See LICENSE.md or http://eligrey.com/blog/about/license
*/

/*! @source http://purl.eligrey.com/github/RetargetMouseScroll/blob/master/
 *          RetargetMouseScroll.js
 */

if (typeof this.RetargetMouseScroll !== "function") (function() {
	var mouseScrollEvents = ["DOMMouseScroll", "mousewheel"];

	function handleScroll (evt, target, preventDefault, scrollMultiplier) {
		if (preventDefault) {
	        if (evt.preventDefault) {
	            evt.preventDefault();
	        } else {
	            event.returnValue = false;
	        }
	    }

		var scrollAmount = evt.detail
		                   || (-evt.wheelDelta / 40); // convert wheelData to lines
		scrollAmount *= 19; // convert lines to pixels

		if (typeof scrollMultiplier === "number" && !isNaN(scrollMultiplier))
			scrollAmount *= scrollMultiplier;

		if (evt.wheelDeltaX || ("axis" in evt && "HORIZONTAL_AXIS" in evt && evt.axis == evt.HORIZONTAL_AXIS))
			// horizontal scroll
			if (target.scrollBy)
				target.scrollBy(scrollAmount, 0);
			else
				target.scrollLeft += scrollAmount;
		else // vertical scroll
			if (target.scrollBy)
				target.scrollBy(0, scrollAmount);
			else
				target.scrollTop += scrollAmount;
	};

	this.RetargetMouseScroll = function (elem, target, preventDefault, scrollMultiplier) {
		if (!elem)
			elem = document;

		if (!target)
			target = window;

		if (typeof preventDefault !== "boolean")
			preventDefault = true;

		var addListener, removeListener, restoreFn,
		handler = function (evt) {
			evt = evt || window.event;
			handleScroll(evt, target, preventDefault, scrollMultiplier);
		};

		if (addListener = elem.addEventListener) {
			addListener.call(elem, mouseScrollEvents[0], handler, false);
			addListener.call(elem, mouseScrollEvents[1], handler, false);
		}
		else if (addListener = elem.attachEvent)
			addListener.call(elem, "on"+mouseScrollEvents[1], handler);

		if (removeListener = elem.removeEventListener)
			restoreFn = function () {
				removeListener.call(elem, mouseScrollEvents[0], handler, false);
				removeListener.call(elem, mouseScrollEvents[1], handler, false);
			};
		else if (removeListener = elem.detachEvent)
			restoreFn = function () {
				removeListener.call(elem, "on"+mouseScrollEvents[1], handler);
			};

		return {
			restore: restoreFn
		};
	};
}).call(this);
<commit_msg>Update license link<commit_after>/*
* RetargetMouseScroll JavaScript Library v0.0.2
* 2009-07-30
* By Eli Grey, http://eligrey.com
*
* http://github.com/eligrey/RetargetMouseScroll/tree/master
*
* Licensed under the MIT License
*   See https://github.com/eligrey/RetargetMouseScroll/blob/master/LICENSE.md
*/

/*! @source http://purl.eligrey.com/github/RetargetMouseScroll/blob/master/
 *          RetargetMouseScroll.js
 */

if (typeof this.RetargetMouseScroll !== "function") (function() {
	var mouseScrollEvents = ["DOMMouseScroll", "mousewheel"];

	function handleScroll (evt, target, preventDefault, scrollMultiplier) {
		if (preventDefault) {
	        if (evt.preventDefault) {
	            evt.preventDefault();
	        } else {
	            event.returnValue = false;
	        }
	    }

		var scrollAmount = evt.detail
		                   || (-evt.wheelDelta / 40); // convert wheelData to lines
		scrollAmount *= 19; // convert lines to pixels

		if (typeof scrollMultiplier === "number" && !isNaN(scrollMultiplier))
			scrollAmount *= scrollMultiplier;

		if (evt.wheelDeltaX || ("axis" in evt && "HORIZONTAL_AXIS" in evt && evt.axis == evt.HORIZONTAL_AXIS))
			// horizontal scroll
			if (target.scrollBy)
				target.scrollBy(scrollAmount, 0);
			else
				target.scrollLeft += scrollAmount;
		else // vertical scroll
			if (target.scrollBy)
				target.scrollBy(0, scrollAmount);
			else
				target.scrollTop += scrollAmount;
	};

	this.RetargetMouseScroll = function (elem, target, preventDefault, scrollMultiplier) {
		if (!elem)
			elem = document;

		if (!target)
			target = window;

		if (typeof preventDefault !== "boolean")
			preventDefault = true;

		var addListener, removeListener, restoreFn,
		handler = function (evt) {
			evt = evt || window.event;
			handleScroll(evt, target, preventDefault, scrollMultiplier);
		};

		if (addListener = elem.addEventListener) {
			addListener.call(elem, mouseScrollEvents[0], handler, false);
			addListener.call(elem, mouseScrollEvents[1], handler, false);
		}
		else if (addListener = elem.attachEvent)
			addListener.call(elem, "on"+mouseScrollEvents[1], handler);

		if (removeListener = elem.removeEventListener)
			restoreFn = function () {
				removeListener.call(elem, mouseScrollEvents[0], handler, false);
				removeListener.call(elem, mouseScrollEvents[1], handler, false);
			};
		else if (removeListener = elem.detachEvent)
			restoreFn = function () {
				removeListener.call(elem, "on"+mouseScrollEvents[1], handler);
			};

		return {
			restore: restoreFn
		};
	};
}).call(this);
<|endoftext|>


<commit_before>'use strict';

angular.module('arethusa.core').directive('sidepanelFolder', [
  'sidepanel',
  function(sidepanel) {
    return {
      scope: {},
      link: function (scope, element, attrs) {
        function addText() {
          var text = sidepanel.folded ? 'Show Panel' : 'Fold Panel';
          element.text(text);
        }

        element.on('click', function () {
          sidepanel.toggle();
          addText();
        });

        scope.sp = sidepanel;

        scope.$watch('sp.folded', function(newVal, oldVal) {
          addText();
        });
      }
    };
  }
]);
<commit_msg>Minor refactor in sidepanelFolder directive<commit_after>'use strict';

angular.module('arethusa.core').directive('sidepanelFolder', [
  'sidepanel',
  function(sidepanel) {
    return {
      scope: {},
      link: function (scope, element, attrs) {
        function addText() {
          var text = sidepanel.folded ? 'Show' : 'Fold';
          element.text(text + ' Panel');
        }

        element.on('click', function () {
          sidepanel.toggle();
          addText();
        });

        scope.sp = sidepanel;

        scope.$watch('sp.folded', function(newVal, oldVal) {
          addText();
        });
      }
    };
  }
]);
<|endoftext|>


<commit_before>module.exports = ReplaceSupers;

import * as messages from "../../messages";
import * as t from "../../types";

function isIllegalBareSuper(node, parent) {
  if (!isSuper(node, parent)) return false;
  if (t.isMemberExpression(parent, { computed: false })) return false;
  if (t.isCallExpression(parent, { callee: node })) return false;
  return true;
}

function isSuper(node, parent) {
  return t.isIdentifier(node, { name: "super" }) && t.isReferenced(node, parent);
}

var visitor = {
  enter(node, parent, scope, state) {
    var topLevel = state.topLevel;
    var self = state.self;

    if (t.isFunction(node) && !t.isArrowFunctionExpression(node)) {
      // we need to call traverseLevel again so we're context aware
      self.traverseLevel(node, false);
      return this.skip();
    }

    if (t.isProperty(node, { method: true }) || t.isMethodDefinition(node)) {
      // break on object methods
      return this.skip();
    }

    var getThisReference = topLevel ?
      // top level so `this` is the instance
      t.thisExpression :
      // not in the top level so we need to create a reference
      self.getThisReference.bind(self);

    var callback = self.specHandle;
    if (self.isLoose) callback = self.looseHandle;
    return callback.call(self, getThisReference, node, parent);
  }
};

export default class ReplaceSupers {

  /**
   * Description
   */

  constructor(opts: Object, inClass?: boolean = false) {
    this.topLevelThisReference = opts.topLevelThisReference;
    this.methodNode            = opts.methodNode;
    this.superRef              = opts.superRef;
    this.isStatic              = opts.isStatic;
    this.hasSuper              = false;
    this.inClass               = inClass;
    this.isLoose               = opts.isLoose;
    this.scope                 = opts.scope;
    this.file                  = opts.file;
    this.opts                  = opts;
  }

  getObjectRef() {
    return this.opts.objectRef || this.opts.getObjectRef();
  }

  /**
   * Sets a super class value of the named property.
   *
   * @example
   *
   *   _set(Object.getPrototypeOf(CLASS.prototype), "METHOD", "VALUE", this)
   *
   */

  setSuperProperty(property: Object, value: Object, isComputed: boolean, thisExpression: Object): Object {
    return t.callExpression(
      this.file.addHelper("set"),
      [
        t.callExpression(
          t.memberExpression(t.identifier("Object"), t.identifier("getPrototypeOf")),
          [
            this.isStatic ? this.getObjectRef() : t.memberExpression(this.getObjectRef(), t.identifier("prototype"))
          ]
        ),
        isComputed ? property : t.literal(property.name),
        value,
        thisExpression
      ]
    );
  }

  /**
   * Gets a node representing the super class value of the named property.
   *
   * @example
   *
   *   _get(Object.getPrototypeOf(CLASS.prototype), "METHOD", this)
   *
   */

  getSuperProperty(property: Object, isComputed: boolean, thisExpression: Object): Object {
    return t.callExpression(
      this.file.addHelper("get"),
      [
        t.callExpression(
          t.memberExpression(t.identifier("Object"), t.identifier("getPrototypeOf")),
          [
            this.isStatic ? this.getObjectRef() : t.memberExpression(this.getObjectRef(), t.identifier("prototype"))
          ]
        ),
        isComputed ? property : t.literal(property.name),
        thisExpression
      ]
    );
  }

  /**
   * Description
   */

  replace() {
    this.traverseLevel(this.methodNode.value, true);
  }

  /**
   * Description
   */

  traverseLevel(node: Object, topLevel: boolean) {
    var state = { self: this, topLevel: topLevel };
    this.scope.traverse(node, visitor, state);
  }

  /**
   * Description
   */

  getThisReference() {
    if (this.topLevelThisReference) {
      return this.topLevelThisReference;
    } else {
      var ref = this.topLevelThisReference = this.scope.generateUidIdentifier("this");
      this.methodNode.value.body.body.unshift(t.variableDeclaration("var", [
        t.variableDeclarator(this.topLevelThisReference, t.thisExpression())
      ]));
      return ref;
    }
  }

  /**
   * Description
   */

  getLooseSuperProperty(id: Object, parent: Object) {
    var methodNode = this.methodNode;
    var methodName = methodNode.key;
    var superRef   = this.superRef || t.identifier("Function");

    if (parent.property === id) {
      return;
    } else if (t.isCallExpression(parent, { callee: id })) {
      // super(); -> objectRef.prototype.MethodName.call(this);
      parent.arguments.unshift(t.thisExpression());

      if (methodName.name === "constructor") {
        // constructor() { super(); }
        return t.memberExpression(superRef, t.identifier("call"));
      } else {
        id = superRef;

        // foo() { super(); }
        if (!methodNode.static) {
          id = t.memberExpression(id, t.identifier("prototype"));
        }

        id = t.memberExpression(id, methodName, methodNode.computed);
        return t.memberExpression(id, t.identifier("call"));
      }
    } else if (t.isMemberExpression(parent) && !methodNode.static) {
      // super.test -> objectRef.prototype.test
      return t.memberExpression(superRef, t.identifier("prototype"));
    } else {
      return superRef;
    }
  }

  /**
   * Description
   */

  looseHandle(getThisReference: Function, node: Object, parent: Object) {
    if (t.isIdentifier(node, { name: "super" })) {
      this.hasSuper = true;
      return this.getLooseSuperProperty(node, parent);
    } else if (t.isCallExpression(node)) {
      var callee = node.callee;
      if (!t.isMemberExpression(callee)) return;
      if (callee.object.name !== "super") return;

      // super.test(); -> objectRef.prototype.MethodName.call(this);
      this.hasSuper = true;
      t.appendToMemberExpression(callee, t.identifier("call"));
      node.arguments.unshift(getThisReference());
    }
  }

  /**
   * Description
   */

  specHandle(getThisReference: Function, node: Object, parent: Object) {
    var methodNode = this.methodNode;
    var property;
    var computed;
    var args;
    var thisReference;

    if (isIllegalBareSuper(node, parent)) {
      throw this.errorWithNode(messages.get("classesIllegalBareSuper"));
    }

    if (t.isCallExpression(node)) {
      var callee = node.callee;
      if (isSuper(callee, node)) {
        // super(); -> _get(Object.getPrototypeOf(objectRef), "MethodName", this).call(this);
        property = methodNode.key;
        computed = methodNode.computed;
        args = node.arguments;

        // bare `super` call is illegal inside non-constructors
        //  - https://esdiscuss.org/topic/super-call-in-methods
        //  - https://twitter.com/wycats/status/544553184396836864
        if (methodNode.key.name !== "constructor" || !this.inClass) {
          var methodName = methodNode.key.name || "METHOD_NAME";
          throw this.file.errorWithNode(node, messages.get("classesIllegalSuperCall", methodName));
        }
      } else if (t.isMemberExpression(callee) && isSuper(callee.object, callee)) {
        // super.test(); -> _get(Object.getPrototypeOf(objectRef.prototype), "test", this).call(this);
        property = callee.property;
        computed = callee.computed;
        args = node.arguments;
      }
    } else if (t.isMemberExpression(node) && isSuper(node.object, node)) {
      // super.name; -> _get(Object.getPrototypeOf(objectRef.prototype), "name", this);
      property = node.property;
      computed = node.computed;
    } else if (t.isAssignmentExpression(node) && isSuper(node.left.object, node.left) && methodNode.kind === "set") {
      // super.name = "val"; -> _set(Object.getPrototypeOf(objectRef.prototype), "name", this);
      this.hasSuper = true;
      return this.setSuperProperty(node.left.property, node.right, node.left.computed, getThisReference());
    }

    if (!property) return;

    this.hasSuper = true;

    thisReference = getThisReference();
    var superProperty = this.getSuperProperty(property, computed, thisReference);
    if (args) {
      if (args.length === 1 && t.isSpreadElement(args[0])) {
        // super(...arguments);
        return t.callExpression(
          t.memberExpression(superProperty, t.identifier("apply")),
          [thisReference, args[0].argument]
        );
      } else {
        return t.callExpression(
          t.memberExpression(superProperty, t.identifier("call")),
          [thisReference, ...args]
        );
      }
    } else {
      return superProperty;
    }
  }
}
<commit_msg>use paths in replace supers helper<commit_after>module.exports = ReplaceSupers;

import * as messages from "../../messages";
import * as t from "../../types";

function isIllegalBareSuper(node, parent) {
  if (!isSuper(node, parent)) return false;
  if (t.isMemberExpression(parent, { computed: false })) return false;
  if (t.isCallExpression(parent, { callee: node })) return false;
  return true;
}

function isSuper(node, parent) {
  return t.isIdentifier(node, { name: "super" }) && t.isReferenced(node, parent);
}

var visitor = {
  enter(node, parent, scope, state) {
    var topLevel = state.topLevel;
    var self = state.self;

    if (t.isFunction(node) && !t.isArrowFunctionExpression(node)) {
      // we need to call traverseLevel again so we're context aware
      self.traverseLevel(node, false);
      return this.skip();
    }

    if (t.isProperty(node, { method: true }) || t.isMethodDefinition(node)) {
      // break on object methods
      return this.skip();
    }

    var getThisReference = topLevel ?
      // top level so `this` is the instance
      t.thisExpression :
      // not in the top level so we need to create a reference
      self.getThisReference.bind(self);

    var callback = self.specHandle;
    if (self.isLoose) callback = self.looseHandle;
    return callback.call(self, this, getThisReference);
  }
};

export default class ReplaceSupers {

  /**
   * Description
   */

  constructor(opts: Object, inClass?: boolean = false) {
    this.topLevelThisReference = opts.topLevelThisReference;
    this.methodNode            = opts.methodNode;
    this.superRef              = opts.superRef;
    this.isStatic              = opts.isStatic;
    this.hasSuper              = false;
    this.inClass               = inClass;
    this.isLoose               = opts.isLoose;
    this.scope                 = opts.scope;
    this.file                  = opts.file;
    this.opts                  = opts;
  }

  getObjectRef() {
    return this.opts.objectRef || this.opts.getObjectRef();
  }

  /**
   * Sets a super class value of the named property.
   *
   * @example
   *
   *   _set(Object.getPrototypeOf(CLASS.prototype), "METHOD", "VALUE", this)
   *
   */

  setSuperProperty(property: Object, value: Object, isComputed: boolean, thisExpression: Object): Object {
    return t.callExpression(
      this.file.addHelper("set"),
      [
        t.callExpression(
          t.memberExpression(t.identifier("Object"), t.identifier("getPrototypeOf")),
          [
            this.isStatic ? this.getObjectRef() : t.memberExpression(this.getObjectRef(), t.identifier("prototype"))
          ]
        ),
        isComputed ? property : t.literal(property.name),
        value,
        thisExpression
      ]
    );
  }

  /**
   * Gets a node representing the super class value of the named property.
   *
   * @example
   *
   *   _get(Object.getPrototypeOf(CLASS.prototype), "METHOD", this)
   *
   */

  getSuperProperty(property: Object, isComputed: boolean, thisExpression: Object): Object {
    return t.callExpression(
      this.file.addHelper("get"),
      [
        t.callExpression(
          t.memberExpression(t.identifier("Object"), t.identifier("getPrototypeOf")),
          [
            this.isStatic ? this.getObjectRef() : t.memberExpression(this.getObjectRef(), t.identifier("prototype"))
          ]
        ),
        isComputed ? property : t.literal(property.name),
        thisExpression
      ]
    );
  }

  /**
   * Description
   */

  replace() {
    this.traverseLevel(this.methodNode.value, true);
  }

  /**
   * Description
   */

  traverseLevel(node: Object, topLevel: boolean) {
    var state = { self: this, topLevel: topLevel };
    this.scope.traverse(node, visitor, state);
  }

  /**
   * Description
   */

  getThisReference() {
    if (this.topLevelThisReference) {
      return this.topLevelThisReference;
    } else {
      var ref = this.topLevelThisReference = this.scope.generateUidIdentifier("this");
      this.methodNode.value.body.body.unshift(t.variableDeclaration("var", [
        t.variableDeclarator(this.topLevelThisReference, t.thisExpression())
      ]));
      return ref;
    }
  }

  /**
   * Description
   */

  getLooseSuperProperty(id: Object, parent: Object) {
    var methodNode = this.methodNode;
    var methodName = methodNode.key;
    var superRef   = this.superRef || t.identifier("Function");

    if (parent.property === id) {
      return;
    } else if (t.isCallExpression(parent, { callee: id })) {
      // super(); -> objectRef.prototype.MethodName.call(this);
      parent.arguments.unshift(t.thisExpression());

      if (methodName.name === "constructor") {
        // constructor() { super(); }
        return t.memberExpression(superRef, t.identifier("call"));
      } else {
        id = superRef;

        // foo() { super(); }
        if (!methodNode.static) {
          id = t.memberExpression(id, t.identifier("prototype"));
        }

        id = t.memberExpression(id, methodName, methodNode.computed);
        return t.memberExpression(id, t.identifier("call"));
      }
    } else if (t.isMemberExpression(parent) && !methodNode.static) {
      // super.test -> objectRef.prototype.test
      return t.memberExpression(superRef, t.identifier("prototype"));
    } else {
      return superRef;
    }
  }

  /**
   * Description
   */

  looseHandle(path: TraversalPath, getThisReference: Function) {
    var node = path.node;
    if (path.isIdentifier({ name: "super" })) {
      this.hasSuper = true;
      return this.getLooseSuperProperty(node, path.parent);
    } else if (path.isCallExpression()) {
      var callee = node.callee;
      if (!t.isMemberExpression(callee)) return;
      if (callee.object.name !== "super") return;

      // super.test(); -> objectRef.prototype.MethodName.call(this);
      this.hasSuper = true;
      t.appendToMemberExpression(callee, t.identifier("call"));
      node.arguments.unshift(getThisReference());
    }
  }

  /**
   * Description
   */

  specHandle(path: TraversalPath, getThisReference: Function) {
    var methodNode = this.methodNode;
    var property;
    var computed;
    var args;
    var thisReference;

    var parent = path.parent;
    var node = path.node;

    if (isIllegalBareSuper(node, parent)) {
      throw path.errorWithNode(messages.get("classesIllegalBareSuper"));
    }

    if (t.isCallExpression(node)) {
      var callee = node.callee;
      if (isSuper(callee, node)) {
        // super(); -> _get(Object.getPrototypeOf(objectRef), "MethodName", this).call(this);
        property = methodNode.key;
        computed = methodNode.computed;
        args = node.arguments;

        // bare `super` call is illegal inside non-constructors
        //  - https://esdiscuss.org/topic/super-call-in-methods
        //  - https://twitter.com/wycats/status/544553184396836864
        if (methodNode.key.name !== "constructor" || !this.inClass) {
          var methodName = methodNode.key.name || "METHOD_NAME";
          throw this.file.errorWithNode(node, messages.get("classesIllegalSuperCall", methodName));
        }
      } else if (t.isMemberExpression(callee) && isSuper(callee.object, callee)) {
        // super.test(); -> _get(Object.getPrototypeOf(objectRef.prototype), "test", this).call(this);
        property = callee.property;
        computed = callee.computed;
        args = node.arguments;
      }
    } else if (t.isMemberExpression(node) && isSuper(node.object, node)) {
      // super.name; -> _get(Object.getPrototypeOf(objectRef.prototype), "name", this);
      property = node.property;
      computed = node.computed;
    } else if (t.isAssignmentExpression(node) && isSuper(node.left.object, node.left) && methodNode.kind === "set") {
      // super.name = "val"; -> _set(Object.getPrototypeOf(objectRef.prototype), "name", this);
      this.hasSuper = true;
      return this.setSuperProperty(node.left.property, node.right, node.left.computed, getThisReference());
    }

    if (!property) return;

    this.hasSuper = true;

    thisReference = getThisReference();
    var superProperty = this.getSuperProperty(property, computed, thisReference);
    if (args) {
      if (args.length === 1 && t.isSpreadElement(args[0])) {
        // super(...arguments);
        return t.callExpression(
          t.memberExpression(superProperty, t.identifier("apply")),
          [thisReference, args[0].argument]
        );
      } else {
        return t.callExpression(
          t.memberExpression(superProperty, t.identifier("call")),
          [thisReference, ...args]
        );
      }
    } else {
      return superProperty;
    }
  }
}
<|endoftext|>


<commit_before>'use strict';
const path = require('path');
const { ipcRenderer, remote } = require('electron');
const fs = require('fs-extra');

const { app, dialog } = remote;
const currentBrowserWindow = remote.getCurrentWindow();
const BaseSection = require(__dirname + '/base-section.js');
const ConfigUtil = require(__dirname + '/../../utils/config-util.js');

class GeneralSection extends BaseSection {
	constructor(props) {
		super();
		this.props = props;
	}

	template() {
		return `

Appearance

Show app icon in system tray

Auto hide Menubar (Press Alt key to display)

Show sidebar (Cmd/Ctrl+S)

Show app unread badge

Bounce dock on new private message

Flash taskbar on new message

Desktop Notifications

Show Desktop Notifications

Mute all sounds from Zulip

App Updates

Enable auto updates

Get beta updates

Functionality

Start app at login

Always start minimized

Enable spellchecker (requires restart)

Advanced

Show downloaded files in file manager

					Add custom CSS

				<button class="custom-css-button green">Upload</button>

${ConfigUtil.getConfigItem('customCSS')}

					indeterminate_check_box
					Delete

							Default download location

						<button class="download-folder-button green">Change</button>

${ConfigUtil.getConfigItem('downloadsPath', `${app.getPath('downloads')}`)}

Reset Application Data

This will delete all application data including all added accounts and preferences

						<button class="reset-data-button green w-150">Reset App Data</button>

		`;
	}

	init() {
		this.props.$root.innerHTML = this.template();
		this.updateTrayOption();
		this.updateBadgeOption();
		this.updateSilentOption();
		this.autoUpdateOption();
		this.betaUpdateOption();
		this.updateSidebarOption();
		this.updateStartAtLoginOption();
		this.updateResetDataOption();
		this.showDesktopNotification();
		this.enableSpellchecker();
		this.minimizeOnStart();
		this.addCustomCSS();
		this.showCustomCSSPath();
		this.removeCustomCSS();
		this.downloadFolder();
		this.showDownloadFolder();

		// Platform specific settings

		// Flashing taskbar on Windows
		if (process.platform === 'win32') {
			this.updateFlashTaskbar();
		}
		// Dock bounce on macOS
		if (process.platform === 'darwin') {
			this.updateDockBouncing();
		}

		// Auto hide menubar on Windows and Linux
		if (process.platform !== 'darwin') {
			this.updateMenubarOption();
		}
	}

	updateTrayOption() {
		this.generateSettingOption({
			$element: document.querySelector('#tray-option .setting-control'),
			value: ConfigUtil.getConfigItem('trayIcon', true),
			clickHandler: () => {
				const newValue = !ConfigUtil.getConfigItem('trayIcon');
				ConfigUtil.setConfigItem('trayIcon', newValue);
				ipcRenderer.send('forward-message', 'toggletray');
				this.updateTrayOption();
			}
		});
	}

	updateMenubarOption() {
		this.generateSettingOption({
			$element: document.querySelector('#menubar-option .setting-control'),
			value: ConfigUtil.getConfigItem('autoHideMenubar', false),
			clickHandler: () => {
				const newValue = !ConfigUtil.getConfigItem('autoHideMenubar');
				ConfigUtil.setConfigItem('autoHideMenubar', newValue);
				ipcRenderer.send('toggle-menubar', newValue);
				this.updateMenubarOption();
			}
		});
	}

	updateBadgeOption() {
		this.generateSettingOption({
			$element: document.querySelector('#badge-option .setting-control'),
			value: ConfigUtil.getConfigItem('badgeOption', true),
			clickHandler: () => {
				const newValue = !ConfigUtil.getConfigItem('badgeOption');
				ConfigUtil.setConfigItem('badgeOption', newValue);
				ipcRenderer.send('toggle-badge-option', newValue);
				this.updateBadgeOption();
			}
		});
	}

	updateDockBouncing() {
		this.generateSettingOption({
			$element: document.querySelector('#dock-bounce-option .setting-control'),
			value: ConfigUtil.getConfigItem('dockBouncing', true),
			clickHandler: () => {
				const newValue = !ConfigUtil.getConfigItem('dockBouncing');
				ConfigUtil.setConfigItem('dockBouncing', newValue);
				this.updateDockBouncing();
			}
		});
	}

	updateFlashTaskbar() {
		this.generateSettingOption({
			$element: document.querySelector('#flash-taskbar-option .setting-control'),
			value: ConfigUtil.getConfigItem('flashTaskbarOnMessage', true),
			clickHandler: () => {
				const newValue = !ConfigUtil.getConfigItem('flashTaskbarOnMessage');
				ConfigUtil.setConfigItem('flashTaskbarOnMessage', newValue);
				this.updateFlashTaskbar();
			}
		});
	}

	autoUpdateOption() {
		this.generateSettingOption({
			$element: document.querySelector('#autoupdate-option .setting-control'),
			value: ConfigUtil.getConfigItem('autoUpdate', true),
			clickHandler: () => {
				const newValue = !ConfigUtil.getConfigItem('autoUpdate');
				ConfigUtil.setConfigItem('autoUpdate', newValue);
				this.autoUpdateOption();
			}
		});
	}

	betaUpdateOption() {
		this.generateSettingOption({
			$element: document.querySelector('#betaupdate-option .setting-control'),
			value: ConfigUtil.getConfigItem('betaUpdate', false),
			clickHandler: () => {
				const newValue = !ConfigUtil.getConfigItem('betaUpdate');
				ConfigUtil.setConfigItem('betaUpdate', newValue);
				this.betaUpdateOption();
			}
		});
	}

	updateSilentOption() {
		this.generateSettingOption({
			$element: document.querySelector('#silent-option .setting-control'),
			value: ConfigUtil.getConfigItem('silent', false),
			clickHandler: () => {
				const newValue = !ConfigUtil.getConfigItem('silent', true);
				ConfigUtil.setConfigItem('silent', newValue);
				this.updateSilentOption();
				currentBrowserWindow.send('toggle-silent', newValue);
			}
		});
	}

	showDesktopNotification() {
		this.generateSettingOption({
			$element: document.querySelector('#show-notification-option .setting-control'),
			value: ConfigUtil.getConfigItem('showNotification', true),
			clickHandler: () => {
				const newValue = !ConfigUtil.getConfigItem('showNotification', true);
				ConfigUtil.setConfigItem('showNotification', newValue);
				this.showDesktopNotification();
			}
		});
	}

	updateSidebarOption() {
		this.generateSettingOption({
			$element: document.querySelector('#sidebar-option .setting-control'),
			value: ConfigUtil.getConfigItem('showSidebar', true),
			clickHandler: () => {
				const newValue = !ConfigUtil.getConfigItem('showSidebar');
				ConfigUtil.setConfigItem('showSidebar', newValue);
				ipcRenderer.send('forward-message', 'toggle-sidebar', newValue);
				this.updateSidebarOption();
			}
		});
	}

	updateStartAtLoginOption() {
		this.generateSettingOption({
			$element: document.querySelector('#startAtLogin-option .setting-control'),
			value: ConfigUtil.getConfigItem('startAtLogin', false),
			clickHandler: () => {
				const newValue = !ConfigUtil.getConfigItem('startAtLogin');
				ConfigUtil.setConfigItem('startAtLogin', newValue);
				ipcRenderer.send('toggleAutoLauncher', newValue);
				this.updateStartAtLoginOption();
			}
		});
	}

	enableSpellchecker() {
		this.generateSettingOption({
			$element: document.querySelector('#enable-spellchecker-option .setting-control'),
			value: ConfigUtil.getConfigItem('enableSpellchecker', true),
			clickHandler: () => {
				const newValue = !ConfigUtil.getConfigItem('enableSpellchecker');
				ConfigUtil.setConfigItem('enableSpellchecker', newValue);
				this.enableSpellchecker();
			}
		});
	}

	clearAppDataDialog() {
		const clearAppDataMessage = 'By clicking proceed you will be removing all added accounts and preferences from Zulip. When the application restarts, it will be as if you are starting Zulip for the first time.';
		const getAppPath = path.join(app.getPath('appData'), app.getName());

		dialog.showMessageBox({
			type: 'warning',
			buttons: ['YES', 'NO'],
			defaultId: 0,
			message: 'Are you sure',
			detail: clearAppDataMessage
		}, response => {
			if (response === 0) {
				fs.remove(getAppPath);
				setTimeout(() => ipcRenderer.send('forward-message', 'hard-reload'), 1000);
			}
		});
	}

	customCssDialog() {
		const showDialogOptions = {
			title: 'Select file',
			defaultId: 1,
			properties: ['openFile'],
			filters: [{ name: 'CSS file', extensions: ['css'] }]
		};

		dialog.showOpenDialog(showDialogOptions, selectedFile => {
			if (selectedFile) {
				ConfigUtil.setConfigItem('customCSS', selectedFile[0]);
				ipcRenderer.send('forward-message', 'hard-reload');
			}
		});
	}

	updateResetDataOption() {
		const resetDataButton = document.querySelector('#resetdata-option .reset-data-button');
		resetDataButton.addEventListener('click', () => {
			this.clearAppDataDialog();
		});
	}

	minimizeOnStart() {
		this.generateSettingOption({
			$element: document.querySelector('#start-minimize-option .setting-control'),
			value: ConfigUtil.getConfigItem('startMinimized', false),
			clickHandler: () => {
				const newValue = !ConfigUtil.getConfigItem('startMinimized');
				ConfigUtil.setConfigItem('startMinimized', newValue);
				this.minimizeOnStart();
			}
		});
	}

	addCustomCSS() {
		const customCSSButton = document.querySelector('#add-custom-css .custom-css-button');
		customCSSButton.addEventListener('click', () => {
			this.customCssDialog();
		});
	}

	showCustomCSSPath() {
		if (!ConfigUtil.getConfigItem('customCSS')) {
			const cssPATH = document.getElementById('remove-custom-css');
			cssPATH.style.display = 'none';
		}
	}

	removeCustomCSS() {
		const removeCSSButton = document.getElementById('css-delete-action');
		removeCSSButton.addEventListener('click', () => {
			ConfigUtil.setConfigItem('customCSS');
			ipcRenderer.send('forward-message', 'hard-reload');
		});
	}

	downloadFolderDialog() {
		const showDialogOptions = {
			title: 'Select Download Location',
			defaultId: 1,
			properties: ['openDirectory']
		};

		dialog.showOpenDialog(showDialogOptions, selectedFolder => {
			if (selectedFolder) {
				ConfigUtil.setConfigItem('downloadsPath', selectedFolder[0]);
				const downloadFolderPath = document.querySelector('.download-folder-path');
				downloadFolderPath.innerText = selectedFolder[0];
			}
		});
	}
	downloadFolder() {
		const downloadFolder = document.querySelector('#download-folder .download-folder-button');
		downloadFolder.addEventListener('click', () => {
			this.downloadFolderDialog();
		});
	}

	showDownloadFolder() {
		this.generateSettingOption({
			$element: document.querySelector('#show-download-folder .setting-control'),
			value: ConfigUtil.getConfigItem('showDownloadFolder', false),
			clickHandler: () => {
				const newValue = !ConfigUtil.getConfigItem('showDownloadFolder');
				ConfigUtil.setConfigItem('showDownloadFolder', newValue);
				this.showDownloadFolder();
			}
		});
	}

}

module.exports = GeneralSection;
<commit_msg>setting: Disable beta updates if auto updates disabled.<commit_after>'use strict';
const path = require('path');
const { ipcRenderer, remote } = require('electron');
const fs = require('fs-extra');

const { app, dialog } = remote;
const currentBrowserWindow = remote.getCurrentWindow();
const BaseSection = require(__dirname + '/base-section.js');
const ConfigUtil = require(__dirname + '/../../utils/config-util.js');

class GeneralSection extends BaseSection {
	constructor(props) {
		super();
		this.props = props;
	}

	template() {
		return `

Appearance

Show app icon in system tray

Auto hide Menubar (Press Alt key to display)

Show sidebar (Cmd/Ctrl+S)

Show app unread badge

Bounce dock on new private message

Flash taskbar on new message

Desktop Notifications

Show Desktop Notifications

Mute all sounds from Zulip

App Updates

Enable auto updates

Get beta updates

Functionality

Start app at login

Always start minimized

Enable spellchecker (requires restart)

Advanced

Show downloaded files in file manager

					Add custom CSS

				<button class="custom-css-button green">Upload</button>

${ConfigUtil.getConfigItem('customCSS')}

					indeterminate_check_box
					Delete

							Default download location

						<button class="download-folder-button green">Change</button>

${ConfigUtil.getConfigItem('downloadsPath', `${app.getPath('downloads')}`)}

Reset Application Data

This will delete all application data including all added accounts and preferences

						<button class="reset-data-button green w-150">Reset App Data</button>

		`;
	}

	init() {
		this.props.$root.innerHTML = this.template();
		this.updateTrayOption();
		this.updateBadgeOption();
		this.updateSilentOption();
		this.autoUpdateOption();
		this.betaUpdateOption();
		this.updateSidebarOption();
		this.updateStartAtLoginOption();
		this.updateResetDataOption();
		this.showDesktopNotification();
		this.enableSpellchecker();
		this.minimizeOnStart();
		this.addCustomCSS();
		this.showCustomCSSPath();
		this.removeCustomCSS();
		this.downloadFolder();
		this.showDownloadFolder();

		// Platform specific settings

		// Flashing taskbar on Windows
		if (process.platform === 'win32') {
			this.updateFlashTaskbar();
		}
		// Dock bounce on macOS
		if (process.platform === 'darwin') {
			this.updateDockBouncing();
		}

		// Auto hide menubar on Windows and Linux
		if (process.platform !== 'darwin') {
			this.updateMenubarOption();
		}
	}

	updateTrayOption() {
		this.generateSettingOption({
			$element: document.querySelector('#tray-option .setting-control'),
			value: ConfigUtil.getConfigItem('trayIcon', true),
			clickHandler: () => {
				const newValue = !ConfigUtil.getConfigItem('trayIcon');
				ConfigUtil.setConfigItem('trayIcon', newValue);
				ipcRenderer.send('forward-message', 'toggletray');
				this.updateTrayOption();
			}
		});
	}

	updateMenubarOption() {
		this.generateSettingOption({
			$element: document.querySelector('#menubar-option .setting-control'),
			value: ConfigUtil.getConfigItem('autoHideMenubar', false),
			clickHandler: () => {
				const newValue = !ConfigUtil.getConfigItem('autoHideMenubar');
				ConfigUtil.setConfigItem('autoHideMenubar', newValue);
				ipcRenderer.send('toggle-menubar', newValue);
				this.updateMenubarOption();
			}
		});
	}

	updateBadgeOption() {
		this.generateSettingOption({
			$element: document.querySelector('#badge-option .setting-control'),
			value: ConfigUtil.getConfigItem('badgeOption', true),
			clickHandler: () => {
				const newValue = !ConfigUtil.getConfigItem('badgeOption');
				ConfigUtil.setConfigItem('badgeOption', newValue);
				ipcRenderer.send('toggle-badge-option', newValue);
				this.updateBadgeOption();
			}
		});
	}

	updateDockBouncing() {
		this.generateSettingOption({
			$element: document.querySelector('#dock-bounce-option .setting-control'),
			value: ConfigUtil.getConfigItem('dockBouncing', true),
			clickHandler: () => {
				const newValue = !ConfigUtil.getConfigItem('dockBouncing');
				ConfigUtil.setConfigItem('dockBouncing', newValue);
				this.updateDockBouncing();
			}
		});
	}

	updateFlashTaskbar() {
		this.generateSettingOption({
			$element: document.querySelector('#flash-taskbar-option .setting-control'),
			value: ConfigUtil.getConfigItem('flashTaskbarOnMessage', true),
			clickHandler: () => {
				const newValue = !ConfigUtil.getConfigItem('flashTaskbarOnMessage');
				ConfigUtil.setConfigItem('flashTaskbarOnMessage', newValue);
				this.updateFlashTaskbar();
			}
		});
	}

	autoUpdateOption() {
		this.generateSettingOption({
			$element: document.querySelector('#autoupdate-option .setting-control'),
			value: ConfigUtil.getConfigItem('autoUpdate', true),
			clickHandler: () => {
				const newValue = !ConfigUtil.getConfigItem('autoUpdate');
				ConfigUtil.setConfigItem('autoUpdate', newValue);
				if (!newValue) {
					ConfigUtil.setConfigItem('betaUpdate', false);
					this.betaUpdateOption();
				}
				this.autoUpdateOption();
			}
		});
	}

	betaUpdateOption() {
		this.generateSettingOption({
			$element: document.querySelector('#betaupdate-option .setting-control'),
			value: ConfigUtil.getConfigItem('betaUpdate', false),
			clickHandler: () => {
				const newValue = !ConfigUtil.getConfigItem('betaUpdate');
				if (ConfigUtil.getConfigItem('autoUpdate')) {
					ConfigUtil.setConfigItem('betaUpdate', newValue);
					this.betaUpdateOption();
				}
			}
		});
	}

	updateSilentOption() {
		this.generateSettingOption({
			$element: document.querySelector('#silent-option .setting-control'),
			value: ConfigUtil.getConfigItem('silent', false),
			clickHandler: () => {
				const newValue = !ConfigUtil.getConfigItem('silent', true);
				ConfigUtil.setConfigItem('silent', newValue);
				this.updateSilentOption();
				currentBrowserWindow.send('toggle-silent', newValue);
			}
		});
	}

	showDesktopNotification() {
		this.generateSettingOption({
			$element: document.querySelector('#show-notification-option .setting-control'),
			value: ConfigUtil.getConfigItem('showNotification', true),
			clickHandler: () => {
				const newValue = !ConfigUtil.getConfigItem('showNotification', true);
				ConfigUtil.setConfigItem('showNotification', newValue);
				this.showDesktopNotification();
			}
		});
	}

	updateSidebarOption() {
		this.generateSettingOption({
			$element: document.querySelector('#sidebar-option .setting-control'),
			value: ConfigUtil.getConfigItem('showSidebar', true),
			clickHandler: () => {
				const newValue = !ConfigUtil.getConfigItem('showSidebar');
				ConfigUtil.setConfigItem('showSidebar', newValue);
				ipcRenderer.send('forward-message', 'toggle-sidebar', newValue);
				this.updateSidebarOption();
			}
		});
	}

	updateStartAtLoginOption() {
		this.generateSettingOption({
			$element: document.querySelector('#startAtLogin-option .setting-control'),
			value: ConfigUtil.getConfigItem('startAtLogin', false),
			clickHandler: () => {
				const newValue = !ConfigUtil.getConfigItem('startAtLogin');
				ConfigUtil.setConfigItem('startAtLogin', newValue);
				ipcRenderer.send('toggleAutoLauncher', newValue);
				this.updateStartAtLoginOption();
			}
		});
	}

	enableSpellchecker() {
		this.generateSettingOption({
			$element: document.querySelector('#enable-spellchecker-option .setting-control'),
			value: ConfigUtil.getConfigItem('enableSpellchecker', true),
			clickHandler: () => {
				const newValue = !ConfigUtil.getConfigItem('enableSpellchecker');
				ConfigUtil.setConfigItem('enableSpellchecker', newValue);
				this.enableSpellchecker();
			}
		});
	}

	clearAppDataDialog() {
		const clearAppDataMessage = 'By clicking proceed you will be removing all added accounts and preferences from Zulip. When the application restarts, it will be as if you are starting Zulip for the first time.';
		const getAppPath = path.join(app.getPath('appData'), app.getName());

		dialog.showMessageBox({
			type: 'warning',
			buttons: ['YES', 'NO'],
			defaultId: 0,
			message: 'Are you sure',
			detail: clearAppDataMessage
		}, response => {
			if (response === 0) {
				fs.remove(getAppPath);
				setTimeout(() => ipcRenderer.send('forward-message', 'hard-reload'), 1000);
			}
		});
	}

	customCssDialog() {
		const showDialogOptions = {
			title: 'Select file',
			defaultId: 1,
			properties: ['openFile'],
			filters: [{ name: 'CSS file', extensions: ['css'] }]
		};

		dialog.showOpenDialog(showDialogOptions, selectedFile => {
			if (selectedFile) {
				ConfigUtil.setConfigItem('customCSS', selectedFile[0]);
				ipcRenderer.send('forward-message', 'hard-reload');
			}
		});
	}

	updateResetDataOption() {
		const resetDataButton = document.querySelector('#resetdata-option .reset-data-button');
		resetDataButton.addEventListener('click', () => {
			this.clearAppDataDialog();
		});
	}

	minimizeOnStart() {
		this.generateSettingOption({
			$element: document.querySelector('#start-minimize-option .setting-control'),
			value: ConfigUtil.getConfigItem('startMinimized', false),
			clickHandler: () => {
				const newValue = !ConfigUtil.getConfigItem('startMinimized');
				ConfigUtil.setConfigItem('startMinimized', newValue);
				this.minimizeOnStart();
			}
		});
	}

	addCustomCSS() {
		const customCSSButton = document.querySelector('#add-custom-css .custom-css-button');
		customCSSButton.addEventListener('click', () => {
			this.customCssDialog();
		});
	}

	showCustomCSSPath() {
		if (!ConfigUtil.getConfigItem('customCSS')) {
			const cssPATH = document.getElementById('remove-custom-css');
			cssPATH.style.display = 'none';
		}
	}

	removeCustomCSS() {
		const removeCSSButton = document.getElementById('css-delete-action');
		removeCSSButton.addEventListener('click', () => {
			ConfigUtil.setConfigItem('customCSS');
			ipcRenderer.send('forward-message', 'hard-reload');
		});
	}

	downloadFolderDialog() {
		const showDialogOptions = {
			title: 'Select Download Location',
			defaultId: 1,
			properties: ['openDirectory']
		};

		dialog.showOpenDialog(showDialogOptions, selectedFolder => {
			if (selectedFolder) {
				ConfigUtil.setConfigItem('downloadsPath', selectedFolder[0]);
				const downloadFolderPath = document.querySelector('.download-folder-path');
				downloadFolderPath.innerText = selectedFolder[0];
			}
		});
	}
	downloadFolder() {
		const downloadFolder = document.querySelector('#download-folder .download-folder-button');
		downloadFolder.addEventListener('click', () => {
			this.downloadFolderDialog();
		});
	}

	showDownloadFolder() {
		this.generateSettingOption({
			$element: document.querySelector('#show-download-folder .setting-control'),
			value: ConfigUtil.getConfigItem('showDownloadFolder', false),
			clickHandler: () => {
				const newValue = !ConfigUtil.getConfigItem('showDownloadFolder');
				ConfigUtil.setConfigItem('showDownloadFolder', newValue);
				this.showDownloadFolder();
			}
		});
	}

}

module.exports = GeneralSection;
<|endoftext|>


<commit_before>(function(){
  'use strict';

  angular.module('app.states')
    .run(appRun);

  /** @ngInject */
  function appRun(routerHelper) {
    routerHelper.configureStates(getStates());
  }

  function getStates() {
    return {
      'orders.details': {
        url: '/details/:orderId',
        templateUrl: 'app/states/orders/details/details.html',
        controller: StateController,
        controllerAs: 'vm',
        title: 'Order Details',
        resolve: {
          order: resolveOrder
        }
      }
    };
  }

  /** @ngInject */
  function resolveOrder($stateParams, Order){
    return Order.get({
      id: $stateParams.orderId,
      'includes[]': ['product', 'project', 'service', 'staff']
    }).$promise;
  }

  /** @ngInject */
  function StateController(order) {
    var vm = this;

    vm.order = order;
    vm.staff = order.staff;
    vm.product = order.product;
    vm.service = order.service;
    vm.project = order.project;

    vm.activate = activate;

    activate();

    function activate() {
    }
  }
})();
<commit_msg>Fix JSCS violation that snuck in while the reporter was broken<commit_after>(function() {
  'use strict';

  angular.module('app.states')
    .run(appRun);

  /** @ngInject */
  function appRun(routerHelper) {
    routerHelper.configureStates(getStates());
  }

  function getStates() {
    return {
      'orders.details': {
        url: '/details/:orderId',
        templateUrl: 'app/states/orders/details/details.html',
        controller: StateController,
        controllerAs: 'vm',
        title: 'Order Details',
        resolve: {
          order: resolveOrder
        }
      }
    };
  }

  /** @ngInject */
  function resolveOrder($stateParams, Order) {
    return Order.get({
      id: $stateParams.orderId,
      'includes[]': ['product', 'project', 'service', 'staff']
    }).$promise;
  }

  /** @ngInject */
  function StateController(order) {
    var vm = this;

    vm.order = order;
    vm.staff = order.staff;
    vm.product = order.product;
    vm.service = order.service;
    vm.project = order.project;

    vm.activate = activate;

    activate();

    function activate() {
    }
  }
})();
<|endoftext|>