<commit_before>// @flow
import React from 'react'
import {
  Alert,
  Keyboard,
  LayoutAnimation,
  NativeModules,
  StyleSheet,
  Text,
  TextInput,
  TouchableOpacity,
  ScrollView,
  View
} from 'react-native'
import SegmentedControlTab from 'react-native-segmented-control-tab'
import expect from 'expect'
import moment from 'moment'
import PropTypes from 'prop-types'
import KeyboardAwareScrollView from './components/keyboard-aware-scrollview/KeyboardAwareScrollView'

import { capitalizeFirstLetter } from './utilities/String+Capitalize'
import { SRDarkColor, SRYellowColor, SRBrightColor, SRRedColor } from './utilities/SRColors'

const studyTaskString = 'Study Task'
const notesString = 'Notes'
const dateString = 'Starting Date'

const SRPlaceholderTextColor = 'rgba(251, 251, 253, 0.5)'

const inactiveColor = SRBrightColor
const buttonHeight = 46
const buttonCornerRadius = buttonHeight/2
const cancelButtonTint = SRDarkColor
const tintColor = SRRedColor

const dateSegmentedControlCornerRadius = 4

// Required to use LayoutAnimation on Android
const { UIManager } = NativeModules;
UIManager.setLayoutAnimationEnabledExperimental &&
  UIManager.setLayoutAnimationEnabledExperimental(true)

const springDamping = 0.6

const animateEditing = {
  duration: 400,
  create: {
    type: LayoutAnimation.Types.spring,
    property: LayoutAnimation.Properties.opacity,
    springDamping: springDamping,
  },
  update: {
    type: LayoutAnimation.Types.spring,
    property: LayoutAnimation.Properties.scaleXY,
    springDamping: springDamping,
  },
  delete: {
    type: LayoutAnimation.Types.spring,
    property: LayoutAnimation.Properties.opacity,
    springDamping: springDamping,
  },
}

export default class SRStudyTaskEditor extends React.Component {

  static navigationOptions = (props) => {
    const { readonly } = props

    return {
      headerTintColor: SRDarkColor,
      headerStyle: {
        backgroundColor: SRBrightColor
      },
      title: readonly ? 'Details' : 'Add a task',
    }
  }

  state: {
    editMode: bool,
    hasChanges: bool,

    taskName: string,
    notes: string,
    date: string,

    studyTaskLabelString: string,
    notesLabelString: string,
    pickedDate: string,
    selectedDateIndex: number,
    selectedIntensityIndex: number,
  }

  _initialState = (props: Object) => {
    const { item } = props

    return {
      editMode: false,
      hasChanges: false,

      taskName: item != null && item.taskName != null ? item.taskName : '',
      notes: item != null && item.notes != null ? item.notes : '',
      date: new Date().toString(),

      studyTaskLabelString: item != null && item.taskName != null ? studyTaskString.toUpperCase() : ' ',
      notesLabelString: item != null && item.notes != null ? notesString.toUpperCase() : ' ',
      pickedDate: 'Other', // TODO: implement
      selectedDateIndex: 0,
      selectedIntensityIndex: 0,
    }
  }

  constructor(props: Object) {
    super(props)
    this.state = this._initialState(props)
  }

  componentWillMount() {
    this.updateUIStates(this.props)
  }

  componentWillReceiveProps(newProps: Object) {
    this.updateUIStates(newProps)
  }

  render() {
    const { readonly } = this.props
    const {
      editMode,
      taskName,
      notes,
      studyTaskLabelString
    } = this.state

    const actionButtonTitle = (readonly && editMode == false) ? 'Edit' : 'Save'
    const destructiveButtonTitle = (readonly && editMode == false) ? 'Delete' : 'Cancel'

    const disableActionButton = taskName == null || taskName == ''
    const editingOrAddingNewTask = editMode || !readonly
    const viewingOnly = readonly && !editMode
    const notesExistOrEditingOrAddingTask = !readonly || editMode || (notes != null && notes != '')
    const NOT_IMPLEMENTED = false

    return (
      <KeyboardAwareScrollView
        style={styles.scrollView}
        keyboardDismissMode={'on-drag'}
        keyboardShouldPersistTaps={'handled'}
        getTextInputRefs={() => { return [this._taskInputRef, this._noteInputRef]}}
        >
        <View style={styles.edgePadding}>

          <View style={styles.sections}>
            <Text style={styles.sectionLabel}>{studyTaskLabelString}</Text>
            <TextInput
              style={[styles.dataInputItemPadding, styles.textInput]}
              placeholder={studyTaskString}
              placeholderTextColor={SRPlaceholderTextColor}
              onSubmitEditing={(event) => {
                this._noteInputRef.focus()
              }}
              onChangeText={(taskName) => this.studyTextFieldOnChangeText(taskName)}
              value={taskName}
              editable={editingOrAddingNewTask}
              multiline={true}
              autoFocus={!readonly}
              returnKeyType={'next'}
              blurOnSubmit={true}
              ref={(r) => { this._taskInputRef = r }}
            />
            {this._renderSeparator(editingOrAddingNewTask)}
          </View>

          {this._renderNotes(notesExistOrEditingOrAddingTask)}
          {this._renderDateSelection(!readonly)}
          {this._renderRatingHistory(NOT_IMPLEMENTED)}

          <View name='dataSeparator' style={[styles.sectionSeparator, styles.lastSectionSeparator]}/>

          <View style={[styles.sections, styles.bottomButtonsView]}>
            <TouchableOpacity
              style={this.disabledButtonStyle(disableActionButton)}
              disabled={disableActionButton}
              onPress={this.actionButtonAction}>
              <Text style={[styles.bottomButtonsText, styles.actionButtonText]}>{actionButtonTitle}</Text>
            </TouchableOpacity>

            <TouchableOpacity
              style={[styles.dataInputItemPadding, styles.bottomButtons, styles.cancelButton]}
              onPress={this.destructiveButtonAction}>
              <Text style={[styles.bottomButtonsText, styles.destructiveButtonText]}>{destructiveButtonTitle}</Text>
            </TouchableOpacity>
          </View>

        </View>
      </KeyboardAwareScrollView>
    )
  }

  // Data input

  studyTextFieldOnChangeText = (taskName: string) => {
    const taskNameExists = taskName == null || taskName == ''
    this.hideStudyTaskLabel(taskNameExists)
    this.setState({
      taskName,
      hasChanges: true
    })
  }

  notesTextFieldOnChangeText = (notes: string) => {
    const notesExist = notes == null || notes == ''
    this.hideNotesLabel(notesExist)
    this.setState({
      notes,
      hasChanges: true
    })
  }

  handleDateSelection = (index: number) => {
    var selectedDate = new Date()
    switch(index) {
      case 0:
        selectedDate = new Date()
        break
      case 1:
        selectedDate = new Date(moment().subtract(1, 'days'))
        break
      case 2:
        this.openDatePicker()
        break
      default:
      throw 'handleDateSelection()'
    }

    this.setState({
      date: selectedDate.toString(),
      selectedDateIndex: index,
      hasChanges: true
    })
  }

  handleIntensitySelection = (index: number) => {
    this.setState({
      selectedIntensityIndex: index,
      hasChanges: true
    })
  }

  // Actions

  actionButtonAction = () => {
    const { readonly, item } = this.props
    const { editMode } = this.state
    const wantsEdit = readonly && !editMode

    if(wantsEdit) {
      LayoutAnimation.configureNext(animateEditing)
      this.setState({editMode: true})
    } else { // Save data input
      const { readonly, saveAction } = this.props
      const { taskName, notes, date } = this.state

      expect(taskName).toExist('No taskName')
      expect(taskName).toNotEqual('','taskName is empty string')

      const newItemChanges = {
        taskName: taskName,
        notes: notes,
        date: date,
      }

      saveAction(newItemChanges, item)
      if(readonly) {
        LayoutAnimation.configureNext(animateEditing)
        this.setState({editMode: false})
      }
    }

  }

  destructiveButtonAction = () => {
    const { readonly, deleteAction, cancelAction, item } = this.props
    const { editMode, hasChanges } = this.state
    const deleteWhenReadonly = readonly && !editMode
    const cancelEditing = readonly && editMode
    const dismissAddingNewTask = !readonly

    if (deleteWhenReadonly) {
      deleteAction(item)
    } else if(cancelEditing) {
      if (hasChanges) {
        Alert.alert(
          'Discard Changes',
          'Are you sure you want to discard your changes?',
          [
            {text: 'Keep Editing', onPress: () => {}, style: 'cancel'},
            {text: 'Discard Changes', onPress: () => {
              this.resetFields()
              LayoutAnimation.configureNext(animateEditing)
              this.setState({editMode: false})
            }},
          ],
          { cancelable: true }
        )
      } else {
        this.resetFields()
        LayoutAnimation.configureNext(animateEditing)
        this.setState({editMode: false})
      }
    } else if (dismissAddingNewTask) {
      cancelAction()
    } else {
      expect(0).toBe(1, 'destructiveButtonAction() else')
    }
  }

  resetFields = () => {
    this.setState({
      ...this.state,
      ...this._initialState(this.props),
    })
  }

  // UI changes

  updateUIStates = (props: Object) => {
    const { item } = props
    if(item != null) {
      const { notes, taskName } = item
      const taskNameTextFieldIsEmpty = taskName == null || taskName == ''
      this.hideStudyTaskLabel(taskNameTextFieldIsEmpty)

      const notesTextFieldIsEmpty = notes == null || notes == ''
      this.hideNotesLabel(notesTextFieldIsEmpty)
    }
  }

  hideStudyTaskLabel = (flag: bool = true) => {
    this.setState({
      studyTaskLabelString: flag ? ' ' : studyTaskString.toUpperCase()
    })
  }

  hideNotesLabel = (flag: bool = true) => {
    this.setState({
      notesLabelString: flag ? ' ' : notesString.toUpperCase()
    })
  }

  openDatePicker = () => {
    // TODO: missing implementation
    // pickedDate = '' // set this to a nicely formatted date
  }

  _renderSeparator = (flag) => {
    if(flag) {
      return <View name='separator' style={styles.sectionSeparator}/>
    } else {
      return <View name='separator' style={{height: 1}}/>
    }
  }

  _renderNotes = (flag) => {
      if(flag) {
        const { readonly } = this.props
        const { notes, notesLabelString, editMode } = this.state
        const editingOrAddingNewTask = editMode || !readonly
        const viewingOnly = readonly && !editMode
        return(
          <View style={styles.sections}>
            <Text style={styles.sectionLabel}>{notesLabelString}</Text>
            <TextInput
              style={[styles.dataInputItemPadding, styles.textInput]}
              placeholder={notesString}
              placeholderTextColor={SRPlaceholderTextColor}
              onSubmitEditing={Keyboard.dismiss}
              onChangeText={(notes) => this.notesTextFieldOnChangeText(notes)}
              value={notes}
              editable={editingOrAddingNewTask}
              multiline={true}
              returnKeyType={'done'}
              blurOnSubmit={true}
              ref={(r) => { this._noteInputRef = r }}
            />
            {this._renderSeparator(!readonly)}
          </View>
        )
      } else {
        return null
      }
  }

  _renderDateSelection = (flag) => {
    if(flag) {
      const { pickedDate } = this.state
      const formattedPickedDate = pickedDate // TODO:
      return (
        <View style={styles.sections}>
          <Text style={styles.sectionLabel}>{dateString.toUpperCase()}</Text>
          <SegmentedControlTab
            borderRadius={dateSegmentedControlCornerRadius}
            tabsContainerStyle={styles.tabsContainerStyle}
            tabStyle={styles.tabStyle}
            tabTextStyle={styles.tabTextStyle}
            activeTabStyle={styles.activeTabStyle}
            activeTabTextStyle={styles.activeTabTextStyle}
            values={['Today', 'Yesterday', formattedPickedDate]}
            selectedIndex={this.state.selectedDateIndex}
            onTabPress={this.handleDateSelection}
          />
        </View>
      )
    } else {
      return null
    }
  }

  _renderRatingHistory = (flag) => {
    if(flag) {
      // TODO: missing implementation
      // if made changes then set hasChanges: true
      return (
        <View style={styles.sections}>
          <Text style={styles.sectionLabel}>Rating History</Text>

        </View>
      )
    } else {
      return null
    }
  }

  disabledButtonStyle = (flag: bool) => {
    const defaultStyle = [styles.dataInputItemPadding, styles.bottomButtons, styles.actionButton]
    if(flag) {
      return [...defaultStyle, styles.actionButtonDisabled]
    } else {
      return defaultStyle
    }
  }

}

SRStudyTaskEditor.propTypes = {
  item: PropTypes.shape({
    title: PropTypes.string,
    notes: PropTypes.string,
  }),

  saveAction: PropTypes.func.isRequired,
  deleteAction: PropTypes.func,
  cancelAction: PropTypes.func,

  readonly: PropTypes.bool.isRequired,
}

const styles = StyleSheet.create({
  scrollView: {
    flex: 1,
    backgroundColor: SRDarkColor,
  },
  edgePadding: {
    padding: 15,
    flex:1,
  },
  dataInputItemPadding: {
    padding: 4,
  },
  textInput: {
    fontSize: 48,
    color: SRBrightColor,
  },
  sectionLabel: {
    fontSize: 20,
    color: SRRedColor,
    fontWeight: 'bold'
  },
  sectionSeparator: {
    backgroundColor: SRBrightColor,
    height: 1,
  },
  lastSectionSeparator: {
    marginTop: 8,
  },
  sections: {
    padding: 8,
  },
  bottomButtonsView: {
    flexDirection: 'column',
    paddingTop: 20,
  },
  bottomButtons: {
    alignItems: 'center',
    height: buttonHeight,
    justifyContent: 'center',
  },
  actionButton: {
    backgroundColor: SRYellowColor,
    borderRadius: buttonCornerRadius,
    flex: 1,
  },
  actionButtonDisabled: {
    backgroundColor: 'rgba(255, 252, 49, 0.5)'
  },
  cancelButton: {
    borderColor: SRYellowColor,
    borderWidth: 1,
    borderRadius: buttonCornerRadius,
    flex: 1,
    marginTop: 10,
  },
  bottomButtonsText: {
    color: SRDarkColor,
    fontSize: 16,
    fontWeight: 'bold',
  },
  actionButtonText: {
    paddingTop: 1,
  },
  destructiveButtonText: {
    color: SRYellowColor,
  },
  tabsContainerStyle: {
    paddingTop: 15,
    paddingBottom: 5,
    height: buttonHeight+14,
  },
  tabStyle: {
    backgroundColor: SRBrightColor,
    borderColor: SRBrightColor,
    borderRadius: dateSegmentedControlCornerRadius,
    marginLeft: 2,
    marginRight: 2,
  },
  tabTextStyle: {
    fontWeight: 'bold',
    color: SRDarkColor,
  },
  activeTabStyle: {
    backgroundColor: SRYellowColor,
    borderColor: SRYellowColor,
  },
  activeTabTextStyle: {
    color: SRDarkColor
  },
})
<commit_msg>Removed not yet implemented 'Other' options from date picker.<commit_after>// @flow
import React from 'react'
import {
  Alert,
  Keyboard,
  LayoutAnimation,
  NativeModules,
  StyleSheet,
  Text,
  TextInput,
  TouchableOpacity,
  ScrollView,
  View
} from 'react-native'
import SegmentedControlTab from 'react-native-segmented-control-tab'
import expect from 'expect'
import moment from 'moment'
import PropTypes from 'prop-types'
import KeyboardAwareScrollView from './components/keyboard-aware-scrollview/KeyboardAwareScrollView'

import { capitalizeFirstLetter } from './utilities/String+Capitalize'
import { SRDarkColor, SRYellowColor, SRBrightColor, SRRedColor } from './utilities/SRColors'

const studyTaskString = 'Study Task'
const notesString = 'Notes'
const dateString = 'Starting Date'

const SRPlaceholderTextColor = 'rgba(251, 251, 253, 0.5)'

const inactiveColor = SRBrightColor
const buttonHeight = 46
const buttonCornerRadius = buttonHeight/2
const cancelButtonTint = SRDarkColor
const tintColor = SRRedColor

const dateSegmentedControlCornerRadius = 4

// Required to use LayoutAnimation on Android
const { UIManager } = NativeModules;
UIManager.setLayoutAnimationEnabledExperimental &&
  UIManager.setLayoutAnimationEnabledExperimental(true)

const springDamping = 0.6

const animateEditing = {
  duration: 400,
  create: {
    type: LayoutAnimation.Types.spring,
    property: LayoutAnimation.Properties.opacity,
    springDamping: springDamping,
  },
  update: {
    type: LayoutAnimation.Types.spring,
    property: LayoutAnimation.Properties.scaleXY,
    springDamping: springDamping,
  },
  delete: {
    type: LayoutAnimation.Types.spring,
    property: LayoutAnimation.Properties.opacity,
    springDamping: springDamping,
  },
}

export default class SRStudyTaskEditor extends React.Component {

  static navigationOptions = (props) => {
    const { readonly } = props

    return {
      headerTintColor: SRDarkColor,
      headerStyle: {
        backgroundColor: SRBrightColor
      },
      title: readonly ? 'Details' : 'Add a task',
    }
  }

  state: {
    editMode: bool,
    hasChanges: bool,

    taskName: string,
    notes: string,
    date: string,

    studyTaskLabelString: string,
    notesLabelString: string,
    pickedDate: string,
    selectedDateIndex: number,
    selectedIntensityIndex: number,
  }

  _initialState = (props: Object) => {
    const { item } = props

    return {
      editMode: false,
      hasChanges: false,

      taskName: item != null && item.taskName != null ? item.taskName : '',
      notes: item != null && item.notes != null ? item.notes : '',
      date: new Date().toString(),

      studyTaskLabelString: item != null && item.taskName != null ? studyTaskString.toUpperCase() : ' ',
      notesLabelString: item != null && item.notes != null ? notesString.toUpperCase() : ' ',
      pickedDate: 'Other', // TODO: implement
      selectedDateIndex: 0,
      selectedIntensityIndex: 0,
    }
  }

  constructor(props: Object) {
    super(props)
    this.state = this._initialState(props)
  }

  componentWillMount() {
    this.updateUIStates(this.props)
  }

  componentWillReceiveProps(newProps: Object) {
    this.updateUIStates(newProps)
  }

  render() {
    const { readonly } = this.props
    const {
      editMode,
      taskName,
      notes,
      studyTaskLabelString
    } = this.state

    const actionButtonTitle = (readonly && editMode == false) ? 'Edit' : 'Save'
    const destructiveButtonTitle = (readonly && editMode == false) ? 'Delete' : 'Cancel'

    const disableActionButton = taskName == null || taskName == ''
    const editingOrAddingNewTask = editMode || !readonly
    const viewingOnly = readonly && !editMode
    const notesExistOrEditingOrAddingTask = !readonly || editMode || (notes != null && notes != '')
    const NOT_IMPLEMENTED = false

    return (
      <KeyboardAwareScrollView
        style={styles.scrollView}
        keyboardDismissMode={'on-drag'}
        keyboardShouldPersistTaps={'handled'}
        getTextInputRefs={() => { return [this._taskInputRef, this._noteInputRef]}}
        >
        <View style={styles.edgePadding}>

          <View style={styles.sections}>
            <Text style={styles.sectionLabel}>{studyTaskLabelString}</Text>
            <TextInput
              style={[styles.dataInputItemPadding, styles.textInput]}
              placeholder={studyTaskString}
              placeholderTextColor={SRPlaceholderTextColor}
              onSubmitEditing={(event) => {
                this._noteInputRef.focus()
              }}
              onChangeText={(taskName) => this.studyTextFieldOnChangeText(taskName)}
              value={taskName}
              editable={editingOrAddingNewTask}
              multiline={true}
              autoFocus={!readonly}
              returnKeyType={'next'}
              blurOnSubmit={true}
              ref={(r) => { this._taskInputRef = r }}
            />
            {this._renderSeparator(editingOrAddingNewTask)}
          </View>

          {this._renderNotes(notesExistOrEditingOrAddingTask)}
          {this._renderDateSelection(!readonly)}
          {this._renderRatingHistory(NOT_IMPLEMENTED)}

          <View name='dataSeparator' style={[styles.sectionSeparator, styles.lastSectionSeparator]}/>

          <View style={[styles.sections, styles.bottomButtonsView]}>
            <TouchableOpacity
              style={this.disabledButtonStyle(disableActionButton)}
              disabled={disableActionButton}
              onPress={this.actionButtonAction}>
              <Text style={[styles.bottomButtonsText, styles.actionButtonText]}>{actionButtonTitle}</Text>
            </TouchableOpacity>

            <TouchableOpacity
              style={[styles.dataInputItemPadding, styles.bottomButtons, styles.cancelButton]}
              onPress={this.destructiveButtonAction}>
              <Text style={[styles.bottomButtonsText, styles.destructiveButtonText]}>{destructiveButtonTitle}</Text>
            </TouchableOpacity>
          </View>

        </View>
      </KeyboardAwareScrollView>
    )
  }

  // Data input

  studyTextFieldOnChangeText = (taskName: string) => {
    const taskNameExists = taskName == null || taskName == ''
    this.hideStudyTaskLabel(taskNameExists)
    this.setState({
      taskName,
      hasChanges: true
    })
  }

  notesTextFieldOnChangeText = (notes: string) => {
    const notesExist = notes == null || notes == ''
    this.hideNotesLabel(notesExist)
    this.setState({
      notes,
      hasChanges: true
    })
  }

  handleDateSelection = (index: number) => {
    var selectedDate = new Date()
    switch(index) {
      case 0:
        selectedDate = new Date()
        break
      case 1:
        selectedDate = new Date(moment().subtract(1, 'days'))
        break
      case 2:
        this.openDatePicker()
        break
      default:
      throw 'handleDateSelection()'
    }

    this.setState({
      date: selectedDate.toString(),
      selectedDateIndex: index,
      hasChanges: true
    })
  }

  handleIntensitySelection = (index: number) => {
    this.setState({
      selectedIntensityIndex: index,
      hasChanges: true
    })
  }

  // Actions

  actionButtonAction = () => {
    const { readonly, item } = this.props
    const { editMode } = this.state
    const wantsEdit = readonly && !editMode

    if(wantsEdit) {
      LayoutAnimation.configureNext(animateEditing)
      this.setState({editMode: true})
    } else { // Save data input
      const { readonly, saveAction } = this.props
      const { taskName, notes, date } = this.state

      expect(taskName).toExist('No taskName')
      expect(taskName).toNotEqual('','taskName is empty string')

      const newItemChanges = {
        taskName: taskName,
        notes: notes,
        date: date,
      }

      saveAction(newItemChanges, item)
      if(readonly) {
        LayoutAnimation.configureNext(animateEditing)
        this.setState({editMode: false})
      }
    }

  }

  destructiveButtonAction = () => {
    const { readonly, deleteAction, cancelAction, item } = this.props
    const { editMode, hasChanges } = this.state
    const deleteWhenReadonly = readonly && !editMode
    const cancelEditing = readonly && editMode
    const dismissAddingNewTask = !readonly

    if (deleteWhenReadonly) {
      deleteAction(item)
    } else if(cancelEditing) {
      if (hasChanges) {
        Alert.alert(
          'Discard Changes',
          'Are you sure you want to discard your changes?',
          [
            {text: 'Keep Editing', onPress: () => {}, style: 'cancel'},
            {text: 'Discard Changes', onPress: () => {
              this.resetFields()
              LayoutAnimation.configureNext(animateEditing)
              this.setState({editMode: false})
            }},
          ],
          { cancelable: true }
        )
      } else {
        this.resetFields()
        LayoutAnimation.configureNext(animateEditing)
        this.setState({editMode: false})
      }
    } else if (dismissAddingNewTask) {
      cancelAction()
    } else {
      expect(0).toBe(1, 'destructiveButtonAction() else')
    }
  }

  resetFields = () => {
    this.setState({
      ...this.state,
      ...this._initialState(this.props),
    })
  }

  // UI changes

  updateUIStates = (props: Object) => {
    const { item } = props
    if(item != null) {
      const { notes, taskName } = item
      const taskNameTextFieldIsEmpty = taskName == null || taskName == ''
      this.hideStudyTaskLabel(taskNameTextFieldIsEmpty)

      const notesTextFieldIsEmpty = notes == null || notes == ''
      this.hideNotesLabel(notesTextFieldIsEmpty)
    }
  }

  hideStudyTaskLabel = (flag: bool = true) => {
    this.setState({
      studyTaskLabelString: flag ? ' ' : studyTaskString.toUpperCase()
    })
  }

  hideNotesLabel = (flag: bool = true) => {
    this.setState({
      notesLabelString: flag ? ' ' : notesString.toUpperCase()
    })
  }

  openDatePicker = () => {
    // TODO: missing implementation
    // pickedDate = '' // set this to a nicely formatted date
  }

  _renderSeparator = (flag) => {
    if(flag) {
      return <View name='separator' style={styles.sectionSeparator}/>
    } else {
      return <View name='separator' style={{height: 1}}/>
    }
  }

  _renderNotes = (flag) => {
      if(flag) {
        const { readonly } = this.props
        const { notes, notesLabelString, editMode } = this.state
        const editingOrAddingNewTask = editMode || !readonly
        const viewingOnly = readonly && !editMode
        return(
          <View style={styles.sections}>
            <Text style={styles.sectionLabel}>{notesLabelString}</Text>
            <TextInput
              style={[styles.dataInputItemPadding, styles.textInput]}
              placeholder={notesString}
              placeholderTextColor={SRPlaceholderTextColor}
              onSubmitEditing={Keyboard.dismiss}
              onChangeText={(notes) => this.notesTextFieldOnChangeText(notes)}
              value={notes}
              editable={editingOrAddingNewTask}
              multiline={true}
              returnKeyType={'done'}
              blurOnSubmit={true}
              ref={(r) => { this._noteInputRef = r }}
            />
            {this._renderSeparator(!readonly)}
          </View>
        )
      } else {
        return null
      }
  }

  _renderDateSelection = (flag) => {
    if(flag) {
      const { pickedDate } = this.state
      const formattedPickedDate = pickedDate // TODO:
      return (
        <View style={styles.sections}>
          <Text style={styles.sectionLabel}>{dateString.toUpperCase()}</Text>
          <SegmentedControlTab
            borderRadius={dateSegmentedControlCornerRadius}
            tabsContainerStyle={styles.tabsContainerStyle}
            tabStyle={styles.tabStyle}
            tabTextStyle={styles.tabTextStyle}
            activeTabStyle={styles.activeTabStyle}
            activeTabTextStyle={styles.activeTabTextStyle}
            values={['Today', 'Yesterday']}
            selectedIndex={this.state.selectedDateIndex}
            onTabPress={this.handleDateSelection}
          />
        </View>
      )
    } else {
      return null
    }
  }

  _renderRatingHistory = (flag) => {
    if(flag) {
      // TODO: missing implementation
      // if made changes then set hasChanges: true
      return (
        <View style={styles.sections}>
          <Text style={styles.sectionLabel}>Rating History</Text>

        </View>
      )
    } else {
      return null
    }
  }

  disabledButtonStyle = (flag: bool) => {
    const defaultStyle = [styles.dataInputItemPadding, styles.bottomButtons, styles.actionButton]
    if(flag) {
      return [...defaultStyle, styles.actionButtonDisabled]
    } else {
      return defaultStyle
    }
  }

}

SRStudyTaskEditor.propTypes = {
  item: PropTypes.shape({
    title: PropTypes.string,
    notes: PropTypes.string,
  }),

  saveAction: PropTypes.func.isRequired,
  deleteAction: PropTypes.func,
  cancelAction: PropTypes.func,

  readonly: PropTypes.bool.isRequired,
}

const styles = StyleSheet.create({
  scrollView: {
    flex: 1,
    backgroundColor: SRDarkColor,
  },
  edgePadding: {
    padding: 15,
    flex:1,
  },
  dataInputItemPadding: {
    padding: 4,
  },
  textInput: {
    fontSize: 48,
    color: SRBrightColor,
  },
  sectionLabel: {
    fontSize: 20,
    color: SRRedColor,
    fontWeight: 'bold'
  },
  sectionSeparator: {
    backgroundColor: SRBrightColor,
    height: 1,
  },
  lastSectionSeparator: {
    marginTop: 8,
  },
  sections: {
    padding: 8,
  },
  bottomButtonsView: {
    flexDirection: 'column',
    paddingTop: 20,
  },
  bottomButtons: {
    alignItems: 'center',
    height: buttonHeight,
    justifyContent: 'center',
  },
  actionButton: {
    backgroundColor: SRYellowColor,
    borderRadius: buttonCornerRadius,
    flex: 1,
  },
  actionButtonDisabled: {
    backgroundColor: 'rgba(255, 252, 49, 0.5)'
  },
  cancelButton: {
    borderColor: SRYellowColor,
    borderWidth: 1,
    borderRadius: buttonCornerRadius,
    flex: 1,
    marginTop: 10,
  },
  bottomButtonsText: {
    color: SRDarkColor,
    fontSize: 16,
    fontWeight: 'bold',
  },
  actionButtonText: {
    paddingTop: 1,
  },
  destructiveButtonText: {
    color: SRYellowColor,
  },
  tabsContainerStyle: {
    paddingTop: 15,
    paddingBottom: 5,
    height: buttonHeight+14,
  },
  tabStyle: {
    backgroundColor: SRBrightColor,
    borderColor: SRBrightColor,
    borderRadius: dateSegmentedControlCornerRadius,
    marginLeft: 2,
    marginRight: 2,
  },
  tabTextStyle: {
    fontWeight: 'bold',
    color: SRDarkColor,
  },
  activeTabStyle: {
    backgroundColor: SRYellowColor,
    borderColor: SRYellowColor,
  },
  activeTabTextStyle: {
    color: SRDarkColor
  },
})
<|endoftext|>


<commit_before>var PlayerWrapper = function() {
	this.underlyingPlayer = 'aurora';
	this.aurora = {};
	this.sm2 = {};
	this.duration = 0;
	var self = this;

	return this;
};

PlayerWrapper.prototype = _.extend({}, OC.Backbone.Events);

PlayerWrapper.prototype.play = function() {
	switch(this.underlyingPlayer) {
		case 'sm2':
			this.sm2.play('ownCloudSound');
			break;
		case 'aurora':
			this.aurora.play();
			break;
	}
};

PlayerWrapper.prototype.stop = function() {
	switch(this.underlyingPlayer) {
		case 'sm2':
			this.sm2.stop('ownCloudSound');
			this.sm2.destroySound('ownCloudSound');
			break;
		case 'aurora':
			if(this.aurora.asset !== undefined) {
				// check if player's constructor has been called,
				// if so, stop() will be available
				this.aurora.stop();
			}
			break;
	}
};

PlayerWrapper.prototype.togglePlayback = function() {
	switch(this.underlyingPlayer) {
		case 'sm2':
			this.sm2.togglePause('ownCloudSound');
			break;
		case 'aurora':
			this.aurora.togglePlayback();
			break;
	}
};

PlayerWrapper.prototype.seek = function(percentage) {
	console.log('seek to '+percentage);
	switch(this.underlyingPlayer) {
		case 'sm2':
			this.sm2.setPosition('ownCloudSound', percentage * this.duration);
			break;
		case 'aurora':
			this.aurora.seek(percentage * this.duration);
			break;
	}
};

PlayerWrapper.prototype.fromURL = function(typeAndURL) {
	var self = this;
	var url = typeAndURL['url'];
	var type = typeAndURL['type'];

	if (soundManager.canPlayURL(url)) {
		this.underlyingPlayer = 'sm2';
	} else {
		this.underlyingPlayer = 'aurora';
	}
	console.log('Using ' + this.underlyingPlayer + ' for type ' + type + ' URL ' + url);

	switch(this.underlyingPlayer) {
		case 'sm2':
			this.sm2 = soundManager.setup({
				html5PollingInterval: 200
			});
			this.sm2.html5Only = true;
			this.sm2.createSound({
				id: 'ownCloudSound',
				url: url,
				whileplaying: function() {
					self.trigger('progress', this.position);
				},
				whileloading: function() {
					self.duration = this.durationEstimate;
					self.trigger('duration', this.durationEstimate);
					// The buffer may contain holes after seeking but just ignore those.
					// Show the buffering status according the last buffered position.
					var bufCount = this.buffered.length;
					var bufEnd = (bufCount > 0) ? this.buffered[bufCount-1].end : 0;
					self.trigger('buffer', bufEnd / this.durationEstimate * 100);
				},
				onfinish: function() {
					self.trigger('end');
				},
				onload: function(success) {
					if (success) {
						self.trigger('ready');
					} else {
						console.log('SM2: sound load error');
					}
				}
			});
			break;
		case 'aurora':
			this.aurora = AV.Player.fromURL(url);
			this.aurora.asset.source.chunkSize=524288;

			this.aurora.on('buffer', function(percent) {
				self.trigger('buffer', percent);
			});
			this.aurora.on('progress', function(currentTime) {
				self.trigger('progress', currentTime);
			});
			this.aurora.on('ready', function() {
				self.trigger('ready');
			});
			this.aurora.on('end', function() {
				self.trigger('end');
			});
			this.aurora.on('duration', function(msecs) {
				self.duration = msecs;
				self.trigger('duration', msecs);
			});
			break;
	}
};
<commit_msg>Disabled seeking with aurora.js<commit_after>var PlayerWrapper = function() {
	this.underlyingPlayer = 'aurora';
	this.aurora = {};
	this.sm2 = {};
	this.duration = 0;
	var self = this;

	return this;
};

PlayerWrapper.prototype = _.extend({}, OC.Backbone.Events);

PlayerWrapper.prototype.play = function() {
	switch(this.underlyingPlayer) {
		case 'sm2':
			this.sm2.play('ownCloudSound');
			break;
		case 'aurora':
			this.aurora.play();
			break;
	}
};

PlayerWrapper.prototype.stop = function() {
	switch(this.underlyingPlayer) {
		case 'sm2':
			this.sm2.stop('ownCloudSound');
			this.sm2.destroySound('ownCloudSound');
			break;
		case 'aurora':
			if(this.aurora.asset !== undefined) {
				// check if player's constructor has been called,
				// if so, stop() will be available
				this.aurora.stop();
			}
			break;
	}
};

PlayerWrapper.prototype.togglePlayback = function() {
	switch(this.underlyingPlayer) {
		case 'sm2':
			this.sm2.togglePause('ownCloudSound');
			break;
		case 'aurora':
			this.aurora.togglePlayback();
			break;
	}
};

PlayerWrapper.prototype.seekingSupported = function() {
	// Seeking is not implemented in aurora/flac.js and does not work on all
	// files with aurora/mp3.js. Hence, we disable seeking with aurora.
	return this.underlyingPlayer == 'sm2';
};

PlayerWrapper.prototype.seek = function(percentage) {
	if (this.seekingSupported()) {
		console.log('seek to '+percentage);
		switch(this.underlyingPlayer) {
			case 'sm2':
				this.sm2.setPosition('ownCloudSound', percentage * this.duration);
				break;
			case 'aurora':
				this.aurora.seek(percentage * this.duration);
				break;
		}
	}
	else {
		console.log('seeking is not supported for this file');
	}
};

PlayerWrapper.prototype.fromURL = function(typeAndURL) {
	var self = this;
	var url = typeAndURL['url'];
	var type = typeAndURL['type'];

	if (soundManager.canPlayURL(url)) {
		this.underlyingPlayer = 'sm2';
	} else {
		this.underlyingPlayer = 'aurora';
	}
	console.log('Using ' + this.underlyingPlayer + ' for type ' + type + ' URL ' + url);

	switch(this.underlyingPlayer) {
		case 'sm2':
			this.sm2 = soundManager.setup({
				html5PollingInterval: 200
			});
			this.sm2.html5Only = true;
			this.sm2.createSound({
				id: 'ownCloudSound',
				url: url,
				whileplaying: function() {
					self.trigger('progress', this.position);
				},
				whileloading: function() {
					self.duration = this.durationEstimate;
					self.trigger('duration', this.durationEstimate);
					// The buffer may contain holes after seeking but just ignore those.
					// Show the buffering status according the last buffered position.
					var bufCount = this.buffered.length;
					var bufEnd = (bufCount > 0) ? this.buffered[bufCount-1].end : 0;
					self.trigger('buffer', bufEnd / this.durationEstimate * 100);
				},
				onfinish: function() {
					self.trigger('end');
				},
				onload: function(success) {
					if (success) {
						self.trigger('ready');
					} else {
						console.log('SM2: sound load error');
					}
				}
			});
			break;
		case 'aurora':
			this.aurora = AV.Player.fromURL(url);
			this.aurora.asset.source.chunkSize=524288;

			this.aurora.on('buffer', function(percent) {
				self.trigger('buffer', percent);
			});
			this.aurora.on('progress', function(currentTime) {
				self.trigger('progress', currentTime);
			});
			this.aurora.on('ready', function() {
				self.trigger('ready');
			});
			this.aurora.on('end', function() {
				self.trigger('end');
			});
			this.aurora.on('duration', function(msecs) {
				self.duration = msecs;
				self.trigger('duration', msecs);
			});
			break;
	}
};
<|endoftext|>


<commit_before>import PropTypes from "prop-types";
import React, { useCallback, useMemo } from "react";
import { useHistory } from "react-router-dom";
import styled from "styled-components";

import { routeConfig } from "@agir/front/app/routes.config";
import style from "@agir/front/genericComponents/_variables.scss";
import { parseDiscountCodes } from "@agir/groups/groupPage/utils";

import Button from "@agir/front/genericComponents/Button";
import Card from "@agir/front/genericComponents/Card";
import Collapsible from "@agir/front/genericComponents/Collapsible.js";
import { Column, Hide, Row } from "@agir/front/genericComponents/grid";
import FeatherIcon from "@agir/front/genericComponents/FeatherIcon";
import Spacer from "@agir/front/genericComponents/Spacer";
import Link from "@agir/front/app/Link";
import ShareLink from "@agir/front/genericComponents/ShareLink";

const GroupIcon = styled.div`
  display: flex;
  align-items: center;
  justify-content: center;
  width: 3rem;
  height: 3rem;
  margin: 0;
  padding: 0;
  border-radius: 100%;
  background-color: ${(props) => props.theme.black50};
  color: ${(props) => props.theme.black1000};
`;

const Label = styled.span`
  font-size: 13px;
  display: inline-block;
  padding: 6px 10px;
  border: 1px solid #dfdfdf;
  margin-right: 8px;
  margin-bottom: 4px;
  line-height: 16px;
  border-radius: 20px;
  ${({ main }) =>
    main
      ? `
    background: #EEEEEE;
    border: 0;
    `
      : ""}
`;

const DiscountCodesSection = styled.section`
  margin: 1.5rem 0 0;

  & > * {
    color: ${style.black700};
    margin: 0.5rem 0;
  }

  ul {
    padding: 0;
  }

  li {
    list-style: none;
  }

  li span {
    font-weight: 400;
  }
`;

const GroupButton = ({ to, href, children, color = "default", icon }) => (
  <Button
    style={{ marginTop: "1rem" }}
    link
    to={to}
    href={href}
    color={color}
    icon={icon}
  >
    {children}
  </Button>
);
GroupButton.propTypes = {
  to: PropTypes.string,
  href: PropTypes.string,
  children: PropTypes.node,
  color: PropTypes.string,
  icon: PropTypes.string,
};

const GroupCard = ({
  id,
  name,
  description,
  eventCount,
  membersCount,
  isMember,
  isManager,
  typeLabel,
  labels,
  routes,
  discountCodes,
  displayGroupLogo,
  displayType,
  displayDescription,
  displayMembership,
  isEmbedded = false,
}) => {
  const history = useHistory();

  const handleClick = useCallback(
    (e) => {
      if (["A", "BUTTON"].includes(e.target.tagName.toUpperCase())) {
        return;
      }
      id &&
        routeConfig.groupDetails &&
        history.push(routeConfig.groupDetails.getLink({ groupPk: id }));
    },
    [history, id]
  );
  const parsedDiscountCodes = useMemo(
    () => parseDiscountCodes(discountCodes),
    [discountCodes]
  );

  return (
    <Card onClick={isEmbedded ? undefined : handleClick}>
      <Row gutter={6}>
        {displayGroupLogo && (
          <Column collapse={0}>
            <Link to={routeConfig.groupDetails.getLink({ groupPk: id })}>
              <GroupIcon>
                <FeatherIcon name="users" />
              </GroupIcon>
            </Link>
          </Column>
        )}
        <Column collapse={0} grow>

            <Link
              style={{
                color: "inherit",
                textDecoration: "none",
              }}
              to={routeConfig.groupDetails.getLink({ groupPk: id })}
            >
              {name}
            </Link>

            {eventCount} événement{eventCount > 1 ? "s" : ""} •{" "}
            {membersCount} membre{membersCount > 1 ? "s" : ""}

        </Column>
      </Row>

      {displayType && typeLabel && (

          {typeLabel}
          {labels &&
            labels.map((label, index) => {label})}

      )}

      {displayDescription && description && (

          <Collapsible
            maxHeight={100}
            expanderLabel="Lire la suite"
            dangerouslySetInnerHTML={{ __html: description }}
            fadingOverflow
          />

      )}

      {parsedDiscountCodes && parsedDiscountCodes.length > 0 && (
        <DiscountCodesSection>

Codes matériels :

            {parsedDiscountCodes.map(({ code, date }) => (

                <ShareLink
                  color="secondary"
                  url={code}
                  label="Copier"
                  $wrap={400}
                />

                  Expiration : {date}

            ))}

        </DiscountCodesSection>
      )}
      <Row gutter={6} style={{ paddingTop: ".5rem" }}>
        {[
          !isEmbedded && !isMember && (
            <GroupButton
              key="join"
              color="primary"
              link
              to={routeConfig.groupDetails.getLink({ groupPk: id })}
            >
              Rejoindre
              <Hide as="span" under={800}>
                 le groupe
              </Hide>
            </GroupButton>
          ),
          <GroupButton
            link
            key="browse"
            color="default"
            to={routeConfig.groupDetails.getLink({ groupPk: id })}
          >
            Voir le groupe
          </GroupButton>,
          routes.fund && (
            <GroupButton key="fund" href={routes.fund} icon="fast-forward">
              Financer
            </GroupButton>
          ),
          isManager && (
            <GroupButton
              key="manage"
              link
              to={routeConfig.groupSettings.getLink({ groupPk: id })}
              icon="settings"
            >
              Gestion
            </GroupButton>
          ),
        ]
          .filter(Boolean)
          .map((button, i) => (
            <React.Fragment key={button.key + i}>
              {i > 0 && <Spacer size="0.5rem" />}
              {button}
            </React.Fragment>
          ))}
      </Row>
      {displayMembership && isMember && (

          <FeatherIcon small inline name="check" /> Vous êtes membre du groupe

      )}
    </Card>
  );
};

GroupCard.propTypes = {
  name: PropTypes.string.isRequired,
  description: PropTypes.string,
  eventCount: PropTypes.number,
  membersCount: PropTypes.number,
  isMember: PropTypes.bool,
  isManager: PropTypes.bool,
  typeLabel: PropTypes.string,
  labels: PropTypes.arrayOf(PropTypes.string),
  routes: PropTypes.object,
  displayGroupLogo: PropTypes.bool,
  displayType: PropTypes.bool,
  displayDescription: PropTypes.bool,
  displayMembership: PropTypes.bool,
  ...DiscountCodesSection.propTypes,
  isEmbedded: PropTypes.bool,
};

GroupCard.defaultProps = {
  displayGroupLogo: true,
  displayType: true,
  displayDescription: true,
  displayMembership: true,
  isManager: false,
};

export default GroupCard;
<commit_msg>fix: ShareLink and GroupCard click event (200~ACTIONPOPULAIRE-FRONT-133)<commit_after>import PropTypes from "prop-types";
import React, { useCallback, useMemo } from "react";
import { useHistory } from "react-router-dom";
import styled from "styled-components";

import { routeConfig } from "@agir/front/app/routes.config";
import style from "@agir/front/genericComponents/_variables.scss";
import { parseDiscountCodes } from "@agir/groups/groupPage/utils";

import Button from "@agir/front/genericComponents/Button";
import Card from "@agir/front/genericComponents/Card";
import Collapsible from "@agir/front/genericComponents/Collapsible.js";
import { Column, Hide, Row } from "@agir/front/genericComponents/grid";
import FeatherIcon from "@agir/front/genericComponents/FeatherIcon";
import Spacer from "@agir/front/genericComponents/Spacer";
import Link from "@agir/front/app/Link";
import ShareLink from "@agir/front/genericComponents/ShareLink";

const GroupIcon = styled.div`
  display: flex;
  align-items: center;
  justify-content: center;
  width: 3rem;
  height: 3rem;
  margin: 0;
  padding: 0;
  border-radius: 100%;
  background-color: ${(props) => props.theme.black50};
  color: ${(props) => props.theme.black1000};
`;

const Label = styled.span`
  font-size: 13px;
  display: inline-block;
  padding: 6px 10px;
  border: 1px solid #dfdfdf;
  margin-right: 8px;
  margin-bottom: 4px;
  line-height: 16px;
  border-radius: 20px;
  ${({ main }) =>
    main
      ? `
    background: #EEEEEE;
    border: 0;
    `
      : ""}
`;

const DiscountCodesSection = styled.section`
  margin: 1.5rem 0 0;

  & > * {
    color: ${style.black700};
    margin: 0.5rem 0;
  }

  ul {
    padding: 0;
  }

  li {
    list-style: none;
  }

  li span {
    font-weight: 400;
  }
`;

const GroupButton = ({ to, href, children, color = "default", icon }) => (
  <Button
    style={{ marginTop: "1rem" }}
    link
    to={to}
    href={href}
    color={color}
    icon={icon}
  >
    {children}
  </Button>
);
GroupButton.propTypes = {
  to: PropTypes.string,
  href: PropTypes.string,
  children: PropTypes.node,
  color: PropTypes.string,
  icon: PropTypes.string,
};

const GroupCard = ({
  id,
  name,
  description,
  eventCount,
  membersCount,
  isMember,
  isManager,
  typeLabel,
  labels,
  routes,
  discountCodes,
  displayGroupLogo,
  displayType,
  displayDescription,
  displayMembership,
  isEmbedded = false,
}) => {
  const history = useHistory();

  const handleClick = useCallback(
    (e) => {
      if (["A", "INPUT", "BUTTON"].includes(e.target.tagName.toUpperCase())) {
        return;
      }
      id &&
        routeConfig.groupDetails &&
        history.push(routeConfig.groupDetails.getLink({ groupPk: id }));
    },
    [history, id]
  );
  const parsedDiscountCodes = useMemo(
    () => parseDiscountCodes(discountCodes),
    [discountCodes]
  );

  return (
    <Card onClick={isEmbedded ? undefined : handleClick}>
      <Row gutter={6}>
        {displayGroupLogo && (
          <Column collapse={0}>
            <Link to={routeConfig.groupDetails.getLink({ groupPk: id })}>
              <GroupIcon>
                <FeatherIcon name="users" />
              </GroupIcon>
            </Link>
          </Column>
        )}
        <Column collapse={0} grow>

            <Link
              style={{
                color: "inherit",
                textDecoration: "none",
              }}
              to={routeConfig.groupDetails.getLink({ groupPk: id })}
            >
              {name}
            </Link>

            {eventCount} événement{eventCount > 1 ? "s" : ""} •{" "}
            {membersCount} membre{membersCount > 1 ? "s" : ""}

        </Column>
      </Row>

      {displayType && typeLabel && (

          {typeLabel}
          {labels &&
            labels.map((label, index) => {label})}

      )}

      {displayDescription && description && (

          <Collapsible
            maxHeight={100}
            expanderLabel="Lire la suite"
            dangerouslySetInnerHTML={{ __html: description }}
            fadingOverflow
          />

      )}

      {parsedDiscountCodes && parsedDiscountCodes.length > 0 && (
        <DiscountCodesSection>

Codes matériels :

            {parsedDiscountCodes.map(({ code, date }) => (

                <ShareLink
                  color="secondary"
                  url={code}
                  label="Copier"
                  $wrap={400}
                />

                  Expiration : {date}

            ))}

        </DiscountCodesSection>
      )}
      <Row gutter={6} style={{ paddingTop: ".5rem" }}>
        {[
          !isEmbedded && !isMember && (
            <GroupButton
              key="join"
              color="primary"
              link
              to={routeConfig.groupDetails.getLink({ groupPk: id })}
            >
              Rejoindre
              <Hide as="span" under={800}>
                 le groupe
              </Hide>
            </GroupButton>
          ),
          <GroupButton
            link
            key="browse"
            color="default"
            to={routeConfig.groupDetails.getLink({ groupPk: id })}
          >
            Voir le groupe
          </GroupButton>,
          routes.fund && (
            <GroupButton key="fund" href={routes.fund} icon="fast-forward">
              Financer
            </GroupButton>
          ),
          isManager && (
            <GroupButton
              key="manage"
              link
              to={routeConfig.groupSettings.getLink({ groupPk: id })}
              icon="settings"
            >
              Gestion
            </GroupButton>
          ),
        ]
          .filter(Boolean)
          .map((button, i) => (
            <React.Fragment key={button.key + i}>
              {i > 0 && <Spacer size="0.5rem" />}
              {button}
            </React.Fragment>
          ))}
      </Row>
      {displayMembership && isMember && (

          <FeatherIcon small inline name="check" /> Vous êtes membre du groupe

      )}
    </Card>
  );
};

GroupCard.propTypes = {
  name: PropTypes.string.isRequired,
  description: PropTypes.string,
  eventCount: PropTypes.number,
  membersCount: PropTypes.number,
  isMember: PropTypes.bool,
  isManager: PropTypes.bool,
  typeLabel: PropTypes.string,
  labels: PropTypes.arrayOf(PropTypes.string),
  routes: PropTypes.object,
  displayGroupLogo: PropTypes.bool,
  displayType: PropTypes.bool,
  displayDescription: PropTypes.bool,
  displayMembership: PropTypes.bool,
  ...DiscountCodesSection.propTypes,
  isEmbedded: PropTypes.bool,
};

GroupCard.defaultProps = {
  displayGroupLogo: true,
  displayType: true,
  displayDescription: true,
  displayMembership: true,
  isManager: false,
};

export default GroupCard;
<|endoftext|>


<commit_before>'use strict';

const { reorder, scrollUp, scrollDown, isObject } = require('../utils');
const Prompt = require('../prompt');

class ArrayPrompt extends Prompt {
  constructor(options) {
    super(options);
    this.initial = options.initial || 0;
    this.cursorHide();
  }

  async initialize() {
    await this.reset();
    await super.initialize();
  }

  async reset() {
    let { choices, initial, autofocus } = this.options;
    this.state._choices = [];
    this.state.choices = [];
    this.choices = await Promise.all(await this.toChoices(choices));

    if (isObject(initial)) initial = Object.keys(initial);
    if (Array.isArray(initial)) {
      if (autofocus != null) this.index = this.findIndex(autofocus);
      initial.forEach(v => this.enable(this.find(v)));
      return this.render();
    }

    if (autofocus != null) initial = autofocus;
    if (typeof initial === 'string') initial = this.findIndex(initial);
    if (typeof initial === 'number' && initial > -1) {
      this.index = Math.max(0, Math.min(initial, this.choices.length));
    }
  }

  async toChoices(items) {
    if (typeof items === 'function') items = items.call(this);
    let list = await Promise.all(await items);
    return list.map(await this.toChoice.bind(this));
  }

  async toChoice(ele, i) {
    if (typeof ele === 'function') ele = await ele.call(this);
    if (typeof ele === 'string') ele = { name: ele };
    if (ele.index != null) return ele;
    ele.name = ele.name || ele.key || ele.title || ele.value || ele.message;
    ele.message = ele.message || ele.name;
    ele.value = ele.value || ele.name;
    ele.index = i;
    ele.enabled = !!(this.multiple && !ele.disabled && (ele.enabled || this.isSelected(ele)));
    return ele;
  }

  async onChoice(choice, i) {
    if (typeof choice.onChoice === 'function') {
      await choice.onChoice(this.state, choice, i);
    }
  }

  dispatch(s, key) {
    this.alert();
  }

  space() {
    if (!this.options.multiple) return this.alert();
    this.toggle();
    return this.render();
  }

  number(n) {
    let i = Number(n);
    if (i > this.choices.length - 1) return this.alert();

    let focused = this.focused;
    let choice = this.choices.find(ch => i === ch.index);

    if (this.visible.indexOf(choice) === -1) {
      let choices = reorder(this.choices);
      let actualIdx = choices.indexOf(choice);

      if (focused.index > actualIdx) {
        let start = choices.slice(actualIdx, actualIdx + this.limit);
        let end = choices.filter(ch => !start.includes(ch));
        this.choices = start.concat(end);
      } else {
        let pos = actualIdx - this.limit + 1;
        this.choices = choices.slice(pos).concat(choices.slice(0, pos));
      }
    }

    this.index = this.choices.indexOf(choice);
    this.toggle();
    return this.render();
  }

  a() {
    let enabled = this.isEnabled(this);
    this.choices.forEach(choice => (choice.enabled = !enabled));
    return this.render();
  }

  i() {
    this.choices.forEach(this.toggle.bind(this));
    return this.render();
  }

  home() {
    this.choices = reorder(this.choices);
    this.index = 0;
    return this.render();
  }

  end() {
    let pos = this.choices.length - this.limit;
    let choices = reorder(this.choices);
    this.choices = choices.slice(pos).concat(choices.slice(0, pos));
    this.index = this.limit - 1;
    return this.render();
  }

  first() {
    this.index = 0;
    return this.render();
  }

  last() {
    this.index = this.visible.length - 1;
    return this.render();
  }

  up(render = true) {
    let len = this.choices.length;
    let vis = this.visible.length;
    if (len > vis && this.index === 0) {
      return this.scrollUp();
    }
    this.index = ((this.index - 1 % len) + len) % len;
    if (this.skipChoice()) {
      return this.up();
    }
    return this.render();
  }

  down(render = true) {
    let len = this.choices.length;
    let vis = this.visible.length;
    if (len > vis && this.index === vis - 1) {
      return this.scrollDown();
    }
    this.index = (this.index + 1) % len;
    if (this.skipChoice()) {
      return this.down();
    }
    return this.render();
  }

  right() {
    if (this.cursor >= this.input.length) return this.alert();
    this.cursor++;
    return this.render();
  }

  left() {
    if (this.cursor <= 0) return this.alert();
    this.cursor--;
    return this.render();
  }

  prev() {
    if (this.visible.length <= 1) return this.alert();
    return this.up();
  }

  next() {
    if (this.visible.length <= 1) return this.alert();
    return this.down();
  }

  shiftUp() {
    return this.scrollUp(this.index);
  }

  shiftDown() {
    return this.scrollDown(this.index);
  }

  scrollUp(i = 0) {
    this.choices = scrollUp(this.choices);
    this.index = i;
    if (this.skipChoice()) {
      return this.up();
    }
    return this.render();
  }

  scrollDown(i = this.visible.length - 1) {
    this.choices = scrollDown(this.choices);
    this.index = i;
    if (this.skipChoice()) {
      return this.down();
    }
    return this.render();
  }

  pageUp() {
    if (this.visible.length <= 1) return this.alert();
    this.limit = Math.max(this.limit - 1, 0);
    this.index = Math.min(this.limit - 1, this.index);
    if (this.skipChoice()) {
      return this.up();
    }
    return this.render();
  }

  pageDown() {
    if (this.visible.length >= this.choices.length) return this.alert();
    this.index = Math.max(0, this.index);
    this.limit = Math.min(this.limit + 1, this.choices.length);
    if (this.skipChoice()) {
      return this.down();
    }
    return this.render();
  }

  skipChoice(choice = this.focused) {
    return choice.disabled || choice.collapsed || choice.hidden;
  }

  focus(choice, enabled) {
    this.index = choice.index;
    if (enabled != null) {
      choice.enabled = !choice.disabled;
    }
  }

  toggle(choice = this.focused) {
    choice.enabled = !choice.enabled && !choice.disabled;
    choice.choices && choice.choices.forEach(this.toggle.bind(this));
    return choice;
  }

  enable(choice = this.focused) {
    choice.enabled = !choice.disabled;
    choice.choices && choice.choices.forEach(this.enable.bind(this));
    return choice;
  }

  isEnabled(ch = this.focused) {
    return ch.choices ? ch.choices.every(this.isEnabled) : !!ch.enabled;
  }

  isChoice(choice, value) {
    return choice.name === value || choice.index === Number(value);
  }

  isSelected(choice) {
    if (Array.isArray(this.initial)) {
      return this.initial.some(value => this.isChoice(choice, value));
    }
    return this.isChoice(choice, this.initial);
  }

  filter(value, prop) {
    let isChoice = (ele, i) => [ele.name, i].includes(value);
    let fn = typeof value === 'function' ? value : isChoice;
    let result = this.choices.filter(fn);
    if (prop) {
      return result.map(ch => ch[prop]);
    }
    return result;
  }

  find(value, prop) {
    let isChoice = (ele, i) => [ele.name, i].includes(value);
    let fn = typeof value === 'function' ? value : isChoice;
    let choice = this.choices.find(fn);
    if (choice) {
      return prop ? choice[prop] : choice;
    }
  }

  findIndex(value) {
    return this.choices.indexOf(this.find(value));
  }

  map(names = [], prop = 'value') {
    return names.reduce((acc, name) => {
      acc[name] = this.find(name, prop);
      return acc;
    }, {});
  }

  async submit(value) {
    this.choices = reorder(this.choices);
    let result = this.options.multiple ? this.selected : this.focused;
    if (result === void 0) return this.alert();

    this.value = Array.isArray(result) ? result.map(ch => ch.name) : result.name;
    return super.submit();
  }

  set choices(choices = []) {
    this.state._choices = this.state._choices || [];
    this.state.choices = choices;

    for (let choice of choices) {
      if (!this.state._choices.some(ch => ch.name === choice.name)) {
        this.state._choices.push(choice);
      }
    }

    if (!this.setInitial && this.options.initial) {
      let init = this.initial;
      if (typeof init === 'string' || typeof init === 'number') {
        this.setInitial = true;
        let choice = this.find(init);
        this.initial = choice.index;
        this.focus(choice, true);
      }
    }
  }
  get choices() {
    return this.state.choices || [];
  }

  set visible(visible) {
    this.state.visible = visible;
  }
  get visible() {
    return (this.state.visible || this.choices).slice(0, this.limit);
  }

  set limit(num) {
    this.state.limit = num;
  }
  get limit() {
    let limit = this.state.limit || this.options.limit || this.choices.length;
    return Math.min(limit, this.stdout.rows - 5);
  }

  set index(i) {
    this.state.index = i;
  }
  get index() {
    return Math.max(0, this.state ? this.state.index : 0);
  }

  get enabled() {
    return this.filter(choice => !choice.disabled && choice.enabled === true);
  }

  get focused() {
    return this.choices[this.index];
  }

  get selected() {
    return this.options.multiple ? this.enabled : this.focused;
  }
}

module.exports = ArrayPrompt;
<commit_msg>fix reset<commit_after>'use strict';

const { reorder, scrollUp, scrollDown, isObject } = require('../utils');
const Prompt = require('../prompt');

class ArrayPrompt extends Prompt {
  constructor(options) {
    super(options);
    this.initial = options.initial || 0;
    this.cursorHide();
  }

  async initialize() {
    await this.reset();
    await super.initialize();
  }

  async reset() {
    let { choices, initial, autofocus } = this.options;
    this.state._choices = [];
    this.state.choices = [];
    this.choices = await Promise.all(await this.toChoices(choices));
    this.choices.forEach(ch => isObject(ch) && (ch.enabled = false));

    if (isObject(initial)) initial = Object.keys(initial);
    if (Array.isArray(initial)) {
      if (autofocus != null) this.index = this.findIndex(autofocus);
      initial.forEach(v => this.enable(this.find(v)));
      return this.render();
    }

    if (autofocus != null) initial = autofocus;
    if (typeof initial === 'string') initial = this.findIndex(initial);
    if (typeof initial === 'number' && initial > -1) {
      this.index = Math.max(0, Math.min(initial, this.choices.length));
      this.enable(this.index);
    }
  }

  async toChoices(items) {
    if (typeof items === 'function') items = items.call(this);
    let list = await Promise.all(await items);
    return list.map(await this.toChoice.bind(this));
  }

  async toChoice(ele, i) {
    if (typeof ele === 'function') ele = await ele.call(this);
    if (typeof ele === 'string') ele = { name: ele };

    if (typeof ele.disabled === 'string') {
      ele.hint = ele.disabled;
      ele.disabled = true;
    }
    if (ele.disabled === true && !ele.hint) {
      ele.hint = '(disabled)';
    }

    if (ele.index != null) return ele;
    ele.name = ele.name || ele.key || ele.title || ele.value || ele.message;
    ele.message = ele.message || ele.name;
    ele.value = ele.value || ele.name;
    ele.index = i;
    ele.enabled = !!(this.multiple && !ele.disabled && (ele.enabled || this.isSelected(ele)));
    return ele;
  }

  async onChoice(choice, i) {
    if (typeof choice.onChoice === 'function') {
      await choice.onChoice(this.state, choice, i);
    }
  }

  dispatch(s, key) {
    this.alert();
  }

  space() {
    if (!this.options.multiple) return this.alert();
    this.toggle();
    return this.render();
  }

  number(n) {
    let i = Number(n);
    if (i > this.choices.length - 1) return this.alert();

    let focused = this.focused;
    let choice = this.choices.find(ch => i === ch.index);

    if (this.visible.indexOf(choice) === -1) {
      let choices = reorder(this.choices);
      let actualIdx = choices.indexOf(choice);

      if (focused.index > actualIdx) {
        let start = choices.slice(actualIdx, actualIdx + this.limit);
        let end = choices.filter(ch => !start.includes(ch));
        this.choices = start.concat(end);
      } else {
        let pos = actualIdx - this.limit + 1;
        this.choices = choices.slice(pos).concat(choices.slice(0, pos));
      }
    }

    this.index = this.choices.indexOf(choice);
    this.toggle();
    return this.render();
  }

  a() {
    let enabled = this.isEnabled(this);
    this.choices.forEach(choice => choice.enabled = !enabled && !choice.disabled);
    return this.render();
  }

  i() {
    this.choices.forEach(this.toggle.bind(this));
    return this.render();
  }

  home() {
    this.choices = reorder(this.choices);
    this.index = 0;
    return this.render();
  }

  end() {
    let pos = this.choices.length - this.limit;
    let choices = reorder(this.choices);
    this.choices = choices.slice(pos).concat(choices.slice(0, pos));
    this.index = this.limit - 1;
    return this.render();
  }

  first() {
    this.index = 0;
    return this.render();
  }

  last() {
    this.index = this.visible.length - 1;
    return this.render();
  }

  up(render = true) {
    let len = this.choices.length;
    let vis = this.visible.length;
    if (len > vis && this.index === 0) {
      return this.scrollUp();
    }
    this.index = ((this.index - 1 % len) + len) % len;
    if (this.skipChoice()) {
      return this.up();
    }
    return this.render();
  }

  down(render = true) {
    let len = this.choices.length;
    let vis = this.visible.length;
    if (len > vis && this.index === vis - 1) {
      return this.scrollDown();
    }
    this.index = (this.index + 1) % len;
    if (this.skipChoice()) {
      return this.down();
    }
    return this.render();
  }

  right() {
    if (this.cursor >= this.input.length) return this.alert();
    this.cursor++;
    return this.render();
  }

  left() {
    if (this.cursor <= 0) return this.alert();
    this.cursor--;
    return this.render();
  }

  prev() {
    if (this.visible.length <= 1) return this.alert();
    return this.up();
  }

  next() {
    if (this.visible.length <= 1) return this.alert();
    return this.down();
  }

  shiftUp() {
    return this.scrollUp(this.index);
  }

  shiftDown() {
    return this.scrollDown(this.index);
  }

  scrollUp(i = 0) {
    this.choices = scrollUp(this.choices);
    this.index = i;
    if (this.skipChoice()) {
      return this.up();
    }
    return this.render();
  }

  scrollDown(i = this.visible.length - 1) {
    this.choices = scrollDown(this.choices);
    this.index = i;
    if (this.skipChoice()) {
      return this.down();
    }
    return this.render();
  }

  pageUp() {
    if (this.visible.length <= 1) return this.alert();
    this.limit = Math.max(this.limit - 1, 0);
    this.index = Math.min(this.limit - 1, this.index);
    if (this.skipChoice()) {
      return this.up();
    }
    return this.render();
  }

  pageDown() {
    if (this.visible.length >= this.choices.length) return this.alert();
    this.index = Math.max(0, this.index);
    this.limit = Math.min(this.limit + 1, this.choices.length);
    if (this.skipChoice()) {
      return this.down();
    }
    return this.render();
  }

  skipChoice(choice = this.focused) {
    if (!this.focused) return;
    return choice.disabled || choice.collapsed || choice.hidden;
  }

  focus(choice, enabled) {
    choice = this.find(choice);
    if (typeof enabled !== 'boolean') enabled = choice.enabled;
    this.index = choice.index;
    choice.enabled = enabled && !choice.disabled;
  }

  toggle(choice = this.focused) {
    choice = this.find(choice);
    choice.enabled = !choice.enabled && !choice.disabled;
    choice.choices && choice.choices.forEach(this.toggle.bind(this));
    return choice;
  }

  enable(choice = this.focused) {
    choice = this.find(choice);
    choice.enabled = !choice.disabled;
    choice.choices && choice.choices.forEach(this.enable.bind(this));
    return choice;
  }

  disable(choice = this.focused) {
    choice = this.find(choice);
    choice.enabled = false;
    choice.choices && choice.choices.forEach(this.disable.bind(this));
    return choice;
  }

  isEnabled(choice = this.focused) {
    if (choice.choices) {
      let choices = choice.choices.filter(ch => !ch.disabled);
      return choices.every(this.isEnabled.bind(this));
    }
    return choice.enabled && !choice.disabled;
  }

  isChoice(choice, value) {
    return choice.name === value || choice.index === Number(value);
  }

  isSelected(choice) {
    if (Array.isArray(this.initial)) {
      return this.initial.some(value => this.isChoice(choice, value));
    }
    return this.isChoice(choice, this.initial);
  }

  filter(value, prop) {
    let isChoice = (ele, i) => [ele.name, i].includes(value);
    let fn = typeof value === 'function' ? value : isChoice;
    let result = this.choices.filter(fn);
    if (prop) {
      return result.map(ch => ch[prop]);
    }
    return result;
  }

  find(value, prop) {
    if (isObject(value)) return prop ? value[prop] : value;
    let isChoice = (ele, i) => [ele.name, i].includes(value);
    let fn = typeof value === 'function' ? value : isChoice;
    let choice = this.choices.find(fn);
    if (choice) {
      return prop ? choice[prop] : choice;
    }
  }

  findIndex(value) {
    return this.choices.indexOf(this.find(value));
  }

  map(names = [], prop = 'value') {
    return names.reduce((acc, name) => {
      acc[name] = this.find(name, prop);
      return acc;
    }, {});
  }

  async submit(value) {
    this.choices = reorder(this.choices);
    let result = this.options.multiple ? this.selected : this.focused;
    if (result === void 0) return this.alert();

    this.value = Array.isArray(result) ? result.map(ch => ch.name) : result.name;
    return super.submit();
  }

  set choices(choices = []) {
    this.state._choices = this.state._choices || [];
    this.state.choices = choices;

    for (let choice of choices) {
      if (!this.state._choices.some(ch => ch.name === choice.name)) {
        this.state._choices.push(choice);
      }
    }

    if (!this.setInitial && this.options.initial) {
      let init = this.initial;
      if (typeof init === 'string' || typeof init === 'number') {
        this.setInitial = true;
        let choice = this.find(init);
        this.initial = choice.index;
        this.focus(choice, true);
      }
    }
  }
  get choices() {
    return this.state.choices || [];
  }

  set visible(visible) {
    this.state.visible = visible;
  }
  get visible() {
    return (this.state.visible || this.choices).slice(0, this.limit);
  }

  set limit(num) {
    this.state.limit = num;
  }
  get limit() {
    let limit = this.state.limit || this.options.limit || this.choices.length;
    return Math.min(limit, this.stdout.rows - 5);
  }

  set index(i) {
    this.state.index = i;
  }
  get index() {
    return Math.max(0, this.state ? this.state.index : 0);
  }

  get enabled() {
    return this.filter(choice => !choice.disabled && choice.enabled);
  }

  get focused() {
    return this.choices[this.index];
  }

  get selected() {
    return this.options.multiple ? this.enabled : this.focused;
  }
}

module.exports = ArrayPrompt;
<|endoftext|>


<commit_before>
const fs = require('fs-extra');
const tasks = require('./tasks.js');
const rp = require('request-promise');
const moment = require('moment');

const _HOST = 'http://127.0.0.1:3000/';
const _PUBLIC = './public';
const _PUBLIC_BK = './public.bk';

console.ok = function() {
    let args = [];
    let title = ['\x1b[32m', 'OK ', '\x1b[37m'];
    for (var key in arguments) {
        if (arguments.hasOwnProperty(key))
            args.push(arguments[key]);
    }
    console.log.apply(null, title.concat(args));
};

async function builder() {
    try {
        await fs.remove(_PUBLIC_BK);
        await fs.move(_PUBLIC, _PUBLIC_BK);
        await fs.mkdir(_PUBLIC);
        await Promise.all(tasks.map( async task => {
            let uri = _HOST + task.page;
            let file = _PUBLIC + '/' + task.page + '.html';
            let html = await rp({ uri });
            await fs.appendFile(file, html);
            console.ok('Page:', task.page);
            return Promise.resolve();
        }));
        await fs.appendFile(_PUBLIC + '/note.md', 'Build at ' + moment().format('YYYY/MM/DD HH:mm'));
        console.log('Total File: %s, build success\n', tasks.length);
    } catch (err) {
        throw err;
    }
}

builder();

<commit_msg>refactoring close #10<commit_after>
const fs = require('fs-extra');
const tasks = require('./tasks.js');
const rp = require('request-promise');
const moment = require('moment');

const _HOST = 'http://127.0.0.1:3000/';
const _PUBLIC = './public';
const _PUBLIC_BK = './public.bk';
const _NOTE = _PUBLIC + '/note.md';

console.ok = function() {
    let args = [];
    let title = ['\x1b[32m', 'OK ', '\x1b[37m'];
    for (var key in arguments) {
        if (arguments.hasOwnProperty(key))
            args.push(arguments[key]);
    }
    console.log.apply(null, title.concat(args));
};

async function builder() {
    try {
        await fs.remove(_PUBLIC_BK);
        await fs.move(_PUBLIC, _PUBLIC_BK);
        await fs.mkdir(_PUBLIC);
        await Promise.all(tasks.map( async task => {
            let uri = _HOST + task.page;
            let file = _PUBLIC + '/' + task.page + '.html';
            let html = await rp({ uri });
            await fs.appendFile(file, html);
            console.ok('Page:', task.page);
            return Promise.resolve();
        }));
        await fs.appendFile(_NOTE, 'Build at ' + moment().format('YYYY/MM/DD HH:mm'));
        console.log('Total File: %s, build success\n', tasks.length);
    } catch (err) {
        throw err;
    }
}

builder();

<|endoftext|>


<commit_before>/* ===========================================================
 * bootstrap-tooltip.js v2.0.3
 * http://twitter.github.com/bootstrap/javascript.html#tooltips
 * Inspired by the original jQuery.tipsy by Jason Frame
 * ===========================================================
 * Copyright 2012 Twitter, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * ========================================================== */

!function ($) {

  "use strict"; // jshint ;_;

 /* TOOLTIP PUBLIC CLASS DEFINITION
  * =============================== */

  var Tooltip = function (element, options) {
    this.init('tooltip', element, options)
  }

  Tooltip.prototype = {

    constructor: Tooltip

  , init: function (type, element, options) {
      var eventIn
        , eventOut

      this.type = type
      this.$element = $(element)
      this.options = this.getOptions(options)
      this.enabled = true

      if (this.options.trigger != 'manual') {
        eventIn  = this.options.trigger == 'hover' ? 'mouseenter' : 'focus'
        eventOut = this.options.trigger == 'hover' ? 'mouseleave' : 'blur'
        this.$element.on(eventIn, this.options.selector, $.proxy(this.enter, this))
        this.$element.on(eventOut, this.options.selector, $.proxy(this.leave, this))
      }

      this.options.selector ?
        (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :
        this.fixTitle()
    }

  , getOptions: function (options) {
      options = $.extend({}, $.fn[this.type].defaults, options, this.$element.data())

      if (options.delay && typeof options.delay == 'number') {
        options.delay = {
          show: options.delay
        , hide: options.delay
        }
      }

      return options
    }

  , enter: function (e) {
      var self = $(e.currentTarget)[this.type](this._options).data(this.type)

      if (!self.options.delay || !self.options.delay.show) return self.show()

      clearTimeout(this.timeout)
      self.hoverState = 'in'
      this.timeout = setTimeout(function() {
        if (self.hoverState == 'in') self.show()
      }, self.options.delay.show)
    }

  , leave: function (e) {
      var self = $(e.currentTarget)[this.type](this._options).data(this.type)

      if (!self.options.delay || !self.options.delay.hide) return self.hide()

      clearTimeout(this.timeout)
      self.hoverState = 'out'
      this.timeout = setTimeout(function() {
        if (self.hoverState == 'out') self.hide()
      }, self.options.delay.hide)
    }

  , show: function () {
      var $tip
        , inside
        , pos
        , actualWidth
        , actualHeight
        , placement
        , tp

      if (this.hasContent() && this.enabled) {
        $tip = this.tip()
        this.setContent()

        if (this.options.animation) {
          $tip.addClass('fade')
        }

        placement = typeof this.options.placement == 'function' ?
          this.options.placement.call(this, $tip[0], this.$element[0]) :
          this.options.placement

        inside = /in/.test(placement)

        $tip
          .remove()
          .css({ top: 0, left: 0, display: 'block' })
          .appendTo(inside ? this.$element : document.body)

        pos = this.getPosition(inside)

        actualWidth = $tip[0].offsetWidth
        actualHeight = $tip[0].offsetHeight

        switch (inside ? placement.split(' ')[1] : placement) {
          case 'bottom':
            tp = {top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2}
            break
          case 'top':
            tp = {top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2}
            break
          case 'left':
            tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth}
            break
          case 'right':
            tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width}
            break
        }

        $tip
          .css(tp)
          .addClass(placement)
          .addClass('in')
      }
    }

  , isHTML: function(text) {
      // html string detection logic adapted from jQuery
      return typeof text != 'string'
        || ( text.charAt(0) === "<"
          && text.charAt( text.length - 1 ) === ">"
          && text.length >= 3
        ) || /^(?:[^<]*<[\w\W]+>[^>]*$)/.exec(text)
    }

  , setContent: function () {
      var $tip = this.tip()
        , title = this.getTitle()

      $tip.find('.tooltip-inner')[this.isHTML(title) ? 'html' : 'text'](title)
      $tip.removeClass('fade in top bottom left right')
    }

  , hide: function () {
      var that = this
        , $tip = this.tip()

      $tip.removeClass('in')

      function removeWithAnimation() {
        var timeout = setTimeout(function () {
          $tip.off($.support.transition.end).remove()
        }, 500)

        $tip.one($.support.transition.end, function () {
          clearTimeout(timeout)
          $tip.remove()
        })
      }

      $.support.transition && this.$tip.hasClass('fade') ?
        removeWithAnimation() :
        $tip.remove()
    }

  , fixTitle: function () {
      var $e = this.$element
      if ($e.attr('title') || typeof($e.attr('data-original-title')) != 'string') {
        $e.attr('data-original-title', $e.attr('title') || '').removeAttr('title')
      }
    }

  , hasContent: function () {
      return this.getTitle()
    }

  , getPosition: function (inside) {
      return $.extend({}, (inside ? {top: 0, left: 0} : this.$element.offset()), {
        width: this.$element[0].offsetWidth
      , height: this.$element[0].offsetHeight
      })
    }

  , getTitle: function () {
      var title
        , $e = this.$element
        , o = this.options

      title = $e.attr('data-original-title')
        || (typeof o.title == 'function' ? o.title.call($e[0]) :  o.title)

      return title
    }

  , tip: function () {
      return this.$tip = this.$tip || $(this.options.template)
    }

  , validate: function () {
      if (!this.$element[0].parentNode) {
        this.hide()
        this.$element = null
        this.options = null
      }
    }

  , enable: function () {
      this.enabled = true
    }

  , disable: function () {
      this.enabled = false
    }

  , toggleEnabled: function () {
      this.enabled = !this.enabled
    }

  , toggle: function () {
      this[this.tip().hasClass('in') ? 'hide' : 'show']()
    }

  }

 /* TOOLTIP PLUGIN DEFINITION
  * ========================= */

  $.fn.tooltip = function ( option ) {
    return this.each(function () {
      var $this = $(this)
        , data = $this.data('tooltip')
        , options = typeof option == 'object' && option
      if (!data) $this.data('tooltip', (data = new Tooltip(this, options)))
      if (typeof option == 'string') data[option]()
    })
  }

  $.fn.tooltip.Constructor = Tooltip

  $.fn.tooltip.defaults = {
    animation: true
  , placement: 'top'
  , selector: false
  , template: '

'
  , trigger: 'hover'
  , title: ''
  , delay: 0
  }

}(window.jQuery);<commit_msg>Cancel running timer for tooltips with delayed show, but instant hide. This prevents delayed tooltips from appearing if the mouse leaves the elements before tooltip is showed and the hiding delay is 0.<commit_after>/* ===========================================================
 * bootstrap-tooltip.js v2.0.3
 * http://twitter.github.com/bootstrap/javascript.html#tooltips
 * Inspired by the original jQuery.tipsy by Jason Frame
 * ===========================================================
 * Copyright 2012 Twitter, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * ========================================================== */

!function ($) {

  "use strict"; // jshint ;_;

 /* TOOLTIP PUBLIC CLASS DEFINITION
  * =============================== */

  var Tooltip = function (element, options) {
    this.init('tooltip', element, options)
  }

  Tooltip.prototype = {

    constructor: Tooltip

  , init: function (type, element, options) {
      var eventIn
        , eventOut

      this.type = type
      this.$element = $(element)
      this.options = this.getOptions(options)
      this.enabled = true

      if (this.options.trigger != 'manual') {
        eventIn  = this.options.trigger == 'hover' ? 'mouseenter' : 'focus'
        eventOut = this.options.trigger == 'hover' ? 'mouseleave' : 'blur'
        this.$element.on(eventIn, this.options.selector, $.proxy(this.enter, this))
        this.$element.on(eventOut, this.options.selector, $.proxy(this.leave, this))
      }

      this.options.selector ?
        (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :
        this.fixTitle()
    }

  , getOptions: function (options) {
      options = $.extend({}, $.fn[this.type].defaults, options, this.$element.data())

      if (options.delay && typeof options.delay == 'number') {
        options.delay = {
          show: options.delay
        , hide: options.delay
        }
      }

      return options
    }

  , enter: function (e) {
      var self = $(e.currentTarget)[this.type](this._options).data(this.type)

      if (!self.options.delay || !self.options.delay.show) return self.show()

      clearTimeout(this.timeout)
      self.hoverState = 'in'
      this.timeout = setTimeout(function() {
        if (self.hoverState == 'in') self.show()
      }, self.options.delay.show)
    }

  , leave: function (e) {
      var self = $(e.currentTarget)[this.type](this._options).data(this.type)

      if (this.timeout) clearTimeout(this.timeout)
      if (!self.options.delay || !self.options.delay.hide) return self.hide()

      clearTimeout(this.timeout)
      self.hoverState = 'out'
      this.timeout = setTimeout(function() {
        if (self.hoverState == 'out') self.hide()
      }, self.options.delay.hide)
    }

  , show: function () {
      var $tip
        , inside
        , pos
        , actualWidth
        , actualHeight
        , placement
        , tp

      if (this.hasContent() && this.enabled) {
        $tip = this.tip()
        this.setContent()

        if (this.options.animation) {
          $tip.addClass('fade')
        }

        placement = typeof this.options.placement == 'function' ?
          this.options.placement.call(this, $tip[0], this.$element[0]) :
          this.options.placement

        inside = /in/.test(placement)

        $tip
          .remove()
          .css({ top: 0, left: 0, display: 'block' })
          .appendTo(inside ? this.$element : document.body)

        pos = this.getPosition(inside)

        actualWidth = $tip[0].offsetWidth
        actualHeight = $tip[0].offsetHeight

        switch (inside ? placement.split(' ')[1] : placement) {
          case 'bottom':
            tp = {top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2}
            break
          case 'top':
            tp = {top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2}
            break
          case 'left':
            tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth}
            break
          case 'right':
            tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width}
            break
        }

        $tip
          .css(tp)
          .addClass(placement)
          .addClass('in')
      }
    }

  , isHTML: function(text) {
      // html string detection logic adapted from jQuery
      return typeof text != 'string'
        || ( text.charAt(0) === "<"
          && text.charAt( text.length - 1 ) === ">"
          && text.length >= 3
        ) || /^(?:[^<]*<[\w\W]+>[^>]*$)/.exec(text)
    }

  , setContent: function () {
      var $tip = this.tip()
        , title = this.getTitle()

      $tip.find('.tooltip-inner')[this.isHTML(title) ? 'html' : 'text'](title)
      $tip.removeClass('fade in top bottom left right')
    }

  , hide: function () {
      var that = this
        , $tip = this.tip()

      $tip.removeClass('in')

      function removeWithAnimation() {
        var timeout = setTimeout(function () {
          $tip.off($.support.transition.end).remove()
        }, 500)

        $tip.one($.support.transition.end, function () {
          clearTimeout(timeout)
          $tip.remove()
        })
      }

      $.support.transition && this.$tip.hasClass('fade') ?
        removeWithAnimation() :
        $tip.remove()
    }

  , fixTitle: function () {
      var $e = this.$element
      if ($e.attr('title') || typeof($e.attr('data-original-title')) != 'string') {
        $e.attr('data-original-title', $e.attr('title') || '').removeAttr('title')
      }
    }

  , hasContent: function () {
      return this.getTitle()
    }

  , getPosition: function (inside) {
      return $.extend({}, (inside ? {top: 0, left: 0} : this.$element.offset()), {
        width: this.$element[0].offsetWidth
      , height: this.$element[0].offsetHeight
      })
    }

  , getTitle: function () {
      var title
        , $e = this.$element
        , o = this.options

      title = $e.attr('data-original-title')
        || (typeof o.title == 'function' ? o.title.call($e[0]) :  o.title)

      return title
    }

  , tip: function () {
      return this.$tip = this.$tip || $(this.options.template)
    }

  , validate: function () {
      if (!this.$element[0].parentNode) {
        this.hide()
        this.$element = null
        this.options = null
      }
    }

  , enable: function () {
      this.enabled = true
    }

  , disable: function () {
      this.enabled = false
    }

  , toggleEnabled: function () {
      this.enabled = !this.enabled
    }

  , toggle: function () {
      this[this.tip().hasClass('in') ? 'hide' : 'show']()
    }

  }

 /* TOOLTIP PLUGIN DEFINITION
  * ========================= */

  $.fn.tooltip = function ( option ) {
    return this.each(function () {
      var $this = $(this)
        , data = $this.data('tooltip')
        , options = typeof option == 'object' && option
      if (!data) $this.data('tooltip', (data = new Tooltip(this, options)))
      if (typeof option == 'string') data[option]()
    })
  }

  $.fn.tooltip.Constructor = Tooltip

  $.fn.tooltip.defaults = {
    animation: true
  , placement: 'top'
  , selector: false
  , template: '

'
  , trigger: 'hover'
  , title: ''
  , delay: 0
  }

}(window.jQuery);
<|endoftext|>


<commit_before>/**
 * Interface to the User model.
 */
var dslogger = require('./dslogger')
    ;

// Initialize logger.
dslogger.init('mb-logging-api-server', false);

/**
 * Constructor to the Log object.
 *
 * @param model
 *   The model of the log document.
 */
function UserImport(model) {
  this.docModel = model;
}

/**
 * Converts a timestamp in seconds to a Date object.
 *
 * @param timestamp
 *   Timestamp in seconds.
 */
var convertToDate = function(timestamp) {
  return new Date(timestamp * 1000);
};

/**
 * Create a log document from the supplied values
 *
 * @param req
 *  The request object in a POST callback.
 * @param res
 *  The response object in a POST callback.
 */
UserImport.prototype.post = function(req, res) {
  this.request = req;
  this.response = res;
  var addArgs = {};

  // Include parameter values in post
  addArgs.source = this.request.query.source;

  if (this.request.body.origin !== undefined) {
    var processedDate = convertToDate(parseInt(this.request.query));
    addArgs.origin = {
      "processed" : processedDate,
      "name" : this.request.body.origin['name'],
    }
  }

  // POST submission values
  if (this.request.body.logging_timestamp !== undefined) {
    // Convert timestamp string to Date object
    var timestamp = parseInt(this.request.body.logging_timestamp);
    addArgs.logged_date = convertToDate(timestamp);
  }
  if (this.request.body.phone !== undefined) {
    addArgs.phone = {
      "number" : this.request.body.phone,
      "status" : this.request.body.phone_status
    }
  }
  if (this.request.body.email !== undefined) {
    addArgs.email = {
      "address" : this.request.body.email,
      "status" : this.request.body.email_status
    }
    if (this.request.body.email_acquired_timestamp !== undefined) {
      var timestamp = parseInt(this.request.body.email_acquired_timestamp);
      addArgs.email.acquired = convertToDate(timestamp);
    }
  }
  if (this.request.body.drupal_uid !== undefined) {
    addArgs.drupal = {
      "email" : this.request.body.email,
      "uid" : this.request.body.drupal_uid
    }
  }

  var logEntry = new this.docModel(addArgs);
  logEntry.save(function(err) {
    if (err) {
      res.send(500, err);
      dslogger.error(err);
    }

    // Added log entry to db
    res.send(201, addArgs);
    dslogger.log('Save executed on: ' + addArgs + '. Raw response from mongo:');
  });
};

/**
 * Retrieve existing user log documents. Example request:
 * /api/v1/imports/2014-09-01/2014-10-01?type=user&exists=1&source=niche.com
 *
 * @param req
 *   The request object in the GET callback.
 * @param res
 *   The response object in the GET callback.
 */
UserImport.prototype.get = function(req, res) {

  if (req.param("start_date") == 0) {
    var targetStartDate = new Date('2014-08-01');
  }
  else {
    var targetStartDate = new Date(req.param("start_date"));
  }
  if (req.param("end_date") == 0) {
    var targetEndDate = new Date();
  }
  else {
    var targetEndDate = new Date(req.param("end_date"));
  }

  var data = {
    request: req,
    response: res
  };
  this.docModel.find( {
    $and : [
      { 'logged_date' : {$gte : targetStartDate, $lte : targetEndDate} },
      { 'source' : req.query.source }
    ]},
    function (err, docs) {
      if (err) {
        data.response.send(500, err);
        dslogger.error(err);
        return;
      }

      // Send results
      console.log('Summary query returned.');
      data.response.send(201, docs);
  })
};

module.exports = UserImport;
<commit_msg>Bug fix, processed timestamp<commit_after>/**
 * Interface to the User model.
 */
var dslogger = require('./dslogger')
    ;

// Initialize logger.
dslogger.init('mb-logging-api-server', false);

/**
 * Constructor to the Log object.
 *
 * @param model
 *   The model of the log document.
 */
function UserImport(model) {
  this.docModel = model;
}

/**
 * Converts a timestamp in seconds to a Date object.
 *
 * @param timestamp
 *   Timestamp in seconds.
 */
var convertToDate = function(timestamp) {
  return new Date(timestamp * 1000);
};

/**
 * Create a log document from the supplied values
 *
 * @param req
 *  The request object in a POST callback.
 * @param res
 *  The response object in a POST callback.
 */
UserImport.prototype.post = function(req, res) {
  this.request = req;
  this.response = res;
  var addArgs = {};

  // Include parameter values in post
  addArgs.source = this.request.query.source;

  if (this.request.body.origin !== undefined) {
    var processedDate = convertToDate(parseInt(this.request.body.origin['processed']));
    addArgs.origin = {
      "processed" : processedDate,
      "name" : this.request.body.origin['name'],
    }
  }

  // POST submission values
  if (this.request.body.logging_timestamp !== undefined) {
    // Convert timestamp string to Date object
    var timestamp = parseInt(this.request.body.logging_timestamp);
    addArgs.logged_date = convertToDate(timestamp);
  }
  if (this.request.body.phone !== undefined) {
    addArgs.phone = {
      "number" : this.request.body.phone,
      "status" : this.request.body.phone_status
    }
  }
  if (this.request.body.email !== undefined) {
    addArgs.email = {
      "address" : this.request.body.email,
      "status" : this.request.body.email_status
    }
    if (this.request.body.email_acquired_timestamp !== undefined) {
      var timestamp = parseInt(this.request.body.email_acquired_timestamp);
      addArgs.email.acquired = convertToDate(timestamp);
    }
  }
  if (this.request.body.drupal_uid !== undefined) {
    addArgs.drupal = {
      "email" : this.request.body.email,
      "uid" : this.request.body.drupal_uid
    }
  }

  var logEntry = new this.docModel(addArgs);
  logEntry.save(function(err) {
    if (err) {
      res.send(500, err);
      dslogger.error(err);
    }

    // Added log entry to db
    res.send(201, addArgs);
    dslogger.log('Save executed on: ' + addArgs + '. Raw response from mongo:');
  });
};

/**
 * Retrieve existing user log documents. Example request:
 * /api/v1/imports/2014-09-01/2014-10-01?type=user&exists=1&source=niche.com
 *
 * @param req
 *   The request object in the GET callback.
 * @param res
 *   The response object in the GET callback.
 */
UserImport.prototype.get = function(req, res) {

  if (req.param("start_date") == 0) {
    var targetStartDate = new Date('2014-08-01');
  }
  else {
    var targetStartDate = new Date(req.param("start_date"));
  }
  if (req.param("end_date") == 0) {
    var targetEndDate = new Date();
  }
  else {
    var targetEndDate = new Date(req.param("end_date"));
  }

  var data = {
    request: req,
    response: res
  };
  this.docModel.find( {
    $and : [
      { 'logged_date' : {$gte : targetStartDate, $lte : targetEndDate} },
      { 'source' : req.query.source }
    ]},
    function (err, docs) {
      if (err) {
        data.response.send(500, err);
        dslogger.error(err);
        return;
      }

      // Send results
      console.log('Summary query returned.');
      data.response.send(201, docs);
  })
};

module.exports = UserImport;
<|endoftext|>


<commit_before>function buzzoff(){

  stories = document.getElementsByClassName("_4-u2 mbm _5jmm _5pat _5v3q _5x16 _x72");
  for(var i=0; i < stories.length; i++){
    var story = stories[i];

  }

  stories = document.getElementsByClassName("_4-u2 mbm _5jmm _5pat _5v3q _5x16");
  for(var i=0; i < stories.length; i++){
    var post = stories[i];

  }

  stories = document.getElementsByClassName("timelineUnitContainer pagesTimelinePinpost");
  for(var i=0; i < stories.length; i++){
    var post = stories[i];
    killStories(post);

  }
}

function killStories(item){
  var links = item.getElementsByClassName("fsm fwn fcg");
  if (links.value.equals("www.buzzfeed.com")){
  	item.style.opacity = "0.0";
  	item.style.display = "None";
  }

    // kill the story that contains this link
    if(linkType !== null){
      killItem(item, linkType, pageType);
    }
  }
}
<commit_msg>fixed killStories in buzzoff.js<commit_after>function buzzoff(){

  stories = document.getElementsByClassName("_4-u2 mbm _5jmm _5pat _5v3q _5x16 _x72");
  for(var i=0; i < stories.length; i++){
    var story = stories[i];
    killStories(post);
  }

  stories = document.getElementsByClassName("_4-u2 mbm _5jmm _5pat _5v3q _5x16");
  for(var i=0; i < stories.length; i++){
    var post = stories[i];
    killStories(post);
  }

  stories = document.getElementsByClassName("timelineUnitContainer pagesTimelinePinpost");
  for(var i=0; i < stories.length; i++){
    var post = stories[i];
    killStories(post);
  }

}

function killStories(item){

  elems = item.getElementsByClassName("fsm fwn fcg");
  for(var i=0; i < elems.length; i++){
    var post = stories[i];
    if (post.value.equals("www.buzzfeed.com")){
    	item.style.opacity = "0.0";
  		item.style.display = "None";
    }

}
<|endoftext|>