<commit_before>/*
 // tabs.js widget for creating and displaying tabs

 // Instantiation
 // optional: content, active, removable
 // you can make all tabs or individual tabs removable

 var tabs = $('#ele').tabs();

 //or

 var tabs = $('#ele').tabs({tabs: [
 {name: 'tab1', content: 'foo text or html', active: true},
 {name: 'tab2', content: 'text or html 2', removable: true}
 ]
 });

 // Add a new tab
 // optional: content, active

 tabs.addTab({name: 'tab3', content: 'new content'})

 // Retrieve a tab button
 // (useful for adding events or other stuff)

 var mytab = tabs.tab('tab1')

 // Add content to existing tab
 // (useful for ajax/event stuff)

 tabs.tab({name: 'tab3', content: 'blah blah blah'})

 // manually show a tab
 // Tab panes are shown when clicked automatically.
 // This is a programmatic way of showing a tab.

 tabs.showTab('tab_name');
 */
/*global
 define
 */
/*jslint
 browser: true,
 white: true
 */
define(['jquery', 'kb.query.widget'], function ($) {
    'use strict';
    $.KBWidget({
        name: "kbTabs",
        version: "1.0.0",
        init: function (options) {
            this._super(options);
            if (!options) {
                options = {};
            }
            var container = this.$elem;
            var self = this;

            var tabs = $('
');
            var tab_contents = $('
');
            container.append(tabs, tab_contents);

            // adds a single tab and content
            this.addTab = function (p) {
                // if tab exists, don't add
                if (tabs.find('a[data-id="' + p.name + '"]').length > 0)
                    return;

                var tab = $('
');
                var tab_link = $('' + p.name + '');

                // animate by sliding tab up
                if (p.animate === false) {
                    tab.append(tab_link);
                    tabs.append(tab);
                } else {
                    tab.append(tab_link).hide();
                    tabs.append(tab);
                    // eap 7/6/15 - disable the following line; must be a hook into
                    // the bootstrap tab plugin; but it does not work with BS 3.
                    // tab.toggle('slide', {direction: 'down', duration: 'fast'});
                }

                // add close button if needed
                if (p.removable || options.removable) {
                    var rm_btn = $('');
                    tab_link.append(rm_btn);

                    rm_btn.click(function (e) {
                        self.rmTab(p.name)
                    });
                }

                // add content pane
                var c = $('
');
                c.append((p.content ? p.content : ''));
                tab_contents.append(c);

                tab.click(function (e) {
                    e.preventDefault();
                    e.stopPropagation();
                    var id = $(this).find('a').data('id');
                    self.showTab(id);
                });

                return p.content;
            };

            // remove tab and tab content
            this.rmTab = function (name) {
                var tab = tabs.find('a[data-id="' + name + '"]').parent('li');
                var tab_content = tab_contents.children('[data-id="' + name + '"]');

                // get previous or next tab
                if (tab.next().length > 0)
                    var id = tab.next().children('a').data('id');
                else
                    var id = tab.prev().children('a').data('id');

                // remove the tab
                tab.remove();
                tab_content.remove();

                // show prev or next tab
                self.showTab(id);
            };

            // returns tab
            this.tab = function (name) {
                return tabs.children('[data-id="' + name + '"]');
            };

            // returns content of tab
            this.tabContent = function (name) {
                return tab_contents.children('[data-id="' + name + '"]');
            };

            // adds content to existing tab pane; useful for ajax
            this.addContent = function (p) {
                var c = tab_contents.children('[data-id="' + p.name + '"]')
                c.append((p.content ? p.content : ''));

                return c;
            };

            // highlights tab and shows content
            this.showTab = function (id) {
                tabs.children('li').removeClass('active');
                tab_contents.children('.tab-pane').removeClass('active');

                tabs.find('a[data-id="' + id + '"]').parent().addClass('active');
                tab_contents.children('[data-id="' + id + '"]').addClass('active');
            };

            this.getTabNav = function () {
                return tabs;
            };

            // if tabs are supplied, add them
            // don't animate intial tabs
            if ('tabs' in options) {
                for (var i in options.tabs) {
                    var p = $.extend(options.tabs[i], {animate: false});
                    this.addTab(p);
                }
            }

            return this;
        }
    });
});
<commit_msg>Typo'd!<commit_after>/*
 // tabs.js widget for creating and displaying tabs

 // Instantiation
 // optional: content, active, removable
 // you can make all tabs or individual tabs removable

 var tabs = $('#ele').tabs();

 //or

 var tabs = $('#ele').tabs({tabs: [
 {name: 'tab1', content: 'foo text or html', active: true},
 {name: 'tab2', content: 'text or html 2', removable: true}
 ]
 });

 // Add a new tab
 // optional: content, active

 tabs.addTab({name: 'tab3', content: 'new content'})

 // Retrieve a tab button
 // (useful for adding events or other stuff)

 var mytab = tabs.tab('tab1')

 // Add content to existing tab
 // (useful for ajax/event stuff)

 tabs.tab({name: 'tab3', content: 'blah blah blah'})

 // manually show a tab
 // Tab panes are shown when clicked automatically.
 // This is a programmatic way of showing a tab.

 tabs.showTab('tab_name');
 */
/*global
 define
 */
/*jslint
 browser: true,
 white: true
 */
define(['jquery', 'kb.jquery.widget'], function ($) {
    'use strict';
    $.KBWidget({
        name: "kbTabs",
        version: "1.0.0",
        init: function (options) {
            this._super(options);
            if (!options) {
                options = {};
            }
            var container = this.$elem;
            var self = this;

            var tabs = $('
');
            var tab_contents = $('
');
            container.append(tabs, tab_contents);

            // adds a single tab and content
            this.addTab = function (p) {
                // if tab exists, don't add
                if (tabs.find('a[data-id="' + p.name + '"]').length > 0)
                    return;

                var tab = $('
');
                var tab_link = $('' + p.name + '');

                // animate by sliding tab up
                if (p.animate === false) {
                    tab.append(tab_link);
                    tabs.append(tab);
                } else {
                    tab.append(tab_link).hide();
                    tabs.append(tab);
                    // eap 7/6/15 - disable the following line; must be a hook into
                    // the bootstrap tab plugin; but it does not work with BS 3.
                    // tab.toggle('slide', {direction: 'down', duration: 'fast'});
                }

                // add close button if needed
                if (p.removable || options.removable) {
                    var rm_btn = $('');
                    tab_link.append(rm_btn);

                    rm_btn.click(function (e) {
                        self.rmTab(p.name)
                    });
                }

                // add content pane
                var c = $('
');
                c.append((p.content ? p.content : ''));
                tab_contents.append(c);

                tab.click(function (e) {
                    e.preventDefault();
                    e.stopPropagation();
                    var id = $(this).find('a').data('id');
                    self.showTab(id);
                });

                return p.content;
            };

            // remove tab and tab content
            this.rmTab = function (name) {
                var tab = tabs.find('a[data-id="' + name + '"]').parent('li');
                var tab_content = tab_contents.children('[data-id="' + name + '"]');

                // get previous or next tab
                if (tab.next().length > 0)
                    var id = tab.next().children('a').data('id');
                else
                    var id = tab.prev().children('a').data('id');

                // remove the tab
                tab.remove();
                tab_content.remove();

                // show prev or next tab
                self.showTab(id);
            };

            // returns tab
            this.tab = function (name) {
                return tabs.children('[data-id="' + name + '"]');
            };

            // returns content of tab
            this.tabContent = function (name) {
                return tab_contents.children('[data-id="' + name + '"]');
            };

            // adds content to existing tab pane; useful for ajax
            this.addContent = function (p) {
                var c = tab_contents.children('[data-id="' + p.name + '"]')
                c.append((p.content ? p.content : ''));

                return c;
            };

            // highlights tab and shows content
            this.showTab = function (id) {
                tabs.children('li').removeClass('active');
                tab_contents.children('.tab-pane').removeClass('active');

                tabs.find('a[data-id="' + id + '"]').parent().addClass('active');
                tab_contents.children('[data-id="' + id + '"]').addClass('active');
            };

            this.getTabNav = function () {
                return tabs;
            };

            // if tabs are supplied, add them
            // don't animate intial tabs
            if ('tabs' in options) {
                for (var i in options.tabs) {
                    var p = $.extend(options.tabs[i], {animate: false});
                    this.addTab(p);
                }
            }

            return this;
        }
    });
});
<|endoftext|>


<commit_before>/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
 * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
 * full text of the license. */

/**
 * Class: OpenLayers.Popup
 * A popup is a small div that can opened and closed on the map.
 * Typically opened in response to clicking on a marker.
 * See <OpenLayers.Marker>.  Popup's don't require their own
 * layer and are added the the map using the <OpenLayers.Map.addPopup>
 * method.
 *
 * Example:
 * (code)
 * popup = new OpenLayers.Popup("chicken",
 *                    new OpenLayers.LonLat(5,40),
 *                    new OpenLayers.Size(200,200),
 *                    "example popup",
 *                    true);
 *
 * map.addPopup(popup);
 * (end)
 */
OpenLayers.Popup = OpenLayers.Class({

    /**
     * Property: events
     * {<OpenLayers.Events>} custom event manager
     */
    events: null,

    /** Property: id
     * {String} the unique identifier assigned to this popup.
     */
    id: "",

    /**
     * Property: lonlat
     * {<OpenLayers.LonLat>} the position of this popup on the map
     */
    lonlat: null,

    /**
     * Property: div
     * {DOMElement} the div that contains this popup.
     */
    div: null,

    /**
     * Property: size
     * {<OpenLayers.Size>} the width and height of the popup.
     */
    size: null,

    /**
     * Property: contentHTML
     * {String} The HTML that this popup displays.
     */
    contentHTML: "",

    /**
     * Property: backgroundColor
     * {String} the background color used by the popup.
     */
    backgroundColor: "",

    /**
     * Property: opacity
     * {float} the opacity of this popup (between 0.0 and 1.0)
     */
    opacity: "",

    /**
     * Property: border
     * {String} the border size of the popup.  (eg 2px)
     */
    border: "",

    /**
     * Property: contentDiv
     * {DOMElement} a reference to the element that holds the content of
     *              the div.
     */
    contentDiv: null,

    /**
     * Property: groupDiv
     * {DOMElement} the parent of <OpenLayers.Popup.contentDiv>
     */
    groupDiv: null,

    /**
     * Property: closeDiv
     * {DOMElement} the optional closer image
     */
    closeDiv: null,

    /**
     * Property: padding
     * {int} the internal padding of the content div.
     */
    padding: 5,

    /**
     * Property: map
     * {<OpenLayers.Map>} this gets set in Map.js when the popup is added to the map
     */
    map: null,

    /**
    * Constructor: OpenLayers.Popup
    * Create a popup.
    *
    * Parameters:
    * id - {String} a unqiue identifier for this popup.  If null is passed
    *               an identifier will be automatically generated.
    * lonlat - {<OpenLayers.LonLat>}  The position on the map the popup will
    *                                 be shown.
    * size - {<OpenLayers.Size>}      The size of the popup.
    * contentHTML - {String}          The HTML content to display inside the
    *                                 popup.
    * closeBox - {Boolean}            Whether to display a close box inside
    *                                 the popup.
    * closeBoxCallback - {Function}   Function to be called on closeBox click.
    */
    initialize:function(id, lonlat, size, contentHTML, closeBox, closeBoxCallback) {
        if (id == null) {
            id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_");
        }

        this.id = id;
        this.lonlat = lonlat;
        this.size = (size != null) ? size
                                  : new OpenLayers.Size(
                                                   OpenLayers.Popup.WIDTH,
                                                   OpenLayers.Popup.HEIGHT);
        if (contentHTML != null) {
             this.contentHTML = contentHTML;
        }
        this.backgroundColor = OpenLayers.Popup.COLOR;
        this.opacity = OpenLayers.Popup.OPACITY;
        this.border = OpenLayers.Popup.BORDER;

        this.div = OpenLayers.Util.createDiv(this.id, null, null,
                                             null, null, null, "hidden");
        this.div.className = 'olPopup';

        this.groupDiv = OpenLayers.Util.createDiv(null, null, null,
                                                    null, "relative", null,
                                                    "hidden");

        var id = this.div.id + "_contentDiv";
        this.contentDiv = OpenLayers.Util.createDiv(id, null, this.size.clone(),
                                                    null, "relative", null,
                                                    "hidden");
        this.contentDiv.className = 'olPopupContent';
        this.groupDiv.appendChild(this.contentDiv);
        this.div.appendChild(this.groupDiv);

        if (closeBox) {
           // close icon
            var closeSize = new OpenLayers.Size(17,17);
            var img = OpenLayers.Util.getImagesLocation() + "close.gif";
            this.closeDiv = OpenLayers.Util.createAlphaImageDiv(
                this.id + "_close", null, closeSize, img
            );
            this.closeDiv.style.right = this.padding + "px";
            this.closeDiv.style.top = this.padding + "px";
            this.groupDiv.appendChild(this.closeDiv);

            var closePopup = closeBoxCallback || function(e) {
                this.hide();
                OpenLayers.Event.stop(e);
            };
            OpenLayers.Event.observe(this.closeDiv, "click",
                    OpenLayers.Function.bindAsEventListener(closePopup, this));

        }

        this.registerEvents();
    },

    /**
     * Method: destroy
     * nullify references to prevent circular references and memory leaks
     */
    destroy: function() {
        if (this.map != null) {
            this.map.removePopup(this);
            this.map = null;
        }
        this.events.destroy();
        this.events = null;
        this.div = null;
    },

    /**
    * Method: draw
    * Constructs the elements that make up the popup.
    *
    * Parameters:
    * px - {<OpenLayers.Pixel>} the position the popup in pixels.
    *
    * Returns:
    * {DOMElement} Reference to a div that contains the drawn popup
    */
    draw: function(px) {
        if (px == null) {
            if ((this.lonlat != null) && (this.map != null)) {
                px = this.map.getLayerPxFromLonLat(this.lonlat);
            }
        }

        this.setSize();
        this.setBackgroundColor();
        this.setOpacity();
        this.setBorder();
        this.setContentHTML();
        this.moveTo(px);

        return this.div;
    },

    /**
     * Method: updatePosition
     * if the popup has a lonlat and its map members set,
     * then have it move itself to its proper position
     */
    updatePosition: function() {
        if ((this.lonlat) && (this.map)) {
                var px = this.map.getLayerPxFromLonLat(this.lonlat);
                if (px) {
                    this.moveTo(px);
                }
        }
    },

    /**
     * Method: moveTo
     *
     * Parameters:
     * px - {<OpenLayers.Pixel>} the top and left position of the popup div.
     */
    moveTo: function(px) {
        if ((px != null) && (this.div != null)) {
            this.div.style.left = px.x + "px";
            this.div.style.top = px.y + "px";
        }
    },

    /**
     * Method: visible
     *
     * Returns:
     * {Boolean} Boolean indicating whether or not the popup is visible
     */
    visible: function() {
        return OpenLayers.Element.visible(this.div);
    },

    /**
     * Method: toggle
     * Toggles visibility of the popup.
     */
    toggle: function() {
        OpenLayers.Element.toggle(this.div);
    },

    /**
     * Method: show
     * Makes the popup visible.
     */
    show: function() {
        OpenLayers.Element.show(this.div);
    },

    /**
     * Method: hide
     * Makes the popup invisible.
     */
    hide: function() {
        OpenLayers.Element.hide(this.div);
    },

    /**
     * Method: setSize
     * Used to adjust the size of the popup.
     *
     * Parameters:
     * size - {<OpenLayers.Size>} the new size of the popup in pixels.
     */
    setSize:function(size) {
        if (size != undefined) {
            this.size = size;
        }

        if (this.div != null) {
            this.div.style.width = this.size.w + "px";
            this.div.style.height = this.size.h + "px";
        }
        if (this.contentDiv != null){
            this.contentDiv.style.width = this.size.w + "px";
            this.contentDiv.style.height = this.size.h + "px";
        }
    },

    /**
    * Method: setBackgroundColor
    * Sets the background color of the popup.
    * Parameters:
    * color - {String} the background color.  eg "#FFBBBB"
    */
    setBackgroundColor:function(color) {
        if (color != undefined) {
            this.backgroundColor = color;
        }

        if (this.div != null) {
            this.div.style.backgroundColor = this.backgroundColor;
        }
    },

    /**
     * Method: setOpacity
     * Sets the opacity of the popup.
     *
     * Parameters:
     * opacity - {float} A value between 0.0 (transparent) and 1.0 (solid).
     */
    setOpacity:function(opacity) {
        if (opacity != undefined) {
            this.opacity = opacity;
        }

        if (this.div != null) {
            // for Mozilla and Safari
            this.div.style.opacity = this.opacity;

            // for IE
            this.div.style.filter = 'alpha(opacity=' + this.opacity*100 + ')';
        }
    },

    /**
     * Method: setBorder
     * Sets the border style of the popup.
     *
     * Parameters:
     * border - {String} The border style value. eg 2px
     */
    setBorder:function(border) {
        if (border != undefined) {
            this.border = border;
        }

        if (this.div != null) {
            this.div.style.border = this.border;
        }
    },

    /**
     * Method: setContentHTML
     * Allows the user to set the HTML content of the popup.
     *
     * Parameters:
     * contentHTML - {String} HTML for the div.
     */
    setContentHTML:function(contentHTML) {
        if (contentHTML != null) {
            this.contentHTML = contentHTML;
        }

        if (this.contentDiv != null) {
            this.contentDiv.innerHTML = this.contentHTML;
        }
    },

    /**
     * Method: registerEvents
     * Registers events on the popup.
     *
     * Do this in a separate function so that subclasses can
     *   choose to override it if they wish to deal differently
     *   with mouse events
     *
     *   Note in the following handler functions that some special
     *    care is needed to deal correctly with mousing and popups.
     *
     *   Because the user might select the zoom-rectangle option and
     *    then drag it over a popup, we need a safe way to allow the
     *    mousemove and mouseup events to pass through the popup when
     *    they are initiated from outside.
     *
     *   Otherwise, we want to essentially kill the event propagation
     *    for all other events, though we have to do so carefully,
     *    without disabling basic html functionality, like clicking on
     *    hyperlinks or drag-selecting text.
     */
     registerEvents:function() {
        this.events = new OpenLayers.Events(this, this.div, null, true);

        this.events.on({
            "mousedown": this.onmousedown,
            "mousemove": this.onmousemove,
            "mouseup": this.onmouseup,
            "click": this.onclick,
            "mouseout": this.onmouseout,
            "dblclick": this.ondblclick,
            scope: this
        });

     },

    /**
     * Method: onmousedown
     * When mouse goes down within the popup, make a note of
     *   it locally, and then do not propagate the mousedown
     *   (but do so safely so that user can select text inside)
     *
     * Parameters:
     * evt - {Event}
     */
    onmousedown: function (evt) {
        this.mousedown = true;
        OpenLayers.Event.stop(evt, true);
    },

    /**
     * Method: onmousemove
     * If the drag was started within the popup, then
     *   do not propagate the mousemove (but do so safely
     *   so that user can select text inside)
     *
     * Parameters:
     * evt - {Event}
     */
    onmousemove: function (evt) {
        if (this.mousedown) {
            OpenLayers.Event.stop(evt, true);
        }
    },

    /**
     * Method: onmouseup
     * When mouse comes up within the popup, after going down
     *   in it, reset the flag, and then (once again) do not
     *   propagate the event, but do so safely so that user can
     *   select text inside
     *
     * Parameters:
     * evt - {Event}
     */
    onmouseup: function (evt) {
        if (this.mousedown) {
            this.mousedown = false;
            OpenLayers.Event.stop(evt, true);
        }
    },

    /**
     * Method: onclick
     * Ignore clicks, but allowing default browser handling
     *
     * Parameters:
     * evt - {Event}
     */
    onclick: function (evt) {
        OpenLayers.Event.stop(evt, true);
    },

    /**
     * Method: onmouseout
     * When mouse goes out of the popup set the flag to false so that
     *   if they let go and then drag back in, we won't be confused.
     *
     * Parameters:
     * evt - {Event}
     */
    onmouseout: function (evt) {
        this.mousedown = false;
    },

    /**
     * Method: ondblclick
     * Ignore double-clicks, but allowing default browser handling
     *
     * Parameters:
     * evt - {Event}
     */
    ondblclick: function (evt) {
        OpenLayers.Event.stop(evt, true);
    },

    CLASS_NAME: "OpenLayers.Popup"
});

OpenLayers.Popup.WIDTH = 200;
OpenLayers.Popup.HEIGHT = 200;
OpenLayers.Popup.COLOR = "white";
OpenLayers.Popup.OPACITY = 1;
OpenLayers.Popup.BORDER = "0px";
<commit_msg>proper tabbing is the artichoke of a good garden.<commit_after>/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
 * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
 * full text of the license. */

/**
 * Class: OpenLayers.Popup
 * A popup is a small div that can opened and closed on the map.
 * Typically opened in response to clicking on a marker.
 * See <OpenLayers.Marker>.  Popup's don't require their own
 * layer and are added the the map using the <OpenLayers.Map.addPopup>
 * method.
 *
 * Example:
 * (code)
 * popup = new OpenLayers.Popup("chicken",
 *                    new OpenLayers.LonLat(5,40),
 *                    new OpenLayers.Size(200,200),
 *                    "example popup",
 *                    true);
 *
 * map.addPopup(popup);
 * (end)
 */
OpenLayers.Popup = OpenLayers.Class({

    /**
     * Property: events
     * {<OpenLayers.Events>} custom event manager
     */
    events: null,

    /** Property: id
     * {String} the unique identifier assigned to this popup.
     */
    id: "",

    /**
     * Property: lonlat
     * {<OpenLayers.LonLat>} the position of this popup on the map
     */
    lonlat: null,

    /**
     * Property: div
     * {DOMElement} the div that contains this popup.
     */
    div: null,

    /**
     * Property: size
     * {<OpenLayers.Size>} the width and height of the popup.
     */
    size: null,

    /**
     * Property: contentHTML
     * {String} The HTML that this popup displays.
     */
    contentHTML: "",

    /**
     * Property: backgroundColor
     * {String} the background color used by the popup.
     */
    backgroundColor: "",

    /**
     * Property: opacity
     * {float} the opacity of this popup (between 0.0 and 1.0)
     */
    opacity: "",

    /**
     * Property: border
     * {String} the border size of the popup.  (eg 2px)
     */
    border: "",

    /**
     * Property: contentDiv
     * {DOMElement} a reference to the element that holds the content of
     *              the div.
     */
    contentDiv: null,

    /**
     * Property: groupDiv
     * {DOMElement} the parent of <OpenLayers.Popup.contentDiv>
     */
    groupDiv: null,

    /**
     * Property: closeDiv
     * {DOMElement} the optional closer image
     */
    closeDiv: null,

    /**
     * Property: padding
     * {int} the internal padding of the content div.
     */
    padding: 5,

    /**
     * Property: map
     * {<OpenLayers.Map>} this gets set in Map.js when the popup is added to the map
     */
    map: null,

    /**
    * Constructor: OpenLayers.Popup
    * Create a popup.
    *
    * Parameters:
    * id - {String} a unqiue identifier for this popup.  If null is passed
    *               an identifier will be automatically generated.
    * lonlat - {<OpenLayers.LonLat>}  The position on the map the popup will
    *                                 be shown.
    * size - {<OpenLayers.Size>}      The size of the popup.
    * contentHTML - {String}          The HTML content to display inside the
    *                                 popup.
    * closeBox - {Boolean}            Whether to display a close box inside
    *                                 the popup.
    * closeBoxCallback - {Function}   Function to be called on closeBox click.
    */
    initialize:function(id, lonlat, size, contentHTML, closeBox, closeBoxCallback) {
        if (id == null) {
            id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_");
        }

        this.id = id;
        this.lonlat = lonlat;
        this.size = (size != null) ? size
                                  : new OpenLayers.Size(
                                                   OpenLayers.Popup.WIDTH,
                                                   OpenLayers.Popup.HEIGHT);
        if (contentHTML != null) {
             this.contentHTML = contentHTML;
        }
        this.backgroundColor = OpenLayers.Popup.COLOR;
        this.opacity = OpenLayers.Popup.OPACITY;
        this.border = OpenLayers.Popup.BORDER;

        this.div = OpenLayers.Util.createDiv(this.id, null, null,
                                             null, null, null, "hidden");
        this.div.className = 'olPopup';

        this.groupDiv = OpenLayers.Util.createDiv(null, null, null,
                                                    null, "relative", null,
                                                    "hidden");

        var id = this.div.id + "_contentDiv";
        this.contentDiv = OpenLayers.Util.createDiv(id, null, this.size.clone(),
                                                    null, "relative", null,
                                                    "hidden");
        this.contentDiv.className = 'olPopupContent';
        this.groupDiv.appendChild(this.contentDiv);
        this.div.appendChild(this.groupDiv);

        if (closeBox) {
           // close icon
            var closeSize = new OpenLayers.Size(17,17);
            var img = OpenLayers.Util.getImagesLocation() + "close.gif";
            this.closeDiv = OpenLayers.Util.createAlphaImageDiv(
                this.id + "_close", null, closeSize, img
            );
            this.closeDiv.style.right = this.padding + "px";
            this.closeDiv.style.top = this.padding + "px";
            this.groupDiv.appendChild(this.closeDiv);

            var closePopup = closeBoxCallback || function(e) {
                this.hide();
                OpenLayers.Event.stop(e);
            };
            OpenLayers.Event.observe(this.closeDiv, "click",
                    OpenLayers.Function.bindAsEventListener(closePopup, this));

        }

        this.registerEvents();
    },

    /**
     * Method: destroy
     * nullify references to prevent circular references and memory leaks
     */
    destroy: function() {
        if (this.map != null) {
            this.map.removePopup(this);
            this.map = null;
        }
        this.events.destroy();
        this.events = null;
        this.div = null;
    },

    /**
    * Method: draw
    * Constructs the elements that make up the popup.
    *
    * Parameters:
    * px - {<OpenLayers.Pixel>} the position the popup in pixels.
    *
    * Returns:
    * {DOMElement} Reference to a div that contains the drawn popup
    */
    draw: function(px) {
        if (px == null) {
            if ((this.lonlat != null) && (this.map != null)) {
                px = this.map.getLayerPxFromLonLat(this.lonlat);
            }
        }

        this.setSize();
        this.setBackgroundColor();
        this.setOpacity();
        this.setBorder();
        this.setContentHTML();
        this.moveTo(px);

        return this.div;
    },

    /**
     * Method: updatePosition
     * if the popup has a lonlat and its map members set,
     * then have it move itself to its proper position
     */
    updatePosition: function() {
        if ((this.lonlat) && (this.map)) {
            var px = this.map.getLayerPxFromLonLat(this.lonlat);
            if (px) {
                this.moveTo(px);
            }
        }
    },

    /**
     * Method: moveTo
     *
     * Parameters:
     * px - {<OpenLayers.Pixel>} the top and left position of the popup div.
     */
    moveTo: function(px) {
        if ((px != null) && (this.div != null)) {
            this.div.style.left = px.x + "px";
            this.div.style.top = px.y + "px";
        }
    },

    /**
     * Method: visible
     *
     * Returns:
     * {Boolean} Boolean indicating whether or not the popup is visible
     */
    visible: function() {
        return OpenLayers.Element.visible(this.div);
    },

    /**
     * Method: toggle
     * Toggles visibility of the popup.
     */
    toggle: function() {
        OpenLayers.Element.toggle(this.div);
    },

    /**
     * Method: show
     * Makes the popup visible.
     */
    show: function() {
        OpenLayers.Element.show(this.div);
    },

    /**
     * Method: hide
     * Makes the popup invisible.
     */
    hide: function() {
        OpenLayers.Element.hide(this.div);
    },

    /**
     * Method: setSize
     * Used to adjust the size of the popup.
     *
     * Parameters:
     * size - {<OpenLayers.Size>} the new size of the popup in pixels.
     */
    setSize:function(size) {
        if (size != undefined) {
            this.size = size;
        }

        if (this.div != null) {
            this.div.style.width = this.size.w + "px";
            this.div.style.height = this.size.h + "px";
        }
        if (this.contentDiv != null){
            this.contentDiv.style.width = this.size.w + "px";
            this.contentDiv.style.height = this.size.h + "px";
        }
    },

    /**
    * Method: setBackgroundColor
    * Sets the background color of the popup.
    * Parameters:
    * color - {String} the background color.  eg "#FFBBBB"
    */
    setBackgroundColor:function(color) {
        if (color != undefined) {
            this.backgroundColor = color;
        }

        if (this.div != null) {
            this.div.style.backgroundColor = this.backgroundColor;
        }
    },

    /**
     * Method: setOpacity
     * Sets the opacity of the popup.
     *
     * Parameters:
     * opacity - {float} A value between 0.0 (transparent) and 1.0 (solid).
     */
    setOpacity:function(opacity) {
        if (opacity != undefined) {
            this.opacity = opacity;
        }

        if (this.div != null) {
            // for Mozilla and Safari
            this.div.style.opacity = this.opacity;

            // for IE
            this.div.style.filter = 'alpha(opacity=' + this.opacity*100 + ')';
        }
    },

    /**
     * Method: setBorder
     * Sets the border style of the popup.
     *
     * Parameters:
     * border - {String} The border style value. eg 2px
     */
    setBorder:function(border) {
        if (border != undefined) {
            this.border = border;
        }

        if (this.div != null) {
            this.div.style.border = this.border;
        }
    },

    /**
     * Method: setContentHTML
     * Allows the user to set the HTML content of the popup.
     *
     * Parameters:
     * contentHTML - {String} HTML for the div.
     */
    setContentHTML:function(contentHTML) {
        if (contentHTML != null) {
            this.contentHTML = contentHTML;
        }

        if (this.contentDiv != null) {
            this.contentDiv.innerHTML = this.contentHTML;
        }
    },

    /**
     * Method: registerEvents
     * Registers events on the popup.
     *
     * Do this in a separate function so that subclasses can
     *   choose to override it if they wish to deal differently
     *   with mouse events
     *
     *   Note in the following handler functions that some special
     *    care is needed to deal correctly with mousing and popups.
     *
     *   Because the user might select the zoom-rectangle option and
     *    then drag it over a popup, we need a safe way to allow the
     *    mousemove and mouseup events to pass through the popup when
     *    they are initiated from outside.
     *
     *   Otherwise, we want to essentially kill the event propagation
     *    for all other events, though we have to do so carefully,
     *    without disabling basic html functionality, like clicking on
     *    hyperlinks or drag-selecting text.
     */
     registerEvents:function() {
        this.events = new OpenLayers.Events(this, this.div, null, true);

        this.events.on({
            "mousedown": this.onmousedown,
            "mousemove": this.onmousemove,
            "mouseup": this.onmouseup,
            "click": this.onclick,
            "mouseout": this.onmouseout,
            "dblclick": this.ondblclick,
            scope: this
        });

     },

    /**
     * Method: onmousedown
     * When mouse goes down within the popup, make a note of
     *   it locally, and then do not propagate the mousedown
     *   (but do so safely so that user can select text inside)
     *
     * Parameters:
     * evt - {Event}
     */
    onmousedown: function (evt) {
        this.mousedown = true;
        OpenLayers.Event.stop(evt, true);
    },

    /**
     * Method: onmousemove
     * If the drag was started within the popup, then
     *   do not propagate the mousemove (but do so safely
     *   so that user can select text inside)
     *
     * Parameters:
     * evt - {Event}
     */
    onmousemove: function (evt) {
        if (this.mousedown) {
            OpenLayers.Event.stop(evt, true);
        }
    },

    /**
     * Method: onmouseup
     * When mouse comes up within the popup, after going down
     *   in it, reset the flag, and then (once again) do not
     *   propagate the event, but do so safely so that user can
     *   select text inside
     *
     * Parameters:
     * evt - {Event}
     */
    onmouseup: function (evt) {
        if (this.mousedown) {
            this.mousedown = false;
            OpenLayers.Event.stop(evt, true);
        }
    },

    /**
     * Method: onclick
     * Ignore clicks, but allowing default browser handling
     *
     * Parameters:
     * evt - {Event}
     */
    onclick: function (evt) {
        OpenLayers.Event.stop(evt, true);
    },

    /**
     * Method: onmouseout
     * When mouse goes out of the popup set the flag to false so that
     *   if they let go and then drag back in, we won't be confused.
     *
     * Parameters:
     * evt - {Event}
     */
    onmouseout: function (evt) {
        this.mousedown = false;
    },

    /**
     * Method: ondblclick
     * Ignore double-clicks, but allowing default browser handling
     *
     * Parameters:
     * evt - {Event}
     */
    ondblclick: function (evt) {
        OpenLayers.Event.stop(evt, true);
    },

    CLASS_NAME: "OpenLayers.Popup"
});

OpenLayers.Popup.WIDTH = 200;
OpenLayers.Popup.HEIGHT = 200;
OpenLayers.Popup.COLOR = "white";
OpenLayers.Popup.OPACITY = 1;
OpenLayers.Popup.BORDER = "0px";
<|endoftext|>


<commit_before>'use strict'

const express = require('express')
const Slapp = require('slapp')
const ConvoStore = require('slapp-convo-beepboop')
const Context = require('slapp-context-beepboop')
const http = require('http');

// use `PORT` env var on Beep Boop - default to 3000 locally
var port = process.env.PORT || 3000

var slapp = Slapp({
  // Beep Boop sets the SLACK_VERIFY_TOKEN env var
  verify_token: process.env.SLACK_VERIFY_TOKEN,
  convo_store: ConvoStore(),
  context: Context()
})

var HELP_TEXT = `
I will respond to the following messages:
\`help\` - to see this message.
\`hi\` - to demonstrate a conversation that tracks state.
\`thanks\` - to demonstrate a simple response.
\`<type-any-other-text>\` - to demonstrate a random emoticon response, some of the time :wink:.
\`attachment\` - to see a Slack attachment message.
`

//*********************************************
// Setup different handlers for messages
//*********************************************

// response to the user typing "help"
slapp.message('help', ['mention', 'direct_message'], (msg) => {
  msg.say(HELP_TEXT)
})

// "Conversation" flow that tracks state - kicks off when user says hi, hello or hey
slapp
  .message('^(hi|hello|hey)$', ['direct_mention', 'direct_message'], (msg, text) => {
    msg
      .say(`${text}, how are you?`)
      // sends next event from user to this route, passing along state
      .route('how-are-you', { greeting: text })
  })
  .route('how-are-you', (msg, state) => {
    var text = (msg.body.event && msg.body.event.text) || ''

    // user may not have typed text as their next action, ask again and re-route
    if (!text) {
      return msg
        .say("Whoops, I'm still waiting to hear how you're doing.")
        .say('How are you?')
        .route('how-are-you', state)
    }

    // add their response to state
    state.status = text

    msg
      .say(`Ok then. What's your favorite color?`)
      .route('color', state)
  })
  .route('color', (msg, state) => {
    var text = (msg.body.event && msg.body.event.text) || ''

    // user may not have typed text as their next action, ask again and re-route
    if (!text) {
      return msg
        .say("I'm eagerly awaiting to hear your favorite color.")
        .route('color', state)
    }

    // add their response to state
    state.color = text

    msg
      .say('Thanks for sharing.')
      .say(`Here's what you've told me so far: \`\`\`${JSON.stringify(state)}\`\`\``)
    // At this point, since we don't route anywhere, the "conversation" is over
  })

// Can use a regex as well
slapp.message(/^(thanks|thank you)/i, ['mention', 'direct_message'], (msg) => {
  // You can provide a list of responses, and a random one will be chosen
  // You can also include slack emoji in your responses
  msg.say([
    "You're welcome :smile:",
    'You bet',
    ':+1: Of course',
    'Anytime :sun_with_face: :full_moon_with_face:'
  ])
})

// demonstrate returning an attachment...
slapp.message('attachment', ['mention', 'direct_message'], (msg) => {
  msg.say({
    text: 'Check out this amazing attachment! :confetti_ball: ',
    attachments: [{
      text: 'Slapp is a robust open source library that sits on top of the Slack APIs',
      title: 'Slapp Library - Open Source',
      image_url: 'https://storage.googleapis.com/beepboophq/_assets/bot-1.22f6fb.png',
      title_link: 'https://beepboophq.com/',
      color: '#7CD197'
    }]
  })
})

// Testing
slapp.command('/bible', /.*/, (msg, text) => {
  console.log('Received slash command for Bible. Command entered: /bible ' + text);
  msg.say('Received slash command for Bible. Command entered: /bible ' + text);

  var parsedVerseData = parseVerseData(text)
  var parsedText = parsedVerseData[0];
  var parsedBooks = parsedVerseData[1];
  var parsedVerses = parsedVerseData[2];

  console.log('Interpreting requested verse as: ' + parsedText);
  msg.say('Interpreting requested verse as: ' + parsedText);

  console.log('got parsedBooks as: ' + parsedBooks);
  console.log('got parsedVerses as: ' + parsedVerses);

  sendRequest(parsedText, parsedBooks, parsedVerses, msg);
})

// Catch-all for any other responses not handled above
slapp.message('.*', ['direct_mention', 'direct_message'], (msg) => {
  // respond only 40% of the time
  if (Math.random() < 0.4) {
    msg.say([':wave:', ':pray:', ':raised_hands:'])
  }
})

// Sanitize verses and get book and first verse data
function parseVerseData(text) {
  console.log('In parseVerse()...');
  var verse = text.replace(/\s/g, '+')
                  .replace(/([A-Za-z])(?=\d)/g, '$1+');

  // Get books
  // var booksArray = verse.match(/[A-Za-z]+/g);

  // Get verses, but only the first verse for each book.
  //   These should return the same thing in capture group 1:
  //   /[A-Za-z]+\+?(\d+:\d+)/g
  //   /(\d+:\d+)(-\d+)?((\+\d+:\d+(-\d+)?)+)?/g
  // var firstVerseRegex = /[A-Za-z]+\+?(\d+:\d+)/g;
  // var firstVerseArray = [];
  // var match;
  // while (match = firstVerseRegex.exec(verse)) {
  //   firstVerseArray.push(match[1]);
  // }

  // ([0-9]?[A-Za-z]{1,}|[A-Za-z]{0,})\+([0-9]+:[0-9]+)
  var regex = /([0-9]?[A-Za-z]{1,}|[A-Za-z]{0,})\+([0-9]+:[0-9]+)/g;
  var booksArray = [];
  var verseArray = [];
  var lastBook;
  var match;

  // For all results of the regex execution
  while (match = regex.exec(verse)) {
    var book = match[1];

    // See if there is a new book for the current verse
    if (book === '') {
      // If the book was not changed
      booksArray.push(lastBook); // uUse the last book
    } else {
      // If the book was changed
      booksArray.push(book); // Use the new book
      lastBook = book; // Save the new book
    }

    // Save the verse
    verseArray.push(match[2]);
  }

  console.log('Got books: ' + booksArray);
  console.log('Got first verses: ' + verseArray);

  var data = [verse, booksArray, verseArray];
  return data;
}

// Send HTTP Request
function sendRequest(parsedText, parsedBooks, parsedVerses, msg) {
  console.log('In sendRequest()...');
  var body;
  var options = {
    host: 'labs.bible.org',
    path: '/api/?passage=' + parsedText + '&formatting=full',
  }

  console.log('got parsedBooks as: ' + parsedBooks);
  console.log('got parsedVerses as: ' + parsedVerses);

  console.log('Request URL: labs.bible.org/api/?passage=' + parsedText + '&formatting=full');

  var request = http.get(options, function(response){
    console.log('STATUS: ' + response.statusCode);
    console.log('HEADERS: ' + JSON.stringify(response.headers));

    // Buffer the body entirely for processing as a whole.
    var bodyStream = [];
    response.on('data', function(chunk) {
      // Partially streamed body
      console.log('-----got chunk-----');
      bodyStream.push(chunk);
    }).on('end', function() {
      // Completely streamed body
      console.log('-----finished body-----');
      body = Buffer.concat(bodyStream).toString();
      console.log('BODY: ' + body);
      formatThenReply(body, parsedBooks, parsedVerses, msg);
    })
  });

  request.on('error', function(e) {
    console.log('error occurred in HTTP GET request: ', e.message);
  });
}

function formatThenReply(body, parsedBooks, parsedVerses, msg) {
  console.log('In formatThenReply()...');

  console.log('got parsedBooks as: ' + parsedBooks);
  console.log('got parsedVerses as: ' + parsedVerses);
  var verse = '';

  console.log('Before sanitization:\n' + body);
  // Replace special characters
  // verse = body.replace(/<\/?b>/g, '*') // Fix bold formatting
  //             .replace(/<\/?i>/g, '_') // Fix italics formatting
  //             .replace(/–/g, '-') // Handle unicode dash character
  //             .replace(/<h\d>/g, '\n\n>*') // Fix space before headings and start bolding
  //             .replace(/<\/h\d>/g, '*') // End bolding headings
  //             .replace(/<p.{0,}?>/g, '\n>') // Fix newlines
  //             .replace(/<.+?>/g, '') // Remove all remaining HTML tags
  //             .replace(/[\s>]{0,}(\*\d+:\d+\*)/g, '\n>$1') // Move new sections of the same book to new lines
  //             .replace(/^[\s]{0,}(?=>\*)/, ''); // Finally, remove all extra newlines at the beginning of the text

  verse = body.replace(/<\/?b>/g, '*') // Fix bold formatting
              .replace(/<\/?i>/g, '_') // Fix italics formatting
              .replace(/–/g, '-') // Handle unicode dash character
              .replace(/(<h\d>.{0,}<\/h\d>)([^\*]{0,}\*[^\*]{0,}\*[\S]{0,}\s)/g,
                       '$2\n>*$1*\n>') // Fix space before headings and start bolding
              .replace(/<p.{0,}?>/g, '\n>') // Fix newlines
              .replace(/<.+?>/g, '') // Remove all remaining HTML tags
              .replace(/[\s>]{0,}(\*\d+:\d+\*)/g, '\n>$1') // Move new sections of the same book to new lines
              .replace(/^[\s]{0,}(?=>\*)/, ''); // Finally, remove all extra newlines at the beginning of the text

  console.log('After sanitization:\n' + verse);

  // Inject book titles
  for (var i = 0; i < parsedVerses.length; i++) {
    var replaceTarget = '>*' + parsedVerses[i] + '*';
    var replacementString = '\n>*' + parsedBooks[i] + ' ' + parsedVerses[i] + '*';

    verse = verse.replace(replaceTarget, replacementString);

    console.log('Changing \"' + replaceTarget + '\" to \"' + replacementString + '\"');
  }

  console.log('After title injection:\n' + verse);

  // // Move extra newlines at the beginning of the formatted verse text
  // while (verse.startsWith('\n>')) {
  //   console.log('Removing newline from beginning of the verse');
  //   verse = verse.replace('\n>', ''); // Remove all leading newlines.
  // }

  // Reply with the formatted verse
  reply(verse, msg);
}

function reply(verse, msg) {
  msg.say('Here\'s your verse!\n' + verse);
}

// attach Slapp to express server
var server = slapp.attachToExpress(express())

// start http server
server.listen(port, (err) => {
  if (err) {
    return console.error(err)
  }

  console.log(`Listening on port ${port}`)
})
<commit_msg>Added ability to distinguish between match and full verses, allowing better formatting<commit_after>'use strict'

const express = require('express')
const Slapp = require('slapp')
const ConvoStore = require('slapp-convo-beepboop')
const Context = require('slapp-context-beepboop')
const http = require('http');

// use `PORT` env var on Beep Boop - default to 3000 locally
var port = process.env.PORT || 3000

var slapp = Slapp({
  // Beep Boop sets the SLACK_VERIFY_TOKEN env var
  verify_token: process.env.SLACK_VERIFY_TOKEN,
  convo_store: ConvoStore(),
  context: Context()
})

var HELP_TEXT = `
I will respond to the following messages:
\`help\` - to see this message.
\`hi\` - to demonstrate a conversation that tracks state.
\`thanks\` - to demonstrate a simple response.
\`<type-any-other-text>\` - to demonstrate a random emoticon response, some of the time :wink:.
\`attachment\` - to see a Slack attachment message.
`

//*********************************************
// Setup different handlers for messages
//*********************************************

// response to the user typing "help"
slapp.message('help', ['mention', 'direct_message'], (msg) => {
  msg.say(HELP_TEXT)
})

// "Conversation" flow that tracks state - kicks off when user says hi, hello or hey
slapp
  .message('^(hi|hello|hey)$', ['direct_mention', 'direct_message'], (msg, text) => {
    msg
      .say(`${text}, how are you?`)
      // sends next event from user to this route, passing along state
      .route('how-are-you', { greeting: text })
  })
  .route('how-are-you', (msg, state) => {
    var text = (msg.body.event && msg.body.event.text) || ''

    // user may not have typed text as their next action, ask again and re-route
    if (!text) {
      return msg
        .say("Whoops, I'm still waiting to hear how you're doing.")
        .say('How are you?')
        .route('how-are-you', state)
    }

    // add their response to state
    state.status = text

    msg
      .say(`Ok then. What's your favorite color?`)
      .route('color', state)
  })
  .route('color', (msg, state) => {
    var text = (msg.body.event && msg.body.event.text) || ''

    // user may not have typed text as their next action, ask again and re-route
    if (!text) {
      return msg
        .say("I'm eagerly awaiting to hear your favorite color.")
        .route('color', state)
    }

    // add their response to state
    state.color = text

    msg
      .say('Thanks for sharing.')
      .say(`Here's what you've told me so far: \`\`\`${JSON.stringify(state)}\`\`\``)
    // At this point, since we don't route anywhere, the "conversation" is over
  })

// Can use a regex as well
slapp.message(/^(thanks|thank you)/i, ['mention', 'direct_message'], (msg) => {
  // You can provide a list of responses, and a random one will be chosen
  // You can also include slack emoji in your responses
  msg.say([
    "You're welcome :smile:",
    'You bet',
    ':+1: Of course',
    'Anytime :sun_with_face: :full_moon_with_face:'
  ])
})

// demonstrate returning an attachment...
slapp.message('attachment', ['mention', 'direct_message'], (msg) => {
  msg.say({
    text: 'Check out this amazing attachment! :confetti_ball: ',
    attachments: [{
      text: 'Slapp is a robust open source library that sits on top of the Slack APIs',
      title: 'Slapp Library - Open Source',
      image_url: 'https://storage.googleapis.com/beepboophq/_assets/bot-1.22f6fb.png',
      title_link: 'https://beepboophq.com/',
      color: '#7CD197'
    }]
  })
})

// Testing
slapp.command('/bible', /.*/, (msg, text) => {
  console.log('Received slash command for Bible. Command entered: /bible ' + text);
  msg.say('Received slash command for Bible. Command entered: /bible ' + text);

  var parsedVerseData = parseVerseData(text)
  var parsedText = parsedVerseData[0];
  var parsedBooks = parsedVerseData[1];
  var parsedFullVerses = parsedVerseData[2];
  var parsedMatchVerses = parsedVerseData[3];

  console.log('Interpreting requested verse as: ' + parsedText);
  msg.say('Interpreting requested verse as: ' + parsedText);

  console.log('got parsedBooks as: ' + parsedBooks);
  console.log('got parsedFullVerses as: ' + parsedFullVerses);
  console.log('got parsedMatchVerses as: ' + parsedMatchVerses);

  sendRequest(parsedText, parsedBooks, parsedFullVerses, parsedMatchVerses, msg);
})

// Catch-all for any other responses not handled above
slapp.message('.*', ['direct_mention', 'direct_message'], (msg) => {
  // respond only 40% of the time
  if (Math.random() < 0.4) {
    msg.say([':wave:', ':pray:', ':raised_hands:'])
  }
})

// Sanitize verses and get book and first verse data
function parseVerseData(text) {
  console.log('In parseVerse()...');
  var verse = text.replace(/\s/g, '+')
                  .replace(/([A-Za-z])(?=\d)/g, '$1+');

  // Get books
  // var booksArray = verse.match(/[A-Za-z]+/g);

  // Get verses, but only the first verse for each book.
  //   These should return the same thing in capture group 1:
  //   /[A-Za-z]+\+?(\d+:\d+)/g
  //   /(\d+:\d+)(-\d+)?((\+\d+:\d+(-\d+)?)+)?/g
  // var firstVerseRegex = /[A-Za-z]+\+?(\d+:\d+)/g;
  // var firstVerseArray = [];
  // var match;
  // while (match = firstVerseRegex.exec(verse)) {
  //   firstVerseArray.push(match[1]);
  // }

  // ([0-9]?[A-Za-z]{1,}|[A-Za-z]{0,})\+([0-9]+:[0-9]+)
  var regex = /([0-9]?[A-Za-z]{1,}|[A-Za-z]{0,})\+(([0-9]+:[0-9]+)(?:-\d+)?)/g;
  var booksArray = [];
  var matchVerseArray = [];
  var fullVerseArray = [];
  var lastBook;
  var match;

  // For all results of the regex execution
  while (match = regex.exec(verse)) {
    var book = match[1];

    // See if there is a new book for the current verse
    if (book === '') {
      // If the book was not changed
      booksArray.push(lastBook); // uUse the last book
    } else {
      // If the book was changed
      booksArray.push(book); // Use the new book
      lastBook = book; // Save the new book
    }

    // Save the full verse
    fullVerseArray.push(match[2]);
    matchVerseArray.push(match[3]);
  }

  console.log('Got books: ' + booksArray);
  console.log('Got first verses: ' + matchVerseArray);

  var data = [verse, booksArray, fullVerseArray, matchVerseArray];
  return data;
}

// Send HTTP Request
function sendRequest(parsedText, parsedBooks, parsedFullVerses, parsedMatchVerses, msg) {
  console.log('In sendRequest()...');
  var body;
  var options = {
    host: 'labs.bible.org',
    path: '/api/?passage=' + parsedText + '&formatting=full',
  }

  console.log('Request URL: labs.bible.org/api/?passage=' + parsedText + '&formatting=full');

  var request = http.get(options, function(response){
    console.log('STATUS: ' + response.statusCode);
    console.log('HEADERS: ' + JSON.stringify(response.headers));

    // Buffer the body entirely for processing as a whole.
    var bodyStream = [];
    response.on('data', function(chunk) {
      // Partially streamed body
      console.log('-----got chunk-----');
      bodyStream.push(chunk);
    }).on('end', function() {
      // Completely streamed body
      console.log('-----finished body-----');
      body = Buffer.concat(bodyStream).toString();
      console.log('BODY: ' + body);
      formatThenReply(body, parsedBooks, parsedFullVerses, parsedMatchVerses, msg);
    })
  });

  request.on('error', function(e) {
    console.log('error occurred in HTTP GET request: ', e.message);
  });
}

function formatThenReply(body, parsedBooks, parsedFullVerses, parsedMatchVerses, msg) {
  console.log('In formatThenReply()...');

  var verse = '';

  console.log('Before sanitization:\n' + body);
  // Replace special characters
  // verse = body.replace(/<\/?b>/g, '*') // Fix bold formatting
  //             .replace(/<\/?i>/g, '_') // Fix italics formatting
  //             .replace(/–/g, '-') // Handle unicode dash character
  //             .replace(/<h\d>/g, '\n\n>*') // Fix space before headings and start bolding
  //             .replace(/<\/h\d>/g, '*') // End bolding headings
  //             .replace(/<p.{0,}?>/g, '\n>') // Fix newlines
  //             .replace(/<.+?>/g, '') // Remove all remaining HTML tags
  //             .replace(/[\s>]{0,}(\*\d+:\d+\*)/g, '\n>$1') // Move new sections of the same book to new lines
  //             .replace(/^[\s]{0,}(?=>\*)/, ''); // Finally, remove all extra newlines at the beginning of the text

  verse = body.replace(/<\/?b>/g, '*') // Fix bold formatting
              .replace(/<\/?i>/g, '_') // Fix italics formatting
              .replace(/–/g, '-') // Handle unicode dash character
              .replace(/(<h\d>.{0,}<\/h\d>)([^\*]{0,}\*[^:\*]{0,}:([^\*]{0,}){0,}\*[\S]{0,}\s)/g,
                       '$2\n>*$1*\n>*$3*') // Fix space before headings and start bolding
              .replace(/<p.{0,}?>/g, '\n>') // Fix newlines
              .replace(/<.+?>/g, '') // Remove all remaining HTML tags
              .replace(/[\s>]{0,}(\*\d+:(\d+)\*(?![^A-Za-z]+>))/g,
                       '\n>$1\n>*$2*') // Move new sections of the same book to new lines and copy verse number
              .replace(/^[\s]{0,}(?=>\*)/, ''); // Finally, remove all extra newlines at the beginning of the text

  console.log('After sanitization:\n' + verse);

  // Inject book titles
  for (var i = 0; i < parsedMatchVerses.length; i++) {
    var replaceTarget = '>*' + parsedMatchVerses[i] + '*';
    var replacementString = '\n>*' + parsedBooks[i] + ' ' + parsedFullVerses[i] + '*';

    verse = verse.replace(replaceTarget, replacementString);

    console.log('Changing \"' + replaceTarget + '\" to \"' + replacementString + '\"');
  }

  console.log('After title injection:\n' + verse);

  // // Move extra newlines at the beginning of the formatted verse text
  // while (verse.startsWith('\n>')) {
  //   console.log('Removing newline from beginning of the verse');
  //   verse = verse.replace('\n>', ''); // Remove all leading newlines.
  // }

  // Reply with the formatted verse
  reply(verse, msg);
}

function reply(verse, msg) {
  msg.say('Here\'s your verse!\n' + verse);
}

// attach Slapp to express server
var server = slapp.attachToExpress(express())

// start http server
server.listen(port, (err) => {
  if (err) {
    return console.error(err)
  }

  console.log(`Listening on port ${port}`)
})
<|endoftext|>


<commit_before>// set up ======================================================================
var express = require('express');
var mongoose = require('mongoose'); 					// mongoose for mongodb
var port = process.env.PORT || 8080;
var database = require('./config/database');

var deviare = express(); 			// create our app w/ express
var logger = require('morgan');		// Logging middleware
var bodyParser = require('body-parser');	// Parsing middleware
var methodOverride = require('method-override');	// Method Override middleware

// configuration ===============================================================
mongoose.connect(database.url); 	// connect to mongoDB database on modulus.io

// all environments
deviare.set('title', 'Deviare Url Shortener');
deviare.use(express.static(__dirname + '/public')); 		// set the static files location /public/img will be /img for users
// development only
if ('development' == deviare.get('env')) {
	deviare.use(logger({ format: 'dev', immediate: true })); 	// log every request to the console
}
// production only
if ('production' == deviare.get('env')) {
	deviare.use(logger({ immediate: true }));		 			// log every request to the console
}
deviare.use(bodyParser()); 						// pull information from html in POST
deviare.use(methodOverride()); 					// simulate DELETE and PUT

// routes ======================================================================
require('./app/routes')(deviare);

// listen (start deviare with node server.js) ======================================
deviare.listen(port);
console.log("Deviare app listening on port " + port);<commit_msg>update port for prod<commit_after>// set up ======================================================================
var express = require('express');
var mongoose = require('mongoose'); 					// mongoose for mongodb
var port = process.env.PORT || 8181;
var database = require('./config/database');

var deviare = express(); 			// create our app w/ express
var logger = require('morgan');		// Logging middleware
var bodyParser = require('body-parser');	// Parsing middleware
var methodOverride = require('method-override');	// Method Override middleware

// configuration ===============================================================
mongoose.connect(database.url); 	// connect to mongoDB database on modulus.io

// all environments
deviare.set('title', 'Deviare Url Shortener');
deviare.use(express.static(__dirname + '/public')); 		// set the static files location /public/img will be /img for users
// development only
if ('development' == deviare.get('env')) {
	deviare.use(logger({ format: 'dev', immediate: true })); 	// log every request to the console
}
// production only
if ('production' == deviare.get('env')) {
	deviare.use(logger({ immediate: true }));		 			// log every request to the console
}
deviare.use(bodyParser()); 						// pull information from html in POST
deviare.use(methodOverride()); 					// simulate DELETE and PUT

// routes ======================================================================
require('./app/routes')(deviare);

// listen (start deviare with node server.js) ======================================
deviare.listen(port);
console.log("Deviare app listening on port " + port);
<|endoftext|>


<commit_before>/**
 * modclean
 * Remove unwanted files and directories from your node_modules folder
 * @version 1.2.1 (4/29/2015)
 * @author Kyle Ross
 */

var glob         = require('glob'),
    rimraf       = require('rimraf'),
    util         = require("util"),
    fs           = require('fs'),
    EventEmitter = require("events").EventEmitter,
    path         = require('path'),
    dir          = require('node-dir'),
    patterns     = require('./patterns.json');

var defaults     = {
    /**
     * The directory to search in, default is `process.cwd()`
     * @type {String}
     */
    cwd:        process.cwd(),
    /**
     * Patterns to search for, default is an array loaded from `patterns.json`
     * @type {Array}
     */
    patterns:   patterns.safe,
    /**
     * Exclude directories from being removed
     * @type {Boolean}
     */
    noDirs:     false,
    /**
     * Ignore the provided glob patterns
     * @type {Array|null}
     */
    ignore:     null,
    /**
     * Ignore the case of the file names when searching by patterns (default `true`)
     * @type {Boolean}
     */
    ignoreCase: true,
    /**
     * Function (async or sync) to call before each file is deleted to give ability to prevent deletion. (Optional, default `null`)
     * If called with 3 arguments (file, files, cb) it's async and `cb` must be called with a result (`false` skips file).
     * If called with 1 or 2 arguments (file, files) it's sync and `return false` will skip the file.
     * @type {Function|null}
     */
    process:    null,
    /**
     * The folder name used for storing modules. Used to append to `options.cwd` if running in parent directory (default `"node_modules"`)
     * @type {String}
     */
    modulesDir: 'node_modules',
    /**
     * Remove empty directories as part of the cleanup process (default `true`)
     * @type {Boolean}
     */
    removeEmptyDirs: true,
    /**
     * Whether file deletion errors should halt the module from running and return the error to the callback (default `false`)
     * @type {Boolean}
     */
    errorHalt:  false,
    /**
     * Use test mode which will get the list of files and run the process without actually deleting the files (default `false`)
     * @type {Boolean}
     */
    test:       false
};

// export modclean
module.exports = modclean;

/**
 * Shortcut for calling `new ModClean(options, cb).clean()`
 * @param  {Object}   options Options to set for ModClean (Optional)
 * @param  {Function} cb      Callback function to call once completed or if error
 * @return {Object}           New ModClean instance
 */
function modclean(options, cb) {
    return new ModClean(options, cb).clean();
}

/**
 * The default options for ModClean that can be overridden.
 * @property {Object} options Default options for ModClean.
 */
modclean.defaults = defaults;

/**
 * The full list of patterns from patterns.json
 * @property {Object} patterns `safe`, `caution` and `danger` patterns from patterns.json
 */
modclean.patterns = patterns;

// Export ModClean class
modclean.ModClean = ModClean;

/**
 * Main ModClean class
 * @class ModClean
 * @param {Object}   options ModClean options (optional)
 * @param {Function} cb  Callback function to call if once completed or if error - `function(error, files)`
 */
function ModClean(options, cb) {
    if(!(this instanceof ModClean)) return new ModClean(options);
    EventEmitter.call(this);

    if(typeof options === 'function') cb = options;
    if(!options || typeof options !== 'object') options = {};

    this.options = options = extend(Object.create(modclean.defaults), options || {});
    if(typeof this.options.patterns === 'string') this.options.patterns = [this.options.patterns];

    if(Array.isArray(this.options.patterns)) {
        var _patterns = [];
        for(var i = 0; i < this.options.patterns.length; i++) {
            if(Array.isArray(this.options.patterns[i])) {
                _patterns = _patterns.concat(this.options.patterns[i]);
            } else {
                _patterns.push(this.options.patterns[i]);
            }
        }

        this.options.patterns = _patterns;
    }

    if(this.options.modulesDir !== false && path.basename(this.options.cwd) !== this.options.modulesDir)
        this.options.cwd = path.join(this.options.cwd, this.options.modulesDir);

    if(cb) this.clean(cb);
}

// Inherit EventEmitter.prototype
util.inherits(ModClean, EventEmitter);

/**
 * Start the clean process
 * @public
 * @param {Function} cb  Callback function to call if once completed or if error - `function(error, files)`
 */
ModClean.prototype.clean = function(cb) {
    var self = this;

    this.finalCallback = function(err, results) {
        /**
         * @event complete
         * @property {Mixed} err     An error string/object if error was thrown
         * @property {Array} results List of files successfully deleted
         */
        this.emit('complete', err, results);
        if(typeof cb === 'function') cb(err, results);
    };

    /**
     * @event start
     * @property {Object} this ModClean instance
     */
    self.emit('start', this);

    self._find(null, function(err, files) {
        if(err) return self.finalCallback(err);

        self._process(files, function(err, results) {
            if(err) return self.finalCallback(err);

            if(self.options.removeEmptyDirs) {
                self._removeEmpty(function(e, res) {
                    self.finalCallback(e, results.concat(res));
                });
            } else self.finalCallback(err, results);
        });
    });
};

/**
 * Find patterns using glob and return file list
 * @protected
 * @param  {Array}    patterns Array of patterns to find (defaults to `options.patterns`)
 * @param  {Function} cb       Callback to call with `error` and `files` when complete
 */
ModClean.prototype._find = function(patterns, cb) {
    var self = this,
        opts = self.options,
        globOpts = {
            cwd: opts.cwd,
            dot: true,
            nocase: opts.ignoreCase,
            ignore: opts.ignore,
            nodir: opts.noDirs
        };

    glob('**/@('+ (patterns || opts.patterns).join('|') +')', globOpts, function(err, files) {
        if(err) self.emit('error', err);
        /**
         * @event files
         * @property {Array} files List of files found based on `options.patterns` in `options.cwd`
         */
        else self.emit('files', files);
        cb(err, files);
    });
};

/**
 * Processes a list of files
 * @protected
 * @param  {Array}    files List of files to process
 * @param  {Function} cb    Callback function to call once completed or if error
 */
ModClean.prototype._process = function(files, cb) {
    var self = this,
        opts = this.options,
        processFn = typeof opts.process === 'function'? opts.process : function() { return true; };

    if(!files.length) return cb(null, []);

    eachSeries(files, function(file, callback) {
        if(processFn.length === 3) {
            // If processFn has 3 arguments (file, files, cb), then assume async
            processFn(file, files, function(result) {
                if(result !== false) self._deleteFile(file, callback);
                else callback();
            });
        } else {
            // If processFn has any other number of arguments (default: file, files), then assume sync
            if(processFn(file, files) !== false) self._deleteFile(file, callback);
            else callback();
        }
    }, function(err, results, errFile) {
        /**
         * @event finish
         * @property {Array} results List of files successfully deleted
         */
        self.emit('finish', results);
        cb(err, results);
    });
};

/**
 * Deletes file/folder at given path
 * @protected
 * @param  {String}   file The file/folder path to delete
 * @param  {Function} cb   Callback function to call when complete or error
 */
ModClean.prototype._deleteFile = function(file, cb) {
    var self = this,
        opts = self.options;
    // If test mode is enabled, just return the file.
    if(opts.test) {
        self.emit('deleted', file);
        return cb(null, file);
    }

    rimraf(path.join(opts.cwd, file), function(err) {
        if(err) {
            /**
             * @event fileError
             * @property {Mixed}  err  The error object/string
             * @property {String} file The file that caused the error
             */
            self.emit('fileError', err, file);
            return opts.errorHalt? cb(err, file) : cb();
        }

        /**
         * @event deleted
         * @property {String} file The deleted file name
         */
        self.emit('deleted', file);
        cb(null, file);
    });
};

/**
 * Remove empty directories from `options.cwd`.
 * @protected
 * @param  {Function} cb Callback function to call when completed `function(err)`.
 */
ModClean.prototype._removeEmpty = function(cb) {
    var self = this,
        opts = self.options;
    // If test mode is enabled or removeEmptyDirs is disabled, just return.
    if(opts.test || !opts.removeEmptyDirs) return cb(null);

    dir.subdirs(opts.cwd, function(err, dirs) {
        if(err) return cb(err);
        if(!Array.isArray(dirs)) return cb();

        eachSeries(dirs, function(dp, icb) {
            isEmptyDir(dp, function(err, empty) {
                if(err || !empty) return icb();

                rimraf(dp, function(err) {
                    if(err) return icb();
                    icb(null, dp);
                });
            });
        }, function(err, results) {
            cb(err, results);
        });
    });
};

/**
 * Determines if provided path is empty.
 * @private
 * @param  {String}   path The path to check if is empty
 * @param  {Function} cb   Callback function to call once completed
 */
function isEmptyDir(path, cb) {
    fs.readdir(path, function (err, files) {
        if(err === null) return cb(null, !!!files.length);
        cb(err);
    });
}

/**
 * Extends object with properties from another object
 * @private
 * @param  {Object} obj The destination object
 * @param  {Object} ... The object in which to copy the properties from
 * @return {Object}     Destination object with properties copied
 */
function extend(obj) {
    var arr = [];

    arr.forEach.call(arr.slice.call(arguments, 1), function(source) {
        if(source) {
            for(var prop in source) {
                obj[prop] = source[prop];
            }
        }
    });
    return obj;
}

/**
 * Iterate through array asynchronously one item at a given time.
 * Modified from `node-async` - https://github.com/caolan/async/blob/master/lib/async.js
 * @private
 * @param  {Array}    arr      The array to iterate through
 * @param  {Function} iterator The function to run for each iteration
 * @param  {Function} callback Final callback function to call once all iterations have completed or if error
 */
function eachSeries(arr, iterator, callback) {
    var results = [];
    callback = callback || function() {};
    if(!arr.length) return callback();
    var completed = 0;
    var iterate = function() {
        iterator(arr[completed], function(err, result) {
            if(err) {
                callback(err, results, result);
                callback = function() {};
            } else {
                if(result) results.push(result);
                completed += 1;
                if (completed >= arr.length) callback(null, results);
                else iterate();
            }
        });
    };
    iterate();
}
<commit_msg>Moving functionality to lib/modclean.js<commit_after>"use strict";

module.exports = require('./lib/modclean');
<|endoftext|>


<commit_before>exports.definition = {
	config : {
		columns : {
			id : "TEXT UNIQUE NOT NULL PRIMARY KEY",
			date : "TEXT NOT NULL",
			amount : "REAL NOT NULL",
			friendId : "TEXT",
			friendAccountId : "TEXT",
			moneyAccountId : "TEXT NOT NULL",
			projectId : "TEXT NOT NULL",
			localCurrencyId : "TEXT NOT NULL",
			exchangeRate : "REAL NOT NULL",
			interest : "REAL NOT　NULL",
			remark : "TEXT",
			moneyLendId : "TEXT",
			ownerUserId : "TEXT NOT NULL",
			serverRecordHash : "TEXT",
			lastServerUpdateTime : "INTEGER"
		},
		belongsTo : {
			friend : {
				type : "Friend",
				attribute : "moneyPaybacks"
			},
			friendAccount : {
				type : "MoneyAccount",
				attribute : null
			},
			moneyAccount : {
				type : "MoneyAccount",
				attribute : "moneyPaybacks"
			},
			project : {
				type : "Project",
				attribute : "moneyPaybacks"
			},
			localCurrency : {
				type : "Currency",
				attribute : null
			},
			moneyLend : {
				type : "MoneyLend",
				attribute : "moneyPaybacks"
			},
			ownerUser : {
				type : "User",
				attribute : "moneyPaybacks"
			}
		},

		adapter : {
			type : "hyjSql"
		}
	},
	extendModel : function(Model) {
		_.extend(Model.prototype, Alloy.Globals.XModel, {
			// extended functions and properties go here
			validators : {
				date : function(xValidateComplete) {
					var error;
					if (this.xGet("moneyLend")) {
						var moneyLend = this.xGet("moneyLend");
						if (this.xGet("date") < moneyLend.xGet("date")) {
							error = {
								msg : "收款日不能在借出日之前（" + moneyLend.xGet("date") + "）"
							}
						}
					}
					xValidateComplete(error);
				},

				amount : function(xValidateComplete) {
					var error;
					if (isNaN(this.xGet("amount"))) {
						error = {
							msg : "请输入金额"
						};
					} else {
						if (this.xGet("amount") < 0) {
							error = {
								msg : "金额不能为负数"
							};
						}
					}
					if (this.xGet("moneyLend")) {
						var paybackRequireAmount;
						var lendRate = this.xGet("moneyLend").xGet("exchangeRate");
						var paybackRate = this.xGet("exchangeRate");
						if (this.isNew()) {
							paybackRequireAmount = this.xGet("moneyLend").xGet("amount") - this.xGet("moneyLend").previous("paybackedAmount");
						} else {
							paybackRequireAmount = this.xGet("moneyLend").xGet("amount") - this.xGet("moneyLend").previous("paybackedAmount") + this.previous("amount");
						}
						if (this.xGet("amount") * paybackRate / lendRate > paybackRequireAmount) {
							error = {
								msg : "收款金额不能大于当前借出的应收款金额（" + paybackRequireAmount + "）"
							}
						}
					}
					xValidateComplete(error);
				},
				interest : function(xValidateComplete) {
					var error;
					if (isNaN(this.xGet("interest"))) {
						error = {
							msg : "金额只能为数字"
						};
					} else {
						if (this.xGet("interest") < 0) {
							error = {
								msg : "金额不能为负数"
							};
						}
					}
					xValidateComplete(error);
				},
				exchangeRate : function(xValidateComplete) {
					var error;
					if (isNaN(this.xGet("exchangeRate"))) {
						error = {
							msg : "汇率只能为数字"
						};
					} else {
						if (this.xGet("exchangeRate") < 0) {
							error = {
								msg : "汇率不能为负数"
							};
						} else if (this.xGet("exchangeRate") === 0) {
							error = {
								msg : "汇率不能为0"
							};
						}
					}
					xValidateComplete(error);
				},
				friendAccount : function(xValidateComplete) {
					var error;
					var friendAccount = this.xGet("friendAccount");
					if (friendAccount) {
						var moneyAccount = this.xGet("moneyAccount");
						if (friendAccount.xGet("currency") !== moneyAccount.xGet("currency")) {
							error = {
								msg : "请选择与账户相同币种的债务人账户"
							};
						}
					}
					xValidateComplete(error);
				},
				project : function(xValidateComplete) {
					var error;
					var project = this.xGet("project");
					if (!project) {
						error = {
							msg : "项目不能为空"
						};
					}
					xValidateComplete(error);
				}
			},
			getLocalAmount : function() {
				return this.xGet("localCurrency").xGet("symbol") + (this.xGet("amount") * this.xGet("exchangeRate")).toUserCurrency();
			},
			getProjectName : function() {
				return this.xGet("project").xGet("name");
			},
			getAccountCurrency : function() {
				var currencySymbol = null;
				if (this.xGet("ownerUserId") === Alloy.Models.User.xGet("id")) {
					var accountCurrency = this.xGet("moneyAccount").xGet("currency");
					var localCurrency = this.xGet("localCurrency");
					if (accountCurrency === localCurrency) {
						currencySymbol = null;
					} else {
						currencySymbol = accountCurrency.xGet("symbol");
					}
				}
				return currencySymbol;
			},
			getOwnerUser : function() {
				var ownerUserSymbol;
				if (!this.xGet("ownerUserId") || this.xGet("ownerUserId") === Alloy.Models.User.xGet("id")) {
					ownerUserSymbol = null;
				} else {
					if (!this.__friends) {
						var friends = Alloy.createCollection("Friend");
						friends.xSetFilter({
							friendUser : this.xGet("ownerUser"),
							ownerUser : Alloy.Models.User
						});
						friends.xSearchInDb({
							friendUserId : this.xGet("ownerUser").xGet("id"),
							ownerUserId : Alloy.Models.User.xGet("id")
						});
						this.__friends = friends;
					}
					var friend = this.__friends.at(0);
					ownerUserSymbol = friend.getDisplayName();
				}

				return ownerUserSymbol;
			},
			getInterest : function() {
				return this.xGet("interest").toUserCurrency();
			},
			xDelete : function(xFinishCallback, options) {
				var moneyAccount = this.xGet("moneyAccount");
				var amount = this.xGet("amount");
				var paybackRate = this.xGet("exchangeRate");
				var interest = this.xGet("interest");

				this._xDelete(function(error) {
					if (!error) {
						var saveOptions = _.extend({}, options);
						saveOptions.patch = true;
						moneyAccount.save({
							currentBalance : moneyAccount.xGet("currentBalance") - amount - interest
						}, saveOptions);

						if (this.xGet("moneyLend")) {
							var moneyLend = this.xGet("moneyLend");
							var lendRate = moneyLend.xGet("exchangeRate");
							moneyLend.save({
								paybackedAmount : moneyLend.xGet("paybackedAmount") - amount * paybackRate / lendRate
							}, saveOptions);
						}

					}
					xFinishCallback(error);
				}, options);
			}
		});
		return Model;
	},
	extendCollection : function(Collection) {
		_.extend(Collection.prototype, Alloy.Globals.XCollection, {
			// extended functions and properties go here
		});

		return Collection;
	}
}

<commit_msg>fix #280<commit_after>exports.definition = {
	config : {
		columns : {
			id : "TEXT UNIQUE NOT NULL PRIMARY KEY",
			date : "TEXT NOT NULL",
			amount : "REAL NOT NULL",
			friendId : "TEXT",
			friendAccountId : "TEXT",
			moneyAccountId : "TEXT NOT NULL",
			projectId : "TEXT NOT NULL",
			localCurrencyId : "TEXT NOT NULL",
			exchangeRate : "REAL NOT NULL",
			interest : "REAL NOT　NULL",
			remark : "TEXT",
			moneyLendId : "TEXT",
			ownerUserId : "TEXT NOT NULL",
			serverRecordHash : "TEXT",
			lastServerUpdateTime : "INTEGER"
		},
		belongsTo : {
			friend : {
				type : "Friend",
				attribute : "moneyPaybacks"
			},
			friendAccount : {
				type : "MoneyAccount",
				attribute : null
			},
			moneyAccount : {
				type : "MoneyAccount",
				attribute : "moneyPaybacks"
			},
			project : {
				type : "Project",
				attribute : "moneyPaybacks"
			},
			localCurrency : {
				type : "Currency",
				attribute : null
			},
			moneyLend : {
				type : "MoneyLend",
				attribute : "moneyPaybacks"
			},
			ownerUser : {
				type : "User",
				attribute : "moneyPaybacks"
			}
		},

		adapter : {
			type : "hyjSql"
		}
	},
	extendModel : function(Model) {
		_.extend(Model.prototype, Alloy.Globals.XModel, {
			// extended functions and properties go here
			validators : {
				date : function(xValidateComplete) {
					var error;
					if (this.xGet("moneyLend")) {
						var moneyLend = this.xGet("moneyLend");
						if (this.xGet("date") < moneyLend.xGet("date")) {
							error = {
								msg : "收款日不能在借出日之前（" + moneyLend.xGet("date") + "）"
							}
						}
					}
					xValidateComplete(error);
				},

				amount : function(xValidateComplete) {
					var error;
					if (isNaN(this.xGet("amount"))) {
						error = {
							msg : "请输入金额"
						};
					} else {
						if (this.xGet("amount") < 0) {
							error = {
								msg : "金额不能为负数"
							};
						}
					}
					if (this.xGet("moneyLend")) {
						var paybackRequireAmount;
						var lendRate = this.xGet("moneyLend").xGet("exchangeRate");
						var paybackRate = this.xGet("exchangeRate");
						if (this.isNew()) {
							paybackRequireAmount = this.xGet("moneyLend").xGet("amount") - this.xGet("moneyLend").previous("paybackedAmount");
						} else {
							paybackRequireAmount = this.xGet("moneyLend").xGet("amount") - this.xGet("moneyLend").previous("paybackedAmount") + this.previous("amount");
						}
						if (this.xGet("amount") * paybackRate / lendRate > paybackRequireAmount) {
							error = {
								msg : "收款金额不能大于当前借出的应收款金额（" + paybackRequireAmount + "）"
							}
						}
					}
					xValidateComplete(error);
				},
				interest : function(xValidateComplete) {
					var error;
					if (isNaN(this.xGet("interest"))) {
						error = {
							msg : "金额只能为数字"
						};
					} else {
						if (this.xGet("interest") < 0) {
							error = {
								msg : "金额不能为负数"
							};
						}
					}
					xValidateComplete(error);
				},
				exchangeRate : function(xValidateComplete) {
					var error;
					if (isNaN(this.xGet("exchangeRate"))) {
						error = {
							msg : "汇率只能为数字"
						};
					} else {
						if (this.xGet("exchangeRate") < 0) {
							error = {
								msg : "汇率不能为负数"
							};
						} else if (this.xGet("exchangeRate") === 0) {
							error = {
								msg : "汇率不能为0"
							};
						}
					}
					xValidateComplete(error);
				},
				friendAccount : function(xValidateComplete) {
					var error;
					var friendAccount = this.xGet("friendAccount");
					if (friendAccount) {
						var moneyAccount = this.xGet("moneyAccount");
						if (friendAccount.xGet("currency") !== moneyAccount.xGet("currency")) {
							error = {
								msg : "请选择与账户相同币种的债务人账户"
							};
						}
					}
					xValidateComplete(error);
				},
				project : function(xValidateComplete) {
					var error;
					var project = this.xGet("project");
					if (!project) {
						error = {
							msg : "项目不能为空"
						};
					}
					xValidateComplete(error);
				}
			},
			getLocalAmount : function() {
				return this.xGet("localCurrency").xGet("symbol") + (this.xGet("amount") * this.xGet("exchangeRate")).toUserCurrency();
			},
			getProjectName : function() {
				return this.xGet("project").xGet("name");
			},
			getAccountCurrency : function() {
				var currencySymbol = null;
				if (this.xGet("ownerUserId") === Alloy.Models.User.xGet("id")) {
					var accountCurrency = this.xGet("moneyAccount").xGet("currency");
					var localCurrency = this.xGet("localCurrency");
					if (accountCurrency === localCurrency) {
						currencySymbol = null;
					} else {
						currencySymbol = accountCurrency.xGet("symbol");
					}
				}
				return currencySymbol;
			},
			getOwnerUser : function() {
				var ownerUserSymbol;
				if (!this.xGet("ownerUserId") || this.xGet("ownerUserId") === Alloy.Models.User.xGet("id")) {
					ownerUserSymbol = null;
				} else {
					if (!this.__friends) {
						var friends = Alloy.createCollection("Friend");
						friends.xSetFilter({
							friendUser : this.xGet("ownerUser"),
							ownerUser : Alloy.Models.User
						});
						friends.xSearchInDb({
							friendUserId : this.xGet("ownerUser").xGet("id"),
							ownerUserId : Alloy.Models.User.xGet("id")
						});
						this.__friends = friends;
					}
					var friend = this.__friends.at(0);
					ownerUserSymbol = friend.getDisplayName();
				}

				return ownerUserSymbol;
			},
			getInterest : function() {
				return this.xGet("interest").toUserCurrency();
			},
			xDelete : function(xFinishCallback, options) {
				var self = this;
				var moneyAccount = this.xGet("moneyAccount");
				var amount = this.xGet("amount");
				var paybackRate = this.xGet("exchangeRate");
				var interest = this.xGet("interest");

				this._xDelete(function(error) {
					if (!error) {
						var saveOptions = _.extend({}, options);
						saveOptions.patch = true;
						moneyAccount.save({
							currentBalance : moneyAccount.xGet("currentBalance") - amount - interest
						}, saveOptions);

						if (self.xGet("moneyLend")) {
							var moneyLend = self.xGet("moneyLend");
							var lendRate = moneyLend.xGet("exchangeRate");
							moneyLend.save({
								paybackedAmount : moneyLend.xGet("paybackedAmount") - amount * paybackRate / lendRate
							}, saveOptions);
						}

					}
					xFinishCallback(error);
				}, options);
			}
		});
		return Model;
	},
	extendCollection : function(Collection) {
		_.extend(Collection.prototype, Alloy.Globals.XCollection, {
			// extended functions and properties go here
		});

		return Collection;
	}
}

<|endoftext|>


<commit_before>'use strict';

var spawn = require('child_process').spawn,
    debug = require('debug')('Camera:Sensr:MjpegToSrtpStream');

/**
 * Stream an MJPEG source to an SRTP destination
 *
 * @param {any} streamOptions
 * @see {@link https://github.com/KhaosT/HAP-NodeJS/wiki/IP-Camera|HAP-NodeJS IP Camera Reference}
 * @see {@link https://github.com/KhaosT/homebridge-camera-ffmpeg/|Homebridge Camera FFMPEG}
 * @see {@link https://ffmpeg.org/ffmpeg.html|FFMPEG Reference}
 */
function MjpegToSrtpStream(streamOptions) {
    if (!streamOptions ||
        !streamOptions.sourceUrl ||
        !streamOptions.destinationUrl ||
        !streamOptions.destinationKey ||
        !streamOptions.width ||
        !streamOptions.height) {
        throw new Error('streamOptions must be specified with a sourceUrl, destinationUrl, destinationKey, width, and height.');
    }

    var self = this;
    self.sourceUrl = streamOptions.sourceUrl;
    //self.sourceUrl = 'http://192.168.123.181/video.cgi';
    self.destinationUrl = streamOptions.destinationUrl;
    self.destinationKey = streamOptions.destinationKey;
    self.width = streamOptions.width;
    self.height = streamOptions.height;
    self.fps = streamOptions.fps || 3;
    self.bitrate = streamOptions.bitrate || 300;

    return self.startStream();
}

MjpegToSrtpStream.prototype.startStream = function () {
    var self = this,
        ffmpegCommand = '';

    // GLOBAL OPTIONS
    ////////////////////////////////////////////
    ffmpegCommand += ' -loglevel debug';
    ffmpegCommand += ' -stats';

    // INPUT OPTIONS
    ////////////////////////////////////////////
    // set the input format explicitly
    ffmpegCommand += ' -f mjpeg';
    // Since we're using MJPEG, treat this as a "screen grabber"
    ffmpegCommand += ' -re';
    // starting with the input MJPEG URL
    ffmpegCommand += ' -i ' + self.sourceUrl;

    // OUTPUT OPTIONS
    ////////////////////////////////////////////
    // set the output codec
    ffmpegCommand += ' -c:v libx264';
    // set number of threads
    ffmpegCommand += ' -threads 0';
    // set the preset and tuning
    ffmpegCommand += ' -tune zerolatency';
    // set the constant rate (higher is lower quality)
    ffmpeg += ' -crf 18';
    // set a frame rate
    ffmpeg += ' -r ' + self.fps;
    // set the pixel format
    //ffmpeg += ' -pix_fmt yuv420p';
    // set a scaling
    ffmpegCommand += ' -vf scale=' + self.width + ':' + self.height;
    // there is no audio feed available
    ffmpegCommand += ' -an';
    // Bit rate and buffer
    ffmpegCommand += ' -b:v '+ self.bitrate +'k';
    ffmpegCommand += ' -bufsize '+ self.bitrate +'k';
    // there is no audio feed available
    ffmpegCommand += ' -ssrc 1 -payload_type 99';
    // setting destination output format to RTP
    ffmpegCommand += ' -f rtp';
    // specify the SRTP version
    ffmpegCommand += ' -srtp_out_suite AES_CM_128_HMAC_SHA1_80';
    // specify the SRTP key and destination for output
    ffmpegCommand += ' -srtp_out_params ' + self.destinationKey;
    // and now the output file
    ffmpegCommand +=' ' + self.destinationUrl;

    debug(ffmpegCommand);

    var ffmpeg = spawn('ffmpeg', ffmpegCommand.split(' '), {
        env: process.env
    });
    ffmpeg.stdout.on('data', debug);
    ffmpeg.stderr.setEncoding('utf8');
    ffmpeg.stderr.on('data', debug);
    ffmpeg.on('err', debug);
    ffmpeg.on('close', function (code) {
        debug('FFMPEG exiting with code ' + code);
    });

    return ffmpeg;
};

module.exports = MjpegToSrtpStream;<commit_msg>Removed extra space as it was creating another output stream<commit_after>'use strict';

var spawn = require('child_process').spawn,
    debug = require('debug')('Camera:Sensr:MjpegToSrtpStream');

/**
 * Stream an MJPEG source to an SRTP destination
 *
 * @param {any} streamOptions
 * @see {@link https://github.com/KhaosT/HAP-NodeJS/wiki/IP-Camera|HAP-NodeJS IP Camera Reference}
 * @see {@link https://github.com/KhaosT/homebridge-camera-ffmpeg/|Homebridge Camera FFMPEG}
 * @see {@link https://ffmpeg.org/ffmpeg.html|FFMPEG Reference}
 */
function MjpegToSrtpStream(streamOptions) {
    if (!streamOptions ||
        !streamOptions.sourceUrl ||
        !streamOptions.destinationUrl ||
        !streamOptions.destinationKey ||
        !streamOptions.width ||
        !streamOptions.height) {
        throw new Error('streamOptions must be specified with a sourceUrl, destinationUrl, destinationKey, width, and height.');
    }

    debug(streamOptions);

    var self = this;
    self.sourceUrl = streamOptions.sourceUrl;
    self.destinationUrl = streamOptions.destinationUrl;
    self.destinationKey = streamOptions.destinationKey;
    self.width = streamOptions.width;
    self.height = streamOptions.height;
    self.fps = streamOptions.fps || 3;
    self.bitrate = streamOptions.bitrate || 300;

    return self.startStream();
}

MjpegToSrtpStream.prototype.startStream = function () {
    var self = this,
        ffmpegCommand = '';

    // GLOBAL OPTIONS
    ////////////////////////////////////////////
    ffmpegCommand += '-loglevel debug';
    ffmpegCommand += ' -stats';

    // INPUT OPTIONS
    ////////////////////////////////////////////
    // set the input format explicitly
    ffmpegCommand += ' -f mjpeg';
    // Since we're using MJPEG, treat this as a "screen grabber"
    ffmpegCommand += ' -re';
    // starting with the input MJPEG URL
    ffmpegCommand += ' -i ' + self.sourceUrl;

    // OUTPUT OPTIONS
    ////////////////////////////////////////////
    // set the output codec
    ffmpegCommand += ' -c:v libx264';
    // set number of threads
    ffmpegCommand += ' -threads 0';
    // set the preset and tuning
    ffmpegCommand += ' -tune zerolatency';
    // set the constant rate (higher is lower quality)
    ffmpeg += ' -crf 8';
    // set a frame rate
    ffmpeg += ' -r ' + self.fps;
    // set the pixel format
    ffmpeg += ' -pix_fmt yuv420p';
    // set a scaling
    ffmpegCommand += ' -vf scale=' + self.width + ':' + self.height;
    // there is no audio feed available
    ffmpegCommand += ' -an';
    // Bit rate and buffer
    ffmpegCommand += ' -b:v '+ self.bitrate +'k';
    ffmpegCommand += ' -bufsize '+ self.bitrate +'k';
    // some defaults for RTP
    ffmpegCommand += ' -ssrc 1 -payload_type 99';
    // setting destination output format to RTP
    ffmpegCommand += ' -f rtp';
    // specify the SRTP version
    ffmpegCommand += ' -srtp_out_suite AES_CM_128_HMAC_SHA1_80';
    // specify the SRTP key and destination for output
    ffmpegCommand += ' -srtp_out_params ' + self.destinationKey;
    // and now the output file
    ffmpegCommand +=' ' + self.destinationUrl;

    debug(ffmpegCommand);

    var ffmpegParameters = ffmpegCommand.split(' '),
        ffmpeg = spawn('ffmpeg', ffmpegParameters, {
        env: process.env
    });

    ffmpeg.stdout.on('data', debug);
    ffmpeg.stderr.setEncoding('utf8');
    ffmpeg.stderr.on('data', debug);
    ffmpeg.on('err', debug);
    ffmpeg.on('close', function (code) {
        debug('FFMPEG exiting with code ' + code);
    });

    return ffmpeg;
};

module.exports = MjpegToSrtpStream;<|endoftext|>