<commit_before>import test from 'tape'

import { transformCellRangeExpression, BROKEN_REF } from '../../src/shared/cellHelpers'

const MSG = 'expression should be transformed correctly'

/*
TODO

ranges:
- range expression inside deleted
- deleted range inside of range expression
- range expression overlaps deleted range on the 'left' side
- range expression overlaps deleted range on the 'right' sides

*/

// cell reference - insert/delete columns

test('transformCellRangeExpression: single / col / insert before', (t) => {
  let expr = 'C1'
  let mode = 'col'
  let idx = 1
  let count = 2
  let transformed = transformCellRangeExpression(expr, { mode, idx, count})
  t.equal(transformed, 'E1', MSG)
  t.end()
})

test('transformCellRangeExpression: single / col / insert after', (t) => {
  let expr = 'C1'
  let mode = 'col'
  let idx = 4
  let count = 1
  let transformed = transformCellRangeExpression(expr, { mode, idx, count})
  t.equal(transformed, 'C1', MSG)
  t.end()
})

test('transformCellRangeExpression: single / col / delete before', (t) => {
  let expr = 'C1'
  let mode = 'col'
  let idx = 0
  let count = -2
  let transformed = transformCellRangeExpression(expr, { mode, idx, count})
  t.equal(transformed, 'A1', MSG)
  t.end()
})

test('transformCellRangeExpression: single / col / delete after', (t) => {
  let expr = 'C1'
  let mode = 'col'
  let idx = 4
  let count = -1
  let transformed = transformCellRangeExpression(expr, { mode, idx, count})
  t.equal(transformed, 'C1', MSG)
  t.end()
})

test('transformCellRangeExpression: single / col / delete same', (t) => {
  let expr = 'C1'
  let mode = 'col'
  let idx = 2
  let count = -1
  let transformed = transformCellRangeExpression(expr, { mode, idx, count})
  t.equal(transformed, BROKEN_REF, MSG)
  t.end()
})

test('transformCellRangeExpression: single / col / delete surrounding', (t) => {
  let expr = 'C1'
  let mode = 'col'
  let idx = 1
  let count = -4
  let transformed = transformCellRangeExpression(expr, { mode, idx, count})
  t.equal(transformed, BROKEN_REF, MSG)
  t.end()
})

// cell reference - insert/delete rows

test('transformCellRangeExpression: single / row / insert before', (t) => {
  let expr = 'C3'
  let mode = 'row'
  let idx = 1
  let count = 3
  let transformed = transformCellRangeExpression(expr, { mode, idx, count})
  t.equal(transformed, 'C6', MSG)
  t.end()
})

test('transformCellRangeExpression: single / row / insert after', (t) => {
  let expr = 'C3'
  let mode = 'row'
  let idx = 4
  let count = 1
  let transformed = transformCellRangeExpression(expr, { mode, idx, count})
  t.equal(transformed, 'C3', MSG)
  t.end()
})

test('transformCellRangeExpression: single / row / delete before', (t) => {
  let expr = 'C3'
  let mode = 'row'
  let idx = 0
  let count = -2
  let transformed = transformCellRangeExpression(expr, { mode, idx, count})
  t.equal(transformed, 'C1', MSG)
  t.end()
})

test('transformCellRangeExpression: single / row / delete after', (t) => {
  let expr = 'C3'
  let mode = 'row'
  let idx = 4
  let count = -1
  let transformed = transformCellRangeExpression(expr, { mode, idx, count})
  t.equal(transformed, 'C3', MSG)
  t.end()
})

test('transformCellRangeExpression: single / row / delete same', (t) => {
  let expr = 'C3'
  let mode = 'row'
  let idx = 2
  let count = -1
  let transformed = transformCellRangeExpression(expr, { mode, idx, count})
  t.equal(transformed, BROKEN_REF, MSG)
  t.end()
})
test('transformCellRangeExpression: single / row / delete surrounding', (t) => {
  let expr = 'C3'
  let mode = 'row'
  let idx = 1
  let count = -4
  let transformed = transformCellRangeExpression(expr, { mode, idx, count})
  t.equal(transformed, BROKEN_REF, MSG)
  t.end()
})

// range - insert/delete cols

test('transformCellRangeExpression: range / col / insert before', (t) => {
  let expr = 'C3:F10'
  let mode = 'col'
  let idx = 2
  let count = 2
  let transformed = transformCellRangeExpression(expr, { mode, idx, count})
  t.equal(transformed, 'E3:H10', MSG)
  t.end()
})

test('transformCellRangeExpression: range / col / insert inside', (t) => {
  let expr = 'C3:F10'
  let mode = 'col'
  let idx = 4
  let count = 3
  let transformed = transformCellRangeExpression(expr, { mode, idx, count})
  t.equal(transformed, 'C3:I10', MSG)
  t.end()
})

test('transformCellRangeExpression: range / col / insert after', (t) => {
  let expr = 'C3:F10'
  let mode = 'col'
  let idx = 7
  let count = 1
  let transformed = transformCellRangeExpression(expr, { mode, idx, count})
  t.equal(transformed, 'C3:F10', MSG)
  t.end()
})

test('transformCellRangeExpression: range / col / delete before', (t) => {
  let expr = 'C3:F10'
  let mode = 'col'
  let idx = 1
  let count = 1
  let transformed = transformCellRangeExpression(expr, { mode, idx, count})
  t.equal(transformed, 'B3:E10', MSG)
  t.end()
})

test('transformCellRangeExpression: range / col / delete overlapping start', (t) => {
  let expr = 'C3:F10'
  let mode = 'col'
  let idx = 1
  let count = 4
  let transformed = transformCellRangeExpression(expr, { mode, idx, count})
  t.equal(transformed, 'B3:B10', MSG)
  t.end()
})

test('transformCellRangeExpression: range / col / delete inside', (t) => {
  let expr = 'C3:F10'
  let mode = 'col'
  let idx = 3
  let count = 2
  let transformed = transformCellRangeExpression(expr, { mode, idx, count})
  t.equal(transformed, 'C3:D10', MSG)
  t.end()
})

test('transformCellRangeExpression: range / col / delete overlapping end', (t) => {
  let expr = 'C3:F10'
  let mode = 'col'
  let idx = 4
  let count = 3
  let transformed = transformCellRangeExpression(expr, { mode, idx, count})
  t.equal(transformed, 'C3:D10', MSG)
  t.end()
})

test('transformCellRangeExpression: range / col / delete after', (t) => {
  let expr = 'C3:F10'
  let mode = 'col'
  let idx = 7
  let count = 1
  let transformed = transformCellRangeExpression(expr, { mode, idx, count})
  t.equal(transformed, 'C3:F10', MSG)
  t.end()
})

// range - insert/delete rows

test('transformCellRangeExpression: range / row / insert before', (t) => {
  let expr = 'C3:F10'
  let mode = 'row'
  let idx = 1
  let count = 3
  let transformed = transformCellRangeExpression(expr, { mode, idx, count})
  t.equal(transformed, 'C6:F13', MSG)
  t.end()
})

test('transformCellRangeExpression: range / row / insert inside', (t) => {
  let expr = 'C3:F10'
  let mode = 'row'
  let idx = 6
  let count = 2
  let transformed = transformCellRangeExpression(expr, { mode, idx, count})
  t.equal(transformed, 'C3:F12', MSG)
  t.end()
})

test('transformCellRangeExpression: range / row / insert after', (t) => {
  let expr = 'C3:F10'
  let mode = 'row'
  let idx = 12
  let count = 1
  let transformed = transformCellRangeExpression(expr, { mode, idx, count})
  t.equal(transformed, 'C3:F10', MSG)
  t.end()
})

test('transformCellRangeExpression: range / row / delete before', (t) => {
  let expr = 'C3:F10'
  let mode = 'row'
  let idx = 1
  let count = 1
  let transformed = transformCellRangeExpression(expr, { mode, idx, count})
  t.equal(transformed, 'C2:F9', MSG)
  t.end()
})

test('transformCellRangeExpression: range / row / delete overlapping start', (t) => {
  let expr = 'C3:F10'
  let mode = 'row'
  let idx = 1
  let count = 4
  let transformed = transformCellRangeExpression(expr, { mode, idx, count})
  t.equal(transformed, 'C2:F6', MSG)
  t.end()
})

test('transformCellRangeExpression: range / row / delete inside', (t) => {
  let expr = 'C3:F10'
  let mode = 'row'
  let idx = 5
  let count = 2
  let transformed = transformCellRangeExpression(expr, { mode, idx, count})
  t.equal(transformed, 'C3:F8', MSG)
  t.end()
})

test('transformCellRangeExpression: range / row / delete overlapping end', (t) => {
  let expr = 'C3:F10'
  let mode = 'row'
  let idx = 7
  let count = 6
  let transformed = transformCellRangeExpression(expr, { mode, idx, count})
  t.equal(transformed, 'C3:F7', MSG)
  t.end()
})

test('transformCellRangeExpression: range / row / delete after', (t) => {
  let expr = 'C3:F10'
  let mode = 'row'
  let idx = 12
  let count = 1
  let transformed = transformCellRangeExpression(expr, { mode, idx, count})
  t.equal(transformed, 'C3:F10', MSG)
  t.end()
})
<commit_msg>Cell range expression tests correction.<commit_after>import test from 'tape'

import { transformCellRangeExpression, BROKEN_REF } from '../../src/shared/cellHelpers'

const MSG = 'expression should be transformed correctly'

/*
TODO

ranges:
- range expression inside deleted
- deleted range inside of range expression
- range expression overlaps deleted range on the 'left' side
- range expression overlaps deleted range on the 'right' sides

*/

// cell reference - insert/delete columns

test('transformCellRangeExpression: single / col / insert before', (t) => {
  let expr = 'C1'
  let mode = 'col'
  let idx = 1
  let count = 2
  let transformed = transformCellRangeExpression(expr, { mode, idx, count})
  t.equal(transformed, 'E1', MSG)
  t.end()
})

test('transformCellRangeExpression: single / col / insert after', (t) => {
  let expr = 'C1'
  let mode = 'col'
  let idx = 4
  let count = 1
  let transformed = transformCellRangeExpression(expr, { mode, idx, count})
  t.equal(transformed, 'C1', MSG)
  t.end()
})

test('transformCellRangeExpression: single / col / delete before', (t) => {
  let expr = 'C1'
  let mode = 'col'
  let idx = 0
  let count = -2
  let transformed = transformCellRangeExpression(expr, { mode, idx, count})
  t.equal(transformed, 'A1', MSG)
  t.end()
})

test('transformCellRangeExpression: single / col / delete after', (t) => {
  let expr = 'C1'
  let mode = 'col'
  let idx = 4
  let count = -1
  let transformed = transformCellRangeExpression(expr, { mode, idx, count})
  t.equal(transformed, 'C1', MSG)
  t.end()
})

test('transformCellRangeExpression: single / col / delete same', (t) => {
  let expr = 'C1'
  let mode = 'col'
  let idx = 2
  let count = -1
  let transformed = transformCellRangeExpression(expr, { mode, idx, count})
  t.equal(transformed, BROKEN_REF, MSG)
  t.end()
})

test('transformCellRangeExpression: single / col / delete surrounding', (t) => {
  let expr = 'C1'
  let mode = 'col'
  let idx = 1
  let count = -4
  let transformed = transformCellRangeExpression(expr, { mode, idx, count})
  t.equal(transformed, BROKEN_REF, MSG)
  t.end()
})

// cell reference - insert/delete rows

test('transformCellRangeExpression: single / row / insert before', (t) => {
  let expr = 'C3'
  let mode = 'row'
  let idx = 1
  let count = 3
  let transformed = transformCellRangeExpression(expr, { mode, idx, count})
  t.equal(transformed, 'C6', MSG)
  t.end()
})

test('transformCellRangeExpression: single / row / insert after', (t) => {
  let expr = 'C3'
  let mode = 'row'
  let idx = 4
  let count = 1
  let transformed = transformCellRangeExpression(expr, { mode, idx, count})
  t.equal(transformed, 'C3', MSG)
  t.end()
})

test('transformCellRangeExpression: single / row / delete before', (t) => {
  let expr = 'C3'
  let mode = 'row'
  let idx = 0
  let count = -2
  let transformed = transformCellRangeExpression(expr, { mode, idx, count})
  t.equal(transformed, 'C1', MSG)
  t.end()
})

test('transformCellRangeExpression: single / row / delete after', (t) => {
  let expr = 'C3'
  let mode = 'row'
  let idx = 4
  let count = -1
  let transformed = transformCellRangeExpression(expr, { mode, idx, count})
  t.equal(transformed, 'C3', MSG)
  t.end()
})

test('transformCellRangeExpression: single / row / delete same', (t) => {
  let expr = 'C3'
  let mode = 'row'
  let idx = 2
  let count = -1
  let transformed = transformCellRangeExpression(expr, { mode, idx, count})
  t.equal(transformed, BROKEN_REF, MSG)
  t.end()
})
test('transformCellRangeExpression: single / row / delete surrounding', (t) => {
  let expr = 'C3'
  let mode = 'row'
  let idx = 1
  let count = -4
  let transformed = transformCellRangeExpression(expr, { mode, idx, count})
  t.equal(transformed, BROKEN_REF, MSG)
  t.end()
})

// range - insert/delete cols

test('transformCellRangeExpression: range / col / insert before', (t) => {
  let expr = 'C3:F10'
  let mode = 'col'
  let idx = 2
  let count = 2
  let transformed = transformCellRangeExpression(expr, { mode, idx, count})
  t.equal(transformed, 'E3:H10', MSG)
  t.end()
})

test('transformCellRangeExpression: range / col / insert inside', (t) => {
  let expr = 'C3:F10'
  let mode = 'col'
  let idx = 4
  let count = 3
  let transformed = transformCellRangeExpression(expr, { mode, idx, count})
  t.equal(transformed, 'C3:I10', MSG)
  t.end()
})

test('transformCellRangeExpression: range / col / insert after', (t) => {
  let expr = 'C3:F10'
  let mode = 'col'
  let idx = 7
  let count = 1
  let transformed = transformCellRangeExpression(expr, { mode, idx, count})
  t.equal(transformed, 'C3:F10', MSG)
  t.end()
})

test('transformCellRangeExpression: range / col / delete before', (t) => {
  let expr = 'C3:F10'
  let mode = 'col'
  let idx = 1
  let count = -1
  let transformed = transformCellRangeExpression(expr, { mode, idx, count})
  t.equal(transformed, 'B3:E10', MSG)
  t.end()
})

test('transformCellRangeExpression: range / col / delete overlapping start', (t) => {
  let expr = 'C3:F10'
  let mode = 'col'
  let idx = 1
  let count = -4
  let transformed = transformCellRangeExpression(expr, { mode, idx, count})
  t.equal(transformed, 'B3:B10', MSG)
  t.end()
})

test('transformCellRangeExpression: range / col / delete inside', (t) => {
  let expr = 'C3:F10'
  let mode = 'col'
  let idx = 3
  let count = -2
  let transformed = transformCellRangeExpression(expr, { mode, idx, count})
  t.equal(transformed, 'C3:D10', MSG)
  t.end()
})

test('transformCellRangeExpression: range / col / delete overlapping end', (t) => {
  let expr = 'C3:F10'
  let mode = 'col'
  let idx = 4
  let count = -3
  let transformed = transformCellRangeExpression(expr, { mode, idx, count})
  t.equal(transformed, 'C3:D10', MSG)
  t.end()
})

test('transformCellRangeExpression: range / col / delete after', (t) => {
  let expr = 'C3:F10'
  let mode = 'col'
  let idx = 7
  let count = -1
  let transformed = transformCellRangeExpression(expr, { mode, idx, count})
  t.equal(transformed, 'C3:F10', MSG)
  t.end()
})

// range - insert/delete rows

test('transformCellRangeExpression: range / row / insert before', (t) => {
  let expr = 'C3:F10'
  let mode = 'row'
  let idx = 1
  let count = 3
  let transformed = transformCellRangeExpression(expr, { mode, idx, count})
  t.equal(transformed, 'C6:F13', MSG)
  t.end()
})

test('transformCellRangeExpression: range / row / insert inside', (t) => {
  let expr = 'C3:F10'
  let mode = 'row'
  let idx = 6
  let count = 2
  let transformed = transformCellRangeExpression(expr, { mode, idx, count})
  t.equal(transformed, 'C3:F12', MSG)
  t.end()
})

test('transformCellRangeExpression: range / row / insert after', (t) => {
  let expr = 'C3:F10'
  let mode = 'row'
  let idx = 12
  let count = 1
  let transformed = transformCellRangeExpression(expr, { mode, idx, count})
  t.equal(transformed, 'C3:F10', MSG)
  t.end()
})

test('transformCellRangeExpression: range / row / delete before', (t) => {
  let expr = 'C3:F10'
  let mode = 'row'
  let idx = 1
  let count = -1
  let transformed = transformCellRangeExpression(expr, { mode, idx, count})
  t.equal(transformed, 'C2:F9', MSG)
  t.end()
})

test('transformCellRangeExpression: range / row / delete overlapping start', (t) => {
  let expr = 'C3:F10'
  let mode = 'row'
  let idx = 1
  let count = -4
  let transformed = transformCellRangeExpression(expr, { mode, idx, count})
  t.equal(transformed, 'C2:F6', MSG)
  t.end()
})

test('transformCellRangeExpression: range / row / delete inside', (t) => {
  let expr = 'C3:F10'
  let mode = 'row'
  let idx = 5
  let count = -2
  let transformed = transformCellRangeExpression(expr, { mode, idx, count})
  t.equal(transformed, 'C3:F8', MSG)
  t.end()
})

test('transformCellRangeExpression: range / row / delete overlapping end', (t) => {
  let expr = 'C3:F10'
  let mode = 'row'
  let idx = 7
  let count = -6
  let transformed = transformCellRangeExpression(expr, { mode, idx, count})
  t.equal(transformed, 'C3:F7', MSG)
  t.end()
})

test('transformCellRangeExpression: range / row / delete after', (t) => {
  let expr = 'C3:F10'
  let mode = 'row'
  let idx = 12
  let count = -1
  let transformed = transformCellRangeExpression(expr, { mode, idx, count})
  t.equal(transformed, 'C3:F10', MSG)
  t.end()
})
<|endoftext|>


<commit_before>/**
 * Copyright (c) 2015-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @providesModule SwipeableRow
 * @flow
 */
'use strict';

const Animated = require('Animated');
const I18nManager = require('I18nManager');
const PanResponder = require('PanResponder');
const React = require('React');
const PropTypes = require('prop-types');
const StyleSheet = require('StyleSheet');
const TimerMixin = require('react-timer-mixin');
const View = require('View');

const emptyFunction = require('fbjs/lib/emptyFunction');

const IS_RTL = I18nManager.isRTL;

// NOTE: Eventually convert these consts to an input object of configurations

// Position of the left of the swipable item when closed
const CLOSED_LEFT_POSITION = 0;
// Minimum swipe distance before we recognize it as such
const HORIZONTAL_SWIPE_DISTANCE_THRESHOLD = 10;
// Minimum swipe speed before we fully animate the user's action (open/close)
const HORIZONTAL_FULL_SWIPE_SPEED_THRESHOLD = 0.3;
// Factor to divide by to get slow speed; i.e. 4 means 1/4 of full speed
const SLOW_SPEED_SWIPE_FACTOR = 4;
// Time, in milliseconds, of how long the animated swipe should be
const SWIPE_DURATION = 300;

/**
 * On SwipeableListView mount, the 1st item will bounce to show users it's
 * possible to swipe
 */
const ON_MOUNT_BOUNCE_DELAY = 700;
const ON_MOUNT_BOUNCE_DURATION = 400;

// Distance left of closed position to bounce back when right-swiping from closed
const RIGHT_SWIPE_BOUNCE_BACK_DISTANCE = 30;
const RIGHT_SWIPE_BOUNCE_BACK_DURATION = 300;
/**
 * Max distance of right swipe to allow (right swipes do functionally nothing).
 * Must be multiplied by SLOW_SPEED_SWIPE_FACTOR because gestureState.dx tracks
 * how far the finger swipes, and not the actual animation distance.
*/
const RIGHT_SWIPE_THRESHOLD = 30 * SLOW_SPEED_SWIPE_FACTOR;

/**
 * Creates a swipable row that allows taps on the main item and a custom View
 * on the item hidden behind the row. Typically this should be used in
 * conjunction with SwipeableListView for additional functionality, but can be
 * used in a normal ListView. See the renderRow for SwipeableListView to see how
 * to use this component separately.
 */
const SwipeableRow = React.createClass({
  _panResponder: {},
  _previousLeft: CLOSED_LEFT_POSITION,

  mixins: [TimerMixin],

  propTypes: {
    children: PropTypes.any,
    isOpen: PropTypes.bool,
    maxSwipeDistance: PropTypes.number.isRequired,
    onOpen: PropTypes.func.isRequired,
    onClose: PropTypes.func.isRequired,
    onSwipeEnd: PropTypes.func.isRequired,
    onSwipeStart: PropTypes.func.isRequired,
    // Should bounce the row on mount
    shouldBounceOnMount: PropTypes.bool,
    /**
     * A ReactElement that is unveiled when the user swipes
     */
    slideoutView: PropTypes.node.isRequired,
    /**
     * The minimum swipe distance required before fully animating the swipe. If
     * the user swipes less than this distance, the item will return to its
     * previous (open/close) position.
     */
    swipeThreshold: PropTypes.number.isRequired,
  },

  getInitialState(): Object {
    return {
      currentLeft: new Animated.Value(this._previousLeft),
      /**
       * In order to render component A beneath component B, A must be rendered
       * before B. However, this will cause "flickering", aka we see A briefly
       * then B. To counter this, _isSwipeableViewRendered flag is used to set
       * component A to be transparent until component B is loaded.
       */
      isSwipeableViewRendered: false,
      rowHeight: (null: ?number),
    };
  },

  getDefaultProps(): Object {
    return {
      isOpen: false,
      maxSwipeDistance: 0,
      onOpen: emptyFunction,
      onClose: emptyFunction,
      onSwipeEnd: emptyFunction,
      onSwipeStart: emptyFunction,
      swipeThreshold: 30,
    };
  },

  componentWillMount(): void {
    this._panResponder = PanResponder.create({
      onMoveShouldSetPanResponderCapture: this._handleMoveShouldSetPanResponderCapture,
      onPanResponderGrant: this._handlePanResponderGrant,
      onPanResponderMove: this._handlePanResponderMove,
      onPanResponderRelease: this._handlePanResponderEnd,
      onPanResponderTerminationRequest: this._onPanResponderTerminationRequest,
      onPanResponderTerminate: this._handlePanResponderEnd,
      onShouldBlockNativeResponder: (event, gestureState) => false,
    });
  },

  componentDidMount(): void {
    if (this.props.shouldBounceOnMount) {
      /**
       * Do the on mount bounce after a delay because if we animate when other
       * components are loading, the animation will be laggy
       */
      this.setTimeout(() => {
        this._animateBounceBack(ON_MOUNT_BOUNCE_DURATION);
      }, ON_MOUNT_BOUNCE_DELAY);
    }
  },

  componentWillReceiveProps(nextProps: Object): void {
    /**
     * We do not need an "animateOpen(noCallback)" because this animation is
     * handled internally by this component.
     */
    if (this.props.isOpen && !nextProps.isOpen) {
      this._animateToClosedPosition();
    }
  },

  shouldComponentUpdate(nextProps: Object, nextState: Object): boolean {
    if (this.props.shouldBounceOnMount && !nextProps.shouldBounceOnMount) {
      // No need to rerender if SwipeableListView is disabling the bounce flag
      return false;
    }

    return true;
  },

  render(): React.Element<any> {
    // The view hidden behind the main view
    let slideOutView;
    if (this.state.isSwipeableViewRendered && this.state.rowHeight) {
      slideOutView = (
        <View style={[
          styles.slideOutContainer,
          {height: this.state.rowHeight},
          ]}>
          {this.props.slideoutView}
        </View>
      );
    }

    // The swipeable item
    const swipeableView = (
      <Animated.View
        onLayout={this._onSwipeableViewLayout}
        style={{transform: [{translateX: this.state.currentLeft}]}}>
        {this.props.children}
      </Animated.View>
    );

    return (
      <View
        {...this._panResponder.panHandlers}>
        {slideOutView}
        {swipeableView}
      </View>
    );
  },

  _onSwipeableViewLayout(event: Object): void {
    this.setState({
      isSwipeableViewRendered: true,
      rowHeight: event.nativeEvent.layout.height,
    });
  },

  _handleMoveShouldSetPanResponderCapture(
    event: Object,
    gestureState: Object,
  ): boolean {
    // Decides whether a swipe is responded to by this component or its child
    return gestureState.dy < 10 && this._isValidSwipe(gestureState);
  },

  _handlePanResponderGrant(event: Object, gestureState: Object): void {

  },

  _handlePanResponderMove(event: Object, gestureState: Object): void {
    if (this._isSwipingExcessivelyRightFromClosedPosition(gestureState)) {
      return;
    }

    this.props.onSwipeStart();

    if (this._isSwipingRightFromClosed(gestureState)) {
      this._swipeSlowSpeed(gestureState);
    } else {
      this._swipeFullSpeed(gestureState);
    }
  },

  _isSwipingRightFromClosed(gestureState: Object): boolean {
    const gestureStateDx = IS_RTL ? -gestureState.dx : gestureState.dx;
    return this._previousLeft === CLOSED_LEFT_POSITION && gestureStateDx > 0;
  },

  _swipeFullSpeed(gestureState: Object): void {
    this.state.currentLeft.setValue(this._previousLeft + gestureState.dx);
  },

  _swipeSlowSpeed(gestureState: Object): void {
    this.state.currentLeft.setValue(
      this._previousLeft + gestureState.dx / SLOW_SPEED_SWIPE_FACTOR,
    );
  },

  _isSwipingExcessivelyRightFromClosedPosition(gestureState: Object): boolean {
    /**
     * We want to allow a BIT of right swipe, to allow users to know that
     * swiping is available, but swiping right does not do anything
     * functionally.
     */
    const gestureStateDx = IS_RTL ? -gestureState.dx : gestureState.dx;
    return (
      this._isSwipingRightFromClosed(gestureState) &&
      gestureStateDx > RIGHT_SWIPE_THRESHOLD
    );
  },

  _onPanResponderTerminationRequest(
    event: Object,
    gestureState: Object,
  ): boolean {
    return false;
  },

  _animateTo(
    toValue: number,
    duration: number = SWIPE_DURATION,
    callback: Function = emptyFunction,
  ): void {
    Animated.timing(
      this.state.currentLeft,
      {
        duration,
        toValue,
      },
    ).start(() => {
      this._previousLeft = toValue;
      callback();
    });
  },

  _animateToOpenPosition(): void {
    const maxSwipeDistance = IS_RTL ? -this.props.maxSwipeDistance : this.props.maxSwipeDistance;
    this._animateTo(-maxSwipeDistance);
  },

  _animateToOpenPositionWith(
    speed: number,
    distMoved: number,
  ): void {
    /**
     * Ensure the speed is at least the set speed threshold to prevent a slow
     * swiping animation
     */
    speed = (
      speed > HORIZONTAL_FULL_SWIPE_SPEED_THRESHOLD ?
      speed :
      HORIZONTAL_FULL_SWIPE_SPEED_THRESHOLD
    );
    /**
     * Calculate the duration the row should take to swipe the remaining distance
     * at the same speed the user swiped (or the speed threshold)
     */
    const duration = Math.abs((this.props.maxSwipeDistance - Math.abs(distMoved)) / speed);
    const maxSwipeDistance = IS_RTL ? -this.props.maxSwipeDistance : this.props.maxSwipeDistance;
    this._animateTo(-maxSwipeDistance, duration);
  },

  _animateToClosedPosition(duration: number = SWIPE_DURATION): void {
    this._animateTo(CLOSED_LEFT_POSITION, duration);
  },

  _animateToClosedPositionDuringBounce(): void {
    this._animateToClosedPosition(RIGHT_SWIPE_BOUNCE_BACK_DURATION);
  },

  _animateBounceBack(duration: number): void {
    /**
     * When swiping right, we want to bounce back past closed position on release
     * so users know they should swipe right to get content.
     */
    const swipeBounceBackDistance = IS_RTL ?
      -RIGHT_SWIPE_BOUNCE_BACK_DISTANCE :
      RIGHT_SWIPE_BOUNCE_BACK_DISTANCE;
    this._animateTo(
      -swipeBounceBackDistance,
      duration,
      this._animateToClosedPositionDuringBounce,
    );
  },

  // Ignore swipes due to user's finger moving slightly when tapping
  _isValidSwipe(gestureState: Object): boolean {
    return Math.abs(gestureState.dx) > HORIZONTAL_SWIPE_DISTANCE_THRESHOLD;
  },

  _shouldAnimateRemainder(gestureState: Object): boolean {
    /**
     * If user has swiped past a certain distance, animate the rest of the way
     * if they let go
     */
    return (
      Math.abs(gestureState.dx) > this.props.swipeThreshold ||
      gestureState.vx > HORIZONTAL_FULL_SWIPE_SPEED_THRESHOLD
    );
  },

  _handlePanResponderEnd(event: Object, gestureState: Object): void {
    const horizontalDistance = IS_RTL ? -gestureState.dx : gestureState.dx;
    if (this._isSwipingRightFromClosed(gestureState)) {
      this.props.onOpen();
      this._animateBounceBack(RIGHT_SWIPE_BOUNCE_BACK_DURATION);
    } else if (this._shouldAnimateRemainder(gestureState)) {
      if (horizontalDistance < 0) {
        // Swiped left
        this.props.onOpen();
        this._animateToOpenPositionWith(gestureState.vx, horizontalDistance);
      } else {
        // Swiped right
        this.props.onClose();
        this._animateToClosedPosition();
      }
    } else {
      if (this._previousLeft === CLOSED_LEFT_POSITION) {
        this._animateToClosedPosition();
      } else {
        this._animateToOpenPosition();
      }
    }

    this.props.onSwipeEnd();
  },
});

const styles = StyleSheet.create({
  slideOutContainer: {
    bottom: 0,
    left: 0,
    position: 'absolute',
    right: 0,
    top: 0,
  },
});

module.exports = SwipeableRow;
<commit_msg>fixed SwipeableRow.js<commit_after>/**
 * Copyright (c) 2015-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @flow
 */
'use strict';

import React from 'react';

import {
  Animated,
  I18nManager,
  PanResponder,
  StyleSheet,
  View
} from 'react-native';

const PropTypes = React.PropTypes;
const TimerMixin = require('react-timer-mixin');

const emptyFunction = require('fbjs/lib/emptyFunction');

const IS_RTL = I18nManager.isRTL;

// NOTE: Eventually convert these consts to an input object of configurations

// Position of the left of the swipable item when closed
const CLOSED_LEFT_POSITION = 0;
// Minimum swipe distance before we recognize it as such
const HORIZONTAL_SWIPE_DISTANCE_THRESHOLD = 10;
// Minimum swipe speed before we fully animate the user's action (open/close)
const HORIZONTAL_FULL_SWIPE_SPEED_THRESHOLD = 0.3;
// Factor to divide by to get slow speed; i.e. 4 means 1/4 of full speed
const SLOW_SPEED_SWIPE_FACTOR = 4;
// Time, in milliseconds, of how long the animated swipe should be
const SWIPE_DURATION = 300;

/**
 * On SwipeableListView mount, the 1st item will bounce to show users it's
 * possible to swipe
 */
const ON_MOUNT_BOUNCE_DELAY = 700;
const ON_MOUNT_BOUNCE_DURATION = 400;

// Distance left of closed position to bounce back when right-swiping from closed
const RIGHT_SWIPE_BOUNCE_BACK_DISTANCE = 30;
const RIGHT_SWIPE_BOUNCE_BACK_DURATION = 300;
/**
 * Max distance of right swipe to allow (right swipes do functionally nothing).
 * Must be multiplied by SLOW_SPEED_SWIPE_FACTOR because gestureState.dx tracks
 * how far the finger swipes, and not the actual animation distance.
*/
const RIGHT_SWIPE_THRESHOLD = 30 * SLOW_SPEED_SWIPE_FACTOR;

/**
 * Creates a swipable row that allows taps on the main item and a custom View
 * on the item hidden behind the row. Typically this should be used in
 * conjunction with SwipeableListView for additional functionality, but can be
 * used in a normal ListView. See the renderRow for SwipeableListView to see how
 * to use this component separately.
 */
const SwipeableRow = React.createClass({
  _panResponder: {},
  _previousLeft: CLOSED_LEFT_POSITION,

  mixins: [TimerMixin],

  propTypes: {
    children: PropTypes.any,
    isOpen: PropTypes.bool,
    maxSwipeDistance: PropTypes.number.isRequired,
    onOpen: PropTypes.func.isRequired,
    onClose: PropTypes.func.isRequired,
    onSwipeEnd: PropTypes.func.isRequired,
    onSwipeStart: PropTypes.func.isRequired,
    // Should bounce the row on mount
    shouldBounceOnMount: PropTypes.bool,
    /**
     * A ReactElement that is unveiled when the user swipes
     */
    slideoutView: PropTypes.node.isRequired,
    /**
     * The minimum swipe distance required before fully animating the swipe. If
     * the user swipes less than this distance, the item will return to its
     * previous (open/close) position.
     */
    swipeThreshold: PropTypes.number.isRequired,
  },

  getInitialState(): Object {
    return {
      currentLeft: new Animated.Value(this._previousLeft),
      /**
       * In order to render component A beneath component B, A must be rendered
       * before B. However, this will cause "flickering", aka we see A briefly
       * then B. To counter this, _isSwipeableViewRendered flag is used to set
       * component A to be transparent until component B is loaded.
       */
      isSwipeableViewRendered: false,
      rowHeight: (null: ?number),
    };
  },

  getDefaultProps(): Object {
    return {
      isOpen: false,
      maxSwipeDistance: 0,
      onOpen: emptyFunction,
      onClose: emptyFunction,
      onSwipeEnd: emptyFunction,
      onSwipeStart: emptyFunction,
      swipeThreshold: 30,
    };
  },

  componentWillMount(): void {
    this._panResponder = PanResponder.create({
      onMoveShouldSetPanResponderCapture: this._handleMoveShouldSetPanResponderCapture,
      onPanResponderGrant: this._handlePanResponderGrant,
      onPanResponderMove: this._handlePanResponderMove,
      onPanResponderRelease: this._handlePanResponderEnd,
      onPanResponderTerminationRequest: this._onPanResponderTerminationRequest,
      onPanResponderTerminate: this._handlePanResponderEnd,
      onShouldBlockNativeResponder: (event, gestureState) => false,
    });
  },

  componentDidMount(): void {
    if (this.props.shouldBounceOnMount) {
      /**
       * Do the on mount bounce after a delay because if we animate when other
       * components are loading, the animation will be laggy
       */
      this.setTimeout(() => {
        this._animateBounceBack(ON_MOUNT_BOUNCE_DURATION);
      }, ON_MOUNT_BOUNCE_DELAY);
    }
  },

  componentWillReceiveProps(nextProps: Object): void {
    /**
     * We do not need an "animateOpen(noCallback)" because this animation is
     * handled internally by this component.
     */
    if (this.props.isOpen && !nextProps.isOpen) {
      this._animateToClosedPosition();
    }
  },

  shouldComponentUpdate(nextProps: Object, nextState: Object): boolean {
    if (this.props.shouldBounceOnMount && !nextProps.shouldBounceOnMount) {
      // No need to rerender if SwipeableListView is disabling the bounce flag
      return false;
    }

    return true;
  },

  render(): React.Element<any> {
    // The view hidden behind the main view
    let slideOutView;
    if (this.state.isSwipeableViewRendered && this.state.rowHeight) {
      slideOutView = (
        <View style={[
          styles.slideOutContainer,
          {height: this.state.rowHeight},
          ]}>
          {this.props.slideoutView}
        </View>
      );
    }

    // The swipeable item
    const swipeableView = (
      <Animated.View
        onLayout={this._onSwipeableViewLayout}
        style={{transform: [{translateX: this.state.currentLeft}]}}>
        {this.props.children}
      </Animated.View>
    );

    return (
      <View
        {...this._panResponder.panHandlers}>
        {slideOutView}
        {swipeableView}
      </View>
    );
  },

  _onSwipeableViewLayout(event: Object): void {
    this.setState({
      isSwipeableViewRendered: true,
      rowHeight: event.nativeEvent.layout.height,
    });
  },

  _handleMoveShouldSetPanResponderCapture(
    event: Object,
    gestureState: Object,
  ): boolean {
    // Decides whether a swipe is responded to by this component or its child
    return gestureState.dy < 10 && this._isValidSwipe(gestureState);
  },

  _handlePanResponderGrant(event: Object, gestureState: Object): void {

  },

  _handlePanResponderMove(event: Object, gestureState: Object): void {
    if (this._isSwipingExcessivelyRightFromClosedPosition(gestureState)) {
      return;
    }

    this.props.onSwipeStart();

    if (this._isSwipingRightFromClosed(gestureState)) {
      this._swipeSlowSpeed(gestureState);
    } else {
      this._swipeFullSpeed(gestureState);
    }
  },

  _isSwipingRightFromClosed(gestureState: Object): boolean {
    const gestureStateDx = IS_RTL ? -gestureState.dx : gestureState.dx;
    return this._previousLeft === CLOSED_LEFT_POSITION && gestureStateDx > 0;
  },

  _swipeFullSpeed(gestureState: Object): void {
    this.state.currentLeft.setValue(this._previousLeft + gestureState.dx);
  },

  _swipeSlowSpeed(gestureState: Object): void {
    this.state.currentLeft.setValue(
      this._previousLeft + gestureState.dx / SLOW_SPEED_SWIPE_FACTOR,
    );
  },

  _isSwipingExcessivelyRightFromClosedPosition(gestureState: Object): boolean {
    /**
     * We want to allow a BIT of right swipe, to allow users to know that
     * swiping is available, but swiping right does not do anything
     * functionally.
     */
    const gestureStateDx = IS_RTL ? -gestureState.dx : gestureState.dx;
    return (
      this._isSwipingRightFromClosed(gestureState) &&
      gestureStateDx > RIGHT_SWIPE_THRESHOLD
    );
  },

  _onPanResponderTerminationRequest(
    event: Object,
    gestureState: Object,
  ): boolean {
    return false;
  },

  _animateTo(
    toValue: number,
    duration: number = SWIPE_DURATION,
    callback: Function = emptyFunction,
  ): void {
    Animated.timing(
      this.state.currentLeft,
      {
        duration,
        toValue,
      },
    ).start(() => {
      this._previousLeft = toValue;
      callback();
    });
  },

  _animateToOpenPosition(): void {
    const maxSwipeDistance = IS_RTL ? -this.props.maxSwipeDistance : this.props.maxSwipeDistance;
    this._animateTo(-maxSwipeDistance);
  },

  _animateToOpenPositionWith(
    speed: number,
    distMoved: number,
  ): void {
    /**
     * Ensure the speed is at least the set speed threshold to prevent a slow
     * swiping animation
     */
    speed = (
      speed > HORIZONTAL_FULL_SWIPE_SPEED_THRESHOLD ?
      speed :
      HORIZONTAL_FULL_SWIPE_SPEED_THRESHOLD
    );
    /**
     * Calculate the duration the row should take to swipe the remaining distance
     * at the same speed the user swiped (or the speed threshold)
     */
    const duration = Math.abs((this.props.maxSwipeDistance - Math.abs(distMoved)) / speed);
    const maxSwipeDistance = IS_RTL ? -this.props.maxSwipeDistance : this.props.maxSwipeDistance;
    this._animateTo(-maxSwipeDistance, duration);
  },

  _animateToClosedPosition(duration: number = SWIPE_DURATION): void {
    this._animateTo(CLOSED_LEFT_POSITION, duration);
  },

  _animateToClosedPositionDuringBounce(): void {
    this._animateToClosedPosition(RIGHT_SWIPE_BOUNCE_BACK_DURATION);
  },

  _animateBounceBack(duration: number): void {
    /**
     * When swiping right, we want to bounce back past closed position on release
     * so users know they should swipe right to get content.
     */
    const swipeBounceBackDistance = IS_RTL ?
      -RIGHT_SWIPE_BOUNCE_BACK_DISTANCE :
      RIGHT_SWIPE_BOUNCE_BACK_DISTANCE;
    this._animateTo(
      -swipeBounceBackDistance,
      duration,
      this._animateToClosedPositionDuringBounce,
    );
  },

  // Ignore swipes due to user's finger moving slightly when tapping
  _isValidSwipe(gestureState: Object): boolean {
    return Math.abs(gestureState.dx) > HORIZONTAL_SWIPE_DISTANCE_THRESHOLD;
  },

  _shouldAnimateRemainder(gestureState: Object): boolean {
    /**
     * If user has swiped past a certain distance, animate the rest of the way
     * if they let go
     */
    return (
      Math.abs(gestureState.dx) > this.props.swipeThreshold ||
      gestureState.vx > HORIZONTAL_FULL_SWIPE_SPEED_THRESHOLD
    );
  },

  _handlePanResponderEnd(event: Object, gestureState: Object): void {
    const horizontalDistance = IS_RTL ? -gestureState.dx : gestureState.dx;
    if (this._isSwipingRightFromClosed(gestureState)) {
      this.props.onOpen();
      this._animateBounceBack(RIGHT_SWIPE_BOUNCE_BACK_DURATION);
    } else if (this._shouldAnimateRemainder(gestureState)) {
      if (horizontalDistance < 0) {
        // Swiped left
        this.props.onOpen();
        this._animateToOpenPositionWith(gestureState.vx, horizontalDistance);
      } else {
        // Swiped right
        this.props.onClose();
        this._animateToClosedPosition();
      }
    } else {
      if (this._previousLeft === CLOSED_LEFT_POSITION) {
        this._animateToClosedPosition();
      } else {
        this._animateToOpenPosition();
      }
    }

    this.props.onSwipeEnd();
  },
});

const styles = StyleSheet.create({
  slideOutContainer: {
    bottom: 0,
    left: 0,
    position: 'absolute',
    right: 0,
    top: 0,
  },
});

module.exports = SwipeableRow;
<|endoftext|>


<commit_before>define(['declare'], function(declare){

	declare.globals("djs.core", function(self, core){

		// constants
		self.const.version = "0.0.1";

		// END LIBRARAY

	}, "djs.core");

});<commit_msg>change<commit_after>define(['declare'], function(declare){

	// tesgin
	declare.globals("djs.core", function(self, core){

		// constants
		self.const.version = "0.0.1";

		// END LIBRARAY

	}, "djs.core");

});<|endoftext|>


<commit_before>import { apiRenderCanvas, apiUpdate, canvasLayer } from 'vizart-core';
import { renderAxis, updateAxis } from '../axis';

import { stackedComposer, standardComposer } from './composers';

const apiRender = (state, animate, hasAxis, stacked) => ({
  render(data) {
    apiRenderCanvas(state).render(data);
    if (hasAxis === true) {
      renderAxis(state, stacked);
    }
    animate(state);
  },
});

const apiUpdateChart = (state, animate, hasAxis, stacked) => ({
  update() {
    apiUpdate(state).update();
    if (hasAxis === true) {
      updateAxis(state, stacked);
    }
    animate(state);
  },
});

const apiColor = state => ({
  color(colorOpt) {
    state._options.color = colorOpt;
    state.update();
  },
});

const addApi = (state, apis) => {
  return apis.reduce((acc, cur) => {
    acc = Object.assign(acc, cur(acc));
    return acc;
  }, state);
};

const build = builderConfig => (ChartOpt, animate, apis = []) => (id, opt) => {
  const { hasAxis = true, stacked = false } = builderConfig;
  const compose = stacked === true ? stackedComposer : standardComposer;

  const baseChart = canvasLayer(id, opt, compose(ChartOpt));

  const chart = Object.assign(
    baseChart,
    apiRender(baseChart, animate, hasAxis, stacked),
    apiUpdateChart(baseChart, animate, hasAxis, stacked)
  );

  return addApi(chart, [apiColor, ...apis]);
};

const cartesian = build({ hasAxis: true, stacked: false });
const polar = build({ hasAxis: false, stacked: false });
const stacked = build({ hasAxis: true, stacked: true });
const polarStacked = build({ hasAxis: false, stacked: true });

export default build;

export { cartesian, polar, stacked, polarStacked };
<commit_msg>safer build approach<commit_after>import { apiRenderCanvas, apiUpdate, canvasLayer } from 'vizart-core';
import { renderAxis, updateAxis } from '../axis';

import { stackedComposer, standardComposer } from './composers';

const apiRender = (state, animate, hasAxis, stacked) => ({
  render(data) {
    apiRenderCanvas(state).render(data);
    if (hasAxis === true) {
      renderAxis(state, stacked);
    }
    animate(state);
  },
});

const apiUpdateChart = (state, animate, hasAxis, stacked) => ({
  update() {
    apiUpdate(state).update();
    if (hasAxis === true) {
      updateAxis(state, stacked);
    }
    animate(state);
  },
});

const apiColor = state => ({
  color(colorOpt) {
    state._options.color = colorOpt;
    state.update();
  },
});

const addApi = (state, apis) => {
  return apis.reduce((acc, cur) => {
    acc = Object.assign(acc, cur(acc));
    return acc;
  }, state);
};

const build = builderConfig => (ChartOpt, animate, apis = []) => (id, opt) => {
  const { hasAxis = true, stacked = false } = builderConfig;
  const compose = stacked === true ? stackedComposer : standardComposer;

  const baseChart = canvasLayer(id, opt, compose(ChartOpt));

  const chart = Object.assign(
    {},
    baseChart,
    apiRender(baseChart, animate, hasAxis, stacked),
    apiUpdateChart(baseChart, animate, hasAxis, stacked)
  );

  return addApi(chart, [apiColor, ...apis]);
};

const cartesian = build({ hasAxis: true, stacked: false });
const polar = build({ hasAxis: false, stacked: false });
const stacked = build({ hasAxis: true, stacked: true });
const polarStacked = build({ hasAxis: false, stacked: true });

export default build;

export { cartesian, polar, stacked, polarStacked };
<|endoftext|>


<commit_before>/**
 * The MIT License (MIT)
 *
 * Copyright (c) 2017-2019 Mickael Jeanroy
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

const fs = require('fs-extra');
const log = require('fancy-log');
const gulp = require('gulp');
const bump = require('gulp-bump');
const git = require('gulp-git');
const config = require('../config');

/**
 * Update version in number in `package.json` file.
 *
 * @param {string} type The semver level identifier (`major`, `minor` or `patch`).
 * @return {WritableStream} The stream pipeline.
 */
function bumpVersion(type) {
  return gulp.src(config.pkg)
      .pipe(bump({type}))
      .on('error', (e) => log.error(e))
      .pipe(gulp.dest(config.root));
}

/**
 * Commit the current changes:
 * - The `dist` directory containing final bundle.
 * - The `package.json` containing the new version number.
 *
 * @return {WritableStream} The stream pipeline.
 */
function performRelease() {
  return gulp.src([config.pkg, config.dist])
      .pipe(git.add({args: '-f'}))
      .pipe(git.commit('release: release version'));
}

/**
 * Tag current version: the tag name will be extracted from
 * the `version` field in the `package.json` file.
 *
 * @param {function} done The `done` callback.
 * @return {void}
 */
function tagRelease(done) {
  fs.readJson(config.pkg)
      .then((pkg) => pkg.version)
      .then((version) => git.tag(`v${version}`, `release: tag version ${version}`, done));
}

/**
 * Prepare the next release cycle:
 * - Remove the `dist` directory containing bundle tagged on given version.
 * - Create a new commit preparing the next release.
 *
 * @return {WritableStream} The stream pipeline.
 */
function prepareNextRelease() {
  return gulp.src(config.dist)
      .pipe(git.rm({args: '-r'}))
      .pipe(git.commit('release: prepare next release'));
}

/**
 * Create the release task.
 *
 * @param {string} level The version level upgrade.
 * @return {function} The release task function.
 */
function createReleaseTask(level) {
  /**
   * Prepare the release: upgrade version number according to
   * the specified level.
   *
   * @return {WritableStream} The stream pipeline.
   */
  function prepareRelease() {
    return bumpVersion(level);
  }

  return gulp.series(
      prepareRelease,
      performRelease,
      tagRelease,
      prepareNextRelease
  );
}

module.exports = {
  patch: createReleaseTask('patch'),
  minor: createReleaseTask('minor'),
  major: createReleaseTask('major'),
};

<commit_msg>chore: fix release task, handle error<commit_after>/**
 * The MIT License (MIT)
 *
 * Copyright (c) 2017-2019 Mickael Jeanroy
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

const fs = require('fs-extra');
const log = require('fancy-log');
const gulp = require('gulp');
const bump = require('gulp-bump');
const git = require('gulp-git');
const config = require('../config');

/**
 * Update version in number in `package.json` file.
 *
 * @param {string} type The semver level identifier (`major`, `minor` or `patch`).
 * @return {WritableStream} The stream pipeline.
 */
function bumpVersion(type) {
  return gulp.src(config.pkg)
      .pipe(bump({type}))
      .on('error', (e) => log.error(e))
      .pipe(gulp.dest(config.root));
}

/**
 * Commit the current changes:
 * - The `dist` directory containing final bundle.
 * - The `package.json` containing the new version number.
 *
 * @return {WritableStream} The stream pipeline.
 */
function performRelease() {
  return gulp.src([config.pkg, config.dist])
      .pipe(git.add({args: '-f'}))
      .pipe(git.commit('release: release version'));
}

/**
 * Tag current version: the tag name will be extracted from
 * the `version` field in the `package.json` file.
 *
 * @param {function} done The `done` callback.
 * @return {void}
 */
function tagRelease(done) {
  fs.readJson(config.pkg)
      .then((pkg) => pkg.version)
      .then((version) => git.tag(`v${version}`, `release: tag version ${version}`, done))
      .catch((err) => done(err));
}

/**
 * Prepare the next release cycle:
 * - Remove the `dist` directory containing bundle tagged on given version.
 * - Create a new commit preparing the next release.
 *
 * @return {WritableStream} The stream pipeline.
 */
function prepareNextRelease() {
  return gulp.src(config.dist)
      .pipe(git.rm({args: '-r'}))
      .pipe(git.commit('release: prepare next release'));
}

/**
 * Create the release task.
 *
 * @param {string} level The version level upgrade.
 * @return {function} The release task function.
 */
function createReleaseTask(level) {
  /**
   * Prepare the release: upgrade version number according to
   * the specified level.
   *
   * @return {WritableStream} The stream pipeline.
   */
  function prepareRelease() {
    return bumpVersion(level);
  }

  return gulp.series(
      prepareRelease,
      performRelease,
      tagRelease,
      prepareNextRelease
  );
}

module.exports = {
  patch: createReleaseTask('patch'),
  minor: createReleaseTask('minor'),
  major: createReleaseTask('major'),
};

<|endoftext|>


<commit_before>var data = {
	hotSite : [
	    	{
	    		name : '榜单tab切换',
	    		desc : '模仿百度爱玩',
	    		url : '/works/demo/03/bd03.html'
	    	},
	    	{
	    		name : 'CSS3弹窗&切换',
	    		desc : '模仿联想招聘',
	    		url : '/works/demo/04/'
	    	},
	    	{
	    		name : '无缝切换图片',
	    		desc : '模仿百度爱玩',
	    		url : '/demo/javascript-seamless-handover/slide/bd01.html'
	    	},
	    	{
	    		name : '电商菜单导航',
	    		desc : '百度微购',
	    		url : '/works/demo/08/'
	    	},
	    	{
	    		name : '查看长图',
	    		desc : '模仿QQ空间',
	    		url : '/works/demo/qqzone-img'
	    	},
	    	{
	    		name : '中秋送好礼',
	    		desc : '百度微购图片轮播',
	    		url : '/works/demo/07/'
	    	},
	    	{
	    		name : '层次图片轮播',
	    		desc : '模仿百度爱玩',
	    		url : '/works/demo/06/'
	    	},
	    	{
	    		name : '阿里云开发者大会',
	    		desc : '拖动图片轮播',
	    		url : '/works/demo/ali/actor.html'
	    	}
    	],
    base : [
	    	{
	    		name : '瀑布流-浮动',
	    		desc : '瀑布流-浮动',
	    		url : '/works/demo/stream/float.html'
	    	},{
	    		name : '阿里响应式笔试题',
	    		desc : '响应式DEMO',
	    		url : '/works/demo/html5/ali.html'
	    	},
	    	{
	    		name : '创新实验室宣传网站',
	    		desc : '重理工软件工程',
	    		url : '/works/demo/ee/index.htm'
	    	}

	    ],

	mobile : [
	    	{
	    		name : '信息轮播',
	    		desc : '神马搜索美团信息轮播',
	    		url : '/works/demo/sm-meituan'
	    	}
	    ],
	project : [
	    	{
	    		name : 'gokk',
	    		desc : '基于Node.js + jade + Mongoose 模仿gokk.tv ',
	    		url : 'https://github.com/hacke2/gokk'
	    	}
	    ]

}
<commit_msg>add inspiration project<commit_after>var data = {
	hotSite : [
	    	{
	    		name : '榜单tab切换',
	    		desc : '模仿百度爱玩',
	    		url : '/works/demo/03/bd03.html'
	    	},
	    	{
	    		name : 'CSS3弹窗&切换',
	    		desc : '模仿联想招聘',
	    		url : '/works/demo/04/'
	    	},
	    	{
	    		name : '无缝切换图片',
	    		desc : '模仿百度爱玩',
	    		url : '/demo/javascript-seamless-handover/slide/bd01.html'
	    	},
	    	{
	    		name : '电商菜单导航',
	    		desc : '百度微购',
	    		url : '/works/demo/08/'
	    	},
	    	{
	    		name : '查看长图',
	    		desc : '模仿QQ空间',
	    		url : '/works/demo/qqzone-img'
	    	},
	    	{
	    		name : '中秋送好礼',
	    		desc : '百度微购图片轮播',
	    		url : '/works/demo/07/'
	    	},
	    	{
	    		name : '层次图片轮播',
	    		desc : '模仿百度爱玩',
	    		url : '/works/demo/06/'
	    	},
	    	{
	    		name : '阿里云开发者大会',
	    		desc : '拖动图片轮播',
	    		url : '/works/demo/ali/actor.html'
	    	}
    	],
    base : [
	    	{
	    		name : '瀑布流-浮动',
	    		desc : '瀑布流-浮动',
	    		url : '/works/demo/stream/float.html'
	    	},{
	    		name : '阿里响应式笔试题',
	    		desc : '响应式DEMO',
	    		url : '/works/demo/html5/ali.html'
	    	},
	    	{
	    		name : '创新实验室宣传网站',
	    		desc : '重理工软件工程',
	    		url : '/works/demo/ee/index.htm'
	    	}

	    ],

	mobile : [
	    	{
	    		name : '信息轮播',
	    		desc : '神马搜索美团信息轮播',
	    		url : '/works/demo/sm-meituan'
	    	}
	    ],
	project : [
	    	{
	    		name : 'gokk',
	    		desc : '基于Node.js + jade + Mongoose 模仿gokk.tv ',
	    		url : 'https://github.com/hacke2/gokk'
	    	},
	    	{
	    		name : '动效平台',
	    		desc : '基于Node.js + Angular + fis3 + orm2的可视化代码编辑页面',
	    		url : 'https://github.com/hacke2/inspiration'
	    	}
	    ]

}
<|endoftext|>


<commit_before>import assert from 'assert';
import makeDebug from 'debug';
import errors from 'feathers-errors';
import { compact, flatten, flattenDeep, get, isArray, map, reduce, set } from 'lodash';
import { plural } from 'pluralize';
import validator from 'validator';
import util from 'util';
import { getField, setField, setFieldByKey } from '../helpers';

const debug = makeDebug('mostly:feathers-mongoose:hooks:populate');

const defaultOptions = {
  idField: 'id'
};

function isPopulated(obj) {
  if (isArray(obj)) {
    return reduce(obj, (result, val) => {
      return result && val && !validator.isMongoId(val.toString());
    }, true);
  } else {
    if (obj) {
      return obj && !validator.isMongoId(obj.toString());
    } else {
      return false;
    }
  }
}

/**
 * The populate hook uses a property from the result (or every item if it is a list)
 * to retrieve a single related object from a service and add it to the original object.
 * It is meant to be used as an after hook on any service method.
 *
 * @param {string} target - The prop name to contain the populated item or array of populated items.
 *    This is also the default for options.field if that is not specified.
 * @param {Object} options - options
 *    For a mongoose model, these are the options for item.toObject().
 *    For a Sequelize model, these are the options for item.toJSON().
 * @param {string} options.service - The service for the related object, e.g. '/messages'.
 * @param {string|Array.<string>} options.field - The field containing the key(s)
 *    for the item(s) in options.service.
 * @returns {Function} hook function(hook):Promise resolving to the hook.
 *
 * 'options.field' is the foreign key for one related item in options.service,
 *    i.e. item[options.field] === foreignItem[idField].
 * 'target' is set to this related item once it is read successfully.
 *
 * If 'options.field' is not present in the hook result item, the hook is ignored.
 *
 * So if the hook result has the message item
 *    { _id: '1...1', senderId: 'a...a', text: 'Jane, are you there?' }
 * and the /users service has the item
 *    { _id: 'a...a', name: 'John Doe'}
 * and then the hook is run
 *    hooks.populate('sender', { field: 'userId', service: '/users' })
 * the hook result will contain
 *    { _id: '1...1', senderId : 'a...a', text: 'Jane, are you there?',
 *      sender: { _id: 'a...a', name: 'John Doe'} }
 *
 * If 'senderId' is an array of keys, then 'sender' will be an array of populated items.
 */
export function populate(target, opts) {
  opts = Object.assign({}, defaultOptions, opts);

  if (!opts.service && !opts.serviceBy && !opts.serviceId) {
    throw new Error('You need to provide a service');
  }

  return function(hook) {
    let options = Object.assign({}, opts);  // clone for change

    // If it was an internal call then do not recursive populate
    options.recursive = !!hook.params.provider;

    function populate(item, target, options) {
      let field = options.field || target;

      // Find by the field value by default or a custom query
      let entry = null;
      if (isArray(item)) {
        entry = compact(flattenDeep(map(item, i => getField(i, field))));
      } else {
        entry = getField(item, field);
      }

      // invalid entry id
      if (!entry || entry.length === 0) {
        return Promise.resolve(item);
      }

      //debug('==> %s populate %s/%s, \n\tid: %j', options.service, target, field, entry);
      //debug(' \n\twith: %j', item);

      if (!options.serviceBy && isPopulated(entry)) {
        debug('==> %s already populate %s/%s, \n\tid: %j', options, target, field, entry);
        return Promise.resolve(item);
      }

      // If it's a mongoose model then
      if (typeof item.toObject === 'function') {
        item = item.toObject(options);
      }
      // If it's a Sequelize model
      else if (typeof item.toJSON === 'function') {
        item = item.toJSON(options);
      }
      // Remove any query from params as it's not related
      let params = {}; // Object.assign({}, hook.params, { query: undefined });
      //console.log('populate:', field, entry, params);

      params.populate = options.recursive; // recursive populate
      params.softDelete = options.softDelete || false; // enforce destroyedAt

      // If the relationship is an array of ids, fetch and resolve an object for each,
      // otherwise just fetch the object.
      let promise = null;

      if (isArray(entry)) {
        let service = options.service;
        let ids = entry;
        if (options.serviceId) {
          service = plural(entry[0].split(':')[0]);
        } else if (options.serviceBy) {
          if (entry[0][options.serviceBy]) {
            service = plural(entry[0][options.serviceBy]);
          } else {
            service = options.serviceBy;
          }
          ids = entry.map(it => it[options.idField]);
          debug('populate service', service, ids);
        }
        params.query = { _id: { $in: ids } };
        params.paginate = false; // disable paginate
        promise = hook.app.service(service).find(params);
      } else {
        let service = options.service;
        let id = entry;
        if (options.serviceId) {
          service = plural(entry.split(':')[0]);
        } else if (options.serviceBy) {
          if (entry[options.serviceBy]) {
            service = plural(entry[options.serviceBy]);
          } else {
            service = options.serviceBy;
          }
          id = entry[options.idField];
          debug('populate service', service, id);
        }
        promise = hook.app.service(service).get(id, params);
      }
      return promise.then(result => {
        let data = result.data || result;
        // debug('setField %j \n ==> %s \n ==> %j', entry, field, data);
        if (isArray(item)) {
          item.forEach(it => setField(it, target, data, field, options));
        } else {
          setField(item, target, data, field, options);
        }

        // try nested populate(s)
        if (options.populate) {
          let pPopulates = isArray(options.populate)? options.populate : [options.populate];
          let pPromises = reduce(pPopulates, (promises, pOptions) => {
            let pItem = flatten(getField(item, field));
            let pTarget = pOptions.target || pOptions.field;
            //debug('>>> nested populate %s/%s(options=%s) with %s',
            //  pTarget, pOptions.field, util.inspect(pOptions), util.inspect(pItem));
            promises.push(populate(pItem, pTarget, pOptions));
            return promises;
          }, []);
          return Promise.all(pPromises).then(() => item);
        } else {
          return item;
        }
      }).catch(function(err) {
        console.error(" ERROR: populate %s error %s", options.service, util.inspect(err));
        setField(item, target, {}, field, options);
        return item;
      });
    }

    if (hook.type !== 'after') {
      throw new errors.GeneralError('Can not populate on before hook. (populate)');
    }

    if (hook.params.populate === false) return hook;

    let isPaginated = hook.method === 'find' && hook.result.data;
    let data = isPaginated ? hook.result.data : hook.result;

    if (isArray(data) && data.length === 0) return hook;

    return populate(data, target, options).then(result => {
      //debug('> populate result', util.inspect(result));
      if (isPaginated) {
        hook.result.data = result;
      } else {
        hook.result = result;
      }
      return hook;
    });
  };
}

export function depopulate(target, opts = { idField: 'id' }) {

  return function(hook) {
    let options = Object.assign({}, opts);

    function getDepopulated(item, target) {
      let field = get(item, target);
      if (field === undefined) return undefined;
      if (isArray(field)) {
        field = map(field, it => it[options.idField] || it);
      } else if (field) {
        field = field[options.idField] || field;
      }
      return field? field : null;
    }

    function setTarget(data, target, value) {
      if (value !== undefined) {
        set(data, target, value);
      }
    }

    if (hook.type === 'before') {
      setTarget(hook.data, target, getDepopulated(hook.data, target));
    } else {
      if (hook.result) {
        if (hook.result.data) {
          setTarget(hook.result.data, target, getDepopulated(hook.result.data, target));
        } else {
          setTarget(hook.result, target, getDepopulated(hook.result, target));
        }
      }
    }
    if (target === 'parent') {
      debug('depopulate parent', hook.data.parent, typeof hook.data.parent, hook.data);
    }
    return hook;
  };
}<commit_msg>ramda populate<commit_after>import assert from 'assert';
import makeDebug from 'debug';
import errors from 'feathers-errors';
import { plural } from 'pluralize';
import fp from 'ramda';
import validator from 'validator';
import util from 'util';
import { getField, setField, setFieldByKey } from '../helpers';

const debug = makeDebug('mostly:feathers-mongoose:hooks:populate');

const defaultOptions = {
  idField: 'id'
};

function isPopulated(obj) {
  if (Array.isArray(obj)) {
    return fp.reduce((result, val) => {
      return result && val && !validator.isMongoId(val.toString());
    }, true, obj);
  } else {
    if (obj) {
      return obj && !validator.isMongoId(obj.toString());
    } else {
      return false;
    }
  }
}

function populateField(hook, item, target, options) {
  let field = options.field || target;

  // Find by the field value by default or a custom query
  let entry = null;
  if (Array.isArray(item)) {
    entry = fp.compose(
      fp.filter(fp.isNil),
      fp.flatten,
      fp.map(it => getField(it, field))
    )(item);
  } else {
    entry = getField(item, field);
  }

  // invalid entry id
  if (!entry || entry.length === 0) {
    return Promise.resolve(item);
  }

  //debug('==> %s populate %s/%s, \n\tid: %j', options.service, target, field, entry);
  //debug(' \n\twith: %j', item);

  if (!options.serviceBy && isPopulated(entry)) {
    debug('==> %s already populate %s/%s, \n\tid: %j', options, target, field, entry);
    return Promise.resolve(item);
  }

  // If it's a mongoose model then
  if (typeof item.toObject === 'function') {
    item = item.toObject(options);
  }
  // If it's a Sequelize model
  else if (typeof item.toJSON === 'function') {
    item = item.toJSON(options);
  }
  // Remove any query from params as it's not related
  let params = {}; // Object.assign({}, hook.params, { query: undefined });
  //console.log('populate:', field, entry, params);

  params.populate = options.recursive; // recursive populate
  params.softDelete = options.softDelete || false; // enforce destroyedAt

  // If the relationship is an array of ids, fetch and resolve an object for each,
  // otherwise just fetch the object.
  let promise = null;

  if (Array.isArray(entry)) {
    let service = options.service;
    let ids = entry;
    if (options.serviceBy) {
      if (entry[0][options.serviceBy]) {
        service = plural(entry[0][options.serviceBy]);
      } else {
        service = options.serviceBy;
      }
      ids = fp.map(fp.prop(options.idField), entry);
      debug('populate service', service, ids);
    }
    params.query = { _id: { $in: ids } };
    params.paginate = false; // disable paginate
    promise = hook.app.service(service).find(params);
  } else {
    let service = options.service;
    let id = entry;
    if (options.serviceBy) {
      if (entry[options.serviceBy]) {
        service = plural(entry[options.serviceBy]);
      } else {
        service = options.serviceBy;
      }
      id = entry[options.idField];
      debug('populate service', service, id);
    }
    promise = hook.app.service(service).get(id, params);
  }
  return promise.then(result => {
    let data = result.data || result;
    // debug('setField %j \n ==> %s \n ==> %j', entry, field, data);
    if (Array.isArray(item)) {
      item.forEach(it => setField(it, target, data, field, options));
    } else {
      setField(item, target, data, field, options);
    }

    // try nested populate(s)
    if (options.populate) {
      let pPopulates = Array.isArray(options.populate)? options.populate : [options.populate];
      let pPromises = fp.reduce((promises, pOptions) => {
        let pItem = fp.flatten(getField(item, field));
        let pTarget = pOptions.target || pOptions.field;
        //debug('>>> nested populate %s/%s(options=%s) with %s',
        //  pTarget, pOptions.field, util.inspect(pOptions), util.inspect(pItem));
        promises.push(populateField(pItem, pTarget, pOptions));
        return promises;
      }, [], pPopulates);
      return Promise.all(pPromises).then(() => item);
    } else {
      return item;
    }
  }).catch(function(err) {
    console.error(" ERROR: populate %s error %s", options.service, util.inspect(err));
    setField(item, target, {}, field, options);
    return item;
  });
}

/**
 * The populate hook uses a property from the result (or every item if it is a list)
 * to retrieve a single related object from a service and add it to the original object.
 * It is meant to be used as an after hook on any service method.
 *
 * @param {string} target - The prop name to contain the populated item or array of populated items.
 *    This is also the default for options.field if that is not specified.
 * @param {Object} options - options
 *    For a mongoose model, these are the options for item.toObject().
 *    For a Sequelize model, these are the options for item.toJSON().
 * @param {string} options.service - The service for the related object, e.g. '/messages'.
 * @param {string|Array.<string>} options.field - The field containing the key(s)
 *    for the item(s) in options.service.
 * @returns {Function} hook function(hook):Promise resolving to the hook.
 *
 * 'options.field' is the foreign key for one related item in options.service,
 *    i.e. item[options.field] === foreignItem[idField].
 * 'target' is set to this related item once it is read successfully.
 *
 * If 'options.field' is not present in the hook result item, the hook is ignored.
 *
 * So if the hook result has the message item
 *    { _id: '1...1', senderId: 'a...a', text: 'Jane, are you there?' }
 * and the /users service has the item
 *    { _id: 'a...a', name: 'John Doe'}
 * and then the hook is run
 *    hooks.populate('sender', { field: 'userId', service: '/users' })
 * the hook result will contain
 *    { _id: '1...1', senderId : 'a...a', text: 'Jane, are you there?',
 *      sender: { _id: 'a...a', name: 'John Doe'} }
 *
 * If 'senderId' is an array of keys, then 'sender' will be an array of populated items.
 */
export function populate(target, opts) {
  opts = Object.assign({}, defaultOptions, opts);

  if (!opts.service && !opts.serviceBy) {
    throw new Error('You need to provide a service');
  }

  return function(hook) {
    let options = Object.assign({}, opts);  // clone for change

    // If it was an internal call then do not recursive populate
    options.recursive = !!hook.params.provider;

    if (hook.type !== 'after') {
      throw new errors.GeneralError('Can not populate on before hook. (populate)');
    }

    if (hook.params.populate === false) return hook;

    let isPaginated = hook.method === 'find' && hook.result.data;
    let data = isPaginated ? hook.result.data : hook.result;

    if (Array.isArray(data) && data.length === 0) return hook;

    return populateField(hook, data, target, options).then(result => {
      //debug('> populate result', util.inspect(result));
      if (isPaginated) {
        hook.result.data = result;
      } else {
        hook.result = result;
      }
      return hook;
    });
  };
}

export function depopulate(target, opts = { idField: 'id' }) {

  return function(hook) {
    let options = Object.assign({}, opts);

    function getDepopulated(item, target) {
      let field = fp.prop(target, item);
      if (field === undefined) return undefined;
      if (Array.isArray(field)) {
        field = fp.map((it) => it[options.idField] || it, field);
      } else if (field) {
        field = field[options.idField] || field;
      }
      return field? field : null;
    }

    function setTarget(data, target, value) {
      if (value !== undefined) {
        return fp.assocPath(target.split('.'), value, data);
      }
    }

    if (hook.type === 'before') {
      hook.data = setTarget(hook.data, target, getDepopulated(hook.data, target));
    } else {
      if (hook.result) {
        if (hook.result.data) {
          hook.result.data = setTarget(hook.result.data, target, getDepopulated(hook.result.data, target));
        } else {
          hook.result = setTarget(hook.result, target, getDepopulated(hook.result, target));
        }
      }
    }
    if (target === 'parent') {
      debug('depopulate parent', hook.data.parent, typeof hook.data.parent, hook.data);
    }
    return hook;
  };
}<|endoftext|>


<commit_before>const JOIN_URL = 'http://join.gov.tw';
const JOIN_COMPLETED_URL = JOIN_URL + '/idea/index/search/COMPLETED?page=1&size=99999';
const JOIN_DETAIL_URL = JOIN_URL + '/idea/detail/';
const JOIN_SUGGESTION_URL = JOIN_URL + '/idea/export/endorse/suggestion/';
const ARCHIVE_FOLDER = 'archive/';
const ENDORSES_JSON = 'endorses.json';
const SUGGESTIONS_CSV = 'suggestions.csv';

var system = require('system');
var page = require('webpage').create();
var fs = require('fs');
var process = require("child_process");

var ENDORSES = [];

page.viewportSize = { width: 1024, height: 768 };

console.log("Crawling Satrt ...")

fs.makeDirectory(ARCHIVE_FOLDER);

execute();

function execute() {
    page.open(JOIN_COMPLETED_URL, function (status) {
        if (status !== "success") {
            console.log("Unable to access network");
        }
        else {

            ENDORSES = page.evaluate(function () { return searchResult.result });
            ENDORSES = ENDORSES.map(function (endorse) { return cleanObject(endorse) });
            fs.write(ARCHIVE_FOLDER + ENDORSES_JSON, JSON.stringify(ENDORSES, null, 2), 'w');
            console.log('Get ' + ENDORSES.length + ' endorses...');

            if (ENDORSES.length > 0) {
                getProjectContent(0);
            } else {
                console.log('Something wrong, no project on list');
                finish();
            }

        }
    });
}

function getProjectContent(current) {
    console.log(JOIN_DETAIL_URL + ENDORSES[current].id);
    var link = JOIN_DETAIL_URL + ENDORSES[current].id;
    page.open(link, function (status) {
        if (status !== "success") {
            console.log("Unable to access network");
        } else {
            waitFor(
                function () {
                    endorse = page.evaluate(function () { return idea });
                    fs.makeDirectory(ARCHIVE_FOLDER + ENDORSES[current].id);
                    fs.write(ARCHIVE_FOLDER + ENDORSES[current].id + '/endorse.json', JSON.stringify(endorse, null, 2), 'w');
                    downloadCSV(ENDORSES[current].id);
                },
                function () {
                    console.log("get " + ENDORSES[current].title + " content success.");
                    if (current < ENDORSES.length - 1) {
                        getProjectContent(current + 1);
                    } else {
                        console.log("Get " + ENDORSES.length + " endorses content. Job done.");
                        finish();
                    }
                });
        }
    });
}

function downloadCSV(id) {
    var execFile = process.execFile;
    execFile("curl", ["-XGET", JOIN_SUGGESTION_URL + id], null, function (err, stdout, stderr) {
    fs.write(ARCHIVE_FOLDER + id + '/' + SUGGESTIONS_CSV, stdout, 'w');
    console.log("execFileSTDERR:", stderr);
    })
}

function cleanObject(object) {
    for (key in object) {
        if (object[key] === null || object[key].length === 0)
            delete object[key];
    }
    return object;
}

function waitFor(testFx, onReady, timeOutMillis) {
    var maxtimeOutMillis = timeOutMillis ? timeOutMillis : 5000,
        start = new Date().getTime(),
        condition = -1,
        interval = setInterval(function () {
            if ((new Date().getTime() - start < maxtimeOutMillis) && condition < 0) {
                // If not time-out yet and condition not yet fulfilled
                condition = (typeof (testFx) === "string" ? eval(testFx) : testFx()); //< defensive code
                if (condition === true) condition = 0;
                if (condition === false) condition = -1;
            } else {
                if (condition < 0) {
                    // If condition still not fulfilled (timeout but condition is 'false')
                    console.log("'waitFor()' timeout");
                    console.log(onReady);
                    page.render('waitfor.png');
                    phantom.exit(1);
                } else {
                    // Condition fulfilled (timeout and/or condition is 'true')
                    //console.log("'waitFor()' finished in " + (new Date().getTime() - start) + "ms.");
                    var func;
                    if (onReady instanceof Array) {
                        func = onReady[condition];
                    } else {
                        func = onReady;
                    }
                    typeof (func) === "string" ?
                        eval(func) : func(); //< Do what it's supposed to do once the condition is fulfilled
                    clearInterval(interval); //< Stop this interval
                }
            }
        }, 250); //< repeat waitForCheckbox every 250ms
};

function finish() {
    phantom.exit();
}<commit_msg>更改儲存方式<commit_after>const JOIN_URL = 'http://join.gov.tw';
const JOIN_COMPLETED_URL = JOIN_URL + '/idea/index/search/COMPLETED?page=1&size=99999';
const JOIN_DETAIL_URL = JOIN_URL + '/idea/detail/';
const JOIN_SUGGESTION_URL = JOIN_URL + '/idea/export/endorse/suggestion/';
const ARCHIVE_FOLDER = 'archive/';
const ENDORSES_JSON = 'endorses.json';

var system = require('system');
var page = require('webpage').create();
var fs = require('fs');
var process = require("child_process");

var ENDORSES = [];

page.viewportSize = { width: 1024, height: 768 };

console.log("Crawling Satrt ...")

fs.makeDirectory(ARCHIVE_FOLDER);

execute();

function execute() {
    page.open(JOIN_COMPLETED_URL, function (status) {
        if (status !== "success") {
            console.log("Unable to access network");
        }
        else {

            ENDORSES = page.evaluate(function () { return searchResult.result });
            ENDORSES = ENDORSES.map(function (endorse) { return cleanObject(endorse) });
            // fs.write(ARCHIVE_FOLDER + ENDORSES_JSON, JSON.stringify(ENDORSES, null, 2), 'w');
            console.log('Get ' + ENDORSES.length + ' endorses...');

            if (ENDORSES.length > 0) {
                getProjectContent(0);
            } else {
                console.log('Something wrong, no project on list');
                finish();
            }

        }
    });
}

function getProjectContent(current) {
    console.log(JOIN_DETAIL_URL + ENDORSES[current].id);
    var link = JOIN_DETAIL_URL + ENDORSES[current].id;
    page.open(link, function (status) {
        if (status !== "success") {
            console.log("Unable to access network");
        } else {
            waitFor(
                function () {
                    endorse = page.evaluate(function () { return idea });
                    // fs.makeDirectory(ARCHIVE_FOLDER + ENDORSES[current].id);
                    // fs.write(ARCHIVE_FOLDER + ENDORSES[current].id + '/endorse.json', JSON.stringify(endorse, null, 2), 'w');
                    ENDORSES[current] = endorse;
                    downloadCSV(ENDORSES[current].id);
                },
                function () {
                    console.log("get " + ENDORSES[current].title + " content success.");
                    if (current < ENDORSES.length - 1) {
                        getProjectContent(current + 1);
                    } else {
                        console.log("Get " + ENDORSES.length + " endorses content. Job done.");
                        fs.write(ARCHIVE_FOLDER + '/' + ENDORSES_JSON, JSON.stringify(ENDORSES, null, 2), 'w');
                        finish();
                    }
                });
        }
    });
}

function downloadCSV(id) {
    var execFile = process.execFile;
    execFile("curl", ["-XGET", JOIN_SUGGESTION_URL + id], null, function (err, stdout, stderr) {
    fs.write(ARCHIVE_FOLDER + '/' + id + '.csv' , stdout, 'w');
    console.log("execFileSTDERR:", stderr);
    })
}

function cleanObject(object) {
    for (key in object) {
        if (object[key] === null || object[key].length === 0)
            delete object[key];
    }
    return object;
}

function waitFor(testFx, onReady, timeOutMillis) {
    var maxtimeOutMillis = timeOutMillis ? timeOutMillis : 5000,
        start = new Date().getTime(),
        condition = -1,
        interval = setInterval(function () {
            if ((new Date().getTime() - start < maxtimeOutMillis) && condition < 0) {
                // If not time-out yet and condition not yet fulfilled
                condition = (typeof (testFx) === "string" ? eval(testFx) : testFx()); //< defensive code
                if (condition === true) condition = 0;
                if (condition === false) condition = -1;
            } else {
                if (condition < 0) {
                    // If condition still not fulfilled (timeout but condition is 'false')
                    console.log("'waitFor()' timeout");
                    console.log(onReady);
                    page.render('waitfor.png');
                    phantom.exit(1);
                } else {
                    // Condition fulfilled (timeout and/or condition is 'true')
                    //console.log("'waitFor()' finished in " + (new Date().getTime() - start) + "ms.");
                    var func;
                    if (onReady instanceof Array) {
                        func = onReady[condition];
                    } else {
                        func = onReady;
                    }
                    typeof (func) === "string" ?
                        eval(func) : func(); //< Do what it's supposed to do once the condition is fulfilled
                    clearInterval(interval); //< Stop this interval
                }
            }
        }, 250); //< repeat waitForCheckbox every 250ms
};

function finish() {
    phantom.exit();
}<|endoftext|>