<commit_before>/**
 * @file DraggableListItem component
 * @author liangxiaojun(liangxiaojun@derbysoft.com)
 */

import React, {Component} from 'react';
import PropTypes from 'prop-types';

import Checkbox from '../Checkbox';
import Radio from '../Radio';
import CircularLoading from '../CircularLoading';
import TipProvider from '../TipProvider';
import Theme from '../Theme';

import Util from '../_vendors/Util';
import Position from '../_statics/Position';
import SelectMode from '../_statics/SelectMode';

export default class DraggableListItem extends Component {

    static SelectMode = SelectMode;
    static Theme = Theme;

    constructor(props, ...restArgs) {

        super(props, ...restArgs);

        this.state = {
            checked: props.checked
        };

        this.checkboxChangeHandler = ::this.checkboxChangeHandler;
        this.radioChangeHandler = ::this.radioChangeHandler;
        this.touchTapHandler = ::this.touchTapHandler;

    }

    checkboxChangeHandler(checked) {
        this.setState({
            checked
        }, () => {

            const {onSelect, onDeselect} = this.props;

            if (checked) {
                onSelect && onSelect();
            } else {
                onDeselect && onDeselect();
            }

        });
    }

    radioChangeHandler() {

        const {checked} = this.state;

        if (!checked) {
            this.setState({
                checked: true
            }, () => {
                const {onSelect} = this.props;
                onSelect && onSelect();
            });
        }

    }

    touchTapHandler(e) {

        e.preventDefault();

        const {disabled, isLoading, readOnly} = this.props;

        if (disabled || isLoading || readOnly) {
            return;
        }

        const {onTouchTap} = this.props;
        onTouchTap && onTouchTap(e);

        switch (this.props.selectMode) {
            case SelectMode.MULTI_SELECT:
                this.checkboxChangeHandler(!this.state.checked);
                return;
            case SelectMode.SINGLE_SELECT:
                this.radioChangeHandler();
                return;
        }

    }

    componentWillReceiveProps(nextProps) {
        if (nextProps.checked !== this.state.checked) {
            this.setState({
                checked: nextProps.checked
            });
        }
    }

    render() {

        const {

                index, className, style, theme, data, text, desc, iconCls, rightIconCls, tip, tipPosition,
                disabled, isLoading, renderer, itemRenderer, readOnly, anchorIconCls,

                selectTheme, selectMode, radioUncheckedIconCls, radioCheckedIconCls,
                checkboxUncheckedIconCls, checkboxCheckedIconCls, checkboxIndeterminateIconCls,

                onMouseEnter, onMouseLeave

            } = this.props,
            {checked} = this.state,

            listItemClassName = (theme ? ` theme-${theme}` : '') + (checked ? ' activated' : '')
                + (className ? ' ' + className : ''),
            loadingIconPosition = (rightIconCls && !iconCls) ? 'right' : 'left';

        return (
            <TipProvider className='block'
                         text={tip}
                         tipPosition={tipPosition}>

                    {
                        selectMode === SelectMode.SINGLE_SELECT && (radioUncheckedIconCls || radioCheckedIconCls) ?
                            <Radio className="draggable-list-item-select"
                                   theme={selectTheme}
                                   checked={checked}
                                   disabled={disabled || isLoading}
                                   uncheckedIconCls={radioUncheckedIconCls}
                                   checkedIconCls={radioCheckedIconCls}
                                   disableTouchRipple={true}/>
                            :
                            null
                    }

                    {
                        selectMode === SelectMode.MULTI_SELECT ?
                            <Checkbox className="draggable-list-item-select"
                                      theme={selectTheme}
                                      checked={checked}
                                      disabled={disabled || isLoading}
                                      uncheckedIconCls={checkboxUncheckedIconCls}
                                      checkedIconCls={checkboxCheckedIconCls}
                                      indeterminateIconCls={checkboxIndeterminateIconCls}
                                      disableTouchRipple={true}/>
                            :
                            null
                    }

                    {
                        isLoading && loadingIconPosition === 'left' ?

                                <CircularLoading className="button-loading-icon"
                                                 size="small"/>

                            :
                            (
                                iconCls ?
                                    <i className={`button-icon button-icon-left ${iconCls}`}
                                       aria-hidden="true">
                                    :
                                    null
                            )
                    }

                    {
                        itemRenderer && typeof itemRenderer === 'function' ?
                            itemRenderer(data, index)
                            :
                            (
                                renderer && typeof renderer === 'function' ?
                                    renderer(data, index)
                                    :
                                    (
                                        desc ?

                                                    {text}

                                                    {desc}

                                            :
                                            text
                                    )
                            )
                    }

                    {
                        isLoading && loadingIconPosition === 'right' ?
                            <CircularLoading className="button-icon button-icon-right button-loading-icon"
                                             size="small"/>
                            :
                            (
                                rightIconCls ?
                                    <i className={`button-icon button-icon-right ${rightIconCls}`}
                                       aria-hidden="true">
                                    :
                                    null
                            )
                    }

                    <i className={`${anchorIconCls} draggable-list-item-anchor`}
                       aria-hidden="true">

            </TipProvider>
        );

    }
};

DraggableListItem.propTypes = {

    index: PropTypes.number,

    className: PropTypes.string,
    style: PropTypes.object,
    theme: PropTypes.oneOf(Util.enumerateValue(Theme)),

    selectTheme: PropTypes.oneOf(Util.enumerateValue(Theme)),
    selectMode: PropTypes.oneOf(Util.enumerateValue(SelectMode)),

    data: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.object]),
    value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
    text: PropTypes.any,
    desc: PropTypes.string,

    disabled: PropTypes.bool,
    isLoading: PropTypes.bool,
    checked: PropTypes.bool,
    readOnly: PropTypes.bool,

    iconCls: PropTypes.string,
    rightIconCls: PropTypes.string,

    tip: PropTypes.string,
    tipPosition: PropTypes.oneOf(Util.enumerateValue(Position)),

    radioUncheckedIconCls: PropTypes.string,
    radioCheckedIconCls: PropTypes.string,
    checkboxUncheckedIconCls: PropTypes.string,
    checkboxCheckedIconCls: PropTypes.string,
    checkboxIndeterminateIconCls: PropTypes.string,

    anchorIconCls: PropTypes.string,

    itemRenderer: PropTypes.func,
    renderer: PropTypes.func,

    onTouchTap: PropTypes.func,
    onSelect: PropTypes.func,
    onDeselect: PropTypes.func,
    onMouseEnter: PropTypes.func,
    onMouseLeave: PropTypes.func

};

DraggableListItem.defaultProps = {

    index: 0,

    className: null,
    style: null,
    theme: Theme.DEFAULT,

    selectTheme: Theme.DEFAULT,
    selectMode: SelectMode.SINGLE_SELECT,

    data: null,
    value: null,
    text: null,
    desc: null,

    disabled: false,
    isLoading: false,
    checked: false,
    readOnly: false,

    iconCls: null,
    rightIconCls: null,

    tip: null,
    tipPosition: Position.BOTTOM,

    radioUncheckedIconCls: null,
    radioCheckedIconCls: null,
    checkboxUncheckedIconCls: 'fa fa-square-o',
    checkboxCheckedIconCls: 'fa fa-check-square',
    checkboxIndeterminateIconCls: 'fa fa-minus-square',

    anchorIconCls: 'fa fa-bars'

};<commit_msg>update DraggableListItem<commit_after>/**
 * @file DraggableListItem component
 * @author liangxiaojun(liangxiaojun@derbysoft.com)
 */

import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {Draggable} from 'react-beautiful-dnd';

import Checkbox from '../Checkbox';
import Radio from '../Radio';
import CircularLoading from '../CircularLoading';
import TipProvider from '../TipProvider';
import Theme from '../Theme';

import Util from '../_vendors/Util';
import Position from '../_statics/Position';
import SelectMode from '../_statics/SelectMode';

export default class DraggableListItem extends Component {

    static SelectMode = SelectMode;
    static Theme = Theme;

    constructor(props, ...restArgs) {

        super(props, ...restArgs);

        this.state = {
            checked: props.checked
        };

        this.checkboxChangeHandler = ::this.checkboxChangeHandler;
        this.radioChangeHandler = ::this.radioChangeHandler;
        this.touchTapHandler = ::this.touchTapHandler;

    }

    checkboxChangeHandler(checked) {
        this.setState({
            checked
        }, () => {

            const {onSelect, onDeselect} = this.props;

            if (checked) {
                onSelect && onSelect();
            } else {
                onDeselect && onDeselect();
            }

        });
    }

    radioChangeHandler() {

        const {checked} = this.state;

        if (!checked) {
            this.setState({
                checked: true
            }, () => {
                const {onSelect} = this.props;
                onSelect && onSelect();
            });
        }

    }

    touchTapHandler(e) {

        e.preventDefault();

        const {disabled, isLoading, readOnly} = this.props;

        if (disabled || isLoading || readOnly) {
            return;
        }

        const {onTouchTap} = this.props;
        onTouchTap && onTouchTap(e);

        switch (this.props.selectMode) {
            case SelectMode.MULTI_SELECT:
                this.checkboxChangeHandler(!this.state.checked);
                return;
            case SelectMode.SINGLE_SELECT:
                this.radioChangeHandler();
                return;
        }

    }

    componentWillReceiveProps(nextProps) {
        if (nextProps.checked !== this.state.checked) {
            this.setState({
                checked: nextProps.checked
            });
        }
    }

    render() {

        const {

                index, className, style, theme, data, text, desc, iconCls, rightIconCls, tip, tipPosition,
                disabled, isLoading, renderer, itemRenderer, readOnly, anchorIconCls,

                selectTheme, selectMode, radioUncheckedIconCls, radioCheckedIconCls,
                checkboxUncheckedIconCls, checkboxCheckedIconCls, checkboxIndeterminateIconCls,

                onMouseEnter, onMouseLeave

            } = this.props,
            {checked} = this.state,

            listItemClassName = (theme ? ` theme-${theme}` : '') + (checked ? ' activated' : '')
                + (className ? ' ' + className : ''),
            loadingIconPosition = (rightIconCls && !iconCls) ? 'right' : 'left';

        return (
            <Draggable key={index}
                       draggableId={index}>
                {
                    dragProvided => (

                                <TipProvider className='block'
                                             text={tip}
                                             tipPosition={tipPosition}>

                                        {
                                            selectMode === SelectMode.SINGLE_SELECT && (radioUncheckedIconCls || radioCheckedIconCls) ?
                                                <Radio className="draggable-list-item-select"
                                                       theme={selectTheme}
                                                       checked={checked}
                                                       disabled={disabled || isLoading}
                                                       uncheckedIconCls={radioUncheckedIconCls}
                                                       checkedIconCls={radioCheckedIconCls}
                                                       disableTouchRipple={true}/>
                                                :
                                                null
                                        }

                                        {
                                            selectMode === SelectMode.MULTI_SELECT ?
                                                <Checkbox className="draggable-list-item-select"
                                                          theme={selectTheme}
                                                          checked={checked}
                                                          disabled={disabled || isLoading}
                                                          uncheckedIconCls={checkboxUncheckedIconCls}
                                                          checkedIconCls={checkboxCheckedIconCls}
                                                          indeterminateIconCls={checkboxIndeterminateIconCls}
                                                          disableTouchRipple={true}/>
                                                :
                                                null
                                        }

                                        {
                                            isLoading && loadingIconPosition === 'left' ?

                                                    <CircularLoading className="button-loading-icon"
                                                                     size="small"/>

                                                :
                                                (
                                                    iconCls ?
                                                        <i className={`button-icon button-icon-left ${iconCls}`}
                                                           aria-hidden="true">
                                                        :
                                                        null
                                                )
                                        }

                                        {
                                            itemRenderer && typeof itemRenderer === 'function' ?
                                                itemRenderer(data, index)
                                                :
                                                (
                                                    renderer && typeof renderer === 'function' ?
                                                        renderer(data, index)
                                                        :
                                                        (
                                                            desc ?

                                                                        {text}

                                                                        {desc}

                                                                :
                                                                text
                                                        )
                                                )
                                        }

                                        {
                                            isLoading && loadingIconPosition === 'right' ?
                                                <CircularLoading
                                                    className="button-icon button-icon-right button-loading-icon"
                                                    size="small"/>
                                                :
                                                (
                                                    rightIconCls ?
                                                        <i className={`button-icon button-icon-right ${rightIconCls}`}
                                                           aria-hidden="true">
                                                        :
                                                        null
                                                )
                                        }

                                        <i className={`${anchorIconCls} draggable-list-item-anchor`}
                                           aria-hidden="true">

                                </TipProvider>

                            {dragProvided.placeholder}

                    )
                }
            </Draggable>
        );

    }
};

DraggableListItem.propTypes = {

    index: PropTypes.number,

    className: PropTypes.string,
    style: PropTypes.object,
    theme: PropTypes.oneOf(Util.enumerateValue(Theme)),

    selectTheme: PropTypes.oneOf(Util.enumerateValue(Theme)),
    selectMode: PropTypes.oneOf(Util.enumerateValue(SelectMode)),

    data: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.object]),
    value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
    text: PropTypes.any,
    desc: PropTypes.string,

    disabled: PropTypes.bool,
    isLoading: PropTypes.bool,
    checked: PropTypes.bool,
    readOnly: PropTypes.bool,

    iconCls: PropTypes.string,
    rightIconCls: PropTypes.string,

    tip: PropTypes.string,
    tipPosition: PropTypes.oneOf(Util.enumerateValue(Position)),

    radioUncheckedIconCls: PropTypes.string,
    radioCheckedIconCls: PropTypes.string,
    checkboxUncheckedIconCls: PropTypes.string,
    checkboxCheckedIconCls: PropTypes.string,
    checkboxIndeterminateIconCls: PropTypes.string,

    anchorIconCls: PropTypes.string,

    itemRenderer: PropTypes.func,
    renderer: PropTypes.func,

    onTouchTap: PropTypes.func,
    onSelect: PropTypes.func,
    onDeselect: PropTypes.func,
    onMouseEnter: PropTypes.func,
    onMouseLeave: PropTypes.func

};

DraggableListItem.defaultProps = {

    index: 0,

    className: null,
    style: null,
    theme: Theme.DEFAULT,

    selectTheme: Theme.DEFAULT,
    selectMode: SelectMode.SINGLE_SELECT,

    data: null,
    value: null,
    text: null,
    desc: null,

    disabled: false,
    isLoading: false,
    checked: false,
    readOnly: false,

    iconCls: null,
    rightIconCls: null,

    tip: null,
    tipPosition: Position.BOTTOM,

    radioUncheckedIconCls: null,
    radioCheckedIconCls: null,
    checkboxUncheckedIconCls: 'fa fa-square-o',
    checkboxCheckedIconCls: 'fa fa-check-square',
    checkboxIndeterminateIconCls: 'fa fa-minus-square',

    anchorIconCls: 'fa fa-bars'

};<|endoftext|>


<commit_before>$(document).ready(function() {
  "use strict";
  var av,
      settings = new JSAV.utils.Settings($(".jsavsettings"));

  function showSteps(steps) {
    var array = av.ds.array(new Array(steps[0].ind.length), {indexed: true, layout: "bar"});

    // redos the steps the student has done based on the log
    steps.forEach(function(step, index) {
      av.umsg(JSON.stringify(step));
      if (index === 0) {
        array.state(step);
      } else {
        var swapIndex = -1;
        step.ind.forEach(function(element, i) {
          if (array.value(i) !== element.v) {
            if (swapIndex === -1) {
              swapIndex = i;
            } else {
              // we assume there will only be one swap per step
              array.swap(swapIndex, i);
            }
          }
          array.index(i).element.attr("class").split(" ").forEach(function(cls) {
            if (cls.indexOf("jsavnode") === -1 && cls.indexOf("jsavindex") === -1 && (!element.cls || element.cls.indexOf(cls) === -1)) {
              array.removeClass(i, cls);
            }
          });
          if (element.cls) {
            element.cls.forEach(function(cls) {
              array.addClass(i, cls);
            });
          }
        });
      }
      array.layout();
      if (index === 0) {
        av.displayInit();
      } else {
        av.step();
      }
    });
  }

  function reset() {
    $(".jsavcanvas").remove();
    $(".jsavcontrols").html("");
    $(".jsavcounter").html("");
    $(".jsavoutput").html("");
  }

  // Execute the "Run" button function
  function runIt() {
    var steps = JSON.parse($("#json-answer").val()).answer;

    reset();
    av = new JSAV($(".avcontainer"), {settings: settings});
    // Create a new array using the layout the user has selected
    showSteps(steps);
    av.recorded(); // mark the end
  }

  // Connect action callbacks to the HTML entities
  $("#run").click(runIt);
  $("#reset").click(function() {
    reset();
    $("#json-answer").val("");
  });
});
<commit_msg>Added code to quicksort2PROSubmissionChecker<commit_after>$(document).ready(function() {
  "use strict";
  var av,
      settings = new JSAV.utils.Settings($(".jsavsettings"));

  function showSteps(steps) {
    var array = av.ds.array(new Array(steps[0].ind.length), {indexed: true, layout: "bar"});
    var pseudo = av.code({
      url: "../../SourceCode/Processing/Sorting/Quicksort.pde",
      startAfter: "/* *** ODSATag: partition *** */",
      endBefore: "/* *** ODSAendTag: Quicksort *** */",
      tags: {
        comments_and_findpivot: [9, 11, 12, 13, 14, 15, 16]
      }
    });
    pseudo.hide("comments_and_findpivot");

    // redos the steps the student has done based on the log
    steps.forEach(function(step, index) {
      av.umsg(JSON.stringify(step));
      if (index === 0) {
        array.state(step);
        array.element.css("top", 0);
      } else {
        var swapIndex = -1;
        step.ind.forEach(function(element, i) {
          if (array.value(i) !== element.v) {
            if (swapIndex === -1) {
              swapIndex = i;
            } else {
              // we assume there will only be one swap per step
              array.swap(swapIndex, i);
            }
          }
          array.index(i).element.attr("class").split(" ").forEach(function(cls) {
            if (cls.indexOf("jsavnode") === -1 && cls.indexOf("jsavindex") === -1 && (!element.cls || element.cls.indexOf(cls) === -1)) {
              array.removeClass(i, cls);
            }
          });
          if (element.cls) {
            element.cls.forEach(function(cls) {
              array.addClass(i, cls);
            });
          }
        });
      }
      array.layout();
      if (index === 0) {
        av.displayInit();
      } else {
        av.step();
      }
    });
  }

  function reset() {
    $(".jsavcanvas").remove();
    $(".jsavcontrols").html("");
    $(".jsavcounter").html("");
    $(".jsavoutput").html("");
  }

  // Execute the "Run" button function
  function runIt() {
    var steps = JSON.parse($("#json-answer").val()).answer;

    reset();
    av = new JSAV($(".avcontainer"), {settings: settings});
    // Create a new array using the layout the user has selected
    showSteps(steps);
    av.recorded(); // mark the end
  }

  // Connect action callbacks to the HTML entities
  $("#run").click(runIt);
  $("#reset").click(function() {
    reset();
    $("#json-answer").val("");
  });
});
<|endoftext|>


<commit_before>import isString from 'vanillajs-helpers/isString';

import isDOMNode from './isDOMNode';
import isDOMContainer from './isDOMContainer';
import selectorToHTML from './selectorToHTML';

/**
 * Prepend DOM element or plain HTML into a given DOM element
 * @function prepend
 * @param {HTMLElement} elm - The DOM element to prepend into
 * @param {String|HTMLElement} insertElm - DOM element or String to prepend to the {elm}
 * @return {HTMLElement|null} The inserted child element
 */
export default function prepend(elm, insertElm) {
  if(!isDOMContainer(elm)) { return null; }

  if(isDOMNode(insertElm)) {
    elm.insertBefore(insertElm, elm.firstChild);
  } else if(isString(insertElm)) {
    if(insertElm.indexOf('<') < 0) {
      insertElm = selectorToHTML(insertElm);
    }

    elm.insertAdjacentHTML('afterbegin', insertElm);
  } else {
    return null;
  }

  return elm.firstChild;
}
<commit_msg>prepend: Check for existing prepend method<commit_after>import isString from 'vanillajs-helpers/isString';

import isDOMNode from './isDOMNode';
import isDOMContainer from './isDOMContainer';
import selectorToHTML from './selectorToHTML';

/**
 * Prepend DOM element or plain HTML into a given DOM element
 * @function prepend
 * @param {HTMLElement} elm - The DOM element to prepend into
 * @param {String|HTMLElement} insertElm - DOM element or String to prepend to the {elm}
 * @return {HTMLElement|null} The inserted child element
 */
export default function prepend(elm, insertElm) {
  if(!isDOMContainer(elm)) { return null; }

  if(isDOMNode(insertElm)) {
    if(elm.prepend) {
      elm.prepend(insertElm);
    } else {
      elm.insertBefore(insertElm, elm.firstChild);
    }
  } else if(isString(insertElm)) {
    if(insertElm.indexOf('<') < 0) {
      insertElm = selectorToHTML(insertElm);
    }

    elm.insertAdjacentHTML('afterbegin', insertElm);
  } else {
    return null;
  }

  return elm.firstChild;
}
<|endoftext|>


<commit_before>define(function(require) {

    'use strict';

    var _           = require('underscore')
      , Base        = require('app/views/Base')
      , log         = require('app/utils/bows.min')('Views:TermsAndConditions')

    return Base.extend({

        template: _.template(require('text!tpl/TermsAndConditions.html')),

        requiresLogin: true,
        termsSigned: false,

        title: 'Terms and Conditions',

        events: {
          'click .js-terms': 'setTerms'
        },

        initialize: function (options) {
          this.router = options.router

          if (!this.router.isLoggedIn()) {
            return
          }
        },

        setTerms: function() {
          if (this.$el.find('.js-terms').is(':checked')) {
            this.complete()
          }
        },

        complete: function() {
          this.router.showChannelList()
        },

        render: function() {
          if (localStorage.getItem('terms')) {
            log('User has signed terms and conditions already')
            return this.complete()
          }
          this.$el.html(this.template)
          return this
        }

    })

})
<commit_msg>fix rules: add className to view<commit_after>define(function(require) {

    'use strict';

    var _           = require('underscore')
      , Base        = require('app/views/Base')
      , log         = require('app/utils/bows.min')('Views:TermsAndConditions')

    return Base.extend({

        template: _.template(require('text!tpl/TermsAndConditions.html')),

        requiresLogin: true,
        termsSigned: false,

        title: 'Terms and Conditions',

        events: {
          'click .js-terms': 'setTerms'
        },

        className: 'rules screen',

        initialize: function (options) {
          this.router = options.router

          if (!this.router.isLoggedIn()) {
            return
          }
        },

        setTerms: function() {
          if (this.$el.find('.js-terms').is(':checked')) {
            this.complete()
          }
        },

        complete: function() {
          this.router.showChannelList()
        },

        render: function() {
          if (localStorage.getItem('terms')) {
            log('User has signed terms and conditions already')
            return this.complete()
          }
          this.$el.html(this.template)
          return this
        }

    })

})
<|endoftext|>


<commit_before>/**
 * Copyright 2021, Google, 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.
 */

'use strict';

async function main(projectId, location, processorId, filePath) {
  // [START documentai_process_specialized_document]
  /**
   * TODO(developer): Uncomment these variables before running the sample.
   */
  // const projectId = 'YOUR_PROJECT_ID';
  // const location = 'YOUR_PROJECT_LOCATION'; // Format is 'us' or 'eu'
  // const processorId = 'YOUR_PROCESSOR_ID'; // Create processor in Cloud Console
  // const filePath = '/path/to/local/pdf';

  const {DocumentProcessorServiceClient} =
    require('@google-cloud/documentai').v1beta3;

  // Instantiates a client
  const client = new DocumentProcessorServiceClient();

  async function processDocument() {
    // The full resource name of the processor, e.g.:
    // projects/project-id/locations/location/processor/processor-id
    // You must create new processors in the Cloud Console first
    const name = `projects/${projectId}/locations/${location}/processors/${processorId}`;

    // Read the file into memory.
    const fs = require('fs').promises;
    const imageFile = await fs.readFile(filePath);

    // Convert the image data to a Buffer and base64 encode it.
    const encodedImage = Buffer.from(imageFile).toString('base64');

    const request = {
      name,
      rawDocument: {
        content: encodedImage,
        mimeType: 'application/pdf',
      },
    };

    // Recognizes text entities in the PDF document
    const [result] = await client.processDocument(request);

    console.log('Document processing complete.');

    // Read fields specificly from the specalized US drivers license processor:
    // https://cloud.google.com/document-ai/docs/processors-list#processor_us-driver-license-parser
    // retriving data from other specalized processors follow a similar pattern.
    // For a complete list of processors see:
    // https://cloud.google.com/document-ai/docs/processors-list
    //
    // OCR and other data is also present in the quality processor's response.
    // Please see the OCR and other samples for how to parse other data in the
    // response.
    const {document} = result;
    for (const entity of document.entities) {
      // Fields detected. For a full list of fields for each processor see
      // the processor documentation:
      // https://cloud.google.com/document-ai/docs/processors-list
      const key = entity.type;
      // some other value formats in addition to text are availible
      // e.g. dates: `entity.normalizedValue.dateValue.year`
      const textValue = entity.textAnchor.content;
      const conf = entity.confidence * 100;
      console.log(
        `* ${JSON.stringify(key)}: ${JSON.stringify(textValue)}(${conf.toFixed(
          2
        )}% confident)`
      );
    }
  }

  // [END documentai_process_specialized_document]
  await processDocument();
}

main(...process.argv.slice(2)).catch(err => {
  console.error(err);
  process.exitCode = 1;
});
<commit_msg>samples: added default value for undefined (#287)<commit_after>/**
 * Copyright 2021, Google, 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.
 */

'use strict';

async function main(projectId, location, processorId, filePath) {
  // [START documentai_process_specialized_document]
  /**
   * TODO(developer): Uncomment these variables before running the sample.
   */
  // const projectId = 'YOUR_PROJECT_ID';
  // const location = 'YOUR_PROJECT_LOCATION'; // Format is 'us' or 'eu'
  // const processorId = 'YOUR_PROCESSOR_ID'; // Create processor in Cloud Console
  // const filePath = '/path/to/local/pdf';

  const {DocumentProcessorServiceClient} =
    require('@google-cloud/documentai').v1beta3;

  // Instantiates a client
  const client = new DocumentProcessorServiceClient();

  async function processDocument() {
    // The full resource name of the processor, e.g.:
    // projects/project-id/locations/location/processor/processor-id
    // You must create new processors in the Cloud Console first
    const name = `projects/${projectId}/locations/${location}/processors/${processorId}`;

    // Read the file into memory.
    const fs = require('fs').promises;
    const imageFile = await fs.readFile(filePath);

    // Convert the image data to a Buffer and base64 encode it.
    const encodedImage = Buffer.from(imageFile).toString('base64');

    const request = {
      name,
      rawDocument: {
        content: encodedImage,
        mimeType: 'application/pdf',
      },
    };

    // Recognizes text entities in the PDF document
    const [result] = await client.processDocument(request);

    console.log('Document processing complete.');

    // Read fields specificly from the specalized US drivers license processor:
    // https://cloud.google.com/document-ai/docs/processors-list#processor_us-driver-license-parser
    // retriving data from other specalized processors follow a similar pattern.
    // For a complete list of processors see:
    // https://cloud.google.com/document-ai/docs/processors-list
    //
    // OCR and other data is also present in the quality processor's response.
    // Please see the OCR and other samples for how to parse other data in the
    // response.
    const {document} = result;
    for (const entity of document.entities) {
      // Fields detected. For a full list of fields for each processor see
      // the processor documentation:
      // https://cloud.google.com/document-ai/docs/processors-list
      const key = entity.type;
      // some other value formats in addition to text are availible
      // e.g. dates: `entity.normalizedValue.dateValue.year`
      const textValue =
        entity.textAnchor !== null ? entity.textAnchor.content : '';
      const conf = entity.confidence * 100;
      console.log(
        `* ${JSON.stringify(key)}: ${JSON.stringify(textValue)}(${conf.toFixed(
          2
        )}% confident)`
      );
    }
  }

  // [END documentai_process_specialized_document]
  await processDocument();
}

main(...process.argv.slice(2)).catch(err => {
  console.error(err);
  process.exitCode = 1;
});
<|endoftext|>


<commit_before>const Fuse = require("fuse.js");
const levenshtein = require("fast-levenshtein");
const { createVaultFacade } = require("../facades/vault.js");
const { fieldsToProperties } = require("../facades/entry.js");
const { ENTRY_URL_TYPE_GENERAL, getEntryURLs } = require("../tools/entry.js");
const StorageInterface = require("../storage/StorageInterface.js");

function domainsRelated(domain1, domain2) {
    if (domain1 === domain2) return true;
    if (domain1.length > domain2.length) {
        const ind = domain1.indexOf(domain2);
        return ind === domain1.length - domain2.length;
    } else {
        const ind = domain2.indexOf(domain1);
        return ind === domain2.length - domain1.length;
    }
}

function extractDomain(str) {
    const match = /^((https?|ftp):\/\/)?([^\/]+)/i.exec(str);
    return (match && match[3]) || "";
}

/**
 * @typedef {Object} SearchResult
 * @property {String} id The entry ID
 * @property {Object.<String, String>} properties Entry properties
 * @property {Array.<String>} urls Entry URLs
 * @property {String} vaultID The ID of the containing vault
 */

/**
 * Search class for searching entries
 * @memberof module:Buttercup
 */
class Search {
    constructor(vaults, memory = new StorageInterface()) {
        this._vaults = vaults;
        this._entries = [];
        this._fuse = null;
        this._memory = memory;
        this._scores = {};
        this._results = [];
    }

    /**
     * Last search results
     * @type {Array.<SearchResult>}
     */
    get results() {
        return this._results;
    }

    /**
     * Increment the score of a URL in an entry
     * @param {String} vaultID The vault ID
     * @param {String} entryID The entry ID
     * @param {String} url The URL to increment for
     */
    async incrementScore(vaultID, entryID, url) {
        const scoresRaw = await this._memory.getValue(`bcup_search_${vaultID}`);
        let vaultScore = {};
        if (scoresRaw) {
            try {
                vaultScore = JSON.parse(scoresRaw);
            } catch (err) {}
        }
        const domain = extractDomain(url);
        if (!domain) return;
        vaultScore[entryID] = vaultScore[entryID] || {};
        vaultScore[entryID][domain] = vaultScore[entryID][domain] ? vaultScore[entryID][domain] + 1 : 1;
        await this._memory.setValue(`bcup_search_${vaultID}`, JSON.stringify(vaultScore));
    }

    /**
     * Prepare the search instance by processing
     * entries
     */
    async prepare() {
        this._entries = [];
        this._scores = {};
        for (const vault of this._vaults) {
            // Get scores
            const scoresRaw = await this._memory.getValue(`bcup_search_${vault.id}`);
            let vaultScore = {};
            if (scoresRaw) {
                try {
                    const scores = JSON.parse(scoresRaw);
                    vaultScore = this._scores[vault.id] = scores;
                } catch (err) {}
            }
            // Get entries
            const { entries } = createVaultFacade(vault);
            this._entries.push(
                ...entries.map(entry => {
                    const properties = fieldsToProperties(entry.fields);
                    const urls = getEntryURLs(properties, ENTRY_URL_TYPE_GENERAL);
                    return {
                        id: entry.id,
                        properties,
                        urls,
                        vaultID: vault.id,
                        domainScores: vaultScore[entry.id] || {}
                    };
                })
            );
        }
    }

    /**
     * Search for entries by term
     * @param {String} term
     * @returns {Array.<SearchResult>}
     */
    searchByTerm(term) {
        this._fuse = new Fuse(this._entries, {
            includeScore: true,
            keys: [
                {
                    name: "properties.title",
                    weight: 0.6
                },
                {
                    name: "properties.username",
                    weight: 0.25
                },
                {
                    name: "urls",
                    weight: 0.15
                }
            ],
            shouldSort: true,
            threshold: 0.5
        });
        this._results = this._fuse.search(term).map(result => result.item);
        return this._results;
    }

    /**
     * Search for entries by URL
     * @param {String} url
     * @returns {Array.<SearchResult>}
     */
    searchByURL(url) {
        const incomingDomain = extractDomain(url);
        if (!incomingDomain) {
            this._results = [];
            return this._results;
        }
        const results = this._entries.reduce((output, entryItem) => {
            let bestScore = Infinity;
            const bestURL = entryItem.urls.reduce((best, next) => {
                const thisDomain = extractDomain(next);
                if (!thisDomain) return best;
                if (!domainsRelated(thisDomain, incomingDomain)) return best;
                const score = levenshtein.get(next, url);
                if (score < bestScore) {
                    bestScore = score;
                    return next;
                }
                return best;
            }, null);
            if (!bestURL) return output;
            const resultDomain = extractDomain(bestURL);
            return [
                ...output,
                {
                    item: entryItem,
                    score: bestScore,
                    url: bestURL,
                    domainScore: Math.max(
                        entryItem.domainScores[incomingDomain] || 0,
                        entryItem.domainScores[resultDomain] || 0
                    )
                }
            ];
        }, []);
        this._results = results
            .sort((a, b) => {
                if (a.domainScore > b.domainScore) {
                    return -1;
                } else if (b.domainScore > b.domainScore) {
                    return 1;
                }
                if (a.score > b.score) {
                    return 1;
                } else if (b.score > a.score) {
                    return -1;
                }
                return 0;
            })
            .map(result => result.item);
        return this._results;
    }
}

module.exports = Search;
<commit_msg>Fix Fuse.js in search on web<commit_after>const FuseImport = require("fuse.js");
const levenshtein = require("fast-levenshtein");
const { createVaultFacade } = require("../facades/vault.js");
const { fieldsToProperties } = require("../facades/entry.js");
const { ENTRY_URL_TYPE_GENERAL, getEntryURLs } = require("../tools/entry.js");
const StorageInterface = require("../storage/StorageInterface.js");

function domainsRelated(domain1, domain2) {
    if (domain1 === domain2) return true;
    if (domain1.length > domain2.length) {
        const ind = domain1.indexOf(domain2);
        return ind === domain1.length - domain2.length;
    } else {
        const ind = domain2.indexOf(domain1);
        return ind === domain2.length - domain1.length;
    }
}

function extractDomain(str) {
    const match = /^((https?|ftp):\/\/)?([^\/]+)/i.exec(str);
    return (match && match[3]) || "";
}

/**
 * @typedef {Object} SearchResult
 * @property {String} id The entry ID
 * @property {Object.<String, String>} properties Entry properties
 * @property {Array.<String>} urls Entry URLs
 * @property {String} vaultID The ID of the containing vault
 */

/**
 * Search class for searching entries
 * @memberof module:Buttercup
 */
class Search {
    constructor(vaults, memory = new StorageInterface()) {
        this._vaults = vaults;
        this._entries = [];
        this._fuse = null;
        this._memory = memory;
        this._scores = {};
        this._results = [];
    }

    /**
     * Last search results
     * @type {Array.<SearchResult>}
     */
    get results() {
        return this._results;
    }

    /**
     * Increment the score of a URL in an entry
     * @param {String} vaultID The vault ID
     * @param {String} entryID The entry ID
     * @param {String} url The URL to increment for
     */
    async incrementScore(vaultID, entryID, url) {
        const scoresRaw = await this._memory.getValue(`bcup_search_${vaultID}`);
        let vaultScore = {};
        if (scoresRaw) {
            try {
                vaultScore = JSON.parse(scoresRaw);
            } catch (err) {}
        }
        const domain = extractDomain(url);
        if (!domain) return;
        vaultScore[entryID] = vaultScore[entryID] || {};
        vaultScore[entryID][domain] = vaultScore[entryID][domain] ? vaultScore[entryID][domain] + 1 : 1;
        await this._memory.setValue(`bcup_search_${vaultID}`, JSON.stringify(vaultScore));
    }

    /**
     * Prepare the search instance by processing
     * entries
     */
    async prepare() {
        this._entries = [];
        this._scores = {};
        for (const vault of this._vaults) {
            // Get scores
            const scoresRaw = await this._memory.getValue(`bcup_search_${vault.id}`);
            let vaultScore = {};
            if (scoresRaw) {
                try {
                    const scores = JSON.parse(scoresRaw);
                    vaultScore = this._scores[vault.id] = scores;
                } catch (err) {}
            }
            // Get entries
            const { entries } = createVaultFacade(vault);
            this._entries.push(
                ...entries.map(entry => {
                    const properties = fieldsToProperties(entry.fields);
                    const urls = getEntryURLs(properties, ENTRY_URL_TYPE_GENERAL);
                    return {
                        id: entry.id,
                        properties,
                        urls,
                        vaultID: vault.id,
                        domainScores: vaultScore[entry.id] || {}
                    };
                })
            );
        }
    }

    /**
     * Search for entries by term
     * @param {String} term
     * @returns {Array.<SearchResult>}
     */
    searchByTerm(term) {
        let Fuse = FuseImport;
        if (typeof BUTTERCUP_WEB === "boolean" && BUTTERCUP_WEB === true) {
            Fuse = FuseImport.default;
        }
        this._fuse = new Fuse(this._entries, {
            includeScore: true,
            keys: [
                {
                    name: "properties.title",
                    weight: 0.6
                },
                {
                    name: "properties.username",
                    weight: 0.25
                },
                {
                    name: "urls",
                    weight: 0.15
                }
            ],
            shouldSort: true,
            threshold: 0.5
        });
        this._results = this._fuse.search(term).map(result => result.item);
        return this._results;
    }

    /**
     * Search for entries by URL
     * @param {String} url
     * @returns {Array.<SearchResult>}
     */
    searchByURL(url) {
        const incomingDomain = extractDomain(url);
        if (!incomingDomain) {
            this._results = [];
            return this._results;
        }
        const results = this._entries.reduce((output, entryItem) => {
            let bestScore = Infinity;
            const bestURL = entryItem.urls.reduce((best, next) => {
                const thisDomain = extractDomain(next);
                if (!thisDomain) return best;
                if (!domainsRelated(thisDomain, incomingDomain)) return best;
                const score = levenshtein.get(next, url);
                if (score < bestScore) {
                    bestScore = score;
                    return next;
                }
                return best;
            }, null);
            if (!bestURL) return output;
            const resultDomain = extractDomain(bestURL);
            return [
                ...output,
                {
                    item: entryItem,
                    score: bestScore,
                    url: bestURL,
                    domainScore: Math.max(
                        entryItem.domainScores[incomingDomain] || 0,
                        entryItem.domainScores[resultDomain] || 0
                    )
                }
            ];
        }, []);
        this._results = results
            .sort((a, b) => {
                if (a.domainScore > b.domainScore) {
                    return -1;
                } else if (b.domainScore > b.domainScore) {
                    return 1;
                }
                if (a.score > b.score) {
                    return 1;
                } else if (b.score > a.score) {
                    return -1;
                }
                return 0;
            })
            .map(result => result.item);
        return this._results;
    }
}

module.exports = Search;
<|endoftext|>


<commit_before>/** @module lib/subscriptions */

import config from 'config';
import moment from 'moment';
import { Op } from 'sequelize';

import models from '../models';
import emailLib from './email';
import * as paymentsLib from './payments';
import { getRecommendedCollectives } from './data';
import status from '../constants/order_status';

/** Maximum number of attempts before an order gets cancelled. */
export const MAX_RETRIES = 5;

/** Find all orders with subscriptions that are active & due.
 *
 * Subscriptions are considered due if their `nextChargeDate` is
 * already past.
 */
export async function ordersWithPendingCharges() {
  return models.Order.findAll({
    where: {
      SubscriptionId: { [Op.ne]: null },
      deletedAt: null,
    },
    include: [
      { model: models.User, as: 'createdByUser' },
      { model: models.Collective, as: 'collective' },
      { model: models.Collective, as: 'fromCollective' },
      { model: models.PaymentMethod, as: 'paymentMethod' },
      {
        model: models.Subscription,
        where: {
          isActive: true,
          deletedAt: null,
          deactivatedAt: null,
          activatedAt: { [Op.lte]: new Date() },
          nextChargeDate: { [Op.lte]: new Date() },
        },
      },
    ],
  });
}

function hasReachedQuantity(order) {
  return order.Subscription.chargeNumber !== null && order.Subscription.chargeNumber === order.Subscription.quantity;
}

/** Process order and trigger result handlers.
 *
 * Uses `lib.payments.processOrder()` to charge subscription and
 * handle both success and failure of that processing.
 */
export async function processOrderWithSubscription(options, order) {
  const csvEntry = {
    orderId: order.id,
    subscriptionId: order.Subscription.id,
    amount: order.totalAmount,
    from: order.fromCollective.slug,
    to: order.collective.slug,
    status: null,
    error: null,
    retriesBefore: order.Subscription.chargeRetryCount,
    retriesAfter: null,
    chargeDateBefore: dateFormat(order.Subscription.nextCharge),
    chargeDateAfter: null,
    nextPeriodStartBefore: dateFormat(order.Subscription.nextPeriodStart),
    nextPeriodStartAfter: null,
  };

  let orderProcessedStatus = 'unattempted';
  let collectiveIsArchived = false;
  let creditCardNeedsConfirmation = false;
  let transaction;

  if (!options.dryRun) {
    if (hasReachedQuantity(order)) {
      orderProcessedStatus = 'failure';
      csvEntry.error = 'Your subscription is over';
      cancelSubscription(order);
    } else if (order.collective.deactivatedAt) {
      // This means the collective has been archived and the subscription should be cancelled.
      orderProcessedStatus = 'failure';
      csvEntry.error = 'The collective has been archived';
      collectiveIsArchived = true;
      cancelSubscription(order);
    } else {
      try {
        transaction = await paymentsLib.processOrder(order);
        orderProcessedStatus = 'success';
      } catch (error) {
        if (error.stripeResponse && error.stripeResponse.paymentIntent) {
          creditCardNeedsConfirmation = true;
        }
        orderProcessedStatus = 'failure';
        csvEntry.error = error.message;
        order.status = status.ERROR;
        order.data = order.data || {};
        order.data.latestError = error.message;
      }

      order.Subscription.chargeRetryCount = getChargeRetryCount(orderProcessedStatus, order);
      order.Subscription = Object.assign(
        order.Subscription,
        getNextChargeAndPeriodStartDates(orderProcessedStatus, order),
      );

      if (orderProcessedStatus === 'success') {
        if (order.Subscription.chargeNumber !== null) {
          order.Subscription.chargeNumber += 1;
        }
        order.status = status.ACTIVE;
      }
    }
  }

  csvEntry.status = orderProcessedStatus;
  csvEntry.retriesAfter = order.Subscription.chargeRetryCount;
  csvEntry.chargeDateAfter = dateFormat(order.Subscription.nextChargeDate);
  csvEntry.nextPeriodStartAfter = dateFormat(order.Subscription.nextPeriodStart);

  if (!options.dryRun) {
    try {
      if (collectiveIsArchived) {
        await sendArchivedCollectiveEmail(order);
      } else if (creditCardNeedsConfirmation) {
        await sendCreditCardConfirmationEmail(order);
      } else {
        await handleRetryStatus(order, transaction);
      }
    } catch (error) {
      console.log(`Error notifying order #${order.id} ${error}`);
    } finally {
      await order.Subscription.save();
      await order.save();
    }
  }

  return csvEntry;
}

/** Standard way to format dates in this script */
function dateFormat(date) {
  return moment(date).format();
}

/** Handle processing result.
 *
 * The result of processing an order is stored within the field
 * `chargeRetryCount`. This function handles the following values for
 * this variable:
 *
 *   1. zero(0): Means success. The counter was reset after a
 *      successful processing.
 *
 *   2. MAX_RETRIES: The order will be cancelled because it reached
 *      the maximum number of retries and the payment method doesn't
 *      work.
 *
 *   3. WARN_USER: The last attempt failed. Warn user about the
 *      failure and allow them to update the payment method.
 */
export async function handleRetryStatus(order, transaction) {
  switch (order.Subscription.chargeRetryCount) {
    case 0:
      await sendThankYouEmail(order, transaction);
      break;
    case MAX_RETRIES:
      await cancelSubscriptionAndNotifyUser(order);
      break;
    default:
      await sendFailedEmail(order, false);
      break;
  }
}

/** Get the date an order should be charged again and it's next period start date
 *
 * The status defines how much time it will take until the same
 * subscription can be charged again. Currently supported status
 * values:
 *
 *   0. new: 1st day of the next month for monthly, 1st day of the
 *      same month of the next year for yearly.
 *   1. success: Increment date by 1 month for monthly or 1 year for
 *      yearly subscriptions
 *   2. failure: Two days after today.
 */
export function getNextChargeAndPeriodStartDates(status, order) {
  const initial = order.Subscription.nextPeriodStart || order.Subscription.createdAt;
  let nextChargeDate = moment(initial);
  const response = {};
  if (status === 'new' || status === 'success') {
    if (order.Subscription.interval === 'month') {
      nextChargeDate.add(1, 'months');
    } else if (order.Subscription.interval === 'year') {
      nextChargeDate.add(1, 'years');
    }
    if (status === 'new') {
      nextChargeDate.startOf('month');
    }
    response.nextPeriodStart = nextChargeDate.toDate();
  } else if (status === 'failure') {
    if (order.Subscription.chargeRetryCount >= 2) {
      nextChargeDate = moment(new Date()).add(5, 'days');
    } else {
      nextChargeDate = moment(new Date()).add(2, 'days');
    }
  } else if (status === 'updated') {
    // used when user updates payment method
    nextChargeDate = moment(new Date()); // sets next charge date to now
  }
  response.nextChargeDate = nextChargeDate.toDate();
  return response;
}

/** Update counter that records retry attempts.
 *
 * When status is 'failure', `order.Subscription.chargeRetryCount` is
 * incremented by one. The counter is reset to zero if the status is
 * 'success'.
 */
export function getChargeRetryCount(status, order) {
  return status === 'success' || status === 'updated' ? 0 : order.Subscription.chargeRetryCount + 1;
}

/** Cancel subscription
 *
 * The `isActive` field will be set to false and the field
 * `deactivatedAt` will be updated with the current time.
 *
 * Notice that this function doesn't save the changes to the database
 * so a call to `order.Subscription.save()` is required after this
 * function.
 */
export function cancelSubscription(order) {
  order.Subscription.isActive = false;
  order.Subscription.deactivatedAt = new Date();
  order.status = status.CANCELLED;
}

/** Group processed orders by their state
 *
 * This function groups a list of entries returned by the function
 * `processOrderWithSubscription()`. Although they do contain
 * information about the order processing, be aware that they aren't
 * really model instances.
 *
 * There are two variables within each entry that decide which group
 * they're going to belong to:
 *
 *  1. entry.status: If it's `success` then the entry is automatically
 *     categorized within the group `charged`. If the value of this
 *     field is `failure`, the other variable will be used in the
 *     decision.
 *
 *  2. entry.retriesAfter: If that's less than MAX_RETRIES than the
 *     entry is grouped under `past_due`. Otherwise, it's marked as
 *     `canceled`.
 */
export function groupProcessedOrders(orders) {
  return orders.reduce((map, value) => {
    const key = value.status === 'success' ? 'charged' : value.retriesAfter >= MAX_RETRIES ? 'canceled' : 'past_due';
    const group = map.get(key);
    if (group) {
      group.total += value.amount;
      group.entries.push(value);
    } else {
      map.set(key, {
        total: value.amount,
        entries: [value],
      });
    }
    return map;
  }, new Map());
}

/** Call cancelation function and then send confirmation email */
export async function cancelSubscriptionAndNotifyUser(order) {
  cancelSubscription(order);
  return sendFailedEmail(order, true);
}

/** Send `archived.collective` email */
export async function sendArchivedCollectiveEmail(order) {
  const user = order.createdByUser;
  return emailLib.send(
    'archived.collective',
    user.email,
    {
      order: order.info,
      collective: order.collective.info,
      fromCollective: order.fromCollective.minimal,
      subscriptionsLink: user.generateLoginLink(`/${order.fromCollective.slug}/subscriptions`),
    },
    {
      from: `${order.collective.name} <hello@${order.collective.slug}.opencollective.com>`,
    },
  );
}

/** Send `payment.failed` email */
export async function sendFailedEmail(order, lastAttempt) {
  const user = order.createdByUser;
  return emailLib.send(
    'payment.failed',
    user.email,
    {
      lastAttempt,
      order: order.info,
      collective: order.collective.info,
      fromCollective: order.fromCollective.minimal,
      subscriptionsLink: `${config.host.website}/${order.fromCollective.slug}/subscriptions`,
    },
    {
      from: `${order.collective.name} <hello@${order.collective.slug}.opencollective.com>`,
    },
  );
}

/** Send `thankyou` email */
export async function sendThankYouEmail(order, transaction) {
  const relatedCollectives = await order.collective.getRelatedCollectives(3, 0);
  const recommendedCollectives = await getRecommendedCollectives(order.collective, 3);
  const user = order.createdByUser;
  return emailLib.send(
    'thankyou',
    user.email,
    {
      order: order.info,
      transaction: transaction.info,
      user: user.info,
      firstPayment: false,
      collective: order.collective.info,
      fromCollective: order.fromCollective.minimal,
      relatedCollectives,
      recommendedCollectives,
      config: { host: config.host },
      interval: order.Subscription.interval,
      subscriptionsLink: `${config.host.website}/${order.fromCollective.slug}/subscriptions`,
    },
    {
      from: `${order.collective.name} <hello@${order.collective.slug}.opencollective.com>`,
    },
  );
}

export async function sendCreditCardConfirmationEmail(order) {
  const user = order.createdByUser;
  return emailLib.send(
    'payment.creditcard.confirmation',
    user.email,
    {
      order: order.info,
      collective: order.collective.info,
      fromCollective: order.fromCollective.minimal,
      confirmOrderLink: `${config.host.website}/orders/${order.id}/confirm`,
    },
    {
      from: `${order.collective.name} <hello@${order.collective.slug}.opencollective.com>`,
    },
  );
}
<commit_msg>enhancement(subscription): set a new next charge date for subsciption starting from middle of the month<commit_after>/** @module lib/subscriptions */

import config from 'config';
import moment from 'moment';
import { Op } from 'sequelize';

import models from '../models';
import emailLib from './email';
import * as paymentsLib from './payments';
import { getRecommendedCollectives } from './data';
import status from '../constants/order_status';

/** Maximum number of attempts before an order gets cancelled. */
export const MAX_RETRIES = 5;

/** Find all orders with subscriptions that are active & due.
 *
 * Subscriptions are considered due if their `nextChargeDate` is
 * already past.
 */
export async function ordersWithPendingCharges() {
  return models.Order.findAll({
    where: {
      SubscriptionId: { [Op.ne]: null },
      deletedAt: null,
    },
    include: [
      { model: models.User, as: 'createdByUser' },
      { model: models.Collective, as: 'collective' },
      { model: models.Collective, as: 'fromCollective' },
      { model: models.PaymentMethod, as: 'paymentMethod' },
      {
        model: models.Subscription,
        where: {
          isActive: true,
          deletedAt: null,
          deactivatedAt: null,
          activatedAt: { [Op.lte]: new Date() },
          nextChargeDate: { [Op.lte]: new Date() },
        },
      },
    ],
  });
}

function hasReachedQuantity(order) {
  return order.Subscription.chargeNumber !== null && order.Subscription.chargeNumber === order.Subscription.quantity;
}

/** Process order and trigger result handlers.
 *
 * Uses `lib.payments.processOrder()` to charge subscription and
 * handle both success and failure of that processing.
 */
export async function processOrderWithSubscription(options, order) {
  const csvEntry = {
    orderId: order.id,
    subscriptionId: order.Subscription.id,
    amount: order.totalAmount,
    from: order.fromCollective.slug,
    to: order.collective.slug,
    status: null,
    error: null,
    retriesBefore: order.Subscription.chargeRetryCount,
    retriesAfter: null,
    chargeDateBefore: dateFormat(order.Subscription.nextCharge),
    chargeDateAfter: null,
    nextPeriodStartBefore: dateFormat(order.Subscription.nextPeriodStart),
    nextPeriodStartAfter: null,
  };

  let orderProcessedStatus = 'unattempted';
  let collectiveIsArchived = false;
  let creditCardNeedsConfirmation = false;
  let transaction;

  if (!options.dryRun) {
    if (hasReachedQuantity(order)) {
      orderProcessedStatus = 'failure';
      csvEntry.error = 'Your subscription is over';
      cancelSubscription(order);
    } else if (order.collective.deactivatedAt) {
      // This means the collective has been archived and the subscription should be cancelled.
      orderProcessedStatus = 'failure';
      csvEntry.error = 'The collective has been archived';
      collectiveIsArchived = true;
      cancelSubscription(order);
    } else {
      try {
        transaction = await paymentsLib.processOrder(order);
        orderProcessedStatus = 'success';
      } catch (error) {
        if (error.stripeResponse && error.stripeResponse.paymentIntent) {
          creditCardNeedsConfirmation = true;
        }
        orderProcessedStatus = 'failure';
        csvEntry.error = error.message;
        order.status = status.ERROR;
        order.data = order.data || {};
        order.data.latestError = error.message;
      }

      order.Subscription.chargeRetryCount = getChargeRetryCount(orderProcessedStatus, order);
      order.Subscription = Object.assign(
        order.Subscription,
        getNextChargeAndPeriodStartDates(orderProcessedStatus, order),
      );

      if (orderProcessedStatus === 'success') {
        if (order.Subscription.chargeNumber !== null) {
          order.Subscription.chargeNumber += 1;
        }
        order.status = status.ACTIVE;
      }
    }
  }

  csvEntry.status = orderProcessedStatus;
  csvEntry.retriesAfter = order.Subscription.chargeRetryCount;
  csvEntry.chargeDateAfter = dateFormat(order.Subscription.nextChargeDate);
  csvEntry.nextPeriodStartAfter = dateFormat(order.Subscription.nextPeriodStart);

  if (!options.dryRun) {
    try {
      if (collectiveIsArchived) {
        await sendArchivedCollectiveEmail(order);
      } else if (creditCardNeedsConfirmation) {
        await sendCreditCardConfirmationEmail(order);
      } else {
        await handleRetryStatus(order, transaction);
      }
    } catch (error) {
      console.log(`Error notifying order #${order.id} ${error}`);
    } finally {
      await order.Subscription.save();
      await order.save();
    }
  }

  return csvEntry;
}

/** Standard way to format dates in this script */
function dateFormat(date) {
  return moment(date).format();
}

/** Handle processing result.
 *
 * The result of processing an order is stored within the field
 * `chargeRetryCount`. This function handles the following values for
 * this variable:
 *
 *   1. zero(0): Means success. The counter was reset after a
 *      successful processing.
 *
 *   2. MAX_RETRIES: The order will be cancelled because it reached
 *      the maximum number of retries and the payment method doesn't
 *      work.
 *
 *   3. WARN_USER: The last attempt failed. Warn user about the
 *      failure and allow them to update the payment method.
 */
export async function handleRetryStatus(order, transaction) {
  switch (order.Subscription.chargeRetryCount) {
    case 0:
      await sendThankYouEmail(order, transaction);
      break;
    case MAX_RETRIES:
      await cancelSubscriptionAndNotifyUser(order);
      break;
    default:
      await sendFailedEmail(order, false);
      break;
  }
}

/** Get the date an order should be charged again and it's next period start date
 *
 * The status defines how much time it will take until the same
 * subscription can be charged again. Currently supported status
 * values:
 *
 *   0. new: 1st day of the next month for monthly, 1st day of the
 *      same month of the next year for yearly.
 *   1. success: Increment date by 1 month for monthly or 1 year for
 *      yearly subscriptions
 *   2. failure: Two days after today.
 */
export function getNextChargeAndPeriodStartDates(status, order) {
  const initial = order.Subscription.nextPeriodStart || order.Subscription.createdAt;
  let nextChargeDate = moment(initial);
  const dayOfTheMonth = nextChargeDate.date();
  const response = {};
  if (status === 'new' || status === 'success') {
    if (order.Subscription.interval === 'month') {
      nextChargeDate.add(1, 'months');
    } else if (order.Subscription.interval === 'year') {
      nextChargeDate.add(1, 'years');
    }

    if (status === 'new' && dayOfTheMonth >= 15) {
      nextChargeDate.add(1, 'months').startOf('month');
    } else if (status === 'new') {
      nextChargeDate.startOf('month');
    }

    response.nextPeriodStart = nextChargeDate.toDate();
  } else if (status === 'failure') {
    if (order.Subscription.chargeRetryCount >= 2) {
      nextChargeDate = moment(new Date()).add(5, 'days');
    } else {
      nextChargeDate = moment(new Date()).add(2, 'days');
    }
  } else if (status === 'updated') {
    // used when user updates payment method
    nextChargeDate = moment(new Date()); // sets next charge date to now
  }
  response.nextChargeDate = nextChargeDate.toDate();
  return response;
}

/** Update counter that records retry attempts.
 *
 * When status is 'failure', `order.Subscription.chargeRetryCount` is
 * incremented by one. The counter is reset to zero if the status is
 * 'success'.
 */
export function getChargeRetryCount(status, order) {
  return status === 'success' || status === 'updated' ? 0 : order.Subscription.chargeRetryCount + 1;
}

/** Cancel subscription
 *
 * The `isActive` field will be set to false and the field
 * `deactivatedAt` will be updated with the current time.
 *
 * Notice that this function doesn't save the changes to the database
 * so a call to `order.Subscription.save()` is required after this
 * function.
 */
export function cancelSubscription(order) {
  order.Subscription.isActive = false;
  order.Subscription.deactivatedAt = new Date();
  order.status = status.CANCELLED;
}

/** Group processed orders by their state
 *
 * This function groups a list of entries returned by the function
 * `processOrderWithSubscription()`. Although they do contain
 * information about the order processing, be aware that they aren't
 * really model instances.
 *
 * There are two variables within each entry that decide which group
 * they're going to belong to:
 *
 *  1. entry.status: If it's `success` then the entry is automatically
 *     categorized within the group `charged`. If the value of this
 *     field is `failure`, the other variable will be used in the
 *     decision.
 *
 *  2. entry.retriesAfter: If that's less than MAX_RETRIES than the
 *     entry is grouped under `past_due`. Otherwise, it's marked as
 *     `canceled`.
 */
export function groupProcessedOrders(orders) {
  return orders.reduce((map, value) => {
    const key = value.status === 'success' ? 'charged' : value.retriesAfter >= MAX_RETRIES ? 'canceled' : 'past_due';
    const group = map.get(key);
    if (group) {
      group.total += value.amount;
      group.entries.push(value);
    } else {
      map.set(key, {
        total: value.amount,
        entries: [value],
      });
    }
    return map;
  }, new Map());
}

/** Call cancelation function and then send confirmation email */
export async function cancelSubscriptionAndNotifyUser(order) {
  cancelSubscription(order);
  return sendFailedEmail(order, true);
}

/** Send `archived.collective` email */
export async function sendArchivedCollectiveEmail(order) {
  const user = order.createdByUser;
  return emailLib.send(
    'archived.collective',
    user.email,
    {
      order: order.info,
      collective: order.collective.info,
      fromCollective: order.fromCollective.minimal,
      subscriptionsLink: user.generateLoginLink(`/${order.fromCollective.slug}/subscriptions`),
    },
    {
      from: `${order.collective.name} <hello@${order.collective.slug}.opencollective.com>`,
    },
  );
}

/** Send `payment.failed` email */
export async function sendFailedEmail(order, lastAttempt) {
  const user = order.createdByUser;
  return emailLib.send(
    'payment.failed',
    user.email,
    {
      lastAttempt,
      order: order.info,
      collective: order.collective.info,
      fromCollective: order.fromCollective.minimal,
      subscriptionsLink: `${config.host.website}/${order.fromCollective.slug}/subscriptions`,
    },
    {
      from: `${order.collective.name} <hello@${order.collective.slug}.opencollective.com>`,
    },
  );
}

/** Send `thankyou` email */
export async function sendThankYouEmail(order, transaction) {
  const relatedCollectives = await order.collective.getRelatedCollectives(3, 0);
  const recommendedCollectives = await getRecommendedCollectives(order.collective, 3);
  const user = order.createdByUser;
  return emailLib.send(
    'thankyou',
    user.email,
    {
      order: order.info,
      transaction: transaction.info,
      user: user.info,
      firstPayment: false,
      collective: order.collective.info,
      fromCollective: order.fromCollective.minimal,
      relatedCollectives,
      recommendedCollectives,
      config: { host: config.host },
      interval: order.Subscription.interval,
      subscriptionsLink: `${config.host.website}/${order.fromCollective.slug}/subscriptions`,
    },
    {
      from: `${order.collective.name} <hello@${order.collective.slug}.opencollective.com>`,
    },
  );
}

export async function sendCreditCardConfirmationEmail(order) {
  const user = order.createdByUser;
  return emailLib.send(
    'payment.creditcard.confirmation',
    user.email,
    {
      order: order.info,
      collective: order.collective.info,
      fromCollective: order.fromCollective.minimal,
      confirmOrderLink: `${config.host.website}/orders/${order.id}/confirm`,
    },
    {
      from: `${order.collective.name} <hello@${order.collective.slug}.opencollective.com>`,
    },
  );
}
<|endoftext|>


<commit_before>window.hipanel = (function () {
    var locale = null;

    var notify = {
        create: function (options) {
            options = $.extend({
                buttons: {
                    sticker: false
                },
                icon: false,
                styling: 'bootstrap3'
            }, options, true);

            return new PNotify(options);
        },
        error: function (text) {
            return notify.create({type: 'error', text: text});
        },
        success: function (text) {
            return notify.create({type: 'success', text: text});
        }
    };

    var publicMethods = {
        updateCart: function (callback) {
            $('.dropdown.notifications-cart a.dropdown-toggle').html('');
            $.get("/cart/cart/topcart", function(data) {
                $("li.dropdown.notifications-menu.notifications-cart").html( data );
            }).done(callback());
        },
        loadingBar: function (options) {
            options = $.extend({}, options, true);

            return '
' +
                '
' +
                    '' + options.text + '' +
                '
' +
            '
';
        },
        notify: notify,
        form: {
            preventSubmitWithEnter: function (formId) {
                $(formId).find('input').on('keyup keypress', function (e) {
                    var keyCode = e.keyCode || e.which;
                    if (keyCode === 13) {
                        e.preventDefault();
                        e.stopPropagation();
                        $(e.target).blur();
                    }
                });
            }
        },
        spinner: {
            small: function() {
                return $('');
            }
        },
        locale: {
            get: function() {
                return locale;
            },
            set: function (newLocale) {
                locale = newLocale;
            }
        },
        googleAnalytics: function ($element, options) {
            $element.on('click', () => {
                ga('send', 'event', options.category, options.action);
            })
        }
    };

    return publicMethods;
})();
<commit_msg>Updated 'googleAnalytics()' method (#102)<commit_after>window.hipanel = (function () {
    var locale = null;

    var notify = {
        create: function (options) {
            options = $.extend({
                buttons: {
                    sticker: false
                },
                icon: false,
                styling: 'bootstrap3'
            }, options, true);

            return new PNotify(options);
        },
        error: function (text) {
            return notify.create({type: 'error', text: text});
        },
        success: function (text) {
            return notify.create({type: 'success', text: text});
        }
    };

    var publicMethods = {
        updateCart: function (callback) {
            $('.dropdown.notifications-cart a.dropdown-toggle').html('');
            $.get("/cart/cart/topcart", function(data) {
                $("li.dropdown.notifications-menu.notifications-cart").html( data );
            }).done(callback());
        },
        loadingBar: function (options) {
            options = $.extend({}, options, true);

            return '
' +
                '
' +
                    '' + options.text + '' +
                '
' +
            '
';
        },
        notify: notify,
        form: {
            preventSubmitWithEnter: function (formId) {
                $(formId).find('input').on('keyup keypress', function (e) {
                    var keyCode = e.keyCode || e.which;
                    if (keyCode === 13) {
                        e.preventDefault();
                        e.stopPropagation();
                        $(e.target).blur();
                    }
                });
            }
        },
        spinner: {
            small: function() {
                return $('');
            }
        },
        locale: {
            get: function() {
                return locale;
            },
            set: function (newLocale) {
                locale = newLocale;
            }
        },
        googleAnalytics: function ($element, options) {
            $element.on('click', () => {
                ga('send', 'pageview', '/virtual/' + options.category + '/' + options.action);
            })
        }
    };

    return publicMethods;
})();
<|endoftext|>


<commit_before>import macros from "../macros.sjs";

function getSizeAndPosition(grid, size, position) {
  var obj = {};
  unpack [width, height] = size;
  unpack [row, col] = position;
  obj.top = row * grid.unitHeight + row * grid.colMargin;
  obj.left = col * grid.unitWidth + col * grid.rowMargin;
//   obj.transform = "translate3d(" + left + "px, " + top + "px, 0)"
  obj.width = width * grid.unitWidth + ((width - 1) * grid.colMargin);
  obj.height = height * grid.unitHeight + ((height - 1) * grid.rowMargin);
  return obj;
}
module.exports.getSizeAndPosition = getSizeAndPosition;

function wrapPosition(grid, ix, obj) {
  var row = Math.floor(ix / grid.cols);
  var col = ix - (row * grid.cols);
  obj.top = row * grid.unitHeight + row * grid.colMargin;
  obj.left = col * grid.unitWidth + col * grid.rowMargin;
  obj.width = grid.unitWidth;
  obj.height = grid.unitHeight;
  return obj;
}
module.exports.wrapPosition = wrapPosition;

function indexToRowCol(grid, size, ix) {
  unpack [width, height] = size;
  var row = Math.floor((ix * width) / grid.cols) * height;
  var col = (ix * width) % grid.cols;
  return [row, col];
}
module.exports.indexToRowCol = indexToRowCol;

function layout(grid) {
  var children = grid.container.children;
  foreach(ix, child of children) {
    var row = Math.floor(ix / grid.cols);
    var col = ix - (row * grid.cols);
    child.style.top = row * grid.unitHeight + row * grid.colMargin;
    child.style.left = col * grid.unitWidth + col * grid.rowMargin;
    child.style.width = grid.unitWidth;
    child.style.height = grid.unitHeight;
  }
}
module.exports.layout = layout;

// options: {
//   dimensions: [1000,1000], // pixel width, height of grid
//   gridSize: [20,20], // number of rows, cols
//   marginSize: [20,20], // margin in pixels
// }
function makeGrid(container, options) {
  var grid = {
    container: container,
    rows: options["gridSize"][0],
    cols: options["gridSize"][1],
    rowMargin: options["marginSize"][0],
    colMargin: options["marginSize"][1],
    width: options["dimensions"][0],
    height: options["dimensions"][1],
    positions: [],
    items: {}
  };

  grid.unitWidth = Math.floor(grid.width / grid.cols) - grid.rowMargin;
  grid.unitHeight = Math.floor(grid.height / grid.rows) - grid.colMargin;

  return grid;
}
module.exports.makeGrid = makeGrid;

function addGridItem(grid, item, position, size) {

}

function firstGap(grid, items, size) {
  size = size || [1, 1];
  unpack [width, height] = size;
  var filled = new Array(grid.cols);
  for(var i = 0; i < grid.cols; i++) {
    filled[i] = new Array(grid.rows);
    for(var j = 0; j < grid.rows; j++) {
      filled[i][j] = false;
    }
  }

  // Populate footprint of existing items.
  foreach(ix, item of items) {
    unpack [w, h] = item.size;
    unpack [x0, y0] = item.pos;
    for(var x = x0; x < x0 + h; x++) {
      for(var y = y0; y < y0 + w; y++) {
        filled[x][y] = true;
      }
    }
  }

  // Find gap >= size.
  for(var x0 = 0; x0 < grid.cols; x0++) {
    for(var y0 = 0; y0 < grid.rows; y0++) {
      if(!filled[x0][y0] && x0 + height < grid.cols && y0 + width < grid.rows) {
        gap = true;
        for(var x = x0; x < x0 + height; x++) {
          for(var y = y0; y < y0 + width; y++) {
            if(filled[x][y]) {
              gap = false;
              break;
            }
          }
          if(!gap) { break; }
        }
//         console.log("Trying", x0, y0);
//         console.log("\n" + filled.map(function(f) {
//           return f.map(function(s) {
//             return (s ? "##" : "  ");
//           }).join("|");
//         }).join("\n"));
        if(gap) {
          return [x0, y0];
        }
      }
    }
  }
}
module.exports.firstGap = firstGap;
<commit_msg><= != <<commit_after>import macros from "../macros.sjs";

function getSizeAndPosition(grid, size, position) {
  var obj = {};
  unpack [width, height] = size;
  unpack [row, col] = position;
  obj.top = row * grid.unitHeight + row * grid.colMargin;
  obj.left = col * grid.unitWidth + col * grid.rowMargin;
//   obj.transform = "translate3d(" + left + "px, " + top + "px, 0)"
  obj.width = width * grid.unitWidth + ((width - 1) * grid.colMargin);
  obj.height = height * grid.unitHeight + ((height - 1) * grid.rowMargin);
  return obj;
}
module.exports.getSizeAndPosition = getSizeAndPosition;

function wrapPosition(grid, ix, obj) {
  var row = Math.floor(ix / grid.cols);
  var col = ix - (row * grid.cols);
  obj.top = row * grid.unitHeight + row * grid.colMargin;
  obj.left = col * grid.unitWidth + col * grid.rowMargin;
  obj.width = grid.unitWidth;
  obj.height = grid.unitHeight;
  return obj;
}
module.exports.wrapPosition = wrapPosition;

function indexToRowCol(grid, size, ix) {
  unpack [width, height] = size;
  var row = Math.floor((ix * width) / grid.cols) * height;
  var col = (ix * width) % grid.cols;
  return [row, col];
}
module.exports.indexToRowCol = indexToRowCol;

function layout(grid) {
  var children = grid.container.children;
  foreach(ix, child of children) {
    var row = Math.floor(ix / grid.cols);
    var col = ix - (row * grid.cols);
    child.style.top = row * grid.unitHeight + row * grid.colMargin;
    child.style.left = col * grid.unitWidth + col * grid.rowMargin;
    child.style.width = grid.unitWidth;
    child.style.height = grid.unitHeight;
  }
}
module.exports.layout = layout;

// options: {
//   dimensions: [1000,1000], // pixel width, height of grid
//   gridSize: [20,20], // number of rows, cols
//   marginSize: [20,20], // margin in pixels
// }
function makeGrid(container, options) {
  var grid = {
    container: container,
    rows: options["gridSize"][0],
    cols: options["gridSize"][1],
    rowMargin: options["marginSize"][0],
    colMargin: options["marginSize"][1],
    width: options["dimensions"][0],
    height: options["dimensions"][1],
    positions: [],
    items: {}
  };

  grid.unitWidth = Math.floor(grid.width / grid.cols) - grid.rowMargin;
  grid.unitHeight = Math.floor(grid.height / grid.rows) - grid.colMargin;

  return grid;
}
module.exports.makeGrid = makeGrid;

function addGridItem(grid, item, position, size) {

}

function firstGap(grid, items, size) {
  size = size || [1, 1];
  unpack [width, height] = size;
  var filled = new Array(grid.cols);
  for(var i = 0; i < grid.cols; i++) {
    filled[i] = new Array(grid.rows);
    for(var j = 0; j < grid.rows; j++) {
      filled[i][j] = false;
    }
  }

  // Populate footprint of existing items.
  foreach(ix, item of items) {
    unpack [w, h] = item.size;
    unpack [x0, y0] = item.pos;
    for(var x = x0; x < x0 + h; x++) {
      for(var y = y0; y < y0 + w; y++) {
        filled[x][y] = true;
      }
    }
  }

  // Find gap >= size.
  for(var x0 = 0; x0 < grid.cols; x0++) {
    for(var y0 = 0; y0 < grid.rows; y0++) {
      if(!filled[x0][y0] && (x0 + height <= grid.cols) && (y0 + width <= grid.rows)) {
        gap = true;
        for(var x = x0; x < x0 + height; x++) {
          for(var y = y0; y < y0 + width; y++) {
            if(filled[x][y]) {
              gap = false;
              break;
            }
          }
          if(!gap) { break; }
        }
//          console.log("Trying", x0, y0, gap);
//          console.log("\n" + filled.map(function(f) {
//            return f.map(function(s) {
//              return (s ? "##" : "  ");
//            }).join("|");
//          }).join("\n"));
        if(gap) {
          return [x0, y0];
        }
      }
    }
  }
}
module.exports.firstGap = firstGap;
<|endoftext|>


<commit_before>searchMatrix.js
<commit_msg>add desc<commit_after>// Write an efficient algorithm that searches for a value in an m x n matrix. This matrix has the following properties:

// Integers in each row are sorted from left to right.
// The first integer of each row is greater than the last integer of the previous row.
// For example,

// Consider the following matrix:

// [
//   [1,   3,  5,  7],
//   [10, 11, 16, 20],
//   [23, 30, 34, 50]
// ]

// Given target = 3, return true.
<|endoftext|>


<commit_before>const React = require('react');
const $ = require('jquery');

const API_HOST = window.location.hostname;
const API_PORT = window.location.port || undefined;
const API_VERSION = '/api/v1/';
const API_URL = `http://${API_HOST}${API_PORT ? ':' + API_PORT : ''}${API_VERSION}`;

function checkForSponsor(campaign) {
  const partners = campaign.affiliates.partners;
  if (partners.length < 1) {
    return false;
  }

  for (let partner of partners) {
    if (partner.sponsor) {
      return true;
    }
  }

  return false;
}

function sortCampaigns(a, b) {
  const aScore = a.time_commitment - (checkForSponsor(a) ? 1 : 0);
  const bScore = b.time_commitment - (checkForSponsor(b) ? 1 : 0);
  return aScore - bScore;
}

/**
 * Card Component
 * <Card />
 */
class FeatureCard extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      campaigns: [],
      current_campaign: '',
      signup_animation: ''
    };
  }

  componentDidMount() {
    this.serverRequest = $.get(`${API_URL}campaigns?random=true&staff_pick=true`, result => {
      // Filter out campaigns users have signed up for & then sort remaining campaigns
      const userSignups = Drupal.settings.dsUser.activity.signups;
      const campaigns = result.data.filter(campaign => userSignups.indexOf(parseInt(campaign.id)) == -1).sort(sortCampaigns);

      this.setState({
        campaigns: campaigns,
        current_campaign_index: 0
      });
    });
  }

  componentWillUnmount() {
    if (this.serverRequest) {
      this.serverRequest.abort();;
    }
  }

  signup() {
    const campaign = this.state.campaigns[this.state.current_campaign_index];

    if (!campaign) {
      return;
    }

    $.ajax({
      url: `${API_URL}campaigns/${campaign.id}/signup`,
      type: 'POST',
      headers: {
        "X-CSRF-Token": $('meta[name=csrf-token]').attr("content")
      },
      data: {
        source: 'onboarding'
      },
      success: (data) => {
        setTimeout(() => {
          this.viewAnother();
        }, 1000);

        this.setState({ //TODO: Fancy animation
          signup_animation: 'tada'
        });
      }
    });
  }

  viewAnother() {
    let newIndex = this.state.current_campaign_index + 1;
    if (newIndex >= this.state.campaigns.length) {
      newIndex = 0;
    }

    this.setState({
      current_campaign_index: newIndex,
      signup_animation: ''
    });
  }

  render() {
    const campaign = this.state.campaigns[this.state.current_campaign_index];

    if (!campaign) {
      return null;
    }

    return (

{this.props.content.title}

{campaign.title}

{campaign.tagline}

<button className={`${this.state.signup_animation} button`} onClick={()=>this.signup()}>Sign Up</button>

this.viewAnother()}>Show me another

    )
  }
}

export default FeatureCard;
<commit_msg>Removing extra semi colon<commit_after>const React = require('react');
const $ = require('jquery');

const API_HOST = window.location.hostname;
const API_PORT = window.location.port || undefined;
const API_VERSION = '/api/v1/';
const API_URL = `http://${API_HOST}${API_PORT ? ':' + API_PORT : ''}${API_VERSION}`;

function checkForSponsor(campaign) {
  const partners = campaign.affiliates.partners;
  if (partners.length < 1) {
    return false;
  }

  for (let partner of partners) {
    if (partner.sponsor) {
      return true;
    }
  }

  return false;
}

function sortCampaigns(a, b) {
  const aScore = a.time_commitment - (checkForSponsor(a) ? 1 : 0);
  const bScore = b.time_commitment - (checkForSponsor(b) ? 1 : 0);
  return aScore - bScore;
}

/**
 * Card Component
 * <Card />
 */
class FeatureCard extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      campaigns: [],
      current_campaign: '',
      signup_animation: ''
    };
  }

  componentDidMount() {
    this.serverRequest = $.get(`${API_URL}campaigns?random=true&staff_pick=true`, result => {
      // Filter out campaigns users have signed up for & then sort remaining campaigns
      const userSignups = Drupal.settings.dsUser.activity.signups;
      const campaigns = result.data.filter(campaign => userSignups.indexOf(parseInt(campaign.id)) == -1).sort(sortCampaigns);

      this.setState({
        campaigns: campaigns,
        current_campaign_index: 0
      });
    });
  }

  componentWillUnmount() {
    if (this.serverRequest) {
      this.serverRequest.abort();
    }
  }

  signup() {
    const campaign = this.state.campaigns[this.state.current_campaign_index];

    if (!campaign) {
      return;
    }

    $.ajax({
      url: `${API_URL}campaigns/${campaign.id}/signup`,
      type: 'POST',
      headers: {
        "X-CSRF-Token": $('meta[name=csrf-token]').attr("content")
      },
      data: {
        source: 'onboarding'
      },
      success: (data) => {
        setTimeout(() => {
          this.viewAnother();
        }, 1000);

        this.setState({ //TODO: Fancy animation
          signup_animation: 'tada'
        });
      }
    });
  }

  viewAnother() {
    let newIndex = this.state.current_campaign_index + 1;
    if (newIndex >= this.state.campaigns.length) {
      newIndex = 0;
    }

    this.setState({
      current_campaign_index: newIndex,
      signup_animation: ''
    });
  }

  render() {
    const campaign = this.state.campaigns[this.state.current_campaign_index];

    if (!campaign) {
      return null;
    }

    return (

{this.props.content.title}

{campaign.title}

{campaign.tagline}

<button className={`${this.state.signup_animation} button`} onClick={()=>this.signup()}>Sign Up</button>

this.viewAnother()}>Show me another

    )
  }
}

export default FeatureCard;
<|endoftext|>


<commit_before>var xs = 18;
var ys = 10;

var map = new Array(xs);

for (var i=0; i<map.length; i++) {
  map[i] = new Array(ys);
};

for (var i=0; i<xs; i++) {
  for (var j=0; j<ys; j++) {
    map[i][j] = { state:0, type:0};
  }
};

var size = 30;

var xo = 0;
var yo = 0;

var xpo = size;
var ypo = size;

canvas = document.getElementById('canvas')
canvas.addEventListener('click', onClick, false);
var g = canvas.getContext('2d');
g.fillStyle='#FF00A5';
repaint();

function repaint() {
  for (var i=-xo; i<map.length-xo; i++) {
    for (var j=-yo; j<map[i+xo].length-yo; j++) {
      drawHex(map[i+xo][j+yo], xpo + i*size*3/2, ypo + j*2*size*Math.sqrt(3)/2 + (i%2==0?Math.sqrt(3)/2*size:0));
    }
  }
}

function onClick(e) {
  var pp = getPosition(e.currentTarget);
  var xp = e.clientX - pp.x - xpo;
  var yp = e.clientY - pp.y - ypo;

  var hq = 2/3 * xp / size + xo;
  var hr = (-1/3 * xp + Math.sqrt(3)/3 * -(-xp - (yp - (xp - (xp&1)) / 2))) / size + yo;

  var h = map[Math.round(hq)][Math.round(hr)];
  h.type++;
  if (h.type>3)
    h.type=0;

  repaint();
}

function drawHex(hex, xp, yp) {
  g.beginPath();
  var i;
  for (i=0; i<=6; i++) {
    var angle = 2 * Math.PI / 6 * i;
    x = xp + size * Math.cos(angle);
    y = yp + size * Math.sin(angle);
    if (i == 0)
      g.moveTo(x,y);
    else
      g.lineTo(x,y);
  }
  g.stroke();

  if (hex.type == 0)
    g.fillStyle='#AAAAAA';
  else if (hex.state==0)
    g.fillStyle='#FFFFFF';
  else
    g.fillStyle='#FFFF00';
  g.fill();

  g.beginPath();
  switch(hex.type) {
    case 0: // Blank
      break;
    case 1: // XOR
      g.moveTo(xp - size/4, yp - size/4);
      g.lineTo(xp + size/4, yp + size/4);
      g.moveTo(xp - size/4, yp + size/4);
      g.lineTo(xp + size/4, yp - size/4);
      break;
    case 2: // OR
      g.moveTo(xp - size/4, yp - size/4);
      g.lineTo(xp + size/4, yp - size/4);
      g.lineTo(xp + size/4, yp + size/4);
      g.lineTo(xp - size/4, yp + size/4);
      g.lineTo(xp - size/4, yp - size/4);
      break;
    case 3: // Power
      g.moveTo(xp - size/4, yp);
      g.lineTo(xp + size/4, yp);
      g.moveTo(xp, yp + size/4);
      g.lineTo(xp, yp - size/4);
      break;
  }
  g.stroke();
}

function getPosition(e) {
  var xp = 0;
  var yp = 0;

  while (e) {
    xp += (e.offsetLeft - e.scrollLeft + e.clientLeft);
    yp += (e.offsetTop - e.scrollTop + e.clientTop);
    e = e.offsetParent;
  }
  return { x: xp, y: yp };
}<commit_msg>Update hexes before drawing. Power is now powered.<commit_after>var xs = 18;
var ys = 10;

var map = new Array(xs);

for (var i=0; i<map.length; i++) {
  map[i] = new Array(ys);
};

for (var i=0; i<xs; i++) {
  for (var j=0; j<ys; j++) {
    map[i][j] = { state:0, type:0};
  }
};

var size = 30;

var xo = 0;
var yo = 0;

var xpo = size;
var ypo = size;

canvas = document.getElementById('canvas')
canvas.addEventListener('click', onClick, false);
var g = canvas.getContext('2d');
g.fillStyle='#FF00A5';
repaint();

function repaint() {
  for (var i=-xo; i<map.length-xo; i++) {
    for (var j=-yo; j<map[i+xo].length-yo; j++) {
      updateHex(map[i+xo][j+yo]);
      drawHex(map[i+xo][j+yo], xpo + i*size*3/2, ypo + j*2*size*Math.sqrt(3)/2 + (i%2==0?Math.sqrt(3)/2*size:0));
    }
  }
}

function updateHex(hex) {
  if (hex.type == 3)
    hex.state = 1;
  if (hex.type == 0)
    hex.state = 0;
}

function onClick(e) {
  var pp = getPosition(e.currentTarget);
  var xp = e.clientX - pp.x - xpo;
  var yp = e.clientY - pp.y - ypo;

  var hq = 2/3 * xp / size + xo;
  var hr = (-1/3 * xp + Math.sqrt(3)/3 * -(-xp - (yp - (xp - (xp&1)) / 2))) / size + yo;

  var h = map[Math.round(hq)][Math.round(hr)];
  h.type++;
  if (h.type>3)
    h.type=0;

  repaint();
}

function drawHex(hex, xp, yp) {
  g.beginPath();
  var i;
  for (i=0; i<=6; i++) {
    var angle = 2 * Math.PI / 6 * i;
    x = xp + size * Math.cos(angle);
    y = yp + size * Math.sin(angle);
    if (i == 0)
      g.moveTo(x,y);
    else
      g.lineTo(x,y);
  }
  g.stroke();

  if (hex.type == 0)
    g.fillStyle='#AAAAAA';
  else if (hex.state==0)
    g.fillStyle='#FFFFFF';
  else
    g.fillStyle='#FFFF00';
  g.fill();

  g.beginPath();
  switch(hex.type) {
    case 0: // Blank
      break;
    case 1: // XOR
      g.moveTo(xp - size/4, yp - size/4);
      g.lineTo(xp + size/4, yp + size/4);
      g.moveTo(xp - size/4, yp + size/4);
      g.lineTo(xp + size/4, yp - size/4);
      break;
    case 2: // OR
      g.moveTo(xp - size/4, yp - size/4);
      g.lineTo(xp + size/4, yp - size/4);
      g.lineTo(xp + size/4, yp + size/4);
      g.lineTo(xp - size/4, yp + size/4);
      g.lineTo(xp - size/4, yp - size/4);
      break;
    case 3: // Power
      g.moveTo(xp - size/4, yp);
      g.lineTo(xp + size/4, yp);
      g.moveTo(xp, yp + size/4);
      g.lineTo(xp, yp - size/4);
      break;
  }
  g.stroke();
}

function getPosition(e) {
  var xp = 0;
  var yp = 0;

  while (e) {
    xp += (e.offsetLeft - e.scrollLeft + e.clientLeft);
    yp += (e.offsetTop - e.scrollTop + e.clientTop);
    e = e.offsetParent;
  }
  return { x: xp, y: yp };
}<|endoftext|>