<commit_before>'use strict'
/*
 react-native-swiper

 @author leecade<leecade@163.com>

 react-native-swiper2

 @author sunnylqm
 */
import React, {
  StyleSheet,
  Text,
  View,
  ScrollView,
  TouchableOpacity,
  Dimensions,
  Platform,
  ViewPagerAndroid
} from 'react-native'

// Using bare setTimeout, setInterval, setImmediate
// and requestAnimationFrame calls is very dangerous
// because if you forget to cancel the request before
// the component is unmounted, you risk the callback
// throwing an exception.
import TimerMixin from 'react-timer-mixin'

let { width, height } = Dimensions.get('window')

/**
 * Default styles
 * @type {StyleSheetPropType}
 */
let styles = StyleSheet.create({
  container: {
    backgroundColor: 'transparent',
    position: 'relative',
  },

  wrapper: {
    backgroundColor: 'transparent',
  },

  slide: {
    backgroundColor: 'transparent',
  },

  pagination: {
    position: 'absolute',
    bottom: 25,
    left: 0,
    right: 0,
    flexDirection: 'row',
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor:'transparent',
  },

  title: {
    height: 30,
    justifyContent: 'center',
    position: 'absolute',
    paddingLeft: 10,
    bottom: -30,
    left: 0,
    flexWrap: 'nowrap',
    width: 250,
    backgroundColor: 'transparent',
  },

  buttonWrapper: {
    backgroundColor: 'transparent',
    flexDirection: 'row',
    position: 'absolute',
    top: 0,
    left: 0,
    flex: 1,
    paddingHorizontal: 10,
    paddingVertical: 10,
    justifyContent: 'space-between',
    alignItems: 'center'
  },

  buttonText: {
    fontSize: 50,
    color: '#007aff',
    fontFamily: 'Arial',
  },
})

export default React.createClass({

  /**
   * Props Validation
   * @type {Object}
   */
  propTypes: {
    horizontal                       : React.PropTypes.bool,
    children                         : React.PropTypes.node.isRequired,
    style                            : View.propTypes.style,
    pagingEnabled                    : React.PropTypes.bool,
    showsHorizontalScrollIndicator   : React.PropTypes.bool,
    showsVerticalScrollIndicator     : React.PropTypes.bool,
    bounces                          : React.PropTypes.bool,
    scrollsToTop                     : React.PropTypes.bool,
    removeClippedSubviews            : React.PropTypes.bool,
    automaticallyAdjustContentInsets : React.PropTypes.bool,
    showsPagination                  : React.PropTypes.bool,
    showsButtons                     : React.PropTypes.bool,
    loop                             : React.PropTypes.bool,
    autoplay                         : React.PropTypes.bool,
    autoplayTimeout                  : React.PropTypes.number,
    autoplayDirection                : React.PropTypes.bool,
    index                            : React.PropTypes.number,
    renderPagination                 : React.PropTypes.func,
    onPageChanged                    : React.PropTypes.func,
  },

  mixins: [TimerMixin],

  /**
   * Default props
   * @return {object} props
   * @see http://facebook.github.io/react-native/docs/scrollview.html
   */
  getDefaultProps() {
    return {
      horizontal                       : true,
      pagingEnabled                    : true,
      showsHorizontalScrollIndicator   : false,
      showsVerticalScrollIndicator     : false,
      bounces                          : false,
      scrollsToTop                     : false,
      removeClippedSubviews            : true,
      automaticallyAdjustContentInsets : false,
      showsPagination                  : true,
      showsButtons                     : false,
      loop                             : true,
      autoplay                         : false,
      autoplayTimeout                  : 2.5,
      autoplayDirection                : true,
      index                            : 0,
    }
  },

  /**
   * Init states
   * @return {object} states
   */
  getInitialState() {
    let props = this.props

    let initState = {
      autoplayEnd: false,
    }

    initState.total = props.children
      ? (props.children.length || 1)
      : 0

    initState.index = initState.total > 1
      ? Math.min(props.index, initState.total - 1)
      : 0

    // Default: horizontal
    initState.width = props.width || width
    initState.height = props.height || height

    return initState
  },

  /**
   * autoplay timer
   * @type {null}
   */
  autoplayTimer: null,

  componentWillMount() {
    this.props = this.injectState(this.props)
  },

  componentDidMount() {
    this.autoplay()
  },

  autoplay(){
    if(!this.props.autoplay
      || this.state.autoplayEnd) return;
    console.log("Will autoplay.");
    clearTimeout(this.autoplayTimer)
    this.autoplayTimer = this.setTimeout(() => {
      if(!this.props.loop && (this.props.autoplayDirection
          ? this.state.index == this.state.total - 1
          : this.state.index == 0)) return this.setState({
        autoplayEnd: true
      })
      this.viewPager && console.log("Now autoplay."+this.state.index + (this.props.loop?1:0) +(this.props.autoplayDirection?1:-1));
      this.viewPager && this.viewPager.setPage(this.state.index + (this.props.loop?1:0) +(this.props.autoplayDirection?1:-1));
    }, this.props.autoplayTimeout * 1000)
  },

  onPageSelected(ev){

  },

  onPageScroll(ev){
    const hasLoop = this.props.loop && this.state.total > 1;
    const lastPage = this.state.index + (hasLoop ? 1 : 0);

    let page = ev.nativeEvent.position;
    if (hasLoop){
      // do Loop
      if (page + ev.nativeEvent.offset <= 0){
        page = this.state.total;
        this.viewPager && this.viewPager.setPageWithoutAnimation(page);
      } else if(page+ev.nativeEvent.offset >= this.state.total+1){
        page = 1;
        this.viewPager && this.viewPager.setPageWithoutAnimation(page);
      }
    }
    this.autoplay();

    if (page != lastPage){
      this.setState({
        index: page - 1,
      });
      this.props.onPageChanged && this.props.onPageChanged(this.state.index);
    }
  },

  renderScrollView(pages) {
    return (
      <ViewPagerAndroid
          style={{flex: 1}}
          initialPage={this.state.index + (this.props.loop?1:0)}
          onPageSelected={this.onPageSelected}
          onPageScroll={this.onPageScroll}
          ref = {val=>this.viewPager = val }
      >
        {pages}
      </ViewPagerAndroid>
    );
  },

  /**
   * Render pagination
   * @return {object} react-dom
   */
  renderPagination() {

    // By default, dots only show when `total` > 2
    if(this.state.total <= 1) return null

    let dots = []
    let ActiveDot = this.props.activeDot || <View style={{
            backgroundColor: '#007aff',
            width: 8,
            height: 8,
            borderRadius: 4,
            marginLeft: 3,
            marginRight: 3,
            marginTop: 3,
            marginBottom: 3,
          }} />;
    let Dot = this.props.dot || <View style={{
            backgroundColor:'rgba(0,0,0,.2)',
            width: 8,
            height: 8,
            borderRadius: 4,
            marginLeft: 3,
            marginRight: 3,
            marginTop: 3,
            marginBottom: 3,
          }} />;
    for(let i = 0; i < this.state.total; i++) {
      dots.push(i === this.state.index
        ?
        React.cloneElement(ActiveDot, {key: i})
        :
        React.cloneElement(Dot, {key: i})
      )
    }

    return (
      <View pointerEvents='none' style={[styles.pagination, this.props.paginationStyle]}>
        {dots}
      </View>
    )
  },

  renderTitle() {
    let child = this.props.children[this.state.index]
    let title = child && child.props.title
    return title
      ? (
      <View style={styles.title}>
        {this.props.children[this.state.index].props.title}
      </View>
    )
      : null
  },

  renderButtons() {

    let nextButton = this.props.nextButton || <Text style={[styles.buttonText, {color: !this.props.loop && this.state.index == this.state.total - 1 ? 'rgba(0,0,0,0)' : '#007aff'}]}>›</Text>

    let prevButton = this.props.prevButton || <Text style={[styles.buttonText, {color: !this.props.loop && this.state.index == 0 ? 'rgba(0,0,0,0)' : '#007aff'}]}>‹</Text>

    return (
      <View pointerEvents='box-none' style={[styles.buttonWrapper, {width: this.state.width, height: this.state.height}, this.props.buttonWrapperStyle]}>
        <TouchableOpacity onPress={() => !(!this.props.loop && this.state.index == 0) && this.scrollTo.call(this, -1)}>
          <View>
            {prevButton}
          </View>
        </TouchableOpacity>
        <TouchableOpacity onPress={() => !(!this.props.loop && this.state.index == this.state.total - 1) && this.scrollTo.call(this, 1)}>
          <View>
            {nextButton}
          </View>
        </TouchableOpacity>
      </View>
    )
  },

  /**
   * Inject state to ScrollResponder
   * @param  {object} props origin props
   * @return {object} props injected props
   */
  injectState(props) {
    /*    const scrollResponders = [
     'onMomentumScrollBegin',
     'onTouchStartCapture',
     'onTouchStart',
     'onTouchEnd',
     'onResponderRelease',
     'onPageChanged',
     ]*/

    for(let prop in props) {
      // if(~scrollResponders.indexOf(prop)
      if(typeof props[prop] === 'function'
        && prop !== 'onMomentumScrollEnd'
        && prop !== 'renderPagination'
        && prop !== 'onScrollBeginDrag'
        && prop !== 'onPageChanged'
      ) {
        let originResponder = props[prop]
        props[prop] = (e) => originResponder(e, this.state, this)
      }
    }

    return props
  },

  /**
   * Default render
   * @return {object} react-dom
   */
  render() {
    let state = this.state
    let props = this.props
    let children = props.children
    let index = state.index
    let total = state.total
    let loop = props.loop
    let dir = state.dir
    let key = 0

    let pages = []
    let pageStyle = [{width: state.width, height: state.height}, styles.slide]

    // For make infinite at least total > 1
    if(total > 1) {

      // Re-design a loop model for avoid img flickering
      pages = Object.keys(children)
      if(loop) {
        pages.unshift(total - 1)
        pages.push(0)
      }

      pages = pages.map((page, i) =>
        <View style={pageStyle} key={i}>{children[page]}</View>
      )
    }
    else pages = <View style={pageStyle}>{children}</View>

    return (
      <View style={[styles.container, {
        width: state.width,
        height: state.height
      }]}>
        {this.renderScrollView(pages)}
        {props.showsPagination && (props.renderPagination
          ? this.props.renderPagination(state.index, state.total, this)
          : this.renderPagination())}
        {this.renderTitle()}
        {this.props.showsButtons && this.renderButtons()}
      </View>
    )
  }
})
<commit_msg>Add missing scrollTo method in Android<commit_after>'use strict'
/*
 react-native-swiper

 @author leecade<leecade@163.com>

 react-native-swiper2

 @author sunnylqm
 */
import React, {
  StyleSheet,
  Text,
  View,
  ScrollView,
  TouchableOpacity,
  Dimensions,
  Platform,
  ViewPagerAndroid
} from 'react-native'

// Using bare setTimeout, setInterval, setImmediate
// and requestAnimationFrame calls is very dangerous
// because if you forget to cancel the request before
// the component is unmounted, you risk the callback
// throwing an exception.
import TimerMixin from 'react-timer-mixin'

let { width, height } = Dimensions.get('window')

/**
 * Default styles
 * @type {StyleSheetPropType}
 */
let styles = StyleSheet.create({
  container: {
    backgroundColor: 'transparent',
    position: 'relative',
  },

  wrapper: {
    backgroundColor: 'transparent',
  },

  slide: {
    backgroundColor: 'transparent',
  },

  pagination: {
    position: 'absolute',
    bottom: 25,
    left: 0,
    right: 0,
    flexDirection: 'row',
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor:'transparent',
  },

  title: {
    height: 30,
    justifyContent: 'center',
    position: 'absolute',
    paddingLeft: 10,
    bottom: -30,
    left: 0,
    flexWrap: 'nowrap',
    width: 250,
    backgroundColor: 'transparent',
  },

  buttonWrapper: {
    backgroundColor: 'transparent',
    flexDirection: 'row',
    position: 'absolute',
    top: 0,
    left: 0,
    flex: 1,
    paddingHorizontal: 10,
    paddingVertical: 10,
    justifyContent: 'space-between',
    alignItems: 'center'
  },

  buttonText: {
    fontSize: 50,
    color: '#007aff',
    fontFamily: 'Arial',
  },
})

export default React.createClass({

  /**
   * Props Validation
   * @type {Object}
   */
  propTypes: {
    horizontal                       : React.PropTypes.bool,
    children                         : React.PropTypes.node.isRequired,
    style                            : View.propTypes.style,
    pagingEnabled                    : React.PropTypes.bool,
    showsHorizontalScrollIndicator   : React.PropTypes.bool,
    showsVerticalScrollIndicator     : React.PropTypes.bool,
    bounces                          : React.PropTypes.bool,
    scrollsToTop                     : React.PropTypes.bool,
    removeClippedSubviews            : React.PropTypes.bool,
    automaticallyAdjustContentInsets : React.PropTypes.bool,
    showsPagination                  : React.PropTypes.bool,
    showsButtons                     : React.PropTypes.bool,
    loop                             : React.PropTypes.bool,
    autoplay                         : React.PropTypes.bool,
    autoplayTimeout                  : React.PropTypes.number,
    autoplayDirection                : React.PropTypes.bool,
    index                            : React.PropTypes.number,
    renderPagination                 : React.PropTypes.func,
    onPageChanged                    : React.PropTypes.func,
  },

  mixins: [TimerMixin],

  /**
   * Default props
   * @return {object} props
   * @see http://facebook.github.io/react-native/docs/scrollview.html
   */
  getDefaultProps() {
    return {
      horizontal                       : true,
      pagingEnabled                    : true,
      showsHorizontalScrollIndicator   : false,
      showsVerticalScrollIndicator     : false,
      bounces                          : false,
      scrollsToTop                     : false,
      removeClippedSubviews            : true,
      automaticallyAdjustContentInsets : false,
      showsPagination                  : true,
      showsButtons                     : false,
      loop                             : true,
      autoplay                         : false,
      autoplayTimeout                  : 2.5,
      autoplayDirection                : true,
      index                            : 0,
    }
  },

  /**
   * Init states
   * @return {object} states
   */
  getInitialState() {
    let props = this.props

    let initState = {
      autoplayEnd: false,
    }

    initState.total = props.children
      ? (props.children.length || 1)
      : 0

    initState.index = initState.total > 1
      ? Math.min(props.index, initState.total - 1)
      : 0

    // Default: horizontal
    initState.width = props.width || width
    initState.height = props.height || height

    return initState
  },

  /**
   * autoplay timer
   * @type {null}
   */
  autoplayTimer: null,

  componentWillMount() {
    this.props = this.injectState(this.props)
  },

  componentDidMount() {
    this.autoplay()
  },

  autoplay(){
    if(!this.props.autoplay
      || this.state.autoplayEnd) return;
    console.log("Will autoplay.");
    clearTimeout(this.autoplayTimer)
    this.autoplayTimer = this.setTimeout(() => {
      if(!this.props.loop && (this.props.autoplayDirection
          ? this.state.index == this.state.total - 1
          : this.state.index == 0)) return this.setState({
        autoplayEnd: true
      })
      this.viewPager && console.log("Now autoplay."+this.state.index + (this.props.loop?1:0) +(this.props.autoplayDirection?1:-1));
      this.viewPager && this.viewPager.setPage(this.state.index + (this.props.loop?1:0) +(this.props.autoplayDirection?1:-1));
    }, this.props.autoplayTimeout * 1000)
  },

  onPageSelected(ev){

  },

  onPageScroll(ev){
    const hasLoop = this.props.loop && this.state.total > 1;
    const lastPage = this.state.index + (hasLoop ? 1 : 0);

    let page = ev.nativeEvent.position;
    if (hasLoop){
      // do Loop
      if (page + ev.nativeEvent.offset <= 0){
        page = this.state.total;
        this.viewPager && this.viewPager.setPageWithoutAnimation(page);
      } else if(page+ev.nativeEvent.offset >= this.state.total+1){
        page = 1;
        this.viewPager && this.viewPager.setPageWithoutAnimation(page);
      }
    }
    this.autoplay();

    if (page != lastPage){
      this.setState({
        index: page - 1,
      });
      this.props.onPageChanged && this.props.onPageChanged(this.state.index);
    }
  },

  renderScrollView(pages) {
    return (
      <ViewPagerAndroid
          style={{flex: 1}}
          initialPage={this.state.index + (this.props.loop?1:0)}
          onPageSelected={this.onPageSelected}
          onPageScroll={this.onPageScroll}
          ref = {val=>this.viewPager = val }
      >
        {pages}
      </ViewPagerAndroid>
    );
  },

  /**
   * Scroll by index
   * @param  {number} index offset index
   */
  scrollTo(indexOffset) {
    let newIndex = (this.props.loop ? 1 : 0) + indexOffset + this.state.index
    this.viewPager && this.viewPager.setPage(newIndex);
  },

  /**
   * Render pagination
   * @return {object} react-dom
   */
  renderPagination() {

    // By default, dots only show when `total` > 2
    if(this.state.total <= 1) return null

    let dots = []
    let ActiveDot = this.props.activeDot || <View style={{
            backgroundColor: '#007aff',
            width: 8,
            height: 8,
            borderRadius: 4,
            marginLeft: 3,
            marginRight: 3,
            marginTop: 3,
            marginBottom: 3,
          }} />;
    let Dot = this.props.dot || <View style={{
            backgroundColor:'rgba(0,0,0,.2)',
            width: 8,
            height: 8,
            borderRadius: 4,
            marginLeft: 3,
            marginRight: 3,
            marginTop: 3,
            marginBottom: 3,
          }} />;
    for(let i = 0; i < this.state.total; i++) {
      dots.push(i === this.state.index
        ?
        React.cloneElement(ActiveDot, {key: i})
        :
        React.cloneElement(Dot, {key: i})
      )
    }

    return (
      <View pointerEvents='none' style={[styles.pagination, this.props.paginationStyle]}>
        {dots}
      </View>
    )
  },

  renderTitle() {
    let child = this.props.children[this.state.index]
    let title = child && child.props.title
    return title
      ? (
      <View style={styles.title}>
        {this.props.children[this.state.index].props.title}
      </View>
    )
      : null
  },

  renderButtons() {

    let nextButton = this.props.nextButton || <Text style={[styles.buttonText, {color: !this.props.loop && this.state.index == this.state.total - 1 ? 'rgba(0,0,0,0)' : '#007aff'}]}>›</Text>

    let prevButton = this.props.prevButton || <Text style={[styles.buttonText, {color: !this.props.loop && this.state.index == 0 ? 'rgba(0,0,0,0)' : '#007aff'}]}>‹</Text>

    return (
      <View pointerEvents='box-none' style={[styles.buttonWrapper, {width: this.state.width, height: this.state.height}, this.props.buttonWrapperStyle]}>
        <TouchableOpacity onPress={() => !(!this.props.loop && this.state.index == 0) && this.scrollTo.call(this, -1)}>
          <View>
            {prevButton}
          </View>
        </TouchableOpacity>
        <TouchableOpacity onPress={() => !(!this.props.loop && this.state.index == this.state.total - 1) && this.scrollTo.call(this, 1)}>
          <View>
            {nextButton}
          </View>
        </TouchableOpacity>
      </View>
    )
  },

  /**
   * Inject state to ScrollResponder
   * @param  {object} props origin props
   * @return {object} props injected props
   */
  injectState(props) {
    /*    const scrollResponders = [
     'onMomentumScrollBegin',
     'onTouchStartCapture',
     'onTouchStart',
     'onTouchEnd',
     'onResponderRelease',
     'onPageChanged',
     ]*/

    for(let prop in props) {
      // if(~scrollResponders.indexOf(prop)
      if(typeof props[prop] === 'function'
        && prop !== 'onMomentumScrollEnd'
        && prop !== 'renderPagination'
        && prop !== 'onScrollBeginDrag'
        && prop !== 'onPageChanged'
      ) {
        let originResponder = props[prop]
        props[prop] = (e) => originResponder(e, this.state, this)
      }
    }

    return props
  },

  /**
   * Default render
   * @return {object} react-dom
   */
  render() {
    let state = this.state
    let props = this.props
    let children = props.children
    let index = state.index
    let total = state.total
    let loop = props.loop
    let dir = state.dir
    let key = 0

    let pages = []
    let pageStyle = [{width: state.width, height: state.height}, styles.slide]

    // For make infinite at least total > 1
    if(total > 1) {

      // Re-design a loop model for avoid img flickering
      pages = Object.keys(children)
      if(loop) {
        pages.unshift(total - 1)
        pages.push(0)
      }

      pages = pages.map((page, i) =>
        <View style={pageStyle} key={i}>{children[page]}</View>
      )
    }
    else pages = <View style={pageStyle}>{children}</View>

    return (
      <View style={[styles.container, {
        width: state.width,
        height: state.height
      }]}>
        {this.renderScrollView(pages)}
        {props.showsPagination && (props.renderPagination
          ? this.props.renderPagination(state.index, state.total, this)
          : this.renderPagination())}
        {this.renderTitle()}
        {this.props.showsButtons && this.renderButtons()}
      </View>
    )
  }
})
<|endoftext|>


<commit_before>// ==UserScript==
// @name         ProgrammersForum Detect Bots
// @namespace    programmersforum.ru
// @version      1.8.3
// @description  adds detectBots function that loads the list of online users and counts bots, and logUsers/startLogDaemon functions to save users into IndexedDB
// @author       Alex P
// @include      *programmersforum.ru/*
// @require      https://cdn.jsdelivr.net/npm/lodash@4.17.15/lodash.min.js
// @require      https://unpkg.com/dexie@2.0.4/dist/dexie.js
// @require      https://cdn.jsdelivr.net/npm/file-saver@2.0.2/dist/FileSaver.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.min.js
// @require      https://unpkg.com/papaparse@5.1.0/papaparse.min.js
// @grant        none
// @downloadURL  https://github.com/AlexP11223/ProgForumRuUserscripts/raw/master/pf_detect_bots.user.js
// ==/UserScript==

(function () {
    'use strict';

    const OUTPUT_TIMEZONE = 3;

    function sleep(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }

    function isBot(user) {
        return user.detections && user.detections.length;
    }

    function identify(users) {
        const botUaParts = [
            'bot', 'crawl', 'spider', 'batch', 'bing',
            'share', 'preview', 'facebook', 'vk.com',
            'curl', 'indy', 'http',
            'media', 'metrics', '(compatible)',
            'zh-cn', 'zh_cn', 'mb2345', 'liebao', 'micromessenger', 'kinza', // chinese https://www.johnlarge.co.uk/blocking-aggressive-chinese-crawlers-scrapers-bots/
        ];

        const ipCounts = _.countBy(users, 'ip');
        const subnet3Counts = _.countBy(users, u => u.subnet(3));
        const subnet2Counts = _.countBy(users, u => u.subnet(2));

        const detectors = [
            user => user.useragent.length < 20 || botUaParts.some(s => user.useragent.includes(s)) ? 'ua' : null,
            user => ipCounts[user.ip] > 2 ? 'ip' : null,
            user => subnet3Counts[user.subnet(3)] > 5 ? 'subnet3' : null,
            user => subnet2Counts[user.subnet(2)] > 20 ? 'subnet2' : null,
        ];

        return users.map(user => {
            const detections = detectors.map(d => d(user)).filter(Boolean);
            return Object.assign(user, {
                detections,
                isBot: function () {
                    return isBot(this);
                },
            })
        });
    }

    async function loadOnlineUsers(url = '/online.php?s=&sortfield=time&sortorder=desc&who=all&ua=1&pp=200') {
        console.log(`Loading ${url}`);
        const html = await $.get(url);

        const users = $(html).find('[id^=resolveip]').toArray()
            .map(el => ({
                ip: $(el).text().trim(),
                useragent: $(el).parent().text().trim().split('\n').slice(-1)[0].toLowerCase().trim(),
                subnet: function (n) {
                    return this.ip.split('.').slice(0, n).join('.');
                },
            }));

        const nextPageLinks = $(html).find('a[href^=online][rel=next]');
        if (!nextPageLinks.length) {
            return users;
        }

        const nextPageUrl = nextPageLinks[0].href;
        return users.concat(await loadOnlineUsers(nextPageUrl));
    }

    window.detectBots = async function () {
        const users = identify(await loadOnlineUsers());

        const bots = users.filter(u => u.isBot());

        const mapToOutput = u => ({ ip: u.ip, useragent: u.useragent, detections: u.detections.join(', ') });
        console.log(`${bots.length} bots, ${users.length - bots.length} normal users`);
        console.log('Bots:');
        console.log(bots.map(mapToOutput));
        console.log('Normal users:');
        console.log(users.filter(u => !u.isBot()).map(mapToOutput));

        window.onlineUsers = users;
    };

    let _db = null;

    function db() {
        if (!_db) {
            _db = new Dexie('UserLogDatabase');
            _db.version(1).stores({ users: '++, date, ip, useragent, *detections' });
        }
        return _db;
    }

    window.userLogsDb = db;

    window.logUsers = async function () {
        const users = identify(await loadOnlineUsers());

        await db().users.bulkPut(users.map(u => ({
            date: new Date(),
            ip: u.ip,
            useragent: u.useragent,
            detections: u.detections,
        })));
        console.log(`Saved ${users.length} users to db`);
    };

    window.startLogDaemon = async function () {
        while (true) {
            try {
                await logUsers();
            } catch (e) {
                console.log(e);
            }

            await sleep(20 * 60 * 1000);
        }
    };

    window.FILTER_IS_BOT = isBot;
    window.FILTER_IS_NORMAL_USER = user => !isBot(user);

    window.filterUserLogs = async function (filter = () => true, startDate = '2019-10-30 00:00:00+03', endDate = new Date()) {
        return await db().users
            .where('date').between(moment(startDate).toDate(), moment(endDate).toDate())
            .and(filter)
            .toArray();
    };

    window.exportToCsv = function (records, header = [], fileName = 'data.csv') {
        const csv = Papa.unparse([header].concat(records), {
            skipEmptyLines: true,
        });

        const blob = new Blob([csv], { type: 'text/csv;charset=utf-8'});
        saveAs(blob, fileName);
    };

    window.exportUsersToCsv = function (users) {
        const header = ['Date/Time', 'IP', 'User-Agent', 'Detections'];
        const records = users.map(u => [
            moment(u.date).utcOffset(OUTPUT_TIMEZONE).format('YYYY-MM-DD HH:mm:ss'),
            u.ip,
            u.useragent,
            u.detections.join(', '),
        ]);

        exportToCsv(records, header, 'users.csv');
    };

    window.uniqueUsers = function (users) {
        return _.uniqBy(users, 'ip');
    };

    window.countVisitorsByTime = function (users) {
        return _.countBy(users, u => moment(u.date).utcOffset(OUTPUT_TIMEZONE).seconds(0).milliseconds(0).format('YYYY-MM-DD HH:mm'));
    };
})();
<commit_msg>count os, browsers<commit_after>// ==UserScript==
// @name         ProgrammersForum Detect Bots
// @namespace    programmersforum.ru
// @version      1.9.0
// @description  adds detectBots function that loads the list of online users and counts bots, and logUsers/startLogDaemon functions to save users into IndexedDB
// @author       Alex P
// @include      *programmersforum.ru/*
// @require      https://cdn.jsdelivr.net/npm/lodash@4.17.15/lodash.min.js
// @require      https://unpkg.com/dexie@2.0.4/dist/dexie.js
// @require      https://cdn.jsdelivr.net/npm/file-saver@2.0.2/dist/FileSaver.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.min.js
// @require      https://unpkg.com/papaparse@5.1.0/papaparse.min.js
// @require      https://cdn.jsdelivr.net/npm/ua-parser-js@0/dist/ua-parser.min.js
// @grant        none
// @downloadURL  https://github.com/AlexP11223/ProgForumRuUserscripts/raw/master/pf_detect_bots.user.js
// ==/UserScript==

(function () {
    'use strict';

    const OUTPUT_TIMEZONE = 3;

    function sleep(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }

    function isBot(user) {
        return user.detections && user.detections.length;
    }

    function identify(users) {
        const botUaParts = [
            'bot', 'crawl', 'spider', 'batch', 'bing',
            'share', 'preview', 'facebook', 'vk.com',
            'curl', 'indy', 'http',
            'media', 'metrics', '(compatible)',
            'zh-cn', 'zh_cn', 'mb2345', 'liebao', 'micromessenger', 'kinza', // chinese https://www.johnlarge.co.uk/blocking-aggressive-chinese-crawlers-scrapers-bots/
        ];

        const ipCounts = _.countBy(users, 'ip');
        const subnet3Counts = _.countBy(users, u => u.subnet(3));
        const subnet2Counts = _.countBy(users, u => u.subnet(2));

        const detectors = [
            user => user.useragent.length < 20 || botUaParts.some(s => user.useragent.includes(s)) ? 'ua' : null,
            user => ipCounts[user.ip] > 2 ? 'ip' : null,
            user => subnet3Counts[user.subnet(3)] > 5 ? 'subnet3' : null,
            user => subnet2Counts[user.subnet(2)] > 20 ? 'subnet2' : null,
        ];

        return users.map(user => {
            const detections = detectors.map(d => d(user)).filter(Boolean);
            return Object.assign(user, {
                detections,
                isBot: function () {
                    return isBot(this);
                },
            })
        });
    }

    async function loadOnlineUsers(url = '/online.php?s=&sortfield=time&sortorder=desc&who=all&ua=1&pp=200') {
        console.log(`Loading ${url}`);
        const html = await $.get(url);

        const users = $(html).find('[id^=resolveip]').toArray()
            .map(el => ({
                ip: $(el).text().trim(),
                useragent: $(el).parent().text().trim().split('\n').slice(-1)[0].toLowerCase().trim(),
                subnet: function (n) {
                    return this.ip.split('.').slice(0, n).join('.');
                },
            }));

        const nextPageLinks = $(html).find('a[href^=online][rel=next]');
        if (!nextPageLinks.length) {
            return users;
        }

        const nextPageUrl = nextPageLinks[0].href;
        return users.concat(await loadOnlineUsers(nextPageUrl));
    }

    window.detectBots = async function () {
        const users = identify(await loadOnlineUsers());

        const bots = users.filter(u => u.isBot());

        const mapToOutput = u => ({ ip: u.ip, useragent: u.useragent, detections: u.detections.join(', ') });
        console.log(`${bots.length} bots, ${users.length - bots.length} normal users`);
        console.log('Bots:');
        console.log(bots.map(mapToOutput));
        console.log('Normal users:');
        console.log(users.filter(u => !u.isBot()).map(mapToOutput));

        window.onlineUsers = users;
    };

    let _db = null;

    function db() {
        if (!_db) {
            _db = new Dexie('UserLogDatabase');
            _db.version(1).stores({ users: '++, date, ip, useragent, *detections' });
        }
        return _db;
    }

    window.userLogsDb = db;

    window.logUsers = async function () {
        const users = identify(await loadOnlineUsers());

        await db().users.bulkPut(users.map(u => ({
            date: new Date(),
            ip: u.ip,
            useragent: u.useragent,
            detections: u.detections,
        })));
        console.log(`Saved ${users.length} users to db`);
    };

    window.startLogDaemon = async function () {
        while (true) {
            try {
                await logUsers();
            } catch (e) {
                console.log(e);
            }

            await sleep(20 * 60 * 1000);
        }
    };

    window.FILTER_IS_BOT = isBot;
    window.FILTER_IS_NORMAL_USER = user => !isBot(user);

    window.filterUserLogs = async function (filter = () => true, startDate = '2019-10-30 00:00:00+03', endDate = new Date()) {
        return await db().users
            .where('date').between(moment(startDate).toDate(), moment(endDate).toDate())
            .and(filter)
            .toArray();
    };

    window.exportToCsv = function (records, header = [], fileName = 'data.csv') {
        const csv = Papa.unparse([header].concat(records), {
            skipEmptyLines: true,
        });

        const blob = new Blob([csv], { type: 'text/csv;charset=utf-8'});
        saveAs(blob, fileName);
    };

    window.exportUsersToCsv = function (users) {
        const header = ['Date/Time', 'IP', 'User-Agent', 'Detections'];
        const records = users.map(u => [
            moment(u.date).utcOffset(OUTPUT_TIMEZONE).format('YYYY-MM-DD HH:mm:ss'),
            u.ip,
            u.useragent,
            u.detections.join(', '),
        ]);

        exportToCsv(records, header, 'users.csv');
    };

    window.uniqueUsers = function (users) {
        return _.uniqBy(users, 'ip');
    };

    window.countVisitorsByTime = function (users) {
        return _.countBy(users, u => moment(u.date).utcOffset(OUTPUT_TIMEZONE).seconds(0).milliseconds(0).format('YYYY-MM-DD HH:mm'));
    };

    // some user-agents are truncated
    function isYandexBrowser(ua) {
        return ua.split(' ').pop().indexOf('Ya') === 0;
    }

    function isChrome(ua) {
        return ua.split(' ').pop().indexOf('Ch') === 0;
    }

    function getOsNameVersion(uaData) {
        const name = uaData.os.name;
        const version = uaData.os.version;
        return [name, version].join(' ').trim();
    }

    function getBrowserNameVersion(uaData) {
        let name = uaData.browser.name;
        let version = uaData.browser.version;
        if (name === 'Chrome' && isYandexBrowser(uaData.ua)) {
            name = 'Yandex';
            version = '';
        }
        return [name, version].join(' ').trim();
    }

    window.countUsersOS = function (users) {
        return _.countBy(users, u => getOsNameVersion(UAParser(u.useragent)));
    };

    window.countUsersBrowsers = function (users) {
        return _.countBy(users, u => getBrowserNameVersion(UAParser(u.useragent)));
    };
})();
<|endoftext|>


<commit_before>// if you don't specify a html file, the sniper will generate a div
var app = require("biojs-vis-keggviewer");
var instance = new app({
  target: yourDiv.id,
  pathId: 'hsa04910',
  proxyUrl: 'http://www.corsproxy.com/',
  expression:{
    upColor:'red',
    downColor:'blue',
    genes: ['hsa:7248', 'hsa:51763', 'hsa:2002', 'hsa:2194'],
    conditions: [
    {
      name: 'condition 1',
      values: [-1, 0.5, 0.7, -0.3]
    },
    {
      name: 'condition 2',
      values: [0.5, -0.1, -0.2, 1]
    },
      {
        name: 'condition 3',
        values: [0, 0.4, -0.2, 0.5]
      }
    ]
  }
});
<commit_msg>refurbished so it works with the new version<commit_after>// if you don't specify a html file, the sniper will generate a div
var biojsviskegg = require("biojs-vis-keggviewer");

var expression = {
    upColor:'red',
    downColor:'blue',
    genes: ['hsa:7248', 'hsa:51763', 'hsa:2002', 'hsa:2194'],
    conditions: [
        {
            name: 'condition 1',
            values: [-1, 0.5, 0.7, -0.3]
        },
        {
            name: 'condition 2',
            values: [0.5, -0.1, -0.2, 1]
        },
        {
            name: 'condition 3',
            values: [0, 0.4, -0.2, 0.5]
        }
    ]
};

// Use heroku proxy
var proxy = function(url){
    return 'https://cors-anywhere.herokuapp.com/'+url;
};

biojsviskegg.pathway('hsa04910').proxy(proxy).expression(expression).target('body').init(); <|endoftext|>


<commit_before>// if you don't specify a html file, the sniper will generate a div
var app = require("biojs-vis-interactions-heatmap-d3");
var instance = new app({
  target: "thediv",
			width: 700,
			height: 350
});

for (var pid=1;pid<=15;pid++)
  instance.addProtein({ "id":"prot_"+pid,"name":pid,"showLegend":false,"typeLegend":"id","organism":"human"+pid%3,"features":{"f1":Math.random(),"f2":"val2","f3":"val3"}});

for (var pid=1;pid<=30;pid++)
  instance.addInteraction("prot_"+Math.floor((Math.random()*15)+1),"prot_"+Math.floor((Math.random()*15)+1) ,{score:Math.random()});

instance.restart();
instance.on("proteinMouseOver", function( p ) {
    instance.activateProteins([p.protein]);
});

instance.on("proteinMouseOut", function( p ) {
    instance.deactivateProteins();
});
instance.on("interactionMouseOver", function( p ) {
  instance.activateProteins([p.interaction.source,p.interaction.target]);
});
instance.on("interactionMouseOut", function( p ) {
    instance.deactivateProteins();
});

var selected = null;
instance.on("interactionClick", function( d ) {
  if ("#cell_"+d.interaction.id==selected){
    instance.highlight("#cell_"+d.interaction.id,false);
    instance.updateInfoFrame(null,"left");
    instance.updateInfoFrame(null,"right");
    selected=null;
  }else{
    if (selected!=null){
      instance.highlight(selected,false);
    }
    instance.updateInfoFrame(d.interaction.source,"left");
    instance.updateInfoFrame(d.interaction.target,"right");
    instance.highlight("#cell_"+d.interaction.id);
    selected="#cell_"+d.interaction.id;
  }
});
<commit_msg>putting colours in the example<commit_after>// if you don't specify a html file, the sniper will generate a div
var app = require("biojs-vis-interactions-heatmap-d3");
var instance = new app({
  target: "snippetDiv",
			width: 700,
			height: 350
});

for (var pid=1;pid<=15;pid++)
  instance.addProtein({ "id":"prot_"+pid,"name":pid,"showLegend":false,"typeLegend":"id","organism":"human"+pid%3,"features":{"f1":Math.random(),"f2":"val2","f3":"val3"}});

for (var pid=1;pid<=30;pid++)
  instance.addInteraction("prot_"+Math.floor((Math.random()*15)+1),"prot_"+Math.floor((Math.random()*15)+1) ,{score:Math.random()});

instance.restart();

//paint the proteins
for (var pid=1;pid<=15;pid++)
	  for (var pid2=pid+1;pid2<=15;pid2++){
	    var rgb = "rgb("+(256-pid*10)+","+pid2*10+",0)";
	    instance.setFillColor("#cell_prot_"+pid+"_prot_"+pid2,rgb);
	    instance.setFillColor("#cell_prot_"+pid2+"_prot_"+pid,rgb);
	  }

//play with the events to make it interactive
instance.on("proteinMouseOver", function( p ) {
    instance.activateProteins([p.protein]);
});
instance.on("proteinMouseOut", function( p ) {
    instance.deactivateProteins();
});
instance.on("interactionMouseOver", function( p ) {
  instance.activateProteins([p.interaction.source,p.interaction.target]);
});
instance.on("interactionMouseOut", function( p ) {
    instance.deactivateProteins();
});
//show the features information in the panels
var selected = null;
instance.on("interactionClick", function( d ) {
  if ("#cell_"+d.interaction.id==selected){
    instance.highlight("#cell_"+d.interaction.id,false);
    instance.updateInfoFrame(null,"left");
    instance.updateInfoFrame(null,"right");
    selected=null;
  }else{
    if (selected!=null){
      instance.highlight(selected,false);
    }
    instance.updateInfoFrame(d.interaction.source,"left");
    instance.updateInfoFrame(d.interaction.target,"right");
    instance.highlight("#cell_"+d.interaction.id);
    selected="#cell_"+d.interaction.id;
  }
});
<|endoftext|>


<commit_before>define(["./map.js"], function (map) {

    function Select (bezl, custNum) {
        if (bezl.vars.markers[custNum]) {
        // Locate this customer and navigate to them on the map
        bezl.vars.infoWindow.setContent(map.getInfoWindowContent(bezl.vars.markers[custNum].title,
                                                                    bezl.vars.markers[custNum].data.Address,
                                                                    bezl.vars.markers[custNum].data.Contacts));

        bezl.vars.selectedCustomer = bezl.vars.markers[custNum].data;
        bezl.vars.selectedCustomer.LastContact = (bezl.vars.selectedCustomer.LastContact || 'T').split('T')[0];
        bezl.vars.infoWindow.open(bezl.vars.map, bezl.vars.markers[custNum]);

        $('html, body').animate({
                scrollTop: $("#map").offset().top
            }, 2000);
        } else {
        // If there is not a marker for the given address, geocode it now
        for (var i = 0; i < bezl.vars.customers.length; i++) {
            if (bezl.vars.customers[i].custNum == custNum) {
                map.geocodeAddress(bezl, bezl.vars.customers[i]);
                break;
            }
        };
        }

        // Load the customers CRM call history
        RunQuery(bezl, 'CRMCallHistory');

        // Load the customers attachments
        RunQuery(bezl, 'Attachments');

        // Load the open tasks
        RunQuery(bezl, 'OpenTasks');
    }

    function RunQuery (bezl, queryName) {

        switch (queryName) {
            case "CustList":
                // Pull in the customer list for the logged in user
                bezl.dataService.add('CustList','brdb','sales-rep-queries','ExecuteQuery', {
                    "QueryName": "GetCustomersWithAddress",
                    "Parameters": [
                        { "Key": "EmailAddress", "Value": bezl.env.currentUser }
                    ] },0);
                break;
            case "CRMCallHistory":
                // Load the customers CRM call history
                bezl.dataService.add('CRMCallHistory','brdb','sales-rep-queries','ExecuteQuery', {
                    "QueryName": "GetCRMCallHistory",
                    "Parameters": [
                        { "Key": "CustNum", "Value": bezl.vars.selectedCustomer.CustNum },
                        { "Key": "CustID", "Value": bezl.vars.selectedCustomer.CustID },
                        { "Key": "EmailAddress", "Value": bezl.env.currentUser }
                    ] },0);
                bezl.vars.loading.crmHistory = true;
                break;
            case "OpenTasks":
                bezl.dataService.add('OpenTasks','brdb','sales-rep-queries','ExecuteQuery', {
                    "QueryName": "GetOpenTasks",
                    "Parameters": [
                        { "Key": "CustNum", "Value": bezl.vars.selectedCustomer.CustNum },
                        { "Key": "CustID", "Value": bezl.vars.selectedCustomer.CustID },
                        { "Key": "EmailAddress", "Value": bezl.env.currentUser }
                    ] },0);
                bezl.vars.loading.openTasks = true;
                break;
            case "Attachments":
                switch (bezl.vars.config.CRMPlatform) {
                    case "Epicor10":
                        bezl.dataService.add('Attachments','brdb','sales-rep-queries','ExecuteQuery', {
                            "QueryName": "GetCustomerAttachments",
                            "Parameters": [
                                { "Key": "CustNum", "Value": bezl.vars.selectedCustomer.CustNum },
                                { "Key": "CustID", "Value": bezl.vars.selectedCustomer.CustID },
                                { "Key": "EmailAddress", "Value": bezl.env.currentUser }
                            ] },0);
                        break;
                    case "Excel":
                        bezl.dataService.add('Attachments','brdb','sales-rep-customerFiles','GetFileList', { },0);
                        break;
                    default:
                        break;
                }

                bezl.vars.loading.attachments = true;
                break;
            default:
                // Otherwise, assume this was a custom inquiry
                bezl.dataService.add('Inquiry','brdb','sales-rep-queries','ExecuteQuery', {
                    "QueryName": queryName,
                    "Parameters": [
                        { "Key": "CustNum", "Value": bezl.vars.selectedCustomer.CustNum },
                        { "Key": "CustID", "Value": bezl.vars.selectedCustomer.CustID }
                    ]
                },0);
                bezl.vars.loading.inquiry = true;

                // Push details about the selected inquiry into the selectedInquiry variable
                for (var i = 0; i < bezl.vars.config.CustomerInquiries.length; i++) {
                    if (bezl.vars.config.CustomerInquiries[i].QueryName == queryName) {
                        bezl.vars.selectedInquiry = bezl.vars.config.CustomerInquiries[i];
                    }
                };

                break;
        }
    }

    function ViewFile (bezl, file) {
        bezl.vars.attachmentFileName = file.FileName;
        bezl.vars.loading.attachment[file.FileName] = true;

        switch(file.FileExt) {
            case ".doc": bezl.vars.attachmentFileType = "application/msword"; break;
            case ".dot": bezl.vars.attachmentFileType = "application/msword"; break;
            case ".onetoc": bezl.vars.attachmentFileType = "application/onenote"; break;
            case ".onetoc2": bezl.vars.attachmentFileType = "application/onenote"; break;
            case ".onetmp": bezl.vars.attachmentFileType = "application/onenote"; break;
            case ".onepkg": bezl.vars.attachmentFileType = "application/onenote"; break;
            case ".pdf": bezl.vars.attachmentFileType = "application/pdf"; break;
            case ".ai": bezl.vars.attachmentFileType = "application/postscript"; break;
            case ".eps": bezl.vars.attachmentFileType = "application/postscript"; break;
            case ".ps": bezl.vars.attachmentFileType = "application/postscript"; break;
            case ".xls": bezl.vars.attachmentFileType = "application/vnd.ms-excel"; break;
            case ".xlm": bezl.vars.attachmentFileType = "application/vnd.ms-excel"; break;
            case ".xla": bezl.vars.attachmentFileType = "application/vnd.ms-excel"; break;
            case ".xlc": bezl.vars.attachmentFileType = "application/vnd.ms-excel"; break;
            case ".xlt": bezl.vars.attachmentFileType = "application/vnd.ms-excel"; break;
            case ".xlw": bezl.vars.attachmentFileType = "application/vnd.ms-excel"; break;
            case ".xlam": bezl.vars.attachmentFileType = "application/vnd.ms-excel.addin.macroenabled.12"; break;
            case ".xlsb": bezl.vars.attachmentFileType = "application/vnd.ms-excel.sheet.binary.macroenabled.12"; break;
            case ".xlsm": bezl.vars.attachmentFileType = "application/vnd.ms-excel.sheet.macroenabled.12"; break;
            case ".xltm": bezl.vars.attachmentFileType = "application/vnd.ms-excel.template.macroenabled.12"; break;
            case ".ppt": bezl.vars.attachmentFileType = "application/vnd.ms-powerpoint"; break;
            case ".pps": bezl.vars.attachmentFileType = "application/vnd.ms-powerpoint"; break;
            case ".pot": bezl.vars.attachmentFileType = "application/vnd.ms-powerpoint"; break;
            case ".ppam": bezl.vars.attachmentFileType = "application/vnd.ms-powerpoint.addin.macroenabled.12"; break;
            case ".pptm": bezl.vars.attachmentFileType = "application/vnd.ms-powerpoint.presentation.macroenabled.12"; break;
            case ".sldm": bezl.vars.attachmentFileType = "application/vnd.ms-powerpoint.slide.macroenabled.12"; break;
            case ".ppsm": bezl.vars.attachmentFileType = "application/vnd.ms-powerpoint.slideshow.macroenabled.12"; break;
            case ".potm": bezl.vars.attachmentFileType = "application/vnd.ms-powerpoint.template.macroenabled.12"; break;
            case ".mpp": bezl.vars.attachmentFileType = "application/vnd.ms-project"; break;
            case ".mpt": bezl.vars.attachmentFileType = "application/vnd.ms-project"; break;
            case ".docm": bezl.vars.attachmentFileType = "application/vnd.ms-word.document.macroenabled.12"; break;
            case ".dotm": bezl.vars.attachmentFileType = "application/vnd.ms-word.template.macroenabled.12"; break;
            case ".wps": bezl.vars.attachmentFileType = "application/vnd.ms-works"; break;
            case ".wks": bezl.vars.attachmentFileType = "application/vnd.ms-works"; break;
            case ".wcm": bezl.vars.attachmentFileType = "application/vnd.ms-works"; break;
            case ".wdb": bezl.vars.attachmentFileType = "application/vnd.ms-works"; break;
            case ".wpl": bezl.vars.attachmentFileType = "application/vnd.ms-wpl"; break;
            case ".xps": bezl.vars.attachmentFileType = "application/vnd.ms-xpsdocument"; break;
            case ".oxt": bezl.vars.attachmentFileType = "application/vnd.openofficeorg.extension"; break;
            case ".pptx": bezl.vars.attachmentFileType = "application/vnd.openxmlformats-officedocument.presentationml.presentation"; break;
            case ".sldx": bezl.vars.attachmentFileType = "application/vnd.openxmlformats-officedocument.presentationml.slide"; break;
            case ".ppsx": bezl.vars.attachmentFileType = "application/vnd.openxmlformats-officedocument.presentationml.slideshow"; break;
            case ".potx": bezl.vars.attachmentFileType = "application/vnd.openxmlformats-officedocument.presentationml.template"; break;
            case ".xlsx": bezl.vars.attachmentFileType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; break;
            case ".xltx": bezl.vars.attachmentFileType = "application/vnd.openxmlformats-officedocument.spreadsheetml.template"; break;
            case ".docx": bezl.vars.attachmentFileType = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"; break;
            case ".dotx": bezl.vars.attachmentFileType = "application/vnd.openxmlformats-officedocument.wordprocessingml.template"; break;
            case ".bmp": bezl.vars.attachmentFileType = "image/bmp"; break;
            case ".gif": bezl.vars.attachmentFileType = "image/gif"; break;
            case ".jpeg": bezl.vars.attachmentFileType = "image/jpeg"; break;
            case ".jpg": bezl.vars.attachmentFileType = "image/jpeg"; break;
            case ".jpe": bezl.vars.attachmentFileType = "image/jpeg"; break;
            case ".pict": bezl.vars.attachmentFileType = "image/pict"; break;
            case ".pic": bezl.vars.attachmentFileType = "image/pict"; break;
            case ".pct": bezl.vars.attachmentFileType = "image/pict"; break;
            case ".png": bezl.vars.attachmentFileType = "image/png"; break;
            case ".svg": bezl.vars.attachmentFileType = "image/svg+xml"; break;
            case ".svgz": bezl.vars.attachmentFileType = "image/svg+xml"; break;
            case ".tiff": bezl.vars.attachmentFileType = "image/tiff"; break;
            case ".tif": bezl.vars.attachmentFileType = "image/tiff"; break;
            case ".psd": bezl.vars.attachmentFileType = "image/vnd.adobe.photoshop"; break;
            case ".dwg": bezl.vars.attachmentFileType = "image/vnd.dwg"; break;
            case ".dxf": bezl.vars.attachmentFileType = "image/vnd.dxf"; break;
            case ".csv": bezl.vars.attachmentFileType = "text/csv"; break;
            case ".html": bezl.vars.attachmentFileType = "text/html"; break;
            case ".htm": bezl.vars.attachmentFileType = "text/html"; break;
            case ".txt": bezl.vars.attachmentFileType = "text/plain"; break;
            case ".text": bezl.vars.attachmentFileType = "text/plain"; break;
            default:
                bezl.vars.attachmentFileType = "application/octet-stream";
        }

        bezl.dataService.add('fileview','brdb','FileSystem','GetFile', { "Context": "Default", "FileName": file.FileName},0);
    }

    return {
        select: Select,
        runQuery: RunQuery,
        viewFile: ViewFile
    }
});<commit_msg>BE - Fix with the date pipe<commit_after>define(["./map.js"], function (map) {

    function Select (bezl, custNum) {
        if (bezl.vars.markers[custNum]) {
        // Locate this customer and navigate to them on the map
        bezl.vars.infoWindow.setContent(map.getInfoWindowContent(bezl.vars.markers[custNum].title,
                                                                    bezl.vars.markers[custNum].data.Address,
                                                                    bezl.vars.markers[custNum].data.Contacts));

        bezl.vars.selectedCustomer = bezl.vars.markers[custNum].data;
        bezl.vars.selectedCustomer.EstDate = (bezl.vars.selectedCustomer.EstDate || 'T').split('T')[0];
        bezl.vars.selectedCustomer.LastContact = (bezl.vars.selectedCustomer.LastContact || 'T').split('T')[0];
        bezl.vars.infoWindow.open(bezl.vars.map, bezl.vars.markers[custNum]);

        $('html, body').animate({
                scrollTop: $("#map").offset().top
            }, 2000);
        } else {
        // If there is not a marker for the given address, geocode it now
        for (var i = 0; i < bezl.vars.customers.length; i++) {
            if (bezl.vars.customers[i].custNum == custNum) {
                map.geocodeAddress(bezl, bezl.vars.customers[i]);
                break;
            }
        };
        }

        // Load the customers CRM call history
        RunQuery(bezl, 'CRMCallHistory');

        // Load the customers attachments
        RunQuery(bezl, 'Attachments');

        // Load the open tasks
        RunQuery(bezl, 'OpenTasks');
    }

    function RunQuery (bezl, queryName) {

        switch (queryName) {
            case "CustList":
                // Pull in the customer list for the logged in user
                bezl.dataService.add('CustList','brdb','sales-rep-queries','ExecuteQuery', {
                    "QueryName": "GetCustomersWithAddress",
                    "Parameters": [
                        { "Key": "EmailAddress", "Value": bezl.env.currentUser }
                    ] },0);
                break;
            case "CRMCallHistory":
                // Load the customers CRM call history
                bezl.dataService.add('CRMCallHistory','brdb','sales-rep-queries','ExecuteQuery', {
                    "QueryName": "GetCRMCallHistory",
                    "Parameters": [
                        { "Key": "CustNum", "Value": bezl.vars.selectedCustomer.CustNum },
                        { "Key": "CustID", "Value": bezl.vars.selectedCustomer.CustID },
                        { "Key": "EmailAddress", "Value": bezl.env.currentUser }
                    ] },0);
                bezl.vars.loading.crmHistory = true;
                break;
            case "OpenTasks":
                bezl.dataService.add('OpenTasks','brdb','sales-rep-queries','ExecuteQuery', {
                    "QueryName": "GetOpenTasks",
                    "Parameters": [
                        { "Key": "CustNum", "Value": bezl.vars.selectedCustomer.CustNum },
                        { "Key": "CustID", "Value": bezl.vars.selectedCustomer.CustID },
                        { "Key": "EmailAddress", "Value": bezl.env.currentUser }
                    ] },0);
                bezl.vars.loading.openTasks = true;
                break;
            case "Attachments":
                switch (bezl.vars.config.CRMPlatform) {
                    case "Epicor10":
                        bezl.dataService.add('Attachments','brdb','sales-rep-queries','ExecuteQuery', {
                            "QueryName": "GetCustomerAttachments",
                            "Parameters": [
                                { "Key": "CustNum", "Value": bezl.vars.selectedCustomer.CustNum },
                                { "Key": "CustID", "Value": bezl.vars.selectedCustomer.CustID },
                                { "Key": "EmailAddress", "Value": bezl.env.currentUser }
                            ] },0);
                        break;
                    case "Excel":
                        bezl.dataService.add('Attachments','brdb','sales-rep-customerFiles','GetFileList', { },0);
                        break;
                    default:
                        break;
                }

                bezl.vars.loading.attachments = true;
                break;
            default:
                // Otherwise, assume this was a custom inquiry
                bezl.dataService.add('Inquiry','brdb','sales-rep-queries','ExecuteQuery', {
                    "QueryName": queryName,
                    "Parameters": [
                        { "Key": "CustNum", "Value": bezl.vars.selectedCustomer.CustNum },
                        { "Key": "CustID", "Value": bezl.vars.selectedCustomer.CustID }
                    ]
                },0);
                bezl.vars.loading.inquiry = true;

                // Push details about the selected inquiry into the selectedInquiry variable
                for (var i = 0; i < bezl.vars.config.CustomerInquiries.length; i++) {
                    if (bezl.vars.config.CustomerInquiries[i].QueryName == queryName) {
                        bezl.vars.selectedInquiry = bezl.vars.config.CustomerInquiries[i];
                    }
                };

                break;
        }
    }

    function ViewFile (bezl, file) {
        bezl.vars.attachmentFileName = file.FileName;
        bezl.vars.loading.attachment[file.FileName] = true;

        switch(file.FileExt) {
            case ".doc": bezl.vars.attachmentFileType = "application/msword"; break;
            case ".dot": bezl.vars.attachmentFileType = "application/msword"; break;
            case ".onetoc": bezl.vars.attachmentFileType = "application/onenote"; break;
            case ".onetoc2": bezl.vars.attachmentFileType = "application/onenote"; break;
            case ".onetmp": bezl.vars.attachmentFileType = "application/onenote"; break;
            case ".onepkg": bezl.vars.attachmentFileType = "application/onenote"; break;
            case ".pdf": bezl.vars.attachmentFileType = "application/pdf"; break;
            case ".ai": bezl.vars.attachmentFileType = "application/postscript"; break;
            case ".eps": bezl.vars.attachmentFileType = "application/postscript"; break;
            case ".ps": bezl.vars.attachmentFileType = "application/postscript"; break;
            case ".xls": bezl.vars.attachmentFileType = "application/vnd.ms-excel"; break;
            case ".xlm": bezl.vars.attachmentFileType = "application/vnd.ms-excel"; break;
            case ".xla": bezl.vars.attachmentFileType = "application/vnd.ms-excel"; break;
            case ".xlc": bezl.vars.attachmentFileType = "application/vnd.ms-excel"; break;
            case ".xlt": bezl.vars.attachmentFileType = "application/vnd.ms-excel"; break;
            case ".xlw": bezl.vars.attachmentFileType = "application/vnd.ms-excel"; break;
            case ".xlam": bezl.vars.attachmentFileType = "application/vnd.ms-excel.addin.macroenabled.12"; break;
            case ".xlsb": bezl.vars.attachmentFileType = "application/vnd.ms-excel.sheet.binary.macroenabled.12"; break;
            case ".xlsm": bezl.vars.attachmentFileType = "application/vnd.ms-excel.sheet.macroenabled.12"; break;
            case ".xltm": bezl.vars.attachmentFileType = "application/vnd.ms-excel.template.macroenabled.12"; break;
            case ".ppt": bezl.vars.attachmentFileType = "application/vnd.ms-powerpoint"; break;
            case ".pps": bezl.vars.attachmentFileType = "application/vnd.ms-powerpoint"; break;
            case ".pot": bezl.vars.attachmentFileType = "application/vnd.ms-powerpoint"; break;
            case ".ppam": bezl.vars.attachmentFileType = "application/vnd.ms-powerpoint.addin.macroenabled.12"; break;
            case ".pptm": bezl.vars.attachmentFileType = "application/vnd.ms-powerpoint.presentation.macroenabled.12"; break;
            case ".sldm": bezl.vars.attachmentFileType = "application/vnd.ms-powerpoint.slide.macroenabled.12"; break;
            case ".ppsm": bezl.vars.attachmentFileType = "application/vnd.ms-powerpoint.slideshow.macroenabled.12"; break;
            case ".potm": bezl.vars.attachmentFileType = "application/vnd.ms-powerpoint.template.macroenabled.12"; break;
            case ".mpp": bezl.vars.attachmentFileType = "application/vnd.ms-project"; break;
            case ".mpt": bezl.vars.attachmentFileType = "application/vnd.ms-project"; break;
            case ".docm": bezl.vars.attachmentFileType = "application/vnd.ms-word.document.macroenabled.12"; break;
            case ".dotm": bezl.vars.attachmentFileType = "application/vnd.ms-word.template.macroenabled.12"; break;
            case ".wps": bezl.vars.attachmentFileType = "application/vnd.ms-works"; break;
            case ".wks": bezl.vars.attachmentFileType = "application/vnd.ms-works"; break;
            case ".wcm": bezl.vars.attachmentFileType = "application/vnd.ms-works"; break;
            case ".wdb": bezl.vars.attachmentFileType = "application/vnd.ms-works"; break;
            case ".wpl": bezl.vars.attachmentFileType = "application/vnd.ms-wpl"; break;
            case ".xps": bezl.vars.attachmentFileType = "application/vnd.ms-xpsdocument"; break;
            case ".oxt": bezl.vars.attachmentFileType = "application/vnd.openofficeorg.extension"; break;
            case ".pptx": bezl.vars.attachmentFileType = "application/vnd.openxmlformats-officedocument.presentationml.presentation"; break;
            case ".sldx": bezl.vars.attachmentFileType = "application/vnd.openxmlformats-officedocument.presentationml.slide"; break;
            case ".ppsx": bezl.vars.attachmentFileType = "application/vnd.openxmlformats-officedocument.presentationml.slideshow"; break;
            case ".potx": bezl.vars.attachmentFileType = "application/vnd.openxmlformats-officedocument.presentationml.template"; break;
            case ".xlsx": bezl.vars.attachmentFileType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; break;
            case ".xltx": bezl.vars.attachmentFileType = "application/vnd.openxmlformats-officedocument.spreadsheetml.template"; break;
            case ".docx": bezl.vars.attachmentFileType = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"; break;
            case ".dotx": bezl.vars.attachmentFileType = "application/vnd.openxmlformats-officedocument.wordprocessingml.template"; break;
            case ".bmp": bezl.vars.attachmentFileType = "image/bmp"; break;
            case ".gif": bezl.vars.attachmentFileType = "image/gif"; break;
            case ".jpeg": bezl.vars.attachmentFileType = "image/jpeg"; break;
            case ".jpg": bezl.vars.attachmentFileType = "image/jpeg"; break;
            case ".jpe": bezl.vars.attachmentFileType = "image/jpeg"; break;
            case ".pict": bezl.vars.attachmentFileType = "image/pict"; break;
            case ".pic": bezl.vars.attachmentFileType = "image/pict"; break;
            case ".pct": bezl.vars.attachmentFileType = "image/pict"; break;
            case ".png": bezl.vars.attachmentFileType = "image/png"; break;
            case ".svg": bezl.vars.attachmentFileType = "image/svg+xml"; break;
            case ".svgz": bezl.vars.attachmentFileType = "image/svg+xml"; break;
            case ".tiff": bezl.vars.attachmentFileType = "image/tiff"; break;
            case ".tif": bezl.vars.attachmentFileType = "image/tiff"; break;
            case ".psd": bezl.vars.attachmentFileType = "image/vnd.adobe.photoshop"; break;
            case ".dwg": bezl.vars.attachmentFileType = "image/vnd.dwg"; break;
            case ".dxf": bezl.vars.attachmentFileType = "image/vnd.dxf"; break;
            case ".csv": bezl.vars.attachmentFileType = "text/csv"; break;
            case ".html": bezl.vars.attachmentFileType = "text/html"; break;
            case ".htm": bezl.vars.attachmentFileType = "text/html"; break;
            case ".txt": bezl.vars.attachmentFileType = "text/plain"; break;
            case ".text": bezl.vars.attachmentFileType = "text/plain"; break;
            default:
                bezl.vars.attachmentFileType = "application/octet-stream";
        }

        bezl.dataService.add('fileview','brdb','FileSystem','GetFile', { "Context": "Default", "FileName": file.FileName},0);
    }

    return {
        select: Select,
        runQuery: RunQuery,
        viewFile: ViewFile
    }
});<|endoftext|>


<commit_before>import Ember from 'ember';
import layout from '../templates/components/ember-attacher-inner';
import { assert } from '@ember/debug';

export default Ember.Component.extend({
  /**
   * ================== PUBLIC CONFIG OPTIONS ==================
   */

  // See ember-attacher.js, which passes all the default values into this component

  /**
   * ================== COMPONENT LIFECYCLE HOOKS ==================
   */

  init() {
    this._super(...arguments);

    // Holds the current popper target so event listeners can be removed if the target changes
    this._currentTarget = null;

    // The debounced _hide() is stored here so it can be cancelled
    // if a _show() is triggered before the _hide() is executed
    this._delayedHide = null;

    // The debounced _show() is stored here so it can be cancelled
    // if a _hide() is triggered before the _show() is executed
    this._delayedShow = null;

    // The final source of truth on whether or not all _hide() or _show() actions have completed
    this._isHidden = true;

    // Holds a delayed function to toggle the visibility of the attachment.
    // Used to make sure animations can complete before the attachment is hidden.
    this._isVisibleTimeout = null;

    this._showListenersOnTargetByEvent = {};
    this._hideListenersOnTargetByEvent = {};

    // Hacks to make sure event listeners have the right context and are still cancellable later
    this._hideIfMouseOutsideTargetOrAttachment =
      this._hideIfMouseOutsideTargetOrAttachment.bind(this);
    this._debouncedHideIfMouseOutsideTargetOrAttachment =
      this._debouncedHideIfMouseOutsideTargetOrAttachment.bind(this);
    this._hideOnLostFocus = this._hideOnLostFocus.bind(this);
    this._hideOnMouseLeaveTarget = this._hideOnMouseLeaveTarget.bind(this);
    this._hideAfterDelay = this._hideAfterDelay.bind(this);
    this._showAfterDelay = this._showAfterDelay.bind(this);
    this._show = this._show.bind(this);
    this._hide = this._hide.bind(this);
  },

  didInsertElement() {
    this._super(...arguments);

    // The Popper does not exist until after the element has been inserted
    Ember.run.next(() => {
      this._addListenersForShowEvents();

      if (this.get('isVisible') && this.get('_target')) {
        this._addListenersforHideEvents();

        this._show();
      } else {
        // When we first render the popper, it has no width if isVisible is false. This can cause
        // the popper to be positioned too far to the right, such that when it expands, it will
        // become larger than its parent. This, in turn, causes the parent to expand to accommodate
        // the popper, which may now be off screen. To get around this, we just remove the
        // positioning from the element to the safest position available: 0x0. The popper will then
        // update its position from this._show()
        this.element.parentNode.style.transform = null;
      }
    });
  },

  _addListenersForShowEvents() {
    const target = this.get('_target');
    const showOn = this.get('_showOn');

    if (!target) {
      return;
    }

    this._currentTarget = target;

    showOn.forEach(event => {
      this._showListenersOnTargetByEvent[event] = this._showAfterDelay;

      target.addEventListener(event, this._showAfterDelay);
    });
  },

  willDestroyElement() {
    this._super(...arguments);

    this._removeEventListeners();
  },

  _removeEventListeners() {
    document.removeEventListener('mousemove', this._hideIfMouseOutsideTargetOrAttachment);

    const target = this._currentTarget;

    [this._hideListenersOnTargetByEvent, this._showListenersOnTargetByEvent]
      .forEach(eventToListener => {
        Object.keys(eventToListener).forEach(event => {
          target.removeEventListener(event, eventToListener[event]);
        });
      });
  },

  /**
   * ================== PRIVATE IMPLEMENTATION DETAILS ==================
   */

  classNameBindings: ['_animation', '_isStartingAnimation:ember-attacher-show:ember-attacher-hide'],
  // Part of the Component superclass. isVisible == false sets 'display: none'
  isVisible: false,
  layout,

  _animation: Ember.computed('animation', function() {
    return `ember-attacher-${this.get('animation')}`;
  }),
  _hideOn: Ember.computed('hideOn', function() {
    return this.get('hideOn').split(' ');
  }),
  _showOn: Ember.computed('showOn', function() {
    return this.get('showOn').split(' ');
  }),
  _target: Ember.computed('target', function() {
    const target = this.get('target');

    let popperTarget;

    // If there is no target, set the target to the parent element
    if (!target) {
      popperTarget = this._initialParentNode;
    } else if (target instanceof Element) {
      popperTarget = target;
    } else {
      const nodes = document.querySelectorAll(target);

      assert(`ember-attacher with target selector "${target}" found ${nodes.length}`
             + 'possible targets when there should be exactly 1', nodes.length === 1);

      popperTarget = nodes[0];
    }

    return popperTarget;
  }),

  // The circle element needs a special duration that is slightly faster than the popper's
  // transition, this prevents text from appearing outside the circle as it fills the background
  circleTransitionDuration: Ember.computed('_transitionDuration', function() {
    return Ember.String.htmlSafe(
      `transition-duration: ${Math.round(this.get('_transitionDuration')/1.25)}ms`
    );
  }),

  _setIsVisibleAfterDelay(isVisible, delay) {
    Ember.run.cancel(this._isVisibleTimeout);

    if (delay) {
      this._isVisibleTimeout =
        Ember.run.later(this, () => { this.set('isVisible', isVisible) }, delay);
    } else {
      this.set('isVisible', isVisible);
    }
  },

  _targetOrTriggersChanged: Ember.observer(
    'hideOn',
    'showOn',
    'target',
    function() {
      this._removeEventListeners();

      // Regardless of whether or not the attachment is hidden, we want to add the show listeners
      this._addListenersForShowEvents();

      if (!this._isHidden) {
        this._addListenersforHideEvents();
      }
    }
  ),

  /**
   * ================== SHOW ATTACHMENT LOGIC ==================
   */

  _showAfterDelay() {
    Ember.run.cancel(this._delayedHide);
    Ember.run.cancel(this._isVisibleTimeout);

    // The attachment is already visible or the target has been destroyed
    if (!this._isHidden || !this.get('_target')) {
      return;
    }

    this._addListenersforHideEvents();

    const showDelay = parseInt(this.get('showDelay'));

    this._delayedShow = Ember.run.debounce(this, this._show, showDelay, !showDelay);
  },

  _show() {
    // The target of interactive tooltips receive the 'active' class
    if (this.get('interactive')) {
      this.get('_target').classList.add('active')
    }

    // Make the attachment visible immediately so transition animations can take place
    this._setIsVisibleAfterDelay(true, 0);

    this.get('scheduleUpdate')();
    this.get('enableEventListeners')();

    // Start the show animation on the next cycle so CSS transitions can have an effect
    // If we start the animation immediately, the transition won't work because isVisible will
    // turn on the same time as our show animation, and `display: none` => `display: anythingElse`
    // is not transition-able
    Ember.run.next(this, () => {
      const showDuration = parseInt(this.get('showDuration'));

      this.element.style.transitionDuration = `${showDuration}ms`;
      this.set('_transitionDuration', showDuration);

      this.set('_isStartingAnimation', true);
    });

    this._isHidden = false;
  },

  _addListenersforHideEvents() {
    const hideOn = this.get('_hideOn');
    const target = this.get('_target');

    if (hideOn.indexOf('click') !== -1) {
      const showOnClickListener = this._showListenersOnTargetByEvent['click'];

      if (showOnClickListener) {
        target.removeEventListener('click', showOnClickListener);

        delete this._showListenersOnTargetByEvent['click'];
      }

      this._hideListenersOnTargetByEvent['click'] = this._hideAfterDelay;
      target.addEventListener('click', this._hideAfterDelay);
    }

    // Hides the attachment when the mouse leaves the target
    // (or leaves both target and attachment for interactive attachments)
    if (hideOn.indexOf('mouseleave') !== -1) {
      this._hideListenersOnTargetByEvent['mouseleave'] = this._hideOnMouseLeaveTarget;
      target.addEventListener('mouseleave', this._hideOnMouseLeaveTarget);
    }

    // Hides the attachment when focus is lost on the target
    if (hideOn.indexOf('blur') !== -1) {
      this._hideListenersOnTargetByEvent['blur'] = this._hideOnLostFocus;
      target.addEventListener('blur', this._hideOnLostFocus);
    }

    if (hideOn.indexOf('focusout') !== -1) {
      this._hideListenersOnTargetByEvent['focusout'] = this._hideOnLostFocus;
      target.addEventListener('focusout', this._hideOnLostFocus);
    }
  },

  _hideOnMouseLeaveTarget() {
    if (this.get('interactive')) {
      // TODO(kjb) Should debounce this, but hiding appears sluggish if you debounce.
      //   - If you debounce with immediate fire, you get a bug where you can move out of the
      //   attachment and not trigger the hide because the hide check was debounced
      //   - Ideally we would debounce with an immediate run, then instead of debouncing, we would
      //   queue another fire at the end of the debounce period
      document.addEventListener('mousemove', this._hideIfMouseOutsideTargetOrAttachment);
    } else {
      this._hideAfterDelay();
    }
  },

  _debouncedHideIfMouseOutsideTargetOrAttachment(event) {
    Ember.run.debounce(this, this._hideIfMouseOutsideTargetOrAttachment, event, 10)
  },

  _hideIfMouseOutsideTargetOrAttachment(event) {
    const target = this.get('_target');

    // If cursor is not on the attachment or target, hide the element
     if (!target.contains(event.target)
         && !(this.get('isOffset') && this._isCursorBetweenTargetAndAttachment(event))
         // The ember-attacher-inner element is wrapped in the ember-attacher element
         && !this.element.parentNode.contains(event.target)) {
      // Remove this listener before hiding the attachment
      document.removeEventListener('mousemove', this._hideIfMouseOutsideTargetOrAttachment);

      target.classList.remove('active');

      this._hideAfterDelay();
    }
  },

  _isCursorBetweenTargetAndAttachment(event) {
    const {clientX, clientY} = event;

    const attachmentPosition = this.element.getBoundingClientRect();
    const targetPosition = this.get('_target').getBoundingClientRect();

    // Check if cursor is between a left-flipped attachment
    if (attachmentPosition.right < targetPosition.left
        && clientX >= attachmentPosition.right && clientX <= targetPosition.left
        && clientY > Math.min(attachmentPosition.top, targetPosition.top)
        && clientY < Math.max(attachmentPosition.bottom, targetPosition.bottom)) {
      return true;
    }

    // Check if cursor is between a right-flipped attachment
    if (attachmentPosition.left > targetPosition.right
        && clientX <= attachmentPosition.left && clientX >= targetPosition.right
        && clientY > Math.min(attachmentPosition.top, targetPosition.top)
        && clientY < Math.max(attachmentPosition.bottom, targetPosition.bottom)) {
      return true;
    }

    // Check if cursor is between a bottom-flipped attachment
    if (attachmentPosition.top > targetPosition.bottom
        && clientY <= attachmentPosition.top && clientY >= targetPosition.bottom
        && clientX > Math.min(attachmentPosition.left, targetPosition.left)
        && clientX < Math.max(attachmentPosition.right, targetPosition.right)) {
      return true;
    }

    // Check if cursor is between a top-flipped attachment
    if (attachmentPosition.bottom < targetPosition.top
        && clientY >= attachmentPosition.bottom && clientY <= targetPosition.top
        && clientX > Math.min(attachmentPosition.left, targetPosition.left)
        && clientX < Math.max(attachmentPosition.right, targetPosition.right)) {
      return true;
    }

    return false;
  },

  _hideOnLostFocus(event) {
    if (event.relatedTarget === null
        || (!this.get('_target').contains(event.relatedTarget)
            && !this.element.contains(event.relatedTarget))) {
      this._hideAfterDelay();
    }
  },

  /**
   * ================== HIDE ATTACHMENT LOGIC ==================
   */

  _hideAfterDelay() {
    Ember.run.cancel(this._delayedShow);
    Ember.run.cancel(this._isVisibleTimeout);

    // The attachment is already hidden or the target was destroyed
    if (this._isHidden || !this.get('_target')) {
      return;
    }

    const hideDelay = parseInt(this.get('hideDelay'));

    this._delayedHide = Ember.run.debounce(this, this._hide, hideDelay, !hideDelay);
  },

  _hide() {
    this._removeListenersForHideEvents();

    const hideDuration = parseInt(this.get('hideDuration'));

    this.element.style.transitionDuration = `${hideDuration}ms`;
    this.set('_transitionDuration', hideDuration);

    this.set('_isStartingAnimation', false);

    // Wait for any animations to complete before hiding the attachment
    this._setIsVisibleAfterDelay(false, hideDuration);

    this.get('disableEventListeners')();

    this._isHidden = true;
  },

  _removeListenersForHideEvents() {
    const target = this.get('_target');
    const showOn = this.get('_showOn');

    // Switch clicking back to a show event
    if (showOn.indexOf('click') !== -1) {
      const hideOnClickListener = this._hideListenersOnTargetByEvent['click'];

      if (hideOnClickListener) {
        target.removeEventListener('click', hideOnClickListener);
        delete this._hideListenersOnTargetByEvent['click'];
      }

      this._showListenersOnTargetByEvent['click'] = this._showAfterDelay;
      target.addEventListener('click', this._showAfterDelay);
    }

    const hideOnMouseleaveListener = this._hideListenersOnTargetByEvent['mouseleave'];

    if (hideOnMouseleaveListener) {
      target.removeEventListener('mouseleave', hideOnMouseleaveListener);
      delete this._hideListenersOnTargetByEvent['mouseleave'];
    }

    const hideOnBlurListener = this._hideListenersOnTargetByEvent['blur'];

    if (hideOnBlurListener) {
      target.removeEventListener('blur', hideOnBlurListener);
      delete this._hideListenersOnTargetByEvent['blur'];
    }

    const hideOnFocusOutListener = this._hideListenersOnTargetByEvent['focusout'];

    if (hideOnFocusOutListener) {
      target.removeEventListener('focusout', hideOnFocusOutListener);
      delete this._hideListenersOnTargetByEvent['focusout'];
    }
  },

  actions: {
    // Exposed via the named yield to enable custom hide events
    hide() {
      this._hide();
    }
  }
});
<commit_msg>fix(destruction) don't set isVisible when being destroyed (#24)<commit_after>import Ember from 'ember';
import layout from '../templates/components/ember-attacher-inner';
import { assert } from '@ember/debug';

export default Ember.Component.extend({
  /**
   * ================== PUBLIC CONFIG OPTIONS ==================
   */

  // See ember-attacher.js, which passes all the default values into this component

  /**
   * ================== COMPONENT LIFECYCLE HOOKS ==================
   */

  init() {
    this._super(...arguments);

    // Holds the current popper target so event listeners can be removed if the target changes
    this._currentTarget = null;

    // The debounced _hide() is stored here so it can be cancelled
    // if a _show() is triggered before the _hide() is executed
    this._delayedHide = null;

    // The debounced _show() is stored here so it can be cancelled
    // if a _hide() is triggered before the _show() is executed
    this._delayedShow = null;

    // The final source of truth on whether or not all _hide() or _show() actions have completed
    this._isHidden = true;

    // Holds a delayed function to toggle the visibility of the attachment.
    // Used to make sure animations can complete before the attachment is hidden.
    this._isVisibleTimeout = null;

    this._showListenersOnTargetByEvent = {};
    this._hideListenersOnTargetByEvent = {};

    // Hacks to make sure event listeners have the right context and are still cancellable later
    this._hideIfMouseOutsideTargetOrAttachment =
      this._hideIfMouseOutsideTargetOrAttachment.bind(this);
    this._debouncedHideIfMouseOutsideTargetOrAttachment =
      this._debouncedHideIfMouseOutsideTargetOrAttachment.bind(this);
    this._hideOnLostFocus = this._hideOnLostFocus.bind(this);
    this._hideOnMouseLeaveTarget = this._hideOnMouseLeaveTarget.bind(this);
    this._hideAfterDelay = this._hideAfterDelay.bind(this);
    this._showAfterDelay = this._showAfterDelay.bind(this);
    this._show = this._show.bind(this);
    this._hide = this._hide.bind(this);
  },

  didInsertElement() {
    this._super(...arguments);

    // The Popper does not exist until after the element has been inserted
    Ember.run.next(() => {
      this._addListenersForShowEvents();

      if (this.get('isVisible') && this.get('_target')) {
        this._addListenersforHideEvents();

        this._show();
      } else {
        // When we first render the popper, it has no width if isVisible is false. This can cause
        // the popper to be positioned too far to the right, such that when it expands, it will
        // become larger than its parent. This, in turn, causes the parent to expand to accommodate
        // the popper, which may now be off screen. To get around this, we just remove the
        // positioning from the element to the safest position available: 0x0. The popper will then
        // update its position from this._show()
        this.element.parentNode.style.transform = null;
      }
    });
  },

  _addListenersForShowEvents() {
    const target = this.get('_target');
    const showOn = this.get('_showOn');

    if (!target) {
      return;
    }

    this._currentTarget = target;

    showOn.forEach(event => {
      this._showListenersOnTargetByEvent[event] = this._showAfterDelay;

      target.addEventListener(event, this._showAfterDelay);
    });
  },

  willDestroyElement() {
    this._super(...arguments);

    this._removeEventListeners();
  },

  _removeEventListeners() {
    document.removeEventListener('mousemove', this._hideIfMouseOutsideTargetOrAttachment);

    const target = this._currentTarget;

    [this._hideListenersOnTargetByEvent, this._showListenersOnTargetByEvent]
      .forEach(eventToListener => {
        Object.keys(eventToListener).forEach(event => {
          target.removeEventListener(event, eventToListener[event]);
        });
      });
  },

  /**
   * ================== PRIVATE IMPLEMENTATION DETAILS ==================
   */

  classNameBindings: ['_animation', '_isStartingAnimation:ember-attacher-show:ember-attacher-hide'],
  // Part of the Component superclass. isVisible == false sets 'display: none'
  isVisible: false,
  layout,

  _animation: Ember.computed('animation', function() {
    return `ember-attacher-${this.get('animation')}`;
  }),
  _hideOn: Ember.computed('hideOn', function() {
    return this.get('hideOn').split(' ');
  }),
  _showOn: Ember.computed('showOn', function() {
    return this.get('showOn').split(' ');
  }),
  _target: Ember.computed('target', function() {
    const target = this.get('target');

    let popperTarget;

    // If there is no target, set the target to the parent element
    if (!target) {
      popperTarget = this._initialParentNode;
    } else if (target instanceof Element) {
      popperTarget = target;
    } else {
      const nodes = document.querySelectorAll(target);

      assert(`ember-attacher with target selector "${target}" found ${nodes.length}`
             + 'possible targets when there should be exactly 1', nodes.length === 1);

      popperTarget = nodes[0];
    }

    return popperTarget;
  }),

  // The circle element needs a special duration that is slightly faster than the popper's
  // transition, this prevents text from appearing outside the circle as it fills the background
  circleTransitionDuration: Ember.computed('_transitionDuration', function() {
    return Ember.String.htmlSafe(
      `transition-duration: ${Math.round(this.get('_transitionDuration')/1.25)}ms`
    );
  }),

  _setIsVisibleAfterDelay(isVisible, delay) {
    Ember.run.cancel(this._isVisibleTimeout);

    if (delay) {
      this._isVisibleTimeout =
        Ember.run.later(this, () => {
          if (!this.isDestroyed && !this.isDestroying) {
            this.set('isVisible', isVisible);
          }
        }, delay);
    } else {
      this.set('isVisible', isVisible);
    }
  },

  _targetOrTriggersChanged: Ember.observer(
    'hideOn',
    'showOn',
    'target',
    function() {
      this._removeEventListeners();

      // Regardless of whether or not the attachment is hidden, we want to add the show listeners
      this._addListenersForShowEvents();

      if (!this._isHidden) {
        this._addListenersforHideEvents();
      }
    }
  ),

  /**
   * ================== SHOW ATTACHMENT LOGIC ==================
   */

  _showAfterDelay() {
    Ember.run.cancel(this._delayedHide);
    Ember.run.cancel(this._isVisibleTimeout);

    // The attachment is already visible or the target has been destroyed
    if (!this._isHidden || !this.get('_target')) {
      return;
    }

    this._addListenersforHideEvents();

    const showDelay = parseInt(this.get('showDelay'));

    this._delayedShow = Ember.run.debounce(this, this._show, showDelay, !showDelay);
  },

  _show() {
    // The target of interactive tooltips receive the 'active' class
    if (this.get('interactive')) {
      this.get('_target').classList.add('active')
    }

    // Make the attachment visible immediately so transition animations can take place
    this._setIsVisibleAfterDelay(true, 0);

    this.get('scheduleUpdate')();
    this.get('enableEventListeners')();

    // Start the show animation on the next cycle so CSS transitions can have an effect
    // If we start the animation immediately, the transition won't work because isVisible will
    // turn on the same time as our show animation, and `display: none` => `display: anythingElse`
    // is not transition-able
    Ember.run.next(this, () => {
      const showDuration = parseInt(this.get('showDuration'));

      this.element.style.transitionDuration = `${showDuration}ms`;
      this.set('_transitionDuration', showDuration);

      this.set('_isStartingAnimation', true);
    });

    this._isHidden = false;
  },

  _addListenersforHideEvents() {
    const hideOn = this.get('_hideOn');
    const target = this.get('_target');

    if (hideOn.indexOf('click') !== -1) {
      const showOnClickListener = this._showListenersOnTargetByEvent['click'];

      if (showOnClickListener) {
        target.removeEventListener('click', showOnClickListener);

        delete this._showListenersOnTargetByEvent['click'];
      }

      this._hideListenersOnTargetByEvent['click'] = this._hideAfterDelay;
      target.addEventListener('click', this._hideAfterDelay);
    }

    // Hides the attachment when the mouse leaves the target
    // (or leaves both target and attachment for interactive attachments)
    if (hideOn.indexOf('mouseleave') !== -1) {
      this._hideListenersOnTargetByEvent['mouseleave'] = this._hideOnMouseLeaveTarget;
      target.addEventListener('mouseleave', this._hideOnMouseLeaveTarget);
    }

    // Hides the attachment when focus is lost on the target
    if (hideOn.indexOf('blur') !== -1) {
      this._hideListenersOnTargetByEvent['blur'] = this._hideOnLostFocus;
      target.addEventListener('blur', this._hideOnLostFocus);
    }

    if (hideOn.indexOf('focusout') !== -1) {
      this._hideListenersOnTargetByEvent['focusout'] = this._hideOnLostFocus;
      target.addEventListener('focusout', this._hideOnLostFocus);
    }
  },

  _hideOnMouseLeaveTarget() {
    if (this.get('interactive')) {
      // TODO(kjb) Should debounce this, but hiding appears sluggish if you debounce.
      //   - If you debounce with immediate fire, you get a bug where you can move out of the
      //   attachment and not trigger the hide because the hide check was debounced
      //   - Ideally we would debounce with an immediate run, then instead of debouncing, we would
      //   queue another fire at the end of the debounce period
      document.addEventListener('mousemove', this._hideIfMouseOutsideTargetOrAttachment);
    } else {
      this._hideAfterDelay();
    }
  },

  _debouncedHideIfMouseOutsideTargetOrAttachment(event) {
    Ember.run.debounce(this, this._hideIfMouseOutsideTargetOrAttachment, event, 10)
  },

  _hideIfMouseOutsideTargetOrAttachment(event) {
    const target = this.get('_target');

    // If cursor is not on the attachment or target, hide the element
     if (!target.contains(event.target)
         && !(this.get('isOffset') && this._isCursorBetweenTargetAndAttachment(event))
         // The ember-attacher-inner element is wrapped in the ember-attacher element
         && !this.element.parentNode.contains(event.target)) {
      // Remove this listener before hiding the attachment
      document.removeEventListener('mousemove', this._hideIfMouseOutsideTargetOrAttachment);

      target.classList.remove('active');

      this._hideAfterDelay();
    }
  },

  _isCursorBetweenTargetAndAttachment(event) {
    const {clientX, clientY} = event;

    const attachmentPosition = this.element.getBoundingClientRect();
    const targetPosition = this.get('_target').getBoundingClientRect();

    // Check if cursor is between a left-flipped attachment
    if (attachmentPosition.right < targetPosition.left
        && clientX >= attachmentPosition.right && clientX <= targetPosition.left
        && clientY > Math.min(attachmentPosition.top, targetPosition.top)
        && clientY < Math.max(attachmentPosition.bottom, targetPosition.bottom)) {
      return true;
    }

    // Check if cursor is between a right-flipped attachment
    if (attachmentPosition.left > targetPosition.right
        && clientX <= attachmentPosition.left && clientX >= targetPosition.right
        && clientY > Math.min(attachmentPosition.top, targetPosition.top)
        && clientY < Math.max(attachmentPosition.bottom, targetPosition.bottom)) {
      return true;
    }

    // Check if cursor is between a bottom-flipped attachment
    if (attachmentPosition.top > targetPosition.bottom
        && clientY <= attachmentPosition.top && clientY >= targetPosition.bottom
        && clientX > Math.min(attachmentPosition.left, targetPosition.left)
        && clientX < Math.max(attachmentPosition.right, targetPosition.right)) {
      return true;
    }

    // Check if cursor is between a top-flipped attachment
    if (attachmentPosition.bottom < targetPosition.top
        && clientY >= attachmentPosition.bottom && clientY <= targetPosition.top
        && clientX > Math.min(attachmentPosition.left, targetPosition.left)
        && clientX < Math.max(attachmentPosition.right, targetPosition.right)) {
      return true;
    }

    return false;
  },

  _hideOnLostFocus(event) {
    if (event.relatedTarget === null
        || (!this.get('_target').contains(event.relatedTarget)
            && !this.element.contains(event.relatedTarget))) {
      this._hideAfterDelay();
    }
  },

  /**
   * ================== HIDE ATTACHMENT LOGIC ==================
   */

  _hideAfterDelay() {
    Ember.run.cancel(this._delayedShow);
    Ember.run.cancel(this._isVisibleTimeout);

    // The attachment is already hidden or the target was destroyed
    if (this._isHidden || !this.get('_target')) {
      return;
    }

    const hideDelay = parseInt(this.get('hideDelay'));

    this._delayedHide = Ember.run.debounce(this, this._hide, hideDelay, !hideDelay);
  },

  _hide() {
    this._removeListenersForHideEvents();

    const hideDuration = parseInt(this.get('hideDuration'));

    this.element.style.transitionDuration = `${hideDuration}ms`;
    this.set('_transitionDuration', hideDuration);

    this.set('_isStartingAnimation', false);

    // Wait for any animations to complete before hiding the attachment
    this._setIsVisibleAfterDelay(false, hideDuration);

    this.get('disableEventListeners')();

    this._isHidden = true;
  },

  _removeListenersForHideEvents() {
    const target = this.get('_target');
    const showOn = this.get('_showOn');

    // Switch clicking back to a show event
    if (showOn.indexOf('click') !== -1) {
      const hideOnClickListener = this._hideListenersOnTargetByEvent['click'];

      if (hideOnClickListener) {
        target.removeEventListener('click', hideOnClickListener);
        delete this._hideListenersOnTargetByEvent['click'];
      }

      this._showListenersOnTargetByEvent['click'] = this._showAfterDelay;
      target.addEventListener('click', this._showAfterDelay);
    }

    const hideOnMouseleaveListener = this._hideListenersOnTargetByEvent['mouseleave'];

    if (hideOnMouseleaveListener) {
      target.removeEventListener('mouseleave', hideOnMouseleaveListener);
      delete this._hideListenersOnTargetByEvent['mouseleave'];
    }

    const hideOnBlurListener = this._hideListenersOnTargetByEvent['blur'];

    if (hideOnBlurListener) {
      target.removeEventListener('blur', hideOnBlurListener);
      delete this._hideListenersOnTargetByEvent['blur'];
    }

    const hideOnFocusOutListener = this._hideListenersOnTargetByEvent['focusout'];

    if (hideOnFocusOutListener) {
      target.removeEventListener('focusout', hideOnFocusOutListener);
      delete this._hideListenersOnTargetByEvent['focusout'];
    }
  },

  actions: {
    // Exposed via the named yield to enable custom hide events
    hide() {
      this._hide();
    }
  }
});
<|endoftext|>