<commit_before>/**
 * This started out as a fork of TimeoutTransitionGroup by Khan Academy
 * @url https://github.com/rhobot/timeout-transition-group
 */

// Use require instead of import until we're on 0.14 and can just use the react-addons-transition-group package
var React = require('react/addons');
import classNames from 'classnames';

var {addons: {TransitionGroup: ReactTransitionGroup}} = React;

const TICK = 17;

/**
 * EVENT_NAME_MAP is used to determine which event fired when a
 * transition/animation ends, based on the style property used to
 * define that event.
 */
var EVENT_NAME_MAP = {
    transitionend: {
        'transition': 'transitionend',
        'WebkitTransition': 'webkitTransitionEnd',
        'MozTransition': 'mozTransitionEnd',
        'OTransition': 'oTransitionEnd',
        'msTransition': 'MSTransitionEnd'
    },

    animationend: {
        'animation': 'animationend',
        'WebkitAnimation': 'webkitAnimationEnd',
        'MozAnimation': 'mozAnimationEnd',
        'OAnimation': 'oAnimationEnd',
        'msAnimation': 'MSAnimationEnd'
    }
};

var endEvents = [];

(function detectEvents() {
    if (typeof window === "undefined") {
        return;
    }

    var testEl = document.createElement('div');
    var style = testEl.style;

    // On some platforms, in particular some releases of Android 4.x, the
    // un-prefixed "animation" and "transition" properties are defined on the
    // style object but the events that fire will still be prefixed, so we need
    // to check if the un-prefixed events are useable, and if not remove them
    // from the map
    if (!('AnimationEvent' in window)) {
        delete EVENT_NAME_MAP.animationend.animation;
    }

    if (!('TransitionEvent' in window)) {
        delete EVENT_NAME_MAP.transitionend.transition;
    }

    for (var baseEventName in EVENT_NAME_MAP) {
        if (EVENT_NAME_MAP.hasOwnProperty(baseEventName)) {
            var baseEvents = EVENT_NAME_MAP[baseEventName];
            for (var styleName in baseEvents) {
                if (styleName in style) {
                    endEvents.push(baseEvents[styleName]);
                    break;
                }
            }

        }
    }
})();

function animationSupported() {
    return endEvents.length !== 0;
}

/**
 * Functions for element class management to replace dependency on jQuery
 * addClass, removeClass and hasClass
 */
function addClass(element, className) {
    if (element.classList) {
        element.classList.add(className);
    } else if (!hasClass(element, className)) {
        element.className = element.className + ' ' + className;
    }
    return element;
}
function removeClass(element, className) {
    if (hasClass(className)) {
        if (element.classList) {
            element.classList.remove(className);
        } else {
            element.className = (' ' + element.className + ' ')
                .replace(' ' + className + ' ', ' ').trim();
        }
    }
    return element;
}
function hasClass(element, className) {
    if (element.classList) {
        return element.classList.contains(className);
    } else {
        return (' ' + element.className + ' ').indexOf(' ' + className + ' ') > -1;
    }
}

var TimeoutTransitionGroupChild = React.createClass({
    transition: function(animationType, finishCallback) {
        var node = this.getDOMNode();
        var className = this.props.name + '-' + animationType;
        var activeClassName = className + '-active';

        var endListener = function() {
            removeClass(node, className);
            removeClass(node, activeClassName);

            // Usually this optional callback is used for informing an owner of
            // a leave animation and telling it to remove the child.
            finishCallback && finishCallback();
        };

        if (!animationSupported()) {
            endListener();
        } else {
            if (animationType === "enter") {
                this.animationTimeout = setTimeout(endListener,
                                                   this.props.enterTimeout);
            } else if (animationType === "leave") {
                this.animationTimeout = setTimeout(endListener,
                                                   this.props.leaveTimeout);
            }
        }

        addClass(node, className);

        // Need to do this to actually trigger a transition.
        this.queueClass(activeClassName);
    },

    queueClass: function(className) {
        this.classNameQueue.push(className);

        if (!this.timeout) {
            this.timeout = setTimeout(this.flushClassNameQueue, TICK);
        }
    },

    flushClassNameQueue: function() {
        if (this.isMounted()) {
            this.classNameQueue.forEach(function(name) {
                addClass(this.getDOMNode(), name);
            }.bind(this));
        }
        this.classNameQueue.length = 0;
        this.timeout = null;
    },

    componentWillMount: function() {
        this.classNameQueue = [];
    },

    componentWillUnmount: function() {
        if (this.timeout) {
            clearTimeout(this.timeout);
        }
        if (this.animationTimeout) {
            clearTimeout(this.animationTimeout);
        }
    },

    componentWillEnter: function(done) {
        if (this.props.enter) {
            this.transition('enter', done);
        } else {
            done();
        }
    },

    componentWillLeave: function(done) {
        if (this.props.leave) {
            this.transition('leave', done);
        } else {
            done();
        }
    },

    render: function() {
        return React.Children.only(this.props.children);
    }
});

export default React.createClass({
  propTypes: {
       enterTimeout: React.PropTypes.number.isRequired,
       leaveTimeout: React.PropTypes.number.isRequired,
       transitionName: React.PropTypes.string.isRequired,
       transitionEnter: React.PropTypes.bool,
       transitionLeave: React.PropTypes.bool,
   },

   getDefaultProps: function() {
       return {
           transitionEnter: true,
           transitionLeave: true
       };
   },

   _wrapChild: function(child) {
       return (
           <TimeoutTransitionGroupChild
                   enterTimeout={this.props.enterTimeout}
                   leaveTimeout={this.props.leaveTimeout}
                   name={this.props.transitionName}
                   enter={this.props.transitionEnter}
                   leave={this.props.transitionLeave}>
               {child}
           </TimeoutTransitionGroupChild>
       );
   },

   render: function() {
       return (
           <ReactTransitionGroup
               {...this.props}
               childFactory={this._wrapChild} />
       );
   }
});<commit_msg>Refactoring to react 0.14.0-beta1<commit_after>/**
 * This started out as a fork of TimeoutTransitionGroup by Khan Academy
 * @url https://github.com/rhobot/timeout-transition-group
 */

// Use require instead of import until we're on 0.14 and can just use the react-addons-transition-group package
import React from 'react';
import classNames from 'classnames';

import TransitionGroup from 'react-addons-transition-group';

var {addons: {TransitionGroup: ReactTransitionGroup}} = React;

const TICK = 17;

/**
 * EVENT_NAME_MAP is used to determine which event fired when a
 * transition/animation ends, based on the style property used to
 * define that event.
 */
var EVENT_NAME_MAP = {
    transitionend: {
        'transition': 'transitionend',
        'WebkitTransition': 'webkitTransitionEnd',
        'MozTransition': 'mozTransitionEnd',
        'OTransition': 'oTransitionEnd',
        'msTransition': 'MSTransitionEnd'
    },

    animationend: {
        'animation': 'animationend',
        'WebkitAnimation': 'webkitAnimationEnd',
        'MozAnimation': 'mozAnimationEnd',
        'OAnimation': 'oAnimationEnd',
        'msAnimation': 'MSAnimationEnd'
    }
};

var endEvents = [];

(function detectEvents() {
    if (typeof window === "undefined") {
        return;
    }

    var testEl = document.createElement('div');
    var style = testEl.style;

    // On some platforms, in particular some releases of Android 4.x, the
    // un-prefixed "animation" and "transition" properties are defined on the
    // style object but the events that fire will still be prefixed, so we need
    // to check if the un-prefixed events are useable, and if not remove them
    // from the map
    if (!('AnimationEvent' in window)) {
        delete EVENT_NAME_MAP.animationend.animation;
    }

    if (!('TransitionEvent' in window)) {
        delete EVENT_NAME_MAP.transitionend.transition;
    }

    for (var baseEventName in EVENT_NAME_MAP) {
        if (EVENT_NAME_MAP.hasOwnProperty(baseEventName)) {
            var baseEvents = EVENT_NAME_MAP[baseEventName];
            for (var styleName in baseEvents) {
                if (styleName in style) {
                    endEvents.push(baseEvents[styleName]);
                    break;
                }
            }

        }
    }
})();

function animationSupported() {
    return endEvents.length !== 0;
}

/**
 * Functions for element class management to replace dependency on jQuery
 * addClass, removeClass and hasClass
 */
function addClass(element, className) {
    if (element.classList) {
        element.classList.add(className);
    } else if (!hasClass(element, className)) {
        element.className = element.className + ' ' + className;
    }
    return element;
}
function removeClass(element, className) {
    if (hasClass(className)) {
        if (element.classList) {
            element.classList.remove(className);
        } else {
            element.className = (' ' + element.className + ' ')
                .replace(' ' + className + ' ', ' ').trim();
        }
    }
    return element;
}
function hasClass(element, className) {
    if (element.classList) {
        return element.classList.contains(className);
    } else {
        return (' ' + element.className + ' ').indexOf(' ' + className + ' ') > -1;
    }
}

var TimeoutTransitionGroupChild = React.createClass({
    transition: function(animationType, finishCallback) {
        var node = React.findDOMNode(this);
        console.log(this.props.name);
        var className = this.props.name + '-' + animationType;
        var activeClassName = className + '-active';

        var endListener = function() {
            removeClass(node, className);
            removeClass(node, activeClassName);

            // Usually this optional callback is used for informing an owner of
            // a leave animation and telling it to remove the child.
            finishCallback && finishCallback();
        };

        if (!animationSupported()) {
            endListener();
        } else {
            if (animationType === "enter") {
                this.animationTimeout = setTimeout(endListener,
                                                   this.props.enterTimeout);
            } else if (animationType === "leave") {
                this.animationTimeout = setTimeout(endListener,
                                                   this.props.leaveTimeout);
            }
        }

        addClass(node, className);

        // Need to do this to actually trigger a transition.
        this.queueClass(activeClassName);
    },

    queueClass: function(className) {
        this.classNameQueue.push(className);

        if (!this.timeout) {
            this.timeout = setTimeout(this.flushClassNameQueue, TICK);
        }
    },

    flushClassNameQueue: function() {
        if (this.isMounted()) {
            this.classNameQueue.forEach(function(name) {
                addClass(React.findDOMNode(this), name);
            }.bind(this));
        }
        this.classNameQueue.length = 0;
        this.timeout = null;
    },

    componentWillMount: function() {
        this.classNameQueue = [];
    },

    componentWillUnmount: function() {
        if (this.timeout) {
            clearTimeout(this.timeout);
        }
        if (this.animationTimeout) {
            clearTimeout(this.animationTimeout);
        }
    },

    componentWillEnter: function(done) {
        if (this.props.enter) {
            this.transition('enter', done);
        } else {
            done();
        }
    },

    componentWillLeave: function(done) {
        if (this.props.leave) {
            this.transition('leave', done);
        } else {
            done();
        }
    },

    render: function() {
        return React.Children.only(this.props.children);
    }
});

export default React.createClass({
  displayName: 'SimpleTransitionGroup',
  propTypes: {
       enterTimeout: React.PropTypes.number.isRequired,
       leaveTimeout: React.PropTypes.number.isRequired,
       transitionName: React.PropTypes.string.isRequired,
       transitionEnter: React.PropTypes.bool,
       transitionLeave: React.PropTypes.bool,
   },

   getDefaultProps: function() {
       return {
           transitionEnter: true,
           transitionLeave: true
       };
   },

   _wrapChild: function(child) {
       return (
           <TimeoutTransitionGroupChild
                   enterTimeout={this.props.enterTimeout}
                   leaveTimeout={this.props.leaveTimeout}
                   name={this.props.transitionName}
                   enter={this.props.transitionEnter}
                   leave={this.props.transitionLeave}>
               {child}
           </TimeoutTransitionGroupChild>
       );
   },

   render: function() {
       return (
           <ReactTransitionGroup
               {...this.props}
               childFactory={this._wrapChild} />
       );
   }
});<|endoftext|>


<commit_before>L.TransitiveLayer = L.Class.extend({

  initialize: function(transitive, options) {
    this._transitive = transitive;
  },

  onAdd: function(map) {
    this._map = map;

    this._initContainer();

    map.on("moveend", this._refresh, this);
    map.on("zoomend", this._refresh, this);
    map.on("drag", this._refresh, this);
    map.on("resize", this._resize, this);

    this._transitive.options.zoomEnabled = false;
    this._transitive.options.autoResize = false;
    this._transitive.setElement(this._container);
    this._transitive.render();
  },

  onRemove: function(map) {
    map.getPanes().overlayPane.removeChild(this._container);
    map.off("moveend", this._refresh, this);
    map.off("zoomend", this._refresh, this);
    map.off("drag", this._refresh, this);
    map.off("resize", this._resize, this);
  },

  _initContainer: function() {
    this._container = L.DomUtil.create('div', 'leaflet-transitive-container', this._map.getPanes().overlayPane);
    this._container.style.position = 'absolute';
    this._container.style.width = this._map.getSize().x+"px";
    this._container.style.height = this._map.getSize().y+"px";
  },

  _refresh: function() {
    var bounds  = this._map.getBounds();
    var topLeft = this._map.latLngToLayerPoint(bounds.getNorthWest());
    L.DomUtil.setPosition(this._container, topLeft);
    this._transitive.setBounds([[bounds.getWest(), bounds.getSouth()],[bounds.getEast(), bounds.getNorth()]]);
  },

  _resize: function(data) {
    this._transitive.resize(data.newSize.x, data.newSize.y);
    this._refresh();
  }

});

L.transitiveLayer = function (transitive, options) {
  return new L.TransitiveLayer(transitive, options);
};<commit_msg>Listen for clear/update data events and refresh layer<commit_after>L.TransitiveLayer = L.Class.extend({

  initialize: function(transitive, options) {
    this._transitive = transitive;
  },

  onAdd: function(map) {
    this._map = map;

    this._initContainer();

    map.on("moveend", this._refresh, this);
    map.on("zoomend", this._refresh, this);
    map.on("drag", this._refresh, this);
    map.on("resize", this._resize, this);

    this._transitive.options.zoomEnabled = false;
    this._transitive.options.autoResize = false;
    this._transitive.setElement(this._container);
    this._transitive.render();

    var self = this;
    this._transitive.on('clear data', function() {
      self._refresh();
    });

    this._transitive.on('update data', function() {
      self._transitive.render();
      self._refresh();
    });
  },

  onRemove: function(map) {
    map.getPanes().overlayPane.removeChild(this._container);
    map.off("moveend", this._refresh, this);
    map.off("zoomend", this._refresh, this);
    map.off("drag", this._refresh, this);
    map.off("resize", this._resize, this);
  },

  _initContainer: function() {
    this._container = L.DomUtil.create('div', 'leaflet-transitive-container', this._map.getPanes().overlayPane);
    this._container.style.position = 'absolute';
    this._container.style.width = this._map.getSize().x+"px";
    this._container.style.height = this._map.getSize().y+"px";
  },

  _refresh: function() {
    var bounds  = this._map.getBounds();
    var topLeft = this._map.latLngToLayerPoint(bounds.getNorthWest());
    L.DomUtil.setPosition(this._container, topLeft);
    this._transitive.setBounds([[bounds.getWest(), bounds.getSouth()],[bounds.getEast(), bounds.getNorth()]]);
  },

  _resize: function(data) {
    this._transitive.resize(data.newSize.x, data.newSize.y);
    this._refresh();
  }

});

L.transitiveLayer = function (transitive, options) {
  return new L.TransitiveLayer(transitive, options);
};<|endoftext|>


<commit_before>/*!
Deck JS - deck.smartsyntax
Copyright (c) 2012 Rémi Emonet
Dual licensed under the MIT license and GPL license.
https://github.com/imakewebthings/deck.js/blob/master/MIT-license.txt
https://github.com/imakewebthings/deck.js/blob/master/GPL-license.txt
*/

/*
This module provides a support for a shorter syntax for slides.
*/

(function($, deck, undefined) {
    var $d = $(document);
    var may = function(f) {return f ? f : function() {}};
    var startsWith = function(longStr, part) {return longStr.substr(0, part.length) == part;}
    var startsWithIgnoreCase = function(longStr, part) {return longStr.substr(0, part.length).toUpperCase() == part.toUpperCase();}
    var maybeAddClasses = function(toWhat, spaceSeparatedClasses, uniqueId) {
        if (uniqueId != "") $(toWhat).attr("id", uniqueId);
        if (spaceSeparatedClasses == "") return;
        var parts = spaceSeparatedClasses.split(/ +/);
        for (i in parts) {
            $(toWhat).addClass(parts[i]);
        }
    }

    var interpretationOfSmartLanguage = function(smart, doc) {
        var res = new Array();
        var inSlide = null;
        var indent = "";
        var deepestList = null;
        var remain = smart;

        var processMath = function(content) {
            return content.replace(/\$([^$][^$]*)\$/g, '\\displaystyle $1').replace(/\$\$/, '$');
        }

        var setEnrichedContent = function(what, content) {
            content = processMath(content);
            return what.innerHTML = content;
        }
        var endSlide = function() {
            inSlide = null;
            indent = new Array();
            indent = "";
            deepestList = null;
        }

        while (true) {
            var nl = remain.indexOf("\n");
            var line = remain.substring(0, nl).replace(/^ */, "");
            // we iterate over the lines
            // treat trailing unique-id and classes before anything
            var uniqueId = "";
            while (line.match(/^(.*)#([^\]\| >]*)$/)) {
                uniqueId = RegExp.$2;
                line = RegExp.$1;
            }
            var addClasses = "";
            {
                while (line.match(/^(.*)\[([^\] >]*)\]$/)) {
                    addClasses = RegExp.$2 + " " + addClasses;
                    line = RegExp.$1;
                }
            }
            if (line == "") {
            } else if (line.match(/^==(.*)==$/)) {
                var title = RegExp.$1;
                if (inSlide) endSlide();
                inSlide = doc.createElement("section");
                $(inSlide).addClass("slide");
                maybeAddClasses(inSlide, addClasses, uniqueId);
                var h = doc.createElement("h1");
                setEnrichedContent(h, title);
                inSlide.appendChild(h);
                deepestList = inSlide;
                res[res.length] = inSlide;
            } else if (line.match(/^=(.*)=$/)) {
                var title = RegExp.$1;
                if (inSlide) endSlide();
                inSlide = doc.createElement("section");
                $(inSlide).addClass("slide");
                maybeAddClasses(inSlide, addClasses, uniqueId);
                var h = doc.createElement("h2");
                setEnrichedContent(h, title);
                inSlide.appendChild(h);
                deepestList = inSlide;
                res[res.length] = inSlide;
            } else if (line.match(/^([-*#]+)(.*)$/)) {
                var pref = RegExp.$1;
                var content = RegExp.$2;
                if (indent == "" && pref == "") {
                    // do not create the li
                } else if (pref == indent) {
                    var li = doc.createElement("li");
                    maybeAddClasses(li, addClasses, uniqueId);
                    setEnrichedContent(li, content);
                    deepestList.appendChild(li);
                } else {
                    // un-push as needed
                    while (! startsWith(pref, indent)) {
                        deepestList = deepestList.parentNode;
                        if (deepestList.tagName == "LI") deepestList = deepestList.parentNode;
                        indent = indent.substr(0, indent.length - 1);
                    }
                    // clean the special '-' that we can use for magic unpush
                    pref = pref.replace(/^-*/, "");
                    // re-push as needed
                    while (pref.length > indent.length) {
                        var asso = {"*": "ul", "#": "ol"};
                        var toPush = pref.substr(indent.length, 1);
                        indent = indent.concat(toPush);
                        var list = doc.createElement(asso[toPush]);
                        if ((deepestList.tagName == "UL" || deepestList.tagName == "OL") && deepestList.childNodes.length > 0) {
                            deepestList.lastChild.appendChild(list);
                        } else {
                            deepestList.appendChild(list);
                        }
                        deepestList = list;
                    }
                    if (indent == "" && pref == "") {
                        // do not create the li
                    } else {
                        var li = doc.createElement("li");
                        maybeAddClasses(li, addClasses, uniqueId);
                        setEnrichedContent(li, content);
                        deepestList.appendChild(li);
                    }
                }
            } else if (startsWithIgnoreCase(line, "@SVG:")) {
                var parts = line.replace(/@SVG\: */i, "").split(/ +/);
                var obj = $("<object type='deckjs/svg'/>");
                $.each(parts[0].split(/,/), function(i,c){obj.addClass(c);});
                obj.append($("<param name='src'/>").attr("value", parts[1]))
                    .append($("<param name='width'/>").attr("value", parts[2]))
                    .append($("<param name='height'/>").attr("value", parts[3]))
                    .appendTo(inSlide);
            } else if (startsWithIgnoreCase(line, "@ANIM-PLAY:")) {
                line = line.replace(/@ANIM-PLAY\: */i, "");
                $("
").addClass("anim-play slide").attr("data-what", line).appendTo(deepestList);
            } else if (startsWithIgnoreCase(line, "@ANIM-PAUSE:")) {
                line = line.replace(/@ANIM-PAUSE\: */i, "");
                $("
").addClass("anim-pause slide").attr("data-what", line).appendTo(deepestList);
            } else if (startsWithIgnoreCase(line, "@ANIM-ATTRIBUTE:")) {
                line = line.replace(/@ANIM-ATTRIBUTE\: */i, "");
                var main = line.split(/ *: */);
                $("
").addClass("anim-attribute slide").attr("data-dur", main[0]).attr("data-what", main[1]).attr("data-attr", main[2]+":"+main[3]).appendTo(deepestList);
            } else if (startsWithIgnoreCase(line, "@ANIM-APPEAR:")) {
                line = line.replace(/@ANIM-APPEAR\: */i, "");
                if (uniqueId != "") line += "#"+uniqueId; // restore possibly removed id
                var main = line.split(/ *: */);
                var dur = main[0];
                var parts = main[1].split(/ *\| */);
                for (i in parts) {
                    // process each group of simultaneous animations
                    var subparts = parts[i].split(/ *\+ */);
                    for (ii in subparts) {
                        var what = subparts[ii];
                        var continuating  = ii != subparts.length-1;
                        var add = $("
");
                        add.attr("data-dur", dur);
                        if (what[0] == '-') {
                            add.addClass("anim-hide");
                            what = what.substring(1);
                        } else if (what[0] == '@') {
                            // TODO if it looks like @....@ then the target is specified, else it is just all SVGs root elements
                            add.addClass("anim-viewboxas");
                            what = what.substring(1);
                            add.attr("data-as", what);
                            what = "svg";
                        } else {
                            add.addClass("anim-show");
                        }
                        add.addClass("slide").attr("data-what", what);
                        if (continuating) add.addClass("anim-continue");
                        add.appendTo(deepestList);
                    }
                }
            } else if (startsWith(line, "@<")) {
                line = line.replace(/^@/, "");
                var contentToAdd = "";
                // test on remain to avoid infinite loop
                while (line != null && remain.length != 0) {
                    if (line.match(/^@<\//)) {
                        // normal stopping condition
                        line = line.replace(/^@/, "");
                        contentToAdd += "  " + line + "\n";
                        break;
                    }
                    if (nl != -1) remain = remain.substring(nl + 1);
                    contentToAdd += "  " + line + "\n";
                    nl = remain.indexOf("\n");
                    line = remain.substring(0, nl).replace(/^ */, "");
                }
                deepestList.innerHTML = deepestList.innerHTML + processMath(contentToAdd) + " ";
            } else {
                while (true) {
                    try {
                        deepestList.innerHTML = deepestList.innerHTML + processMath(line) + " ";
                        break;
                    } catch (e) {
                        // TODO was ok with xhtml not really now
                        remain = remain.substring(nl + 1);
                        nl = remain.indexOf("\n");
                        var line2 = remain.substring(0, nl).replace(/^ */, "");
                        line = line + "\n" + line2;
                    }
                }
            }
            if (nl != -1) remain = remain.substring(nl + 1);
            else break;
        }
        return res;
    }

    // this have to be executed before the deck init
    $d.bind('deck.beforeInit', function() {
            $('.smart').each(function() {
                    var it = this;
                    var slides = interpretationOfSmartLanguage(it.innerHTML, document);
                    it.innerHTML = ""; // clear the smart node
                    $(it).after(slides);
                });
        });

})(jQuery, 'deck');<commit_msg>[smartsyntax] accept leading tabs in additions of spaces<commit_after>/*!
Deck JS - deck.smartsyntax
Copyright (c) 2012 Rémi Emonet
Dual licensed under the MIT license and GPL license.
https://github.com/imakewebthings/deck.js/blob/master/MIT-license.txt
https://github.com/imakewebthings/deck.js/blob/master/GPL-license.txt
*/

/*
This module provides a support for a shorter syntax for slides.
*/

(function($, deck, undefined) {
    var $d = $(document);
    var may = function(f) {return f ? f : function() {}};
    var startsWith = function(longStr, part) {return longStr.substr(0, part.length) == part;}
    var startsWithIgnoreCase = function(longStr, part) {return longStr.substr(0, part.length).toUpperCase() == part.toUpperCase();}
    var maybeAddClasses = function(toWhat, spaceSeparatedClasses, uniqueId) {
        if (uniqueId != "") $(toWhat).attr("id", uniqueId);
        if (spaceSeparatedClasses == "") return;
        var parts = spaceSeparatedClasses.split(/ +/);
        for (i in parts) {
            $(toWhat).addClass(parts[i]);
        }
    }

    var interpretationOfSmartLanguage = function(smart, doc) {
        var res = new Array();
        var inSlide = null;
        var indent = "";
        var deepestList = null;
        var remain = smart;

        var processMath = function(content) {
            return content.replace(/\$([^$][^$]*)\$/g, '\\displaystyle $1').replace(/\$\$/, '$');
        }

        var setEnrichedContent = function(what, content) {
            content = processMath(content);
            return what.innerHTML = content;
        }
        var endSlide = function() {
            inSlide = null;
            indent = new Array();
            indent = "";
            deepestList = null;
        }

        while (true) {
            var nl = remain.indexOf("\n");
            var line = remain.substring(0, nl).replace(/^[ \t]*/, "");
            // we iterate over the lines
            // treat trailing unique-id and classes before anything
            var uniqueId = "";
            while (line.match(/^(.*)#([^\]\| >]*)$/)) {
                uniqueId = RegExp.$2;
                line = RegExp.$1;
            }
            var addClasses = "";
            {
                while (line.match(/^(.*)\[([^\] >]*)\]$/)) {
                    addClasses = RegExp.$2 + " " + addClasses;
                    line = RegExp.$1;
                }
            }
            if (line == "") {
            } else if (line.match(/^==(.*)==$/)) {
                var title = RegExp.$1;
                if (inSlide) endSlide();
                inSlide = doc.createElement("section");
                $(inSlide).addClass("slide");
                maybeAddClasses(inSlide, addClasses, uniqueId);
                var h = doc.createElement("h1");
                setEnrichedContent(h, title);
                inSlide.appendChild(h);
                deepestList = inSlide;
                res[res.length] = inSlide;
            } else if (line.match(/^=(.*)=$/)) {
                var title = RegExp.$1;
                if (inSlide) endSlide();
                inSlide = doc.createElement("section");
                $(inSlide).addClass("slide");
                maybeAddClasses(inSlide, addClasses, uniqueId);
                var h = doc.createElement("h2");
                setEnrichedContent(h, title);
                inSlide.appendChild(h);
                deepestList = inSlide;
                res[res.length] = inSlide;
            } else if (line.match(/^([-*#]+)(.*)$/)) {
                var pref = RegExp.$1;
                var content = RegExp.$2;
                if (indent == "" && pref == "") {
                    // do not create the li
                } else if (pref == indent) {
                    var li = doc.createElement("li");
                    maybeAddClasses(li, addClasses, uniqueId);
                    setEnrichedContent(li, content);
                    deepestList.appendChild(li);
                } else {
                    // un-push as needed
                    while (! startsWith(pref, indent)) {
                        deepestList = deepestList.parentNode;
                        if (deepestList.tagName == "LI") deepestList = deepestList.parentNode;
                        indent = indent.substr(0, indent.length - 1);
                    }
                    // clean the special '-' that we can use for magic unpush
                    pref = pref.replace(/^-*/, "");
                    // re-push as needed
                    while (pref.length > indent.length) {
                        var asso = {"*": "ul", "#": "ol"};
                        var toPush = pref.substr(indent.length, 1);
                        indent = indent.concat(toPush);
                        var list = doc.createElement(asso[toPush]);
                        if ((deepestList.tagName == "UL" || deepestList.tagName == "OL") && deepestList.childNodes.length > 0) {
                            deepestList.lastChild.appendChild(list);
                        } else {
                            deepestList.appendChild(list);
                        }
                        deepestList = list;
                    }
                    if (indent == "" && pref == "") {
                        // do not create the li
                    } else {
                        var li = doc.createElement("li");
                        maybeAddClasses(li, addClasses, uniqueId);
                        setEnrichedContent(li, content);
                        deepestList.appendChild(li);
                    }
                }
            } else if (startsWithIgnoreCase(line, "@SVG:")) {
                var parts = line.replace(/@SVG\: */i, "").split(/ +/);
                var obj = $("<object type='deckjs/svg'/>");
                $.each(parts[0].split(/,/), function(i,c){obj.addClass(c);});
                obj.append($("<param name='src'/>").attr("value", parts[1]))
                    .append($("<param name='width'/>").attr("value", parts[2]))
                    .append($("<param name='height'/>").attr("value", parts[3]))
                    .appendTo(inSlide);
            } else if (startsWithIgnoreCase(line, "@ANIM-PLAY:")) {
                line = line.replace(/@ANIM-PLAY\: */i, "");
                $("
").addClass("anim-play slide").attr("data-what", line).appendTo(deepestList);
            } else if (startsWithIgnoreCase(line, "@ANIM-PAUSE:")) {
                line = line.replace(/@ANIM-PAUSE\: */i, "");
                $("
").addClass("anim-pause slide").attr("data-what", line).appendTo(deepestList);
            } else if (startsWithIgnoreCase(line, "@ANIM-ATTRIBUTE:")) {
                line = line.replace(/@ANIM-ATTRIBUTE\: */i, "");
                var main = line.split(/ *: */);
                $("
").addClass("anim-attribute slide").attr("data-dur", main[0]).attr("data-what", main[1]).attr("data-attr", main[2]+":"+main[3]).appendTo(deepestList);
            } else if (startsWithIgnoreCase(line, "@ANIM-APPEAR:")) {
                line = line.replace(/@ANIM-APPEAR\: */i, "");
                if (uniqueId != "") line += "#"+uniqueId; // restore possibly removed id
                var main = line.split(/ *: */);
                var dur = main[0];
                var parts = main[1].split(/ *\| */);
                for (i in parts) {
                    // process each group of simultaneous animations
                    var subparts = parts[i].split(/ *\+ */);
                    for (ii in subparts) {
                        var what = subparts[ii];
                        var continuating  = ii != subparts.length-1;
                        var add = $("
");
                        add.attr("data-dur", dur);
                        if (what[0] == '-') {
                            add.addClass("anim-hide");
                            what = what.substring(1);
                        } else if (what[0] == '@') {
                            // TODO if it looks like @....@ then the target is specified, else it is just all SVGs root elements
                            add.addClass("anim-viewboxas");
                            what = what.substring(1);
                            add.attr("data-as", what);
                            what = "svg";
                        } else {
                            add.addClass("anim-show");
                        }
                        add.addClass("slide").attr("data-what", what);
                        if (continuating) add.addClass("anim-continue");
                        add.appendTo(deepestList);
                    }
                }
            } else if (startsWith(line, "@<")) {
                line = line.replace(/^@/, "");
                var contentToAdd = "";
                // test on remain to avoid infinite loop
                while (line != null && remain.length != 0) {
                    if (line.match(/^@<\//)) {
                        // normal stopping condition
                        line = line.replace(/^@/, "");
                        contentToAdd += "  " + line + "\n";
                        break;
                    }
                    if (nl != -1) remain = remain.substring(nl + 1);
                    contentToAdd += "  " + line + "\n";
                    nl = remain.indexOf("\n");
                    line = remain.substring(0, nl).replace(/^ */, "");
                }
                deepestList.innerHTML = deepestList.innerHTML + processMath(contentToAdd) + " ";
            } else {
                while (true) {
                    try {
                        deepestList.innerHTML = deepestList.innerHTML + processMath(line) + " ";
                        break;
                    } catch (e) {
                        // TODO was ok with xhtml not really now
                        remain = remain.substring(nl + 1);
                        nl = remain.indexOf("\n");
                        var line2 = remain.substring(0, nl).replace(/^ */, "");
                        line = line + "\n" + line2;
                    }
                }
            }
            if (nl != -1) remain = remain.substring(nl + 1);
            else break;
        }
        return res;
    }

    // this have to be executed before the deck init
    $d.bind('deck.beforeInit', function() {
            $('.smart').each(function() {
                    var it = this;
                    var slides = interpretationOfSmartLanguage(it.innerHTML, document);
                    it.innerHTML = ""; // clear the smart node
                    $(it).after(slides);
                });
        });

})(jQuery, 'deck');
<|endoftext|>


<commit_before>/*! Copyright (c) 2016, Salesforce.com, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
 *
 *      Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
 *
 *      Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 *
 *      Neither the name of Salesforce.com nor the names of its contributors may be used to endorse or promote products derived from this software
 *      without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
'use strict';
/*global angular:false */

angular.module('argus.urlConfig', [])
.constant('CONFIG', {
	version: '2.7.1',
	wsUrl: 'http://localhost:8080/argusws/',
	emailUrl: 'https://mail.google.com/mail/?view=cm&fs=1&tf=1&to=argus-dev@mycompany.com',
	feedUrl: 'https://groups.google.com/a/mycompany.com/forum/?hl=en#!forum/argus-user',
	wikiUrl: 'https://github.com/salesforce/Argus/wiki',
	issueUrl: 'https://groups.google.com/a/salesforce.com/forum/?hl=en#!forum/argus-dev',
	templatePath: 'app/js/templates'
});
<commit_msg>Update config.js<commit_after>/*! Copyright (c) 2016, Salesforce.com, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
 *
 *      Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
 *
 *      Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 *
 *      Neither the name of Salesforce.com nor the names of its contributors may be used to endorse or promote products derived from this software
 *      without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
'use strict';
/*global angular:false */

angular.module('argus.urlConfig', [])
.constant('CONFIG', {
	version: '2.8.0',
	wsUrl: 'http://localhost:8080/argusws/',
	emailUrl: 'https://mail.google.com/mail/?view=cm&fs=1&tf=1&to=argus-dev@mycompany.com',
	feedUrl: 'https://groups.google.com/a/mycompany.com/forum/?hl=en#!forum/argus-user',
	wikiUrl: 'https://github.com/salesforce/Argus/wiki',
	issueUrl: 'https://groups.google.com/a/salesforce.com/forum/?hl=en#!forum/argus-dev',
	templatePath: 'app/js/templates'
});
<|endoftext|>


<commit_before>var path   =  require('path')
  , fs     =  require('fs')
  , util   =  require('util')
  , existsSync = fs.existsSync || path.existsSync // support node 0.6
  , active =  false
  ;

function ProxyquireError(msg) {
  this.name = 'ProxyquireError';
  this.message = msg || 'An error occurred inside proxyquire.';
}

var config = { }
  , api = proxyquireApi()
  ;

function resolve (mdl, caller__dirname) {

  // Resolve relative file requires, e.g., './mylib'
  if ( mdl.match(/^(\.{1,2}\/)/) ) {

    if (!caller__dirname) {
      console.trace();
      throw new ProxyquireError('In order to resolve relative modules, caller__dirname is required');
    }

    // We use the __dirname of the script that is requiring, to get same behavior as if real require was called from it directly.
    return path.join(caller__dirname, mdl);

  } else {

    // Don't change references to global or 'node_module' requires, e.g. 'path' or absolute paths e.g. '/user/bin/module.js'
    return mdl;
  }
}

function addMissingProperties(mdl, forceStrict) {
  var orig = mdl.__proxyquire.original;

  // In strict mode we 'require' all properties to be used in tests to be overridden beforehand
  if (mdl.__proxyquire && (forceStrict || mdl.__proxyquire.strict)) return;

  // In non strict mode (default), we fill in all missing properties from the original module
  Object.keys(orig).forEach(function (key) {
    if (!mdl[key]) {
      mdl[key] = orig[key];
    }
  });
}

function proxyquireApi () {

  function clearRequireCache() {
    Object.keys(require.cache).forEach(function (key) {
      if (key !==  __filename)
        delete require.cache[key];
    });
  }

  function addOverrides(mdl, name) {

    // Store it under the given name
    if (!config[name]) {

      // configure entire module if it was not configured before
      config[name] = mdl;

    } else {

      // otherwise just reconfigure it by adding/overriding given properties
      Object.keys(mdl).forEach(function (key) {
        config[name][key] = mdl[key];
      });

    }
  }

  function removeProperty (mdl, prop) {
    if (config[mdl].__proxyquire && config[mdl].__proxyquire.strict) {
      delete config[mdl][prop];
    } else {

      // replace overridden property with the original one from the real module
      if (config[mdl].__proxyquire && config[mdl].__proxyquire.original) {

        if (!config[mdl].__proxyquire.original[prop]) {
          throw new ProxyquireError(
            'The property [' + prop + '] you are trying to remove does not exist on the original module!' +
            ' What are you up to?'
          );
        }

        config[mdl][prop] = config[mdl].__proxyquire.original[prop];

      } else {
        throw new ProxyquireError(
          'Did not find original module when trying to replace stubbed property with original one.' +
          '\nPlease make sure to cause the module to be required before removing properties.'
        );
      }
    }
  }

  return {
      reset: function () {
        config = { };
        clearRequireCache();
        active = true;
        return this;
      }
    , setup: function (forceStrict) {
        // Needs to be called at root of test file, so we can resolve its __dirname
        // Ideally this is done like so: var proxyquire = require('proxyquire').setup();

        var callerArgs = arguments.callee.caller.arguments
        ,  caller__dirname = callerArgs[4];

        if (!caller__dirname) {
          throw new ProxyquireError('Please call proxyquire.setup only from the TOP LEVEL of your test file!');
        }

        this.__testdirname = caller__dirname;
        return this;
      }
    , forceStrict: function (forceStrict) {
        this.__forceStrict = forceStrict === undefined || forceStrict;
        return this;
      }
    , add: function (arg) {
        Object.keys(arg).forEach(function (key) {
          addOverrides(arg[key], key);
        });

        active = true;
        return this;
      }
    , del: function (arg) {

        // Remove entire module
        if (typeof arg === 'string') {
          // Cannot delete module property here, since dependant holds reference to it and thus wouldn't be affected
          // Instead we need to remove all props to get them to point at the real required module

          Object.keys(config[arg]).forEach( function (p) {
            if (p !== '__proxyquire') removeProperty(arg, p);
          });

          return this;
        }

        // Remove only specified properties of the given module
        Object.keys(arg).forEach( function (mdl) {

          if (config[mdl]) {
            var prop = arg[mdl];

            if (typeof prop === 'string') {

              removeProperty(mdl, prop);

            } else if (Array.isArray(prop)) {

              prop.forEach(function (p) {
                removeProperty(mdl, p);
              });

            } else {
              throw new ProxyquireError('argument to delete needs to be key: String, or key: Array[String] object');
            }
          }

        });

        return this;
      }
    , require: function (arg, caller__dirname) {

        if (!this.__testdirname && !caller__dirname) {
          throw new ProxyquireError(
            'Please call proxyquire.setup() from TOP LEVEL of your test file before using proxyquire.require!\n' +
            'Alternatively pass __dirname of test file as second argument to proxyquire.require.'
          );
        }

        // Automatically injects require override into code of the file to be required.
        // Saves result as new file and requires that file instead of the original one.
        // That way no change to original code is necessary in order to hook into proxyquire.

        function find(file) {
          var jsfile;

          // finds foo.js even if it is required as foo
          if (existsSync(file))
            return file;
          else {
            jsfile = file + '.js';
            if (existsSync(jsfile))
              return jsfile;
          }

          // Cannot find file
          throw new ProxyquireError(
            util.format('Cannot find file you required.\nTried [%s] and [%s]', file, jsfile) +
            '\nIf you are running tests from different files asynchronously, pass in scripts __dirname instead of using ' +
            'proxyquire.setup().'
          );
        }

        var originalFile    =  find(resolve(arg, caller__dirname || this.__testdirname))
          , originalCode    =  fs.readFileSync(originalFile)
          , proxyquiredFile =  originalFile + '.proxyquirefied'
          , proxyquiredCode =
              // all on first line (don't introduce new line in order to keep original and proxified line numbers matching)
              '/* START proxyquirefying (This file should have been removed after testing, please remove!) */'  +
              'var require = require("' + this._proxyquire + '"); '                                             +
              '/* END proxyquirefying Original code on this line: */ '                                          +
              originalCode
          , dependency
          ;

          fs.writeFileSync(proxyquiredFile, proxyquiredCode);

          try {
            dependency = require(proxyquiredFile);
          } catch (err) {
            console.trace();
            console.error(err);
            throw (err);
          } finally {
            // Make sure we remove the generated file even if require fails
            fs.unlinkSync(proxyquiredFile);
          }

        return dependency;
      }

    // Don't touch below props as they are only here for diagnostics and testing
    , _getConfig: function () { return config; }
    , _proxyquire: 'proxyquire'
    , __testdirname: undefined
    , __forceStrict: undefined
    , print: function () {
        console.log('config:');
        console.dir(this._getConfig());
        console.log('__testdirname: ', this.__testdirname);
      }
  };
}

function proxyquire(arg) {

  var callerArgs = arguments.callee.caller.arguments
    , caller__dirname = callerArgs[4]
    ;

  // Two options:
  //   a) arg is string - used by module that we are testing when it requires its dependencies
  //   b) arg is object - used as a shortcut to invoke reset and then add in one step

  if (arg) {

    if (typeof arg === 'string') {

      var resolvedPath = resolve(arg, caller__dirname);

      // Shortcut the process if we are not testing
      if (!active) return require(resolvedPath);

      // a) get overridden module or resolve it through original require
      if (config[arg]) {
        config[arg].__proxyquire = config[arg].__proxyquire || { };

        // Here is the only sure way to resolve the original require, so we attach it to the overridden module for later use
        // If non-strict and we didn't fill missing properties before ...
        if (!config[arg].__proxyquire.strict && !config[arg].__proxyquire.original) {
          config[arg].__proxyquire.original = require(resolvedPath);
          addMissingProperties(config[arg], proxyquire.__forceStrict);
        }

        return config[arg];
      } else {

        var original = require(resolvedPath);
        return original;
      }

    } else if (typeof arg === 'object') {

      // b) shortcut to reset and add overrides in one call
      return proxyquire
        .reset()
        .add(arg);

    } else {
      throw new ProxyquireError('arg needs to be string or object');
    }
  } else {
      throw new ProxyquireError('need to pass string or object argument');
  }
}

// Attach api to exported function to allow things like
// proxyquire.reset()
Object.keys(api).forEach(function (key) {
  proxyquire[key] = api[key];
});

module.exports = proxyquire;
<commit_msg>cleaned up api attachment and made testdirname and forceStrict truly private<commit_after>var path   =  require('path')
  , fs     =  require('fs')
  , util   =  require('util')
  , existsSync = fs.existsSync || path.existsSync // support node 0.6
  , active =  false
  , config = { }
  , testdirname
  , forceStrict
  ;

function ProxyquireError(msg) {
  this.name = 'ProxyquireError';
  this.message = msg || 'An error occurred inside proxyquire.';
}

function resolve (mdl, caller__dirname) {

  // Resolve relative file requires, e.g., './mylib'
  if ( mdl.match(/^(\.{1,2}\/)/) ) {

    if (!caller__dirname) {
      console.trace();
      throw new ProxyquireError('In order to resolve relative modules, caller__dirname is required');
    }

    // We use the __dirname of the script that is requiring, to get same behavior as if real require was called from it directly.
    return path.join(caller__dirname, mdl);

  } else {

    // Don't change references to global or 'node_module' requires, e.g. 'path' or absolute paths e.g. '/user/bin/module.js'
    return mdl;
  }
}

function addMissingProperties(mdl) {
  var orig = mdl.__proxyquire.original;

  // In strict mode we 'require' all properties to be used in tests to be overridden beforehand
  if (mdl.__proxyquire && (forceStrict || mdl.__proxyquire.strict)) return;

  // In non strict mode (default), we fill in all missing properties from the original module
  Object.keys(orig).forEach(function (key) {
    if (!mdl[key]) {
      mdl[key] = orig[key];
    }
  });
}

//
// Main proxyquire function
// Replaces 'require' in to be tested files and serves as shortcut 'reset().add(..)' in tests
//
function proxyquire(arg) {

  var callerArgs = arguments.callee.caller.arguments
    , caller__dirname = callerArgs[4]
    ;

  // Two options:
  //   a) arg is string - used by module that we are testing when it requires its dependencies
  //   b) arg is object - used as a shortcut to invoke reset and then add in one step

  if (arg) {

    if (typeof arg === 'string') {

      var resolvedPath = resolve(arg, caller__dirname);

      // Shortcut the process if we are not testing
      if (!active) return require(resolvedPath);

      // a) get overridden module or resolve it through original require
      if (config[arg]) {
        config[arg].__proxyquire = config[arg].__proxyquire || { };

        // Here is the only sure way to resolve the original require, so we attach it to the overridden module for later use
        // If non-strict and we didn't fill missing properties before ...
        if (!config[arg].__proxyquire.strict && !config[arg].__proxyquire.original) {
          config[arg].__proxyquire.original = require(resolvedPath);
          addMissingProperties(config[arg]);
        }

        return config[arg];
      } else {

        var original = require(resolvedPath);
        return original;
      }

    } else if (typeof arg === 'object') {

      // b) shortcut to reset and add overrides in one call
      return proxyquire
        .reset()
        .add(arg);

    } else {
      throw new ProxyquireError('arg needs to be string or object');
    }
  } else {
      throw new ProxyquireError('need to pass string or object argument');
  }
}

//
// proxyquire function also functions (npi) as an object that exposes proxyquire api.
// The proxyquire API contains all methods to be used in tests to configure proxyquire and require the to be tested module
//
(function attachApi () {

  function clearRequireCache() {
    Object.keys(require.cache).forEach(function (key) {
      if (key !==  __filename)
        delete require.cache[key];
    });
  }

  function addOverrides(mdl, name) {

    // Store it under the given name
    if (!config[name]) {

      // configure entire module if it was not configured before
      config[name] = mdl;

    } else {

      // otherwise just reconfigure it by adding/overriding given properties
      Object.keys(mdl).forEach(function (key) {
        config[name][key] = mdl[key];
      });

    }
  }

  function removeProperty (mdl, prop) {
    if (config[mdl].__proxyquire && config[mdl].__proxyquire.strict) {
      delete config[mdl][prop];
    } else {

      // replace overridden property with the original one from the real module
      if (config[mdl].__proxyquire && config[mdl].__proxyquire.original) {

        if (!config[mdl].__proxyquire.original[prop]) {
          throw new ProxyquireError(
            'The property [' + prop + '] you are trying to remove does not exist on the original module!' +
            ' What are you up to?'
          );
        }

        config[mdl][prop] = config[mdl].__proxyquire.original[prop];

      } else {
        throw new ProxyquireError(
          'Did not find original module when trying to replace stubbed property with original one.' +
          '\nPlease make sure to cause the module to be required before removing properties.'
        );
      }
    }
  }

  proxyquire.reset = function () {
      config = { };
      clearRequireCache();
      active = true;
      return this;
  };

  proxyquire.setup = function (forceStrict) {
      // Needs to be called at root of test file, so we can resolve its __dirname
      // Ideally this is done like so: var proxyquire = require('proxyquire').setup();

      var callerArgs = arguments.callee.caller.arguments
      ,  caller__dirname = callerArgs[4];

      if (!caller__dirname) {
        throw new ProxyquireError('Please call proxyquire.setup only from the TOP LEVEL of your test file!');
      }

      testdirname = caller__dirname;
      return this;
  };

  proxyquire.forceStrict = function (force) {
      forceStrict = force === undefined || force;
      return this;
  };

  proxyquire.add = function (arg) {
      Object.keys(arg).forEach(function (key) {
        addOverrides(arg[key], key);
      });

      active = true;
      return this;
  };

  proxyquire.del = function (arg) {

      // Remove entire module
      if (typeof arg === 'string') {
        // Cannot delete module property here, since dependant holds reference to it and thus wouldn't be affected
        // Instead we need to remove all props to get them to point at the real required module

        Object.keys(config[arg]).forEach( function (p) {
          if (p !== '__proxyquire') removeProperty(arg, p);
        });

        return this;
      }

      // Remove only specified properties of the given module
      Object.keys(arg).forEach( function (mdl) {

        if (config[mdl]) {
          var prop = arg[mdl];

          if (typeof prop === 'string') {

            removeProperty(mdl, prop);

          } else if (Array.isArray(prop)) {

            prop.forEach(function (p) {
              removeProperty(mdl, p);
            });

          } else {
            throw new ProxyquireError('argument to delete needs to be key: String, or key: Array[String] object');
          }
        }

      });

      return this;
  };

  proxyquire.require = function (arg, caller__dirname) {

      if (!testdirname && !caller__dirname) {
        throw new ProxyquireError(
          'Please call proxyquire.setup() from TOP LEVEL of your test file before using proxyquire.require!\n' +
          'Alternatively pass __dirname of test file as second argument to proxyquire.require.'
        );
      }

      // Automatically injects require override into code of the file to be required.
      // Saves result as new file and requires that file instead of the original one.
      // That way no change to original code is necessary in order to hook into proxyquire.

      function find(file) {
        var jsfile;

        // finds foo.js even if it is required as foo
        if (existsSync(file))
          return file;
        else {
          jsfile = file + '.js';
          if (existsSync(jsfile))
            return jsfile;
        }

        // Cannot find file
        throw new ProxyquireError(
          util.format('Cannot find file you required.\nTried [%s] and [%s]', file, jsfile) +
          '\nIf you are running tests from different files asynchronously, pass in scripts __dirname instead of using ' +
          'proxyquire.setup().'
        );
      }

      var originalFile    =  find(resolve(arg, caller__dirname || testdirname))
        , originalCode    =  fs.readFileSync(originalFile)
        , proxyquiredFile =  originalFile + '.proxyquirefied'
        , proxyquiredCode =
            // all on first line (don't introduce new line in order to keep original and proxified line numbers matching)
            '/* START proxyquirefying (This file should have been removed after testing, please remove!) */'  +
            'var require = require("' + this._proxyquire + '"); '                                             +
            '/* END proxyquirefying Original code on this line: */ '                                          +
            originalCode
        , dependency
        ;

        fs.writeFileSync(proxyquiredFile, proxyquiredCode);

        try {
          dependency = require(proxyquiredFile);
        } catch (err) {
          console.trace();
          console.error(err);
          throw (err);
        } finally {
          // Make sure we remove the generated file even if require fails
          fs.unlinkSync(proxyquiredFile);
        }

      return dependency;
  };

  proxyquire.print = function () {
      console.log('config:');
      console.dir(this._getConfig());
      console.log('testdirname: ', testdirname);
      console.log('forceStrict: ', forceStrict);
  };

  // Diagnostics
  proxyquire._getConfig      =  function () { return config; };
  proxyquire._getForceStrict =  function () { return forceStrict; };
  proxyquire._getTestdirname =  function () { return testdirname; };

  // Don't touch below prop. Only here for testing purposes.
  proxyquire._proxyquire     =  'proxyquire';
}) ();

module.exports = proxyquire;
<|endoftext|>


<commit_before>/**********************************************************\
|                                                          |
|                          hprose                          |
|                                                          |
| Official WebSite: http://www.hprose.com/                 |
|                   http://www.hprose.org/                 |
|                                                          |
\**********************************************************/
/**********************************************************\
 *                                                        *
 * WebSocketClient.js                                     *
 *                                                        *
 * hprose websocket client for JavaScript.                *
 *                                                        *
 * LastModified: Dec 2, 2016                              *
 * Author: Ma Bingyao <andot@hprose.com>                  *
 *                                                        *
\**********************************************************/

(function (hprose, global, undefined) {
    'use strict';

    var StringIO = hprose.StringIO;
    var Client = hprose.Client;
    var Future = hprose.Future;
    var TimeoutError = global.TimeoutError;

    var defineProperties = hprose.defineProperties;
    var toBinaryString = hprose.toBinaryString;
    var toUint8Array = hprose.toUint8Array;
    var parseuri = hprose.parseuri;
    var WebSocket = global.WebSocket || global.MozWebSocket;

    function noop(){}
    function WebSocketClient(uri, functions, settings) {
        if (typeof(WebSocket) === "undefined") {
            throw new Error('WebSocket is not supported by this browser.');
        }
        if (this.constructor !== WebSocketClient) {
            return new WebSocketClient(uri, functions, settings);
        }

        Client.call(this, uri, functions, settings);

        var _id = 0;
        var _count = 0;
        var _futures = [];
        var _contexts = [];
        var _requests = [];
        var _ready = null;
        var ws = null;

        var self = this;

        function getNextId() {
            return (_id < 0x7fffffff) ? ++_id : _id = 0;
        }
        function send(id, request) {
            var stream = new StringIO();
            stream.writeInt32BE(id);
            if (_contexts[id].binary) {
                stream.write(request);
            }
            else {
                stream.writeUTF16AsUTF8(request);
            }
            var message = toUint8Array(stream.take());
            if (ArrayBuffer.isView) {
                ws.send(message);
            }
            else {
                ws.send(message.buffer);
            }
        }
        function onopen(e) {
            _ready.resolve(e);
        }
        function onmessage(e) {
            var stream;
            if (typeof e.data === "string") {
                stream = new StringIO(StringIO.utf8Encode(e.data));
            }
            else {
                stream = new StringIO(toBinaryString(e.data));
            }
            var id = stream.readInt32BE();
            var future = _futures[id];
            var context = _contexts[id];
            delete _futures[id];
            delete _contexts[id];
            if (future !== undefined) {
                --_count;
                var data = stream.read(stream.length() - 4);
                if (!context.binary) {
                    data = StringIO.utf8Decode(data);
                }
                future.resolve(data);
            }
            if ((_count < 100) && (_requests.length > 0)) {
                ++_count;
                var request = _requests.pop();
                _ready.then(function() { send(request[0], request[1]); });
            }
            if (_count === 0 && !self.keepAlive()) { close(); }
        }
        function onclose(e) {
            _futures.forEach(function(future, id) {
                future.reject(new Error(e.code + ':' + e.reason));
                delete _futures[id];
            });
            _count = 0;
            ws = null;
        }
        function connect() {
            _ready = new Future();
            ws = new WebSocket(self.uri());
            ws.binaryType = 'arraybuffer';
            ws.onopen = onopen;
            ws.onmessage = onmessage;
            ws.onerror = noop;
            ws.onclose = onclose;
        }
        function sendAndReceive(request, context) {
            var id = getNextId();
            var future = new Future();
            _futures[id] = future;
            _contexts[id] = context;
            if (context.timeout > 0) {
                future = future.timeout(context.timeout).catchError(function(e) {
                    ws = null;
                    delete _futures[id];
                    --_count;
                    throw e;
                },
                function(e) {
                    return e instanceof TimeoutError;
                });
            }
            if (ws === null ||
                ws.readyState === WebSocket.CLOSING ||
                ws.readyState === WebSocket.CLOSED) {
                connect();
            }
            if (_count < 100) {
                ++_count;
                _ready.then(function() { send(id, request); });
            }
            else {
                _requests.push([id, request]);
            }
            if (context.oneway) { future.resolve(); }
            return future;
        }
        function close() {
            if (ws !== null) {
                ws.onopen = noop;
                ws.onmessage = noop;
                ws.onclose = noop;
                ws.close();
            }
        }

        defineProperties(this, {
            sendAndReceive: { value: sendAndReceive },
            close: { value: close }
        });
    }

    function checkuri(uri) {
        var parser = parseuri(uri);
        if (parser.protocol === 'ws:' ||
            parser.protocol === 'wss:') {
            return;
        }
        throw new Error('This client desn\'t support ' + parser.protocol + ' scheme.');
    }

    function create(uri, functions, settings) {
        if (typeof uri === 'string') {
            checkuri(uri);
        }
        else if (Array.isArray(uri)) {
            uri.forEach(function(uri) { checkuri(uri); });
        }
        else {
            throw new Error('You should set server uri first!');
        }
        return new WebSocketClient(uri, functions, settings);
    }

    defineProperties(WebSocketClient, {
        create: { value: create }
    });

    global.HproseWebSocketClient =
    hprose.WebSocketClient = WebSocketClient;

})(hprose, hprose.global);
<commit_msg>Fixed connection leak.<commit_after>/**********************************************************\
|                                                          |
|                          hprose                          |
|                                                          |
| Official WebSite: http://www.hprose.com/                 |
|                   http://www.hprose.org/                 |
|                                                          |
\**********************************************************/
/**********************************************************\
 *                                                        *
 * WebSocketClient.js                                     *
 *                                                        *
 * hprose websocket client for JavaScript.                *
 *                                                        *
 * LastModified: Aug 20, 2017                             *
 * Author: Ma Bingyao <andot@hprose.com>                  *
 *                                                        *
\**********************************************************/

(function (hprose, global, undefined) {
    'use strict';

    var StringIO = hprose.StringIO;
    var Client = hprose.Client;
    var Future = hprose.Future;
    var TimeoutError = global.TimeoutError;

    var defineProperties = hprose.defineProperties;
    var toBinaryString = hprose.toBinaryString;
    var toUint8Array = hprose.toUint8Array;
    var parseuri = hprose.parseuri;
    var WebSocket = global.WebSocket || global.MozWebSocket;

    function noop(){}
    function WebSocketClient(uri, functions, settings) {
        if (typeof(WebSocket) === "undefined") {
            throw new Error('WebSocket is not supported by this browser.');
        }
        if (this.constructor !== WebSocketClient) {
            return new WebSocketClient(uri, functions, settings);
        }

        Client.call(this, uri, functions, settings);

        var _id = 0;
        var _count = 0;
        var _futures = [];
        var _contexts = [];
        var _requests = [];
        var _ready = null;
        var ws = null;

        var self = this;

        function getNextId() {
            return (_id < 0x7fffffff) ? ++_id : _id = 0;
        }
        function send(id, request) {
            var stream = new StringIO();
            stream.writeInt32BE(id);
            if (_contexts[id].binary) {
                stream.write(request);
            }
            else {
                stream.writeUTF16AsUTF8(request);
            }
            var message = toUint8Array(stream.take());
            if (ArrayBuffer.isView) {
                ws.send(message);
            }
            else {
                ws.send(message.buffer);
            }
        }
        function onopen(e) {
            _ready.resolve(e);
        }
        function onmessage(e) {
            var stream;
            if (typeof e.data === "string") {
                stream = new StringIO(StringIO.utf8Encode(e.data));
            }
            else {
                stream = new StringIO(toBinaryString(e.data));
            }
            var id = stream.readInt32BE();
            var future = _futures[id];
            var context = _contexts[id];
            delete _futures[id];
            delete _contexts[id];
            if (future !== undefined) {
                --_count;
                var data = stream.read(stream.length() - 4);
                if (!context.binary) {
                    data = StringIO.utf8Decode(data);
                }
                future.resolve(data);
            }
            if ((_count < 100) && (_requests.length > 0)) {
                ++_count;
                var request = _requests.pop();
                _ready.then(function() { send(request[0], request[1]); });
            }
            if (_count === 0 && !self.keepAlive()) { close(); }
        }
        function onclose(e) {
            _futures.forEach(function(future, id) {
                future.reject(new Error(e.code + ':' + e.reason));
                delete _futures[id];
            });
            _count = 0;
            ws = null;
        }
        function connect() {
            _ready = new Future();
            ws = new WebSocket(self.uri());
            ws.binaryType = 'arraybuffer';
            ws.onopen = onopen;
            ws.onmessage = onmessage;
            ws.onerror = noop;
            ws.onclose = onclose;
        }
        function sendAndReceive(request, context) {
            var id = getNextId();
            var future = new Future();
            _futures[id] = future;
            _contexts[id] = context;
            if (context.timeout > 0) {
                future = future.timeout(context.timeout).catchError(function(e) {
                    delete _futures[id];
                    --_count;
                    close();
                    throw e;
                },
                function(e) {
                    return e instanceof TimeoutError;
                });
            }
            if (ws === null ||
                ws.readyState === WebSocket.CLOSING ||
                ws.readyState === WebSocket.CLOSED) {
                connect();
            }
            if (_count < 100) {
                ++_count;
                _ready.then(function() { send(id, request); });
            }
            else {
                _requests.push([id, request]);
            }
            if (context.oneway) { future.resolve(); }
            return future;
        }
        function close() {
            if (ws !== null) {
                ws.onopen = noop;
                ws.onmessage = noop;
                ws.onclose = noop;
                ws.close();
            }
        }

        defineProperties(this, {
            sendAndReceive: { value: sendAndReceive },
            close: { value: close }
        });
    }

    function checkuri(uri) {
        var parser = parseuri(uri);
        if (parser.protocol === 'ws:' ||
            parser.protocol === 'wss:') {
            return;
        }
        throw new Error('This client desn\'t support ' + parser.protocol + ' scheme.');
    }

    function create(uri, functions, settings) {
        if (typeof uri === 'string') {
            checkuri(uri);
        }
        else if (Array.isArray(uri)) {
            uri.forEach(function(uri) { checkuri(uri); });
        }
        else {
            throw new Error('You should set server uri first!');
        }
        return new WebSocketClient(uri, functions, settings);
    }

    defineProperties(WebSocketClient, {
        create: { value: create }
    });

    global.HproseWebSocketClient =
    hprose.WebSocketClient = WebSocketClient;

})(hprose, hprose.global);
<|endoftext|>


<commit_before>/*
Copyright 2017 New Vector Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

/**
 * Handle widget postMessage events
 * @param  {Event} event Event to handle
 * @return {undefined}
 */
function onMessage(event) {
    console.warn("Checking for widget event", event);
    if (!event.origin) { // Handle chrome
        event.origin = event.originalEvent.origin;
    }

    // Event origin is empty string if undefined
    if (event.origin.length === 0 || !event.data.widgetData) {
        // TODO / FIXME -- check for valid origin URLs!!
        return; // don't log this - debugging APIs like to spam postMessage which floods the log otherwise
    }

    // TODO -- handle widget actions
    alert(event.data.widgetData);
}

let listenerCount = 0;
module.exports = {
    /**
     * Register widget message event listeners
     * @return {undefined}
     */
    startListening() {
        if (listenerCount === 0) {
            window.addEventListener("message", onMessage, false);
        }
        listenerCount += 1;
    },

    stopListening() {
        listenerCount -= 1;
        if (listenerCount === 0) {
            window.removeEventListener("message", onMessage);
        }
        if (listenerCount < 0) {
            // Make an error so we get a stack trace
            const e = new Error(
                "WidgetMessaging: mismatched startListening / stopListening detected." +
                " Negative count",
            );
            console.error(e);
        }
    },
};
<commit_msg>Register and de-register widget postMessage endpoints.<commit_after>/*
Copyright 2017 New Vector Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

let listenerCount = 0;
let messageEndpoints = [];

/**
 * Handle widget postMessage events
 * @param  {Event} event Event to handle
 * @return {undefined}
 */
function onMessage(event) {
    if (!event.origin) { // Handle chrome
        event.origin = event.originalEvent.origin;
    }

    // Event origin is empty string if undefined
    if (event.origin.length === 0 || trustedEndpoint(event.origin) || !event.data.widgetData) {
        console.warn("Ignoring postMessage", event);
        return; // don't log this - debugging APIs like to spam postMessage which floods the log otherwise
    }

    // TODO -- handle widget actions
    alert(event.data.widgetData);
}

/**
 * Check if message origin is registered as trusted
 * @param  {string} origin PostMessage origin to check
 * @return {boolean}       True if trusted
 */
function trustedEndpoint(origin) {
    if (origin) {
        if (messageEndpoints.filter(function(endpoint) {
            if (endpoint.endpointUrl == origin) {
                return true;
            }
        }).length > 0) {
            return true;
        }
    }

    return false;
}

/**
 * Represents mapping of widget instance to URLs for trusted postMessage communication.
 */
class WidgetMessageEndpoint {
    /**
     * Mapping of widget instance to URL for trusted postMessage communication.
     * @param  {string} widgetId    Unique widget identifier
     * @param  {string} endpointUrl Widget wurl origin.
     */
    constructor(widgetId, endpointUrl) {
        if (!widgetId) {
            throw new Error("No widgetId specified in widgetMessageEndpoint constructor");
        }
        if (!endpointUrl) {
            throw new Error("No endpoint specified in widgetMessageEndpoint constructor");
        }
        this.widgetId = widgetId;
        this.endpointUrl = endpointUrl;
    }
}

module.exports = {
    /**
     * Register widget message event listeners
     */
    startListening() {
        if (listenerCount === 0) {
            window.addEventListener("message", onMessage, false);
        }
        listenerCount += 1;
    },

    /**
     * De-register widget message event listeners
     */
    stopListening() {
        listenerCount -= 1;
        if (listenerCount === 0) {
            window.removeEventListener("message", onMessage);
        }
        if (listenerCount < 0) {
            // Make an error so we get a stack trace
            const e = new Error(
                "WidgetMessaging: mismatched startListening / stopListening detected." +
                " Negative count",
            );
            console.error(e);
        }
    },

    /**
     * Register a widget endpoint for trusted postMessage communication
     * @param {string} widgetId    Unique widget identifier
     * @param {string} endpointUrl Widget wurl origin (protocol + (optional port) + host)
     */
    addEndpoint(widgetId, endpointUrl) {
        const endpoint = new WidgetMessageEndpoint(widgetId, endpointUrl);
        if (messageEndpoints && messageEndpoints.length > 0) {
            if (messageEndpoints.filter(function(ep) {
                return (ep.widgetId == widgetId && ep.endpointUrl == endpointUrl);
            }).length > 0) {
                // Message endpoint already registered
                return;
            }
            messageEndpoints.push(endpoint);
        }
    },

    /**
     * De-register a widget endpoint from trusted communication sources
     * @param  {string} widgetId Unique widget identifier
     * @param  {string} endpointUrl Widget wurl origin (protocol + (optional port) + host)
     * @return {boolean} True if endpoint was successfully removed
     */
    removeOrigin(widgetId, endpointUrl) {
        if (messageEndpoints && messageEndpoints.length > 0) {
            const length = messageEndpoints.length;
            messageEndpoints = messageEndpoints.filter(function(endpoint) {
                return (endpoint.widgetId != widgetId || endpoint.endpointUrl != endpointUrl);
            });
            return (length > messageEndpoints.length);
        }
        return false;
    },
};
<|endoftext|>


<commit_before>/* jslint node: true */
'use strict';

//	ENiGMA½
const MenuModule					= require('./menu_module.js').MenuModule;
const ViewController				= require('./view_controller.js').ViewController;
const messageArea					= require('./message_area.js');
const stringFormat					= require('./string_format.js');
const MessageAreaConfTempSwitcher	= require('./mod_mixins.js').MessageAreaConfTempSwitcher;

//	deps
const async				= require('async');
const _					= require('lodash');
const moment			= require('moment');

/*
	Available listFormat/focusListFormat members (VM1):

	msgNum			: Message number
	to				: To username/handle
	from			: From username/handle
	subj			: Subject
	ts				: Message mod timestamp (format with config.dateTimeFormat)
	newIndicator	: New mark/indicator (config.newIndicator)

	MCI codes:

	VM1			: Message list
	TL2			: Message info 1: { msgNumSelected, msgNumTotal }
*/

exports.moduleInfo = {
	name	: 'Message List',
	desc	: 'Module for listing/browsing available messages',
	author	: 'NuSkooler',
};

const MCICodesIDs = {
	MsgList			: 1,	//	VM1
	MsgInfo1		: 2,	//	TL2
};

exports.getModule = class MessageListModule extends MessageAreaConfTempSwitcher(MenuModule) {
	constructor(options) {
		super(options);

		const self		= this;
		const config	= this.menuConfig.config;

		this.messageAreaTag = config.messageAreaTag;

		this.lastMessageReachedExit = _.get(options, 'lastMenuResult.lastMessageReached', false);

		if(options.extraArgs) {
			//
			//	|extraArgs| can override |messageAreaTag| provided by config
			//	as well as supply a pre-defined message list
			//
			if(options.extraArgs.messageAreaTag) {
				this.messageAreaTag = options.extraArgs.messageAreaTag;
			}

			if(options.extraArgs.messageList) {
				this.messageList = options.extraArgs.messageList;
			}
		}

		this.menuMethods = {
			selectMessage : function(formData, extraArgs, cb) {
				if(1 === formData.submitId) {
					self.initialFocusIndex = formData.value.message;

					const modOpts = {
						extraArgs 	: {
							messageAreaTag		: self.messageAreaTag,
							messageList			: self.messageList,
							messageIndex		: formData.value.message,
							lastMessageNextExit	: true,
						}
					};

					//
					//	Provide a serializer so we don't dump *huge* bits of information to the log
					//	due to the size of |messageList|. See https://github.com/trentm/node-bunyan/issues/189
					//
					modOpts.extraArgs.toJSON = function() {
						const logMsgList = (this.messageList.length <= 4) ?
							this.messageList :
							this.messageList.slice(0, 2).concat(this.messageList.slice(-2));

						return {
							messageAreaTag		: this.messageAreaTag,
							apprevMessageList	: logMsgList,
							messageCount		: this.messageList.length,
							messageIndex		: formData.value.message,
						};
					};

					return self.gotoMenu(config.menuViewPost || 'messageAreaViewPost', modOpts, cb);
				} else {
					return cb(null);
				}
			},

			fullExit : function(formData, extraArgs, cb) {
				self.menuResult = { fullExit : true };
				return self.prevMenu(cb);
			}
		};
	}

	enter() {
		if(this.lastMessageReachedExit) {
			return this.prevMenu();
		}

		super.enter();

		//
		//	Config can specify |messageAreaTag| else it comes from
		//	the user's current area
		//
		if(this.messageAreaTag) {
			this.tempMessageConfAndAreaSwitch(this.messageAreaTag);
		} else {
			this.messageAreaTag = this.client.user.properties.message_area_tag;
		}
	}

	leave() {
		this.tempMessageConfAndAreaRestore();
		super.leave();
	}

	mciReady(mciData, cb) {
		super.mciReady(mciData, err => {
			if(err) {
				return cb(err);
			}

			const self	= this;
			const vc	= self.viewControllers.allViews = new ViewController( { client : self.client } );

			async.series(
				[
					function loadFromConfig(callback) {
						const loadOpts = {
							callingMenu		: self,
							mciMap			: mciData.menu
						};

						return vc.loadFromMenuConfig(loadOpts, callback);
					},
					function fetchMessagesInArea(callback) {
						//
						//	Config can supply messages else we'll need to populate the list now
						//
						if(_.isArray(self.messageList)) {
							return callback(0 === self.messageList.length ? new Error('No messages in area') : null);
						}

						messageArea.getMessageListForArea( { client : self.client }, self.messageAreaTag, function msgs(err, msgList) {
							if(!msgList || 0 === msgList.length) {
								return callback(new Error('No messages in area'));
							}

							self.messageList = msgList;
							return callback(err);
						});
					},
					function getLastReadMesageId(callback) {
						messageArea.getMessageAreaLastReadId(self.client.user.userId, self.messageAreaTag, function lastRead(err, lastReadId) {
							self.lastReadId = lastReadId || 0;
							return callback(null);	//	ignore any errors, e.g. missing value
						});
					},
					function updateMessageListObjects(callback) {
						const dateTimeFormat	= self.menuConfig.config.dateTimeFormat || 'ddd MMM Do';
						const newIndicator		= self.menuConfig.config.newIndicator || '*';
						const regIndicator		= new Array(newIndicator.length + 1).join(' ');	//	fill with space to avoid draw issues

						let msgNum = 1;
						self.messageList.forEach( (listItem, index) => {
							listItem.msgNum			= msgNum++;
							listItem.ts				= moment(listItem.modTimestamp).format(dateTimeFormat);
							listItem.newIndicator	= listItem.messageId > self.lastReadId ? newIndicator : regIndicator;

							if(_.isUndefined(self.initialFocusIndex) && listItem.messageId > self.lastReadId) {
								self.initialFocusIndex = index;
							}
						});
						return callback(null);
					},
					function populateList(callback) {
						const msgListView			= vc.getView(MCICodesIDs.MsgList);
						const listFormat			= self.menuConfig.config.listFormat || '{msgNum} - {subject} - {toUserName}';
						const focusListFormat		= self.menuConfig.config.focusListFormat || listFormat;	//	:TODO: default change color here
						const messageInfo1Format	= self.menuConfig.config.messageInfo1Format || '{msgNumSelected} / {msgNumTotal}';

						//	:TODO: This can take a very long time to load large lists. What we need is to implement the "owner draw" concept in
						//	which items are requested (e.g. their format at least) *as-needed* vs trying to get the format for all of them at once

						msgListView.setItems(_.map(self.messageList, listEntry => {
							return stringFormat(listFormat, listEntry);
						}));

						msgListView.setFocusItems(_.map(self.messageList, listEntry => {
							return stringFormat(focusListFormat, listEntry);
						}));

						msgListView.on('index update', idx => {
							self.setViewText(
								'allViews',
								MCICodesIDs.MsgInfo1,
								stringFormat(messageInfo1Format, { msgNumSelected : (idx + 1), msgNumTotal : self.messageList.length } ));
						});

						if(self.initialFocusIndex > 0) {
							//	note: causes redraw()
							msgListView.setFocusItemIndex(self.initialFocusIndex);
						} else {
							msgListView.redraw();
						}

						return callback(null);
					},
					function drawOtherViews(callback) {
						const messageInfo1Format = self.menuConfig.config.messageInfo1Format || '{msgNumSelected} / {msgNumTotal}';
						self.setViewText(
							'allViews',
							MCICodesIDs.MsgInfo1,
							stringFormat(messageInfo1Format, { msgNumSelected : self.initialFocusIndex + 1, msgNumTotal : self.messageList.length } ));
						return callback(null);
					},
				],
				err => {
					if(err) {
						self.client.log.error( { error : err.message }, 'Error loading message list');
					}
					return cb(err);
				}
			);
		});
	}

	getSaveState() {
		return { initialFocusIndex : this.initialFocusIndex };
	}

	restoreSavedState(savedState) {
		if(savedState) {
			this.initialFocusIndex = savedState.initialFocusIndex;
		}
	}

	getMenuResult() {
		return this.menuResult;
	}
};
<commit_msg>options -> client, since client was only option ;)<commit_after>/* jslint node: true */
'use strict';

//	ENiGMA½
const MenuModule					= require('./menu_module.js').MenuModule;
const ViewController				= require('./view_controller.js').ViewController;
const messageArea					= require('./message_area.js');
const stringFormat					= require('./string_format.js');
const MessageAreaConfTempSwitcher	= require('./mod_mixins.js').MessageAreaConfTempSwitcher;

//	deps
const async				= require('async');
const _					= require('lodash');
const moment			= require('moment');

/*
	Available listFormat/focusListFormat members (VM1):

	msgNum			: Message number
	to				: To username/handle
	from			: From username/handle
	subj			: Subject
	ts				: Message mod timestamp (format with config.dateTimeFormat)
	newIndicator	: New mark/indicator (config.newIndicator)

	MCI codes:

	VM1			: Message list
	TL2			: Message info 1: { msgNumSelected, msgNumTotal }
*/

exports.moduleInfo = {
	name	: 'Message List',
	desc	: 'Module for listing/browsing available messages',
	author	: 'NuSkooler',
};

const MCICodesIDs = {
	MsgList			: 1,	//	VM1
	MsgInfo1		: 2,	//	TL2
};

exports.getModule = class MessageListModule extends MessageAreaConfTempSwitcher(MenuModule) {
	constructor(options) {
		super(options);

		const self		= this;
		const config	= this.menuConfig.config;

		this.messageAreaTag = config.messageAreaTag;

		this.lastMessageReachedExit = _.get(options, 'lastMenuResult.lastMessageReached', false);

		if(options.extraArgs) {
			//
			//	|extraArgs| can override |messageAreaTag| provided by config
			//	as well as supply a pre-defined message list
			//
			if(options.extraArgs.messageAreaTag) {
				this.messageAreaTag = options.extraArgs.messageAreaTag;
			}

			if(options.extraArgs.messageList) {
				this.messageList = options.extraArgs.messageList;
			}
		}

		this.menuMethods = {
			selectMessage : function(formData, extraArgs, cb) {
				if(1 === formData.submitId) {
					self.initialFocusIndex = formData.value.message;

					const modOpts = {
						extraArgs 	: {
							messageAreaTag		: self.messageAreaTag,
							messageList			: self.messageList,
							messageIndex		: formData.value.message,
							lastMessageNextExit	: true,
						}
					};

					//
					//	Provide a serializer so we don't dump *huge* bits of information to the log
					//	due to the size of |messageList|. See https://github.com/trentm/node-bunyan/issues/189
					//
					modOpts.extraArgs.toJSON = function() {
						const logMsgList = (this.messageList.length <= 4) ?
							this.messageList :
							this.messageList.slice(0, 2).concat(this.messageList.slice(-2));

						return {
							messageAreaTag		: this.messageAreaTag,
							apprevMessageList	: logMsgList,
							messageCount		: this.messageList.length,
							messageIndex		: formData.value.message,
						};
					};

					return self.gotoMenu(config.menuViewPost || 'messageAreaViewPost', modOpts, cb);
				} else {
					return cb(null);
				}
			},

			fullExit : function(formData, extraArgs, cb) {
				self.menuResult = { fullExit : true };
				return self.prevMenu(cb);
			}
		};
	}

	enter() {
		if(this.lastMessageReachedExit) {
			return this.prevMenu();
		}

		super.enter();

		//
		//	Config can specify |messageAreaTag| else it comes from
		//	the user's current area
		//
		if(this.messageAreaTag) {
			this.tempMessageConfAndAreaSwitch(this.messageAreaTag);
		} else {
			this.messageAreaTag = this.client.user.properties.message_area_tag;
		}
	}

	leave() {
		this.tempMessageConfAndAreaRestore();
		super.leave();
	}

	mciReady(mciData, cb) {
		super.mciReady(mciData, err => {
			if(err) {
				return cb(err);
			}

			const self	= this;
			const vc	= self.viewControllers.allViews = new ViewController( { client : self.client } );

			async.series(
				[
					function loadFromConfig(callback) {
						const loadOpts = {
							callingMenu		: self,
							mciMap			: mciData.menu
						};

						return vc.loadFromMenuConfig(loadOpts, callback);
					},
					function fetchMessagesInArea(callback) {
						//
						//	Config can supply messages else we'll need to populate the list now
						//
						if(_.isArray(self.messageList)) {
							return callback(0 === self.messageList.length ? new Error('No messages in area') : null);
						}

						messageArea.getMessageListForArea(self.client, self.messageAreaTag, function msgs(err, msgList) {
							if(!msgList || 0 === msgList.length) {
								return callback(new Error('No messages in area'));
							}

							self.messageList = msgList;
							return callback(err);
						});
					},
					function getLastReadMesageId(callback) {
						messageArea.getMessageAreaLastReadId(self.client.user.userId, self.messageAreaTag, function lastRead(err, lastReadId) {
							self.lastReadId = lastReadId || 0;
							return callback(null);	//	ignore any errors, e.g. missing value
						});
					},
					function updateMessageListObjects(callback) {
						const dateTimeFormat	= self.menuConfig.config.dateTimeFormat || 'ddd MMM Do';
						const newIndicator		= self.menuConfig.config.newIndicator || '*';
						const regIndicator		= new Array(newIndicator.length + 1).join(' ');	//	fill with space to avoid draw issues

						let msgNum = 1;
						self.messageList.forEach( (listItem, index) => {
							listItem.msgNum			= msgNum++;
							listItem.ts				= moment(listItem.modTimestamp).format(dateTimeFormat);
							listItem.newIndicator	= listItem.messageId > self.lastReadId ? newIndicator : regIndicator;

							if(_.isUndefined(self.initialFocusIndex) && listItem.messageId > self.lastReadId) {
								self.initialFocusIndex = index;
							}
						});
						return callback(null);
					},
					function populateList(callback) {
						const msgListView			= vc.getView(MCICodesIDs.MsgList);
						const listFormat			= self.menuConfig.config.listFormat || '{msgNum} - {subject} - {toUserName}';
						const focusListFormat		= self.menuConfig.config.focusListFormat || listFormat;	//	:TODO: default change color here
						const messageInfo1Format	= self.menuConfig.config.messageInfo1Format || '{msgNumSelected} / {msgNumTotal}';

						//	:TODO: This can take a very long time to load large lists. What we need is to implement the "owner draw" concept in
						//	which items are requested (e.g. their format at least) *as-needed* vs trying to get the format for all of them at once

						msgListView.setItems(_.map(self.messageList, listEntry => {
							return stringFormat(listFormat, listEntry);
						}));

						msgListView.setFocusItems(_.map(self.messageList, listEntry => {
							return stringFormat(focusListFormat, listEntry);
						}));

						msgListView.on('index update', idx => {
							self.setViewText(
								'allViews',
								MCICodesIDs.MsgInfo1,
								stringFormat(messageInfo1Format, { msgNumSelected : (idx + 1), msgNumTotal : self.messageList.length } ));
						});

						if(self.initialFocusIndex > 0) {
							//	note: causes redraw()
							msgListView.setFocusItemIndex(self.initialFocusIndex);
						} else {
							msgListView.redraw();
						}

						return callback(null);
					},
					function drawOtherViews(callback) {
						const messageInfo1Format = self.menuConfig.config.messageInfo1Format || '{msgNumSelected} / {msgNumTotal}';
						self.setViewText(
							'allViews',
							MCICodesIDs.MsgInfo1,
							stringFormat(messageInfo1Format, { msgNumSelected : self.initialFocusIndex + 1, msgNumTotal : self.messageList.length } ));
						return callback(null);
					},
				],
				err => {
					if(err) {
						self.client.log.error( { error : err.message }, 'Error loading message list');
					}
					return cb(err);
				}
			);
		});
	}

	getSaveState() {
		return { initialFocusIndex : this.initialFocusIndex };
	}

	restoreSavedState(savedState) {
		if(savedState) {
			this.initialFocusIndex = savedState.initialFocusIndex;
		}
	}

	getMenuResult() {
		return this.menuResult;
	}
};
<|endoftext|>