<commit_before>/*!
 * Copyright 2014, Digium, Inc.
 * All rights reserved.
 *
 * This source code is licensed under The MIT License found in the
 * LICENSE file in the root directory of this source tree.
 *
 * For all details and documentation:  https://www.respoke.io
 */

/* global respoke: true */
var Q = require('q');
var respoke = require('./respoke');

/**
 * A conference call to one or more people with audio. Eventually this will handle video, too.
 * @class respoke.Conference
 * @constructor
 * @augments respoke.EventEmitter
 * @param {object} params
 * @param {string} params.id - The id that should be used to create the conference call or the ID
 * of the call to join.
 * @param {string} params.instanceId - client id
 * @param {string} params.key - The key that indicates an endpoint can join.
 * @param {boolean} params.open - whether endpoints can join this conference without a key.
 * @param {boolean} params.caller - whether or not we initiated the call
 * @param {boolean} [params.receiveOnly] - whether or not we accept media
 * @param {boolean} [params.sendOnly] - whether or not we send media
 * @param {boolean} [params.needDirectConnection] - flag to enable skipping media & opening direct connection.
 * @param {boolean} [params.forceTurn] - If true, media is not allowed to flow peer-to-peer and must flow through
 * relay servers. If it cannot flow through relay servers, the call will fail.
 * @param {boolean} [params.disableTurn] - If true, media is not allowed to flow through relay servers; it is
 * required to flow peer-to-peer. If it cannot, the call will fail.
 * @param {respoke.Endpoint} params.remoteEndpoint - The endpoint who is being called.
 * @param {string} [params.connectionId] - The connection ID of the remoteEndpoint.
 * @param {respoke.Call.previewLocalMedia} [params.previewLocalMedia] - A function to call if the developer
 * wants to perform an action between local media becoming available and calling approve().
 * @param {function} params.signalOffer - Signaling action from SignalingChannel.
 * @param {function} params.signalConnected - Signaling action from SignalingChannel.
 * @param {function} params.signalAnswer - Signaling action from SignalingChannel.
 * @param {function} params.signalHangup - Signaling action from SignalingChannel.
 * @param {function} params.signalReport - Signaling action from SignalingChannel.
 * @param {function} params.signalCandidate - Signaling action from SignalingChannel.
 * @param {Array<RTCConstraints>} params.constraints - Array of WebRTC constraints.
 * @param {respoke.Conference.onJoin} [params.onJoin] - Callback for when a participant joins the conference.
 * @param {respoke.Conference.onLeave} [params.onLeave] - Callback for when a participant leaves the conference.
 * @param {respoke.Conference.onMessage} [params.onMessage] - Callback for when a message is sent to the conference.
 * @param {respoke.Conference.onMute} [params.onMute] - Callback for when local or remote media is muted or unmuted.
 * @param {respoke.Conference.onTopic} [params.onTopic] - Callback for the conference topic changes.
 * @param {respoke.Conference.onPresenter} [params.onPresenter] - Callback for when the presenter changes.
 * @param {respoke.Call.onError} [params.onError] - Callback for errors that happen during call setup or
 * media renegotiation.
 * @param {respoke.Call.onLocalMedia} [params.onLocalMedia] - Callback for receiving an HTML5 Video
 * element with the local audio and/or video attached.
 * @param {respoke.Call.onRemoteMedia} [params.onRemoteMedia] - Callback for receiving an HTML5 Video
 * element with the remote audio and/or video attached.
 * @param {respoke.Call.onConnect} [params.onConnect] - Callback for when we've found a suitable network path
 * to the other party and we're reasonably sure the media will start flowing soon.
 * @param {respoke.Call.onHangup} [params.onHangup] - Callback for when the call is ended, whether or not
 * it was ended in a graceful manner.
 * @param {respoke.Call.onMute} [params.onMute] - Callback for changing the mute state on any type of media.
 * This callback will be called when media is muted or unmuted.
 * @param {respoke.Call.onAnswer} [params.onAnswer] - Callback for when the callee answers the call.
 * @param {respoke.Call.onRequestingMedia} [params.onRequestingMedia] - Callback for when the app is waiting
 * for the user to give permission to start getting audio or video.
 * @param {respoke.Call.onApprove} [params.onApprove] - Callback for when the user approves local media. This
 * callback will be called whether or not the approval was based on user feedback. I. e., it will be called even if
 * the approval was automatic.
 * @param {respoke.Call.onAllow} [params.onAllow] - Callback for when the browser gives us access to the
 * user's media.  This event gets called even if the allow process is automatic, i. e., permission and media is
 * granted by the browser without asking the user to approve it.
 * @param {HTMLVideoElement} params.videoLocalElement - Pass in an optional html video element to have local
 * video attached to it.
 * @param {HTMLVideoElement} params.videoRemoteElement - Pass in an optional html video element to have remote
 * video attached to it.
 * @returns {respoke.Conference}
 */
module.exports = function (params) {
    "use strict";
    params = params || {};
    /**
     * @memberof! respoke.Client
     * @name instanceId
     * @private
     * @type {string}
     */
    var instanceId = params.instanceId;
    var signalingChannel = params.signalingChannel;
    var that = respoke.EventEmitter({
        open: params.open,
        key: params.key,
        id: params.id
    });

    that.listen('join', params.onJoin);
    that.listen('leave', params.onLeave);
    that.listen('message', params.onMessage);
    that.listen('mute', params.onMute);
    that.listen('topic', params.onTopic);
    that.listen('presenter', params.onPresenter);
    delete params.onJoin;
    delete params.onLeave;
    delete params.onMessage;
    delete params.onMute;
    delete params.onTopic;
    delete params.onPresenter;

    params.caller = true;
    params.conferenceId = params.id;
    delete params.id;
    delete params.key;
    params.remoteEndpoint = that;
    that.call = respoke.Call(params);

    // Redirect a bunch of events.
    [
        'mute', 'hangup', 'connect', 'stats', 'error', 'local-stream-received',
         'remote-stream-received', 'requesting-media', 'approve', 'allow'
    ].forEach(function (eventName) {
        that.call.listen(eventName, function (evt) {
            evt.call = that.call; // target will be updated to point to this conference object.
            that.fire(eventName, evt);
        });
    });

    delete that.instanceId;

    /**
     * A name to identify this class
     * @memberof! respoke.Conference
     * @name className
     * @type {string}
     */
    that.className = 'respoke.Conference';

    /**
     * @memberof! respoke.Conference
     * @name client
     * @type {respoke.Client}
     * @private
     */
    var client = respoke.getClient(instanceId);

    /**
     * Leave the conference.
     * @memberof! respoke.Conference
     * @method respoke.Conference.leave
     */
    that.leave = that.call.hangup;

    /**
     * Mute local user's audio.
     * @memberof! respoke.Conference
     * @method respoke.Conference.muteAudio
     */
    that.muteAudio = that.call.muteAudio;

    /**
     * ## The plugin `respoke.MediaStats` must be loaded before using this method.
     *
     * Start the process of listening for a continuous stream of statistics about the flow of audio and/or video.
     * Since we have to wait for both the answer and offer to be available before starting
     * statistics, the library returns a promise for the stats object. The statistics object does not contain the
     * statistics; rather it contains methods of interacting with the actions of obtaining statistics. To obtain
     * the actual statistics one time, use stats.getStats(); use the onStats callback to obtain a continuous
     * stream of statistics every `interval` seconds.  Returns null if stats module is not loaded.
     *
     *     conference.getStats({
     *         onStats: function (evt) {
     *             console.log('Stats', evt.stats);
     *         }
     *     }).done(function () {
     *         console.log('Stats started');
     *     }, function (err) {
     *         console.log('Call is already hung up.');
     *     });
     *
     * @memberof! respoke.Conference
     * @method respoke.Conference.getStats
     * @param {object} params
     * @param {number} [params.interval=5000] - How often in milliseconds to fetch statistics.
     * @param {respoke.MediaStatsParser.statsHandler} [params.onStats] - An optional callback to receive
     * the stats. If no callback is provided, the call's report will contain stats but the developer will not
     * receive them on the client-side.
     * @param {respoke.Call.statsSuccessHandler} [params.onSuccess] - Success handler for this invocation of
     * this method only.
     * @param {respoke.Call.errorHandler} [params.onError] - Error handler for this invocation of this method only.
     * @returns {Promise<object>|null}
     */
    if (respoke.MediaStats) {
        that.getStats = that.call.getStats;
    }

    return that;
};
<commit_msg>Rename stream-received events in conferencing apis.<commit_after>/*!
 * Copyright 2014, Digium, Inc.
 * All rights reserved.
 *
 * This source code is licensed under The MIT License found in the
 * LICENSE file in the root directory of this source tree.
 *
 * For all details and documentation:  https://www.respoke.io
 */

/* global respoke: true */
var Q = require('q');
var respoke = require('./respoke');

/**
 * A conference call to one or more people with audio. Eventually this will handle video, too.
 * @class respoke.Conference
 * @constructor
 * @augments respoke.EventEmitter
 * @param {object} params
 * @param {string} params.id - The id that should be used to create the conference call or the ID
 * of the call to join.
 * @param {string} params.instanceId - client id
 * @param {string} params.key - The key that indicates an endpoint can join.
 * @param {boolean} params.open - whether endpoints can join this conference without a key.
 * @param {boolean} params.caller - whether or not we initiated the call
 * @param {boolean} [params.receiveOnly] - whether or not we accept media
 * @param {boolean} [params.sendOnly] - whether or not we send media
 * @param {boolean} [params.needDirectConnection] - flag to enable skipping media & opening direct connection.
 * @param {boolean} [params.forceTurn] - If true, media is not allowed to flow peer-to-peer and must flow through
 * relay servers. If it cannot flow through relay servers, the call will fail.
 * @param {boolean} [params.disableTurn] - If true, media is not allowed to flow through relay servers; it is
 * required to flow peer-to-peer. If it cannot, the call will fail.
 * @param {respoke.Endpoint} params.remoteEndpoint - The endpoint who is being called.
 * @param {string} [params.connectionId] - The connection ID of the remoteEndpoint.
 * @param {respoke.Call.previewLocalMedia} [params.previewLocalMedia] - A function to call if the developer
 * wants to perform an action between local media becoming available and calling approve().
 * @param {function} params.signalOffer - Signaling action from SignalingChannel.
 * @param {function} params.signalConnected - Signaling action from SignalingChannel.
 * @param {function} params.signalAnswer - Signaling action from SignalingChannel.
 * @param {function} params.signalHangup - Signaling action from SignalingChannel.
 * @param {function} params.signalReport - Signaling action from SignalingChannel.
 * @param {function} params.signalCandidate - Signaling action from SignalingChannel.
 * @param {Array<RTCConstraints>} params.constraints - Array of WebRTC constraints.
 * @param {respoke.Conference.onJoin} [params.onJoin] - Callback for when a participant joins the conference.
 * @param {respoke.Conference.onLeave} [params.onLeave] - Callback for when a participant leaves the conference.
 * @param {respoke.Conference.onMessage} [params.onMessage] - Callback for when a message is sent to the conference.
 * @param {respoke.Conference.onMute} [params.onMute] - Callback for when local or remote media is muted or unmuted.
 * @param {respoke.Conference.onTopic} [params.onTopic] - Callback for the conference topic changes.
 * @param {respoke.Conference.onPresenter} [params.onPresenter] - Callback for when the presenter changes.
 * @param {respoke.Call.onError} [params.onError] - Callback for errors that happen during call setup or
 * media renegotiation.
 * @param {respoke.Call.onLocalMedia} [params.onLocalMedia] - Callback for receiving an HTML5 Video
 * element with the local audio and/or video attached.
 * @param {respoke.Call.onRemoteMedia} [params.onRemoteMedia] - Callback for receiving an HTML5 Video
 * element with the remote audio and/or video attached.
 * @param {respoke.Call.onConnect} [params.onConnect] - Callback for when we've found a suitable network path
 * to the other party and we're reasonably sure the media will start flowing soon.
 * @param {respoke.Call.onHangup} [params.onHangup] - Callback for when the call is ended, whether or not
 * it was ended in a graceful manner.
 * @param {respoke.Call.onMute} [params.onMute] - Callback for changing the mute state on any type of media.
 * This callback will be called when media is muted or unmuted.
 * @param {respoke.Call.onAnswer} [params.onAnswer] - Callback for when the callee answers the call.
 * @param {respoke.Call.onRequestingMedia} [params.onRequestingMedia] - Callback for when the app is waiting
 * for the user to give permission to start getting audio or video.
 * @param {respoke.Call.onApprove} [params.onApprove] - Callback for when the user approves local media. This
 * callback will be called whether or not the approval was based on user feedback. I. e., it will be called even if
 * the approval was automatic.
 * @param {respoke.Call.onAllow} [params.onAllow] - Callback for when the browser gives us access to the
 * user's media.  This event gets called even if the allow process is automatic, i. e., permission and media is
 * granted by the browser without asking the user to approve it.
 * @param {HTMLVideoElement} params.videoLocalElement - Pass in an optional html video element to have local
 * video attached to it.
 * @param {HTMLVideoElement} params.videoRemoteElement - Pass in an optional html video element to have remote
 * video attached to it.
 * @returns {respoke.Conference}
 */
module.exports = function (params) {
    "use strict";
    params = params || {};
    /**
     * @memberof! respoke.Client
     * @name instanceId
     * @private
     * @type {string}
     */
    var instanceId = params.instanceId;
    var signalingChannel = params.signalingChannel;
    var that = respoke.EventEmitter({
        open: params.open,
        key: params.key,
        id: params.id
    });

    that.listen('join', params.onJoin);
    that.listen('leave', params.onLeave);
    that.listen('message', params.onMessage);
    that.listen('mute', params.onMute);
    that.listen('topic', params.onTopic);
    that.listen('presenter', params.onPresenter);
    delete params.onJoin;
    delete params.onLeave;
    delete params.onMessage;
    delete params.onMute;
    delete params.onTopic;
    delete params.onPresenter;

    params.caller = true;
    params.conferenceId = params.id;
    delete params.id;
    delete params.key;
    params.remoteEndpoint = that;
    that.call = respoke.Call(params);

    // Redirect a bunch of events.
    [
        'mute', 'hangup', 'connect', 'stats', 'error', 'local-media',
         'remote-media', 'requesting-media', 'approve', 'allow'
    ].forEach(function (eventName) {
        that.call.listen(eventName, function (evt) {
            evt.call = that.call; // target will be updated to point to this conference object.
            that.fire(eventName, evt);
        });
    });

    delete that.instanceId;

    /**
     * A name to identify this class
     * @memberof! respoke.Conference
     * @name className
     * @type {string}
     */
    that.className = 'respoke.Conference';

    /**
     * @memberof! respoke.Conference
     * @name client
     * @type {respoke.Client}
     * @private
     */
    var client = respoke.getClient(instanceId);

    /**
     * Leave the conference.
     * @memberof! respoke.Conference
     * @method respoke.Conference.leave
     */
    that.leave = that.call.hangup;

    /**
     * Mute local user's audio.
     * @memberof! respoke.Conference
     * @method respoke.Conference.muteAudio
     */
    that.muteAudio = that.call.muteAudio;

    /**
     * ## The plugin `respoke.MediaStats` must be loaded before using this method.
     *
     * Start the process of listening for a continuous stream of statistics about the flow of audio and/or video.
     * Since we have to wait for both the answer and offer to be available before starting
     * statistics, the library returns a promise for the stats object. The statistics object does not contain the
     * statistics; rather it contains methods of interacting with the actions of obtaining statistics. To obtain
     * the actual statistics one time, use stats.getStats(); use the onStats callback to obtain a continuous
     * stream of statistics every `interval` seconds.  Returns null if stats module is not loaded.
     *
     *     conference.getStats({
     *         onStats: function (evt) {
     *             console.log('Stats', evt.stats);
     *         }
     *     }).done(function () {
     *         console.log('Stats started');
     *     }, function (err) {
     *         console.log('Call is already hung up.');
     *     });
     *
     * @memberof! respoke.Conference
     * @method respoke.Conference.getStats
     * @param {object} params
     * @param {number} [params.interval=5000] - How often in milliseconds to fetch statistics.
     * @param {respoke.MediaStatsParser.statsHandler} [params.onStats] - An optional callback to receive
     * the stats. If no callback is provided, the call's report will contain stats but the developer will not
     * receive them on the client-side.
     * @param {respoke.Call.statsSuccessHandler} [params.onSuccess] - Success handler for this invocation of
     * this method only.
     * @param {respoke.Call.errorHandler} [params.onError] - Error handler for this invocation of this method only.
     * @returns {Promise<object>|null}
     */
    if (respoke.MediaStats) {
        that.getStats = that.call.getStats;
    }

    return that;
};
<|endoftext|>


<commit_before>/* jshint browser: true, debug: true */

'use strict';
// EXAMPLE USAGE CALL:
//
// init({
//     baseUrl: 'data-img-desktop', // will be used as largest, AND default if no other responsive tags are used
//     responsive: [ // Array of breakpoint objects
//         {
//             breakpoint: 1024,
//             url: 'data-img-tablet'
//         },
//         {
//             breakpoint: 769,
//             url: 'data-img-mobile'
//         }
//     ],
//     lazyLoad: true
// });

var getClientWidth = function () {
    return document.body.clientWidth;
};

var getClientHeight = function () {
    return document.documentElement.clientHeight;
};

var getElemVerticalOffset = function (el) {
    return el.getBoundingClientRect().top;
};

var getImages = function (attr) {
    return [].slice.call(document.querySelectorAll('[' + attr + ']'));
};

var hasBackgroundImage = function (el) {
    return el.style.backgroundImage.length > 0;
};

var hasSameBackgroundImage = function (el, url) {
    // Match 'url([group 1])' format, so that address can be compared
    return el.style.backgroundImage.match(/url\((.+?)\)/)[1] === url;
};

var applyImage = function (el, url) {
    // If no backgroundImage, apply backgroundImage
    if (!hasBackgroundImage(el)) {
        el.style.backgroundImage = 'url(' + url + ')';
        return;

    // If there's already a backgroudImage, and it's the same as the responsive url, do nothing
    // This is important for mobile
    } else if (hasSameBackgroundImage(el, url)) {
        return;

    // Otherwise, the responsive url is new, and the image should be swapped
    } else {
        el.style.backgroundImage = 'url(' + url + ')';
        return;
    }
};

var orderBreakpoints = function(arr) {
    return arr.sort(function(a, b) {
        return a.breakpoint - b.breakpoint;
    });
};

var getFirstMatch = function(arr, size) {
    var breakpoints = orderBreakpoints(arr, 'breakpoint');
    for (var i = 0; i < breakpoints.length; i++) {
        if (size < breakpoints[i].breakpoint) {
            return breakpoints[i].url;
        }
    }
    return false;
};

var isInViewport = function (el) {
    return (getElemVerticalOffset(el) - 200) < getClientHeight(); // 200px buffer to trigger load shortly before image comes into view
};

var init = function(options) {
    var images = getImages(options.baseUrl);
    var size = getClientWidth();
    var url = getFirstMatch(options.responsive, size) || options.baseUrl;

    images.forEach(function(cur) {
        var setUrl = cur.attributes[url] || cur.attributes[options.baseUrl];
        // Lazyload variation
        if (isInViewport(cur) && options.lazyLoad) {
            applyImage(cur, setUrl.value);
        }

        // Non-lazyload variation
        if (!options.lazyLoad) {
            applyImage(cur, setUrl.value);
        }
    });
};

module.exports = init;
<commit_msg>Add quotes to prevent issues with image paths that include special characters<commit_after>/* jshint browser: true, debug: true */

'use strict';
// EXAMPLE USAGE CALL:
//
// init({
//     baseUrl: 'data-img-desktop', // will be used as largest, AND default if no other responsive tags are used
//     responsive: [ // Array of breakpoint objects
//         {
//             breakpoint: 1024,
//             url: 'data-img-tablet'
//         },
//         {
//             breakpoint: 769,
//             url: 'data-img-mobile'
//         }
//     ],
//     lazyLoad: true
// });

var getClientWidth = function () {
    return document.body.clientWidth;
};

var getClientHeight = function () {
    return document.documentElement.clientHeight;
};

var getElemVerticalOffset = function (el) {
    return el.getBoundingClientRect().top;
};

var getImages = function (attr) {
    return [].slice.call(document.querySelectorAll('[' + attr + ']'));
};

var hasBackgroundImage = function (el) {
    return el.style.backgroundImage.length > 0;
};

var hasSameBackgroundImage = function (el, url) {
    // Match 'url([group 1])' format, so that address can be compared
    return el.style.backgroundImage.match(/url\((.+?)\)/)[1] === url;
};

var applyImage = function (el, url) {
    // If no backgroundImage, apply backgroundImage
    if (!hasBackgroundImage(el)) {
        el.style.backgroundImage = 'url("' + url + '")';
        return;

    // If there's already a backgroudImage, and it's the same as the responsive url, do nothing
    // This is important for mobile
    } else if (hasSameBackgroundImage(el, url)) {
        return;

    // Otherwise, the responsive url is new, and the image should be swapped
    } else {
        el.style.backgroundImage = 'url("' + url + '")';
        return;
    }
};

var orderBreakpoints = function(arr) {
    return arr.sort(function(a, b) {
        return a.breakpoint - b.breakpoint;
    });
};

var getFirstMatch = function(arr, size) {
    var breakpoints = orderBreakpoints(arr, 'breakpoint');
    for (var i = 0; i < breakpoints.length; i++) {
        if (size < breakpoints[i].breakpoint) {
            return breakpoints[i].url;
        }
    }
    return false;
};

var isInViewport = function (el) {
    return (getElemVerticalOffset(el) - 200) < getClientHeight(); // 200px buffer to trigger load shortly before image comes into view
};

var init = function(options) {
    var images = getImages(options.baseUrl);
    var size = getClientWidth();
    var url = getFirstMatch(options.responsive, size) || options.baseUrl;

    images.forEach(function(cur) {
        var setUrl = cur.attributes[url] || cur.attributes[options.baseUrl];
        // Lazyload variation
        if (isInViewport(cur) && options.lazyLoad) {
            applyImage(cur, setUrl.value);
        }

        // Non-lazyload variation
        if (!options.lazyLoad) {
            applyImage(cur, setUrl.value);
        }
    });
};

module.exports = init;
<|endoftext|>


<commit_before>var objects = 0; // counts number of created objects to create unique @keyframes rules for CSS with the properly width
function Sidebar(sidebar, buttons, options){

  objects = objects + 1; // adds 1 for every created object
  var id = objects; // unique id for this object

  // Assign variables
  var sidebar = document.querySelectorAll(sidebar)[0];
  var buttons = document.querySelectorAll(buttons);
  var sidebar =  sidebar;
  var buttons = buttons;
  var isOpen = false; // Declares state of sidebar
  var time = 1000;
  var width = '80%';
  var overlayStyles = {};
  var animationTimingFunction = 'ease-in';
  if(options !== undefined){
    if(options.time !== undefined){
      time = options.time;
    }
    if(options.width !== undefined){
      width = options.width;
    }
    if(options.overlayStyles !== undefined){
      overlayStyles = options.overlayStyles;
    }
    if(options.animationTimingFunction !== undefined){
      animationTimingFunction = options.animationTimingFunction;
    }
  }

  // Creates overlay
  var bodyHeight = document.getElementsByTagName('body')[0].offsetHeight + 'px';
  var bodyWidth = document.getElementsByTagName('body')[0].offsetWidth + 'px';
  var overlayParent = document.createElement('div');
  overlayParent.setAttribute('class', 'overlay');

  addStyles(overlayParent,overlayStyles); // Sets customized styles
  // Sets absolute necessary styles to make sure everything works well
  addStyles(
    overlayParent,
    {
      display: 'none',
      position: 'absolute',
      top: '0px',
      left: '0px',
      height: bodyHeight,
      width: bodyWidth
    }
  );

  // Check if animations are supported in the calling browser to provide a fallback if not
  var animation  = false,
    animationstring  = 'animation',
    browserPrefix = '',
    domPrefixes = 'Webkit Moz O ms Khtml'.split(' '),
    pfx  = '';

  if(sidebar.style.animationName !== undefined )
    {
      animation = true;
    }
  if( animation === false ) {
    for( var i = 0; i < domPrefixes.length; i++ ) {
      if( sidebar.style[ domPrefixes[i] + 'AnimationName' ] !== undefined ) {
        pfx = domPrefixes[i];
        animationstring = pfx + 'Animation';
        browserPrefix = '-' + pfx.toLowerCase() + '-';
        animation = true;
        break;
      }
    }
  }
  // Adds @Keyframes rules to styles
  var openSidebarKeyframes = '@' + browserPrefix + 'keyframes openSidebar' + id + ' { '+
                    'from {' + browserPrefix + 'transform:translateX( 0px ) }'+
                    'to {' + browserPrefix + 'transform:translateX( ' + width + ' ) }'+
                  '}';
  var closeSidebarKeyframes = '@' + browserPrefix + 'keyframes closeSidebar' + id + ' { '+
                    'from {' + browserPrefix + 'transform:translateX( 0px ) }'+
                    'to {' + browserPrefix + 'transform:translateX( ' + width + ' ) }'+
                  '}';

  var opacity = 0;
  if(overlayStyles.opacity !== undefined){
    opacity = overlayStyles.opacity;
    overlayParent.style[browserPrefix + 'filter'] = "progid:DXImageTransform.Microsoft.Alpha(Opacity=" + opacity * 100 + ")"; // Support for IE 8

  }
  var fadeInKeyframes = '@' + browserPrefix + 'keyframes fadeInOverlay' + id + ' { '+
                    'from {' + browserPrefix + 'opacity: 0 }'+
                    'to {' + browserPrefix + 'opacity: ' + opacity + '}'+
                  '}';
  var fadeOutKeyframes = '@' + browserPrefix + 'keyframes fadeOutOverlay' + id + ' { '+
                    'from {' + browserPrefix + 'opacity: 0 }'+
                    'to {' + browserPrefix + 'opacity: ' + opacity + ' }'+
                  '}';
  if(animation){
    var s = document.createElement( 'style' );
    s.innerHTML = openSidebarKeyframes + ' ' + closeSidebarKeyframes + ' ' + fadeInKeyframes + ' ' + fadeOutKeyframes;
    document.getElementsByTagName( 'head' )[ 0 ].appendChild( s );
}

  // Adds overlay
  var body = document.getElementsByTagName('body')[0];
  var overlay = body.appendChild(overlayParent);

  // Method to close the sidebar
  var closeSidebar = function(){
    if(isOpen){ // if sidebar is already closed then don't close it again
      isOpen = false;
      if(animation){  // if animations are supported run @Keyframes
        overlay.style[animationstring] = 'fadeOutOverlay' + id + ' ' + time  + 'ms ' + animationTimingFunction + ' 1 reverse both';
        body.style[animationstring] = 'closeSidebar' + id + ' ' + time + 'ms ' + animationTimingFunction + ' 1 reverse both';
        setTimeout(function(){

          overlay.style.display = 'none';
          addStyles(
            sidebar,
            {
              marginLeft: '',
              position: '',
              top: '',
              left: '',
              width: '',
              height: '',
              display: ''
            }
          );
        },time)
      }
      else{
        overlay.style.display = 'none';
        sidebar.style.marginLeft = '';
        body.style.marginLeft = '';
        addStyles(
            sidebar,
            {
              marginLeft: '',
              position: '',
              top: '',
              left: '',
              width: '',
              height: '',
              display: ''
            }
        );
      }
    }
  }

  var openSidebar = function(){
    if(!(isOpen)){
      isOpen = true;

      if(animation){
        overlay.style[animationstring] = 'fadeInOverlay' + id + ' ' + time + 'ms ' + animationTimingFunction + ' 1 forwards';
        sidebar.style.marginLeft = '-' + width;

        body.style[animationstring] = 'openSidebar' + id + ' ' + time + 'ms ' + animationTimingFunction + ' 1 forwards';

        overlay.style.display = 'block';

      }else{
        sidebar.style.marginLeft = '0px';
        body.style.marginLeft = width;
        overlay.style.marginLeft = width;
      }
      addStyles(
          sidebar,
          {
            position: 'absolute',
            top: '0px',
            left: '0px',
            width: width,
            height: bodyHeight
          }
      );
      sidebar.style.display = 'block';
      overlay.style.display = 'block';

    }
  }

  // Assign click listener to overlay that closes the sidebar
  addEventListener(overlay, 'click', closeSidebar);

  var disposeListeners = function(){
    removeEventListener(overlay, 'click', closeSidebar);
    forEach(
      buttons,
      function(item, i){
        removeEventListener(item, 'click', toggleSidebar);
      }
    );
  }

  var toggleSidebar = function(){
    if (isOpen){
      closeSidebar();
    }else{
      openSidebar();
    }
  }

  // Sets listener that closes and opens sidebar for every button
  forEach(
    buttons,
    function(item, i){
      addEventListener(item, 'click', toggleSidebar);
    }
  );

  // Add class to identify sidebars
  addClass(sidebar, 'my-sidebar');

  this.close = closeSidebar;
  this.open = openSidebar;
  this.dispose = disposeListeners;

}

// addClass function
function addClass(element, className){
   if (element.classList)
    element.classList.add(className);
  else
    element.className += ' ' + className;
}

// event listener methods that adds support for old browsers
function addEventListener(el, eventName, handler) {
  if (el.addEventListener) {
    el.addEventListener(eventName, handler);
  } else if(el.attachEvent){
    el.attachEvent('on' + eventName, function(){
      handler.call(el);
    });
  }
}
function removeEventListener(el, eventName, handler) {
  if (el.removeEventListener)
    el.removeEventListener(eventName, handler);
  else if (el.detachEvent)
    el.detachEvent('on' + eventName, handler);
}

// forEach loop function to support old browsers
function forEach(array, fn) {
  for (i = 0; i < array.length; i++)
    fn(array[i], i);
}

// function to add styles fast and easy with object literal
function addStyles(element, styles){
  for (var i in styles) {
    if (styles.hasOwnProperty(i)) {
      element.style[i] = styles[i];
    }
  }
}
<commit_msg>Wraps Sidebar inside closure; Beautifies code.<commit_after>(function (global) {
  var objects = 0; // counts number of created objects to create unique @keyframes rules for CSS with the properly width
  function Sidebar(sidebar, buttons, options) {
    objects = objects + 1; // adds 1 for every created object
    var id = objects; // unique id for this object

    // Assign variables
    var sidebar = document.querySelectorAll(sidebar)[0];
    var buttons = document.querySelectorAll(buttons);
    var sidebar = sidebar;
    var buttons = buttons;
    var isOpen = false; // Declares state of sidebar
    var time = 1000;
    var width = '80%';
    var overlayStyles = {};
    var animationTimingFunction = 'ease-in';
    if (options !== undefined) {
      if (options.time !== undefined) {
        time = options.time;
      }
      if (options.width !== undefined) {
        width = options.width;
      }
      if (options.overlayStyles !== undefined) {
        overlayStyles = options.overlayStyles;
      }
      if (options.animationTimingFunction !== undefined) {
        animationTimingFunction = options.animationTimingFunction;
      }
    }

    // Creates overlay
    var bodyHeight = document.getElementsByTagName('body')[0].offsetHeight + 'px';
    var bodyWidth = document.getElementsByTagName('body')[0].offsetWidth + 'px';
    var overlayParent = document.createElement('div');
    overlayParent.setAttribute('class', 'overlay');

    addStyles(overlayParent, overlayStyles); // Sets customized styles
    // Sets absolute necessary styles to make sure everything works well
    addStyles(
      overlayParent, {
        display: 'none',
        position: 'absolute',
        top: '0px',
        left: '0px',
        height: bodyHeight,
        width: bodyWidth
      }
    );

    // Check if animations are supported in the calling browser to provide a fallback if not
    var animation = false,
      animationstring = 'animation',
      browserPrefix = '',
      domPrefixes = 'Webkit Moz O ms Khtml'.split(' '),
      pfx = '';

    if (sidebar.style.animationName !== undefined) {
      animation = true;
    }
    if (animation === false) {
      for (var i = 0; i < domPrefixes.length; i++) {
        if (sidebar.style[domPrefixes[i] + 'AnimationName'] !== undefined) {
          pfx = domPrefixes[i];
          animationstring = pfx + 'Animation';
          browserPrefix = '-' + pfx.toLowerCase() + '-';
          animation = true;
          break;
        }
      }
    }
    // Adds @Keyframes rules to styles
    var openSidebarKeyframes = '@' + browserPrefix + 'keyframes openSidebar' + id + ' { ' +
      'from {' + browserPrefix + 'transform:translateX( 0px ) }' +
      'to {' + browserPrefix + 'transform:translateX( ' + width + ' ) }' +
      '}';
    var closeSidebarKeyframes = '@' + browserPrefix + 'keyframes closeSidebar' + id + ' { ' +
      'from {' + browserPrefix + 'transform:translateX( 0px ) }' +
      'to {' + browserPrefix + 'transform:translateX( ' + width + ' ) }' +
      '}';

    var opacity = 0;
    if (overlayStyles.opacity !== undefined) {
      opacity = overlayStyles.opacity;
      overlayParent.style[browserPrefix + 'filter'] = "progid:DXImageTransform.Microsoft.Alpha(Opacity=" + opacity * 100 + ")"; // Support for IE 8

    }
    var fadeInKeyframes = '@' + browserPrefix + 'keyframes fadeInOverlay' + id + ' { ' +
      'from {' + browserPrefix + 'opacity: 0 }' +
      'to {' + browserPrefix + 'opacity: ' + opacity + '}' +
      '}';
    var fadeOutKeyframes = '@' + browserPrefix + 'keyframes fadeOutOverlay' + id + ' { ' +
      'from {' + browserPrefix + 'opacity: 0 }' +
      'to {' + browserPrefix + 'opacity: ' + opacity + ' }' +
      '}';
    if (animation) {
      var s = document.createElement('style');
      s.innerHTML = openSidebarKeyframes + ' ' + closeSidebarKeyframes + ' ' + fadeInKeyframes + ' ' + fadeOutKeyframes;
      document.getElementsByTagName('head')[0].appendChild(s);
    }

    // Adds overlay
    var body = document.getElementsByTagName('body')[0];
    var overlay = body.appendChild(overlayParent);

    // Method to close the sidebar
    var closeSidebar = function () {
      if (isOpen) { // if sidebar is already closed then don't close it again
        isOpen = false;
        if (animation) { // if animations are supported run @Keyframes
          overlay.style[animationstring] = 'fadeOutOverlay' + id + ' ' + time + 'ms ' + animationTimingFunction + ' 1 reverse both';
          body.style[animationstring] = 'closeSidebar' + id + ' ' + time + 'ms ' + animationTimingFunction + ' 1 reverse both';
          setTimeout(function () {

            overlay.style.display = 'none';
            addStyles(
              sidebar, {
                marginLeft: '',
                position: '',
                top: '',
                left: '',
                width: '',
                height: '',
                display: ''
              }
            );
          }, time)
        } else {
          overlay.style.display = 'none';
          sidebar.style.marginLeft = '';
          body.style.marginLeft = '';
          addStyles(
            sidebar, {
              marginLeft: '',
              position: '',
              top: '',
              left: '',
              width: '',
              height: '',
              display: ''
            }
          );
        }
      }
    }

    var openSidebar = function () {
      if (!(isOpen)) {
        isOpen = true;

        if (animation) {
          overlay.style[animationstring] = 'fadeInOverlay' + id + ' ' + time + 'ms ' + animationTimingFunction + ' 1 forwards';
          sidebar.style.marginLeft = '-' + width;

          body.style[animationstring] = 'openSidebar' + id + ' ' + time + 'ms ' + animationTimingFunction + ' 1 forwards';

          overlay.style.display = 'block';

        } else {
          sidebar.style.marginLeft = '0px';
          body.style.marginLeft = width;
          overlay.style.marginLeft = width;
        }
        addStyles(
          sidebar, {
            position: 'absolute',
            top: '0px',
            left: '0px',
            width: width,
            height: bodyHeight
          }
        );
        sidebar.style.display = 'block';
        overlay.style.display = 'block';

      }
    }

    // Assign click listener to overlay that closes the sidebar
    addEventListener(overlay, 'click', closeSidebar);

    var disposeListeners = function () {
      removeEventListener(overlay, 'click', closeSidebar);
      forEach(
        buttons,
        function (item, i) {
          removeEventListener(item, 'click', toggleSidebar);
        }
      );
    }

    var toggleSidebar = function () {
      if (isOpen) {
        closeSidebar();
      } else {
        openSidebar();
      }
    }

    // Sets listener that closes and opens sidebar for every button
    forEach(
      buttons,
      function (item, i) {
        addEventListener(item, 'click', toggleSidebar);
      }
    );

    // Add class to identify sidebars
    addClass(sidebar, 'my-sidebar');

    this.close = closeSidebar;
    this.open = openSidebar;
    this.dispose = disposeListeners;

  }

  // addClass function
  function addClass(element, className) {
    if (element.classList)
      element.classList.add(className);
    else
      element.className += ' ' + className;
  }

  // event listener methods that adds support for old browsers
  function addEventListener(el, eventName, handler) {
    if (el.addEventListener) {
      el.addEventListener(eventName, handler);
    } else if (el.attachEvent) {
      el.attachEvent('on' + eventName, function () {
        handler.call(el);
      });
    }
  }

  function removeEventListener(el, eventName, handler) {
    if (el.removeEventListener)
      el.removeEventListener(eventName, handler);
    else if (el.detachEvent)
      el.detachEvent('on' + eventName, handler);
  }

  // forEach loop function to support old browsers
  function forEach(array, fn) {
    for (i = 0; i < array.length; i++)
      fn(array[i], i);
  }

  // function to add styles fast and easy with object literal
  function addStyles(element, styles) {
    for (var i in styles) {
      if (styles.hasOwnProperty(i)) {
        element.style[i] = styles[i];
      }
    }
  }

  global.Sidebar = Sidebar;
})(window);
<|endoftext|>


<commit_before>// $ Musicmood
// $ Authors: John
// $ Created on: Wed Mar 30 03:16:24 UTC 2016
// - musicmood artista - música: Responde com uma cor e um sentimento
// - musicmood playlist mood: Responde uma playlist baseada em um sentimento
// - musicmood lista: Mostra todos os meus sentimentos

var Base = require('../src/base_module');

var Musicmood = function(bot) {
    Base.call(this, bot);

    this.hear(/musicmood lista$/i, (response) => {
        response.sendTyping();
        this.http({
                uri: "http://api.musicmood.me/moods"
            })
            .then((moods) => {
                response.send("Meus sentimentos: `" + JSON.parse(moods).moods.map((mood) => {
                    return mood.mood
                }).join("` `") + "`");
                response.send("Para pedir uma playlist: musicmood playlist `mood`");
            })
            .catch((err) => {
                response.send("Não achei meus sentimentos, desculpe :(");
            })
    });

    this.hear(/musicmood playlist (.+)$/i, (response) => {
        response.sendTyping();
        this.http({
                uri: "http://api.musicmood.me/get_playlist/" + response.match[1]
            })
            .then((playlist) => {
                var url = JSON.parse(playlist).playlist_url
                if (url) {
                    response.send(url);
                } else {
                    response.send("Não tenho esse sentimento, AINDA :). Tenta outro!");
                }
            })
            .catch((err) => {
                response.send("Não achei meus sentimentos, desculpe :(");
            })
    });

    this.rgbToHex = function(color) {
        return "#" + ((1 << 24) + (color[0] << 16) + (color[1] << 8) + color[2]).toString(16).slice(1);
    }

    this.hear(/musicmood (.+)$/i, (response) => {
        response.sendTyping();
        var query = response.match[1].split("-").map((part) => {
            return part.trim();
        });
        if (query && query.length === 2) {
            this.http({
                    uri: `http://api.musicmood.me/mood/${encodeURIComponent(query[0])}/${encodeURIComponent(query[1])}`
                })
                .then((mood) => {
                    var mood = JSON.parse(mood);
                    if (!mood.error) {
                        response.send(`Música    : ${query[0]} - ${query[1]}`);
                        response.send("Sentimento: `" + mood.mood + "`");
                        response.send("Cor       : `" + this.rgbToHex(mood.color) + "`");
                    } else {
                        response.send("Não achei essa música :(");
                    }
                })
                .catch((err) => {
                    response.send("Não achei meus sentimentos, desculpe :(");
                })
        } else {
            response.send("Por favor, envie assim: `Artista - Música` que eu faço minha mágica!");
        }
    });
};

Base.setup(Musicmood, 'Musicmood');
module.exports = Musicmood;
<commit_msg>Improve Musicmood help text<commit_after>// $ Musicmood
// $ Authors: John
// $ Created on: Wed Mar 30 03:16:24 UTC 2016
// - musicmood <artista> - <música>: Responde com uma cor e um sentimento
// - musicmood playlist <mood>: Responde uma playlist baseada em um sentimento
// - musicmood lista: Mostra todos os meus sentimentos

var Base = require('../src/base_module');

var Musicmood = function(bot) {
    Base.call(this, bot);

    this.hear(/musicmood lista$/i, (response) => {
        response.sendTyping();
        this.http({
                uri: "http://api.musicmood.me/moods"
            })
            .then((moods) => {
                response.send("Meus sentimentos: `" + JSON.parse(moods).moods.map((mood) => {
                    return mood.mood
                }).join("` `") + "`");
                response.send("Para pedir uma playlist: musicmood playlist `mood`");
            })
            .catch((err) => {
                response.send("Não achei meus sentimentos, desculpe :(");
            })
    });

    this.hear(/musicmood playlist (.+)$/i, (response) => {
        response.sendTyping();
        this.http({
                uri: "http://api.musicmood.me/get_playlist/" + response.match[1]
            })
            .then((playlist) => {
                var url = JSON.parse(playlist).playlist_url
                if (url) {
                    response.send(url);
                } else {
                    response.send("Não tenho esse sentimento, AINDA :). Tenta outro!");
                }
            })
            .catch((err) => {
                response.send("Não achei meus sentimentos, desculpe :(");
            })
    });

    this.rgbToHex = function(color) {
        return "#" + ((1 << 24) + (color[0] << 16) + (color[1] << 8) + color[2]).toString(16).slice(1);
    }

    this.hear(/musicmood (.+)$/i, (response) => {
        response.sendTyping();
        var query = response.match[1].split("-").map((part) => {
            return part.trim();
        });
        if (query && query.length === 2) {
            this.http({
                    uri: `http://api.musicmood.me/mood/${encodeURIComponent(query[0])}/${encodeURIComponent(query[1])}`
                })
                .then((mood) => {
                    var mood = JSON.parse(mood);
                    if (!mood.error) {
                        response.send(`Música    : ${query[0]} - ${query[1]}`);
                        response.send("Sentimento: `" + mood.mood + "`");
                        response.send("Cor       : `" + this.rgbToHex(mood.color) + "`");
                    } else {
                        response.send("Não achei essa música :(");
                    }
                })
                .catch((err) => {
                    response.send("Não achei meus sentimentos, desculpe :(");
                })
        } else {
            response.send("Por favor, envie assim: `Artista - Música` que eu faço minha mágica!");
        }
    });
};

Base.setup(Musicmood, 'Musicmood');
module.exports = Musicmood;
<|endoftext|>


<commit_before>var os = require('os');
var fs = require('fs');
var opcodes = require('./messages').opcodes;
var TFTPSocket = require('./tftp_socket').TFTPSocket;

var errors = {
  undefined: 0,
  notFound: 1,
  accessViolation: 2,
  diskFull: 3,
  illegalOperation: 4,
  unknownTransferID: 5,
  fileExists: 6,
  noSuchUser: 7
};

function Session(id, socket, destination) {
  var self = this;
  this.id = id;
  this.socket = new TFTPSocket(socket, destination);
  this.block = 1;
  this.buffer = [];
  this.lastMsgAt = -1;
  this.state = "";
  this.stream = undefined;

  this.initRead = function(msg) {
    console.log("%s: GET %s", self.id, msg.file);
    if(self.stream === undefined) {
      fs.stat(msg.file, function(error, stats) {
        if(error) {
          self.socket.sendError(errors.notFound, error.toString());
        } else if(!stats.isFile()) {
          self.socket.sendError(errors.notFound, msg.file + " is not a file");
        } else {
          initValidRead(msg.file);
        }
      });
    } else {
      self.socket.sendError(errors.illegalOperation, "Unexpected read request");
    }
  };

  var initValidRead = function(path) {
    self.state = "data_wait";
    var stream = fs.createReadStream(path, { flags: 'r', bufferSize: 512 });
    stream.on("data", function(data) {
      stream.pause();
      self.buffer.push(data);
      if(self.state === "data_wait") {
        sendUntilAcked(self.block, data, 0);
        self.state = "ack_wait";
      }
    });
    stream.on("error", function(error) {
      self.socket.sendError(errors.undefined, "Read error");
    });
    stream.on("end", function() {
      self.finished = true;
    });
    self.stream = stream;
  };

  var sendUntilAcked = function(block, data, errors) {
    if(block == self.block) {
      if(errors > 0) {
        console.log("%s: %d attempt to send block %d",
                    self.id, errors+1, block);
      }
      self.socket.sendData(block, data);
      if(errors < 5) {
        errors += 1;
        setTimeout(sendUntilAcked, errors*1000, block, data, errors);
      } else {
        console.log("%s: Block %d was never acked. Stopping transmission.",
                    self.id);
      }
    } // Else, block has been acked
  };

  this.continueRead = function(msg) {
    if(msg.block === self.block) {
      self.stream.resume();
      self.buffer.shift();
      self.block += 1;
      if(self.buffer.length === 0) {
        if(self.finished === true) {
          console.log("%s: File downloaded successfully!", self.id);
        } else {
          self.state = "data_wait";
        }
      } else {
        sendUntilAcked(self.block, self.buffer[0], 0);
      }
    } else {
      // Special case:
      // The file we sent was exactly 512*n bytes.
      // We must send an empty data package.
      if(msg.block === self.block-1 && self.finished &&
          self.buffer.length == 0) {
        sendUntilAcked(self.block, new Buffer(0), 0);
      } else {
        console.log("Waiting for ack for %d, but got for %d.",
                    self.block, ackedBlock);
      }
    }
  };

  this.initWrite = function(msg) {
    console.log("%s: PUT %s", self.id, msg.file);
    var stream = fs.createWriteStream(msg.file, { flags: 'w'});
    stream.on("error", function(error) {
      self.socket.sendError(errors.undefined, "Write error!")
    });
    stream.on("drain", function() {
      self.socket.sendAck(self.block);
      self.block += 1;
    });
    self.stream = stream;
    self.socket.sendAck(0);
  };

  this.continueWrite = function(msg) {
    if(msg.block == self.block) {
      if (self.stream.write(msg.data)) {
        self.socket.sendAck(self.block);
        self.block += 1;
      }
      if(msg.data.length < 512) {
        self.stream.end();
        console.log("%s: Write finished successfully!", self.id);
      }
    } else if(msg.block > self.block) {
      self.socket.sendError(errors.undefined, "Unexpected block number")
    } else if(msg.block < self.block) {
      self.socket.sendAck(msg.block); // Our ack may have been lost
    }
  };

  this.handleError = function(msg) {
    console.log("%s: Client reported error! Code: %d, msg: %s",
                self.id, msg.code, msg.message);
  };

}

Session.prototype.handleMessage = function(msg) {
  this.lastMsgAt = os.uptime();
  switch(msg.opcode) {
    case opcodes.read:
      this.initRead(msg);
      break;
    case opcodes.write:
      this.initWrite(msg);
      break;
    case opcodes.data:
      this.continueWrite(msg);
      break;
    case opcodes.ack:
      this.continueRead(msg);
      break;
    case opcodes.error:
      this.handleError(ession, msg);
      break;
    default:
      console.log("%s: Invalid opcode: %d", this.id, msg.opcode);
      break;
  }
};

Session.prototype.destroy = function() {
  if(this.stream !== undefined &&
      (this.stream.readable || this.stream.writable)) {
    this.stream.destroy();
  }
}

exports.Session = Session;
<commit_msg>fix: Forgotten param to console.log<commit_after>var os = require('os');
var fs = require('fs');
var opcodes = require('./messages').opcodes;
var TFTPSocket = require('./tftp_socket').TFTPSocket;

var errors = {
  undefined: 0,
  notFound: 1,
  accessViolation: 2,
  diskFull: 3,
  illegalOperation: 4,
  unknownTransferID: 5,
  fileExists: 6,
  noSuchUser: 7
};

function Session(id, socket, destination) {
  var self = this;
  this.id = id;
  this.socket = new TFTPSocket(socket, destination);
  this.block = 1;
  this.buffer = [];
  this.lastMsgAt = -1;
  this.state = "";
  this.stream = undefined;

  this.initRead = function(msg) {
    console.log("%s: GET %s", self.id, msg.file);
    if(self.stream === undefined) {
      fs.stat(msg.file, function(error, stats) {
        if(error) {
          self.socket.sendError(errors.notFound, error.toString());
        } else if(!stats.isFile()) {
          self.socket.sendError(errors.notFound, msg.file + " is not a file");
        } else {
          initValidRead(msg.file);
        }
      });
    } else {
      self.socket.sendError(errors.illegalOperation, "Unexpected read request");
    }
  };

  var initValidRead = function(path) {
    self.state = "data_wait";
    var stream = fs.createReadStream(path, { flags: 'r', bufferSize: 512 });
    stream.on("data", function(data) {
      stream.pause();
      self.buffer.push(data);
      if(self.state === "data_wait") {
        sendUntilAcked(self.block, data, 0);
        self.state = "ack_wait";
      }
    });
    stream.on("error", function(error) {
      self.socket.sendError(errors.undefined, "Read error");
    });
    stream.on("end", function() {
      self.finished = true;
    });
    self.stream = stream;
  };

  var sendUntilAcked = function(block, data, errors) {
    if(block == self.block) {
      if(errors > 0) {
        console.log("%s: %d attempt to send block %d",
                    self.id, errors+1, block);
      }
      self.socket.sendData(block, data);
      if(errors < 5) {
        errors += 1;
        setTimeout(sendUntilAcked, errors*1000, block, data, errors);
      } else {
        console.log("%s: Block %d was never acked. Stopping transmission.",
                    self.id, block);
      }
    } // Else, block has been acked
  };

  this.continueRead = function(msg) {
    if(msg.block === self.block) {
      self.stream.resume();
      self.buffer.shift();
      self.block += 1;
      if(self.buffer.length === 0) {
        if(self.finished === true) {
          console.log("%s: File downloaded successfully!", self.id);
        } else {
          self.state = "data_wait";
        }
      } else {
        sendUntilAcked(self.block, self.buffer[0], 0);
      }
    } else {
      // Special case:
      // The file we sent was exactly 512*n bytes.
      // We must send an empty data package.
      if(msg.block === self.block-1 && self.finished &&
          self.buffer.length == 0) {
        sendUntilAcked(self.block, new Buffer(0), 0);
      } else {
        console.log("Waiting for ack for %d, but got for %d.",
                    self.block, ackedBlock);
      }
    }
  };

  this.initWrite = function(msg) {
    console.log("%s: PUT %s", self.id, msg.file);
    var stream = fs.createWriteStream(msg.file, { flags: 'w'});
    stream.on("error", function(error) {
      self.socket.sendError(errors.undefined, "Write error!")
    });
    stream.on("drain", function() {
      self.socket.sendAck(self.block);
      self.block += 1;
    });
    self.stream = stream;
    self.socket.sendAck(0);
  };

  this.continueWrite = function(msg) {
    if(msg.block == self.block) {
      if (self.stream.write(msg.data)) {
        self.socket.sendAck(self.block);
        self.block += 1;
      }
      if(msg.data.length < 512) {
        self.stream.end();
        console.log("%s: Write finished successfully!", self.id);
      }
    } else if(msg.block > self.block) {
      self.socket.sendError(errors.undefined, "Unexpected block number")
    } else if(msg.block < self.block) {
      self.socket.sendAck(msg.block); // Our ack may have been lost
    }
  };

  this.handleError = function(msg) {
    console.log("%s: Client reported error! Code: %d, msg: %s",
                self.id, msg.code, msg.message);
  };

}

Session.prototype.handleMessage = function(msg) {
  this.lastMsgAt = os.uptime();
  switch(msg.opcode) {
    case opcodes.read:
      this.initRead(msg);
      break;
    case opcodes.write:
      this.initWrite(msg);
      break;
    case opcodes.data:
      this.continueWrite(msg);
      break;
    case opcodes.ack:
      this.continueRead(msg);
      break;
    case opcodes.error:
      this.handleError(ession, msg);
      break;
    default:
      console.log("%s: Invalid opcode: %d", this.id, msg.opcode);
      break;
  }
};

Session.prototype.destroy = function() {
  if(this.stream !== undefined &&
      (this.stream.readable || this.stream.writable)) {
    this.stream.destroy();
  }
}

exports.Session = Session;
<|endoftext|>


<commit_before>var http = require('http');
var agent = new (http.Agent)({maxSockets: 8});
var IncomingMessage = http.IncomingMessage;
var tls = require('tls');
var CRLF = '\r\n';
var CRLF_BUF = new Buffer(CRLF);
var MAX_BYTES = 1024 * 128;

function isCRLF(buffer, start) {
	   return buffer[start] ==  CRLF_BUF[0] && buffer[start + 1] == CRLF_BUF[1];
}

function getStringHeaders(req) {
	var headers = [req.method + ' ' + req.url + ' HTTP/' + req.httpVersion];
	if (req.headers && !req.headers.Host) {
		req.headers.Host = req.headers.host;
	}
	for (var key in req.headers) {
		var line = req.headers[key];
		if (Array.isArray(line)) {
			line.forEach(function(value) {
				headers.push(key + ': ' + value);
			});
		} else {
			headers.push(key + ': ' + line);
		}
	}
	headers.push(CRLF);

	return headers.join(CRLF);
}

function resolveResponse(proxyReq, callback) {
	var proxyRes = new IncomingMessage(proxyReq);
	var index = 0;
	var buffer, ended, done, over;

	function execCallback(err) {
		if (!done) {
			callback(err, proxyRes);
			done = true;
		}
	}

	function setHeaders(proxyRes, buffer) {
		var headers = {};
		buffer = buffer.toString().split(/\r\n/g);
		var firstLine = getEntry(buffer[0]);
		if (firstLine) {
			proxyRes.httpVersion = firstLine.key.split('/')[1];
			proxyRes.statusCode = firstLine.value;
		}

		for (var i = 1, len = buffer.length; i < len; i++) {
			var entry = getEntry(buffer[i]);
			if (entry) {
				var value = headers[entry.key];
				if (value) {
					if (!Array.isArray(value)) {
						value = [value];
					}
					value.push(entry.value);
				} else {
					headers[entry.key] = entry.value;
				}
			}
		}
		proxyRes.headers = headers;
		return proxyRes;
	}

	function getEntry(line) {
		if (line) {
			line = line.trim().split(/\s+/g);
			if (line[0] && line[1]) {
				line = {
						key: line[0].toLowerCase(),
						value: line[1]
				}
			} else {
				line = null;
			}
		}

		return line;
	}

   proxyReq.on('data', function(data) {
	   if (ended) {
		   proxyRes.push(data);
		   return;
	   }

	   buffer = buffer ? Buffer.concat([buffer, data]) : data;
	   while(buffer[index]) {
		   if (isCRLF(buffer, index)) {
			   ended = isCRLF(buffer, index + 2);
			   if (!over) {
				   over = buffer.length >= MAX_BYTES;
				   if (ended) {
					   index += 4;
					   setHeaders(proxyRes, buffer.slice(0, index));
					   execCallback();
					   buffer = buffer.slice(index);
					   index = 0;
				   }
			   } else if (ended) {
				   proxyRes.push(buffer.slice(index + 4));
			   }
		   }
		   index++;
	   }

   });

   proxyReq.on('end', function() {
	   execCallback(new Error('Bad response'));
	   proxyRes.push(null);
   });
}

module.exports = function proxy(req, callback) {
	var headers = req.headers;
	var options = req.options;
	var done;

	function execCallback(err, proxyRes) {
		if (!done) {
			callback && callback(err, proxyRes);
			done = true;
		}
	}

	options.method = 'CONNECT';
	options.path = headers.host;
	if (options.agent == null) {
		options.agent = agent;
	}
	options.headers = {
			host : headers.host,
			'proxy-connection': 'keep-alive',
			'user-agent': headers['user-agent']
		};

	http.request(options).on('error', execCallback)
	.on('connect', function (res, socket, head) {
		socket.on('error', execCallback);
	    var proxyReq = tls.connect({
	        rejectUnauthorized: false,
	        socket: socket
	    }, function () {
	        proxyReq.write(getStringHeaders(req));
	        req.pipe(proxyReq);
	    }).on('error', execCallback);

	 resolveResponse(proxyReq, execCallback);
	}).end();
};

<commit_msg>https proxy<commit_after>var http = require('http');
var agent = new (http.Agent)({maxSockets: 8});
var IncomingMessage = http.IncomingMessage;
var tls = require('tls');
var CRLF = '\r\n';
var CRLF_BUF = new Buffer(CRLF);
var MAX_BYTES = 1024 * 128;

function isCRLF(buffer, start) {
	   return buffer[start] ==  CRLF_BUF[0] && buffer[start + 1] == CRLF_BUF[1];
}

function getStringHeaders(req) {
	var headers = [req.method + ' ' + req.url + ' HTTP/' + req.httpVersion];
	if (req.headers && !req.headers.Host) {
		req.headers.Host = req.headers.host;
	}
	for (var key in req.headers) {
		var line = req.headers[key];
		if (Array.isArray(line)) {
			line.forEach(function(value) {
				headers.push(key + ': ' + value);
			});
		} else {
			headers.push(key + ': ' + line);
		}
	}
	headers.push(CRLF);

	return headers.join(CRLF);
}

function resolveResponse(proxyReq, callback) {
	var proxyRes = new IncomingMessage(proxyReq);
	var index = 0;
	var buffer, ended, done, over;

	function execCallback(err) {
		if (!done) {
			callback(err, proxyRes);
			done = true;
		}
	}

	function setHeaders(proxyRes, buffer) {
		var headers = {};
		buffer = buffer.toString().split(/\r\n/g);
		var firstLine = getEntry(buffer[0]);
		if (firstLine) {
			proxyRes.httpVersion = firstLine.key.split('/')[1];
			proxyRes.statusCode = firstLine.value;
		}

		for (var i = 1, len = buffer.length; i < len; i++) {
			var entry = getEntry(buffer[i]);
			if (entry) {
				var value = headers[entry.key];
				if (value) {
					if (!Array.isArray(value)) {
						value = [value];
					}
					value.push(entry.value);
				} else {
					headers[entry.key] = entry.value;
				}
			}
		}
		proxyRes.headers = headers;
		return proxyRes;
	}

	function getEntry(line) {
		if (line) {
			line = line.trim().split(/\s+/g);
			if (line[0] && line[1]) {
				line = {
						key: line[0].toLowerCase(),
						value: line[1]
				}
			} else {
				line = null;
			}
		}

		return line;
	}

   proxyReq.on('data', function(data) {
	   if (ended) {
		   proxyRes.push(data);
		   return;
	   }

	   buffer = buffer ? Buffer.concat([buffer, data]) : data;
	   while(buffer[index]) {
		   if (isCRLF(buffer, index)) {
			   ended = isCRLF(buffer, index + 2);
			   if (!over) {
				   over = buffer.length >= MAX_BYTES;
				   if (ended) {
					   index += 4;
					   setHeaders(proxyRes, buffer.slice(0, index));
					   execCallback();
					   buffer = buffer.slice(index);
					   index = 0;
				   }
			   } else if (ended) {
				   proxyRes.push(buffer.slice(index + 4));
			   }
		   }
		   index++;
	   }

   });

   proxyReq.on('end', function() {
	   execCallback(new Error('Bad response'));
	   proxyRes.push(null);
   });
}

module.exports = function proxy(req, callback) {
	var headers = req.headers;
	var options = req.options;
	var done;

	function execCallback(err, proxyRes) {
		if (!done) {
			callback && callback(err, proxyRes);
			done = true;
		}
	}

	options.method = 'CONNECT';
	options.path = headers.host;
	if (options.agent == null) {
		options.agent = agent;
	}
	options.headers = {
			host : headers.host,
			'proxy-connection': 'keep-alive',
			'user-agent': headers['user-agent']
		};

	http.request(options).on('error', execCallback)
	.on('connect', function (res, socket, head) {
		socket.on('error', execCallback);
	    var proxyReq = tls.connect({
	        rejectUnauthorized: false,
	        socket: socket
	    }, function () {
	        proxyReq.write(getStringHeaders(req));
	        req.on('data', function(data) {
	        	proxyReq.write(data);
	        });
	        req.on('end', function() {
	        	proxyReq.end();
	        });
	    }).on('error', execCallback);

	 resolveResponse(proxyReq, execCallback);
	}).end();
};

<|endoftext|>


<commit_before>/**
 * Created by petermares on 27/04/2016.
 */

module.exports = TimerControl;

function TimerControl() {
    this.timers = {};
}

TimerControl.prototype.startTimer = function(id, data, timeout, cb, context) {
    var timerFn = context ? vertx.setTimer.bind(context) : vertx.setTimer;

    this.timers[id] = {
        id: id,
        data: data,
        cb: cb,
        timerID: timerFn(timeout, function(tID) {
            if ( cb ) {
                var o = this.timers[id];
                o.reason = 'timeout';
                delete this.timers[id];
                cb(o);
            }
        })
    }
};

TimerControl.prototype.cancelTimer = function(id) {
    if ( this.timers[id] ) {
        var o = this.timers[id];

        vertx.cancelTimer(o.timerID);
        delete this.timers[id];
        o.reason = 'cancelled';
        o.cb(o);
    }
}<commit_msg>Fixed context issues in timercontrol<commit_after>/**
 * Created by petermares on 27/04/2016.
 */

module.exports = TimerControl;

function TimerControl() {
    this.timers = {};
}

TimerControl.prototype.startTimer = function(id, data, timeout, cb, context) {
    var _this = this;
    var timerFn = context ? vertx.setTimer.bind(context) : vertx.setTimer;

    this.timers[id] = {
        id: id,
        data: data,
        cb: cb,
        timerID: timerFn(timeout, function(tID) {
            if ( cb ) {
                var o = _this.timers[id];
                o.reason = 'timeout';
                delete _this.timers[id];
                cb(o);
            }
        })
    }
};

TimerControl.prototype.cancelTimer = function(id) {
    if ( this.timers[id] ) {
        var o = this.timers[id];

        vertx.cancelTimer(o.timerID);
        delete this.timers[id];
        o.reason = 'cancelled';
        o.cb(o);
    }
}<|endoftext|>


<commit_before>'use strict'
const Bluebird = require('bluebird')
const concat = require('concat-stream')
const iconv = require('iconv-lite')
const pumpCB = require('pump')
const Transform = require('readable-stream').Transform

const normalizeHtml = use('normalize-html')

class ParseRTF extends Transform {
  constructor () {
    super({objectMode: true})
    this.charset = 'ascii' // 'windows-1252'
    this.char = null
    this.path = []
    this.ctx = null
    this.buffer = ''
    this.cmd = ''
    this.arg = ''
    this.parserState = this.parserGeneral
    this.uc = 1
  }
  _transform (buf, encoding, done) {
    const text = buf.toString('ascii')
    for (let ii = 0; ii < text.length; ++ii) {
      this.parserState(text[ii])
    }
    done()
  }
  _flush (done) {
    this.endStr()
    this.ctx = this.path[0] || this.ctx
    if (this !== this.ctx && this.ctx) {
      this.push(this.ctx)
    }
    done()
  }
  parserGeneral (char) {
    if (char === '{') {
      this.endStr()
      this.path.push(this.ctx)
      if (this.ctx == null) {
        this.ctx = this
      } else {
        this.ctx = doesPushable({command: 'group', args: []})
        this.ctx.uc = this.uc
      }
    } else if (char === '}') {
      this.endStr()
      const finished = this.ctx
      this.ctx = this.path.pop()
      if (this.ctx) this.ctx.push(finished)
    } else if (char === '\\') {
      this.parserState = this.parserCmd0
    } else {
      if (char !== '\n') this.buffer += char
    }
  }
  parserCmd0 (char) {
    if (char === '\n') {
      this.endStr()
      this.ctx.push({command: 'fakeParagraph', args: []})
      this.parserState = this.parserGeneral
    } else if (char === '\'') {
      this.parserState = this.parserHexCharArgs
      this.arg = ''
    } else {
      this.cmd = char
      this.parserState = this.parserCmd1
    }
  }
  parserHexCharArgs (char) {
    this.arg += char
    if (this.arg.length === 2) {
      this.buffer += iconv.decode(new Buffer(this.arg, 'hex'), this.charset)
      this.parserState = this.parserGeneral
    }
  }
  parserCmd1 (char) {
    if (/^[a-zA-Z*]$/.test(char)) {
      this.cmd += char
    } else {
      this.arg = ''
      this.parserState = this.parserCmd2
      this.parserState(char)
    }
  }
  parserCmd2 (char) {
    if (/^[-0-9]$/.test(char)) {
      this.arg += char
    } else {
      if (this.cmd === 'ansicpg') {
        this.charset = 'windows-' + this.arg
      } else if (this.cmd === 'u') {
        var charBuf = Buffer.alloc ? Buffer.alloc(2) : new Buffer(2)
        charBuf.writeUInt16LE(Number(this.arg), 0)
        this.buffer += iconv.decode(charBuf, 'ucs2')
        if (this.ctx.uc) return this.skipChars(this.ctx.uc)
      } else {
        this.endStr()
        this.ctx.push({command: this.cmd, args: [this.arg]})
      }
      this.parserState = this.parserGeneral
      if (char !== ' ') return this.parserState(char)
    }
  }
  skipChars (num) {
    if (num > 1) {
      this.parserState = () => this.skipChars(num - 1)
    } else {
      this.parserState = this.parserGeneral
    }
  }

  endStr () {
    if (this.buffer === '') return
    (this.ctx || this).push({command: 'text', args: [this.buffer]})
    this.buffer = ''
  }
}

const Pushable = {
  push (value) {
    this.args.push(value)
  }
}

function doesPushable (obj) {
  return Object.assign(Object.create(Pushable), obj)
}

// We assume formatting expressions are paired as they have to be in HTML.
// They don't in RTF, so it's entirely possible for valid RTF to produce weird HTML.
// This will likely be cleaned up by the parse/serialize step, but still.
const htmlExpression = {
  group: (exp, state, content) => {
    let group = exp.args.shift()
    if (group.command === '*') group = exp.args.shift()
//    console.log('STARTING', group, exp.args)
    return evaluate(group, state, exp.args.map(arg => evaluate(arg, state)))
  },
  fakeParagraph: (exp, state) => {
    if (!state.inPara) return ''
    state.inPara = false
    return '
\n'
  },
  text: (exp) => {
    return exp.args[0]
  },
  b: (exp) => {
    if (exp.args[0] === '0') {
      return ''
    } else {
      return ''
    }
  },
  i: (exp) => {
    if (exp.args[0] === '0') {
      return ''
    } else {
      return ''
    }
  },
  ul: (exp) => {
    if (exp.args[0] === '0') {
      return ''
    } else {
      return ''
    }
  },
  ulnone: (exp) => {
    return ''
  },
  field: (exp, state, content) => {
    const args = []
    for (let item of content) {
      if (Array.isArray(item)) {
        args.push.apply(args, item)
      } else {
        args.push(item)
      }
    }
    const matchHyperlink = /HYPERLINK "(.*)"/
    const href = args[0].match(matchHyperlink)
    if (href) {
      return `<a href="${href[1]}">${args.slice(1).join('')}</a>`
    } else {
      process.emit('debug', 'FIELD', args)
      return args[1]
    }
  },
  '*': (exp, state, content) => {
    return content.filter(c => c !== '')
  },
  fldinst: (exp, state, content) => {
    return ''
  },
  fldrslt: (exp, state, content) => {
    return content.filter(c => c !== '')
  },
  fs: (exp, state) => {
    state.style.size = Number(exp.args[0]) / 2
    return ''
  },
  pard: (exp, state) => {
    state.style = Object.assign({}, state.defaultStyle)
    return ''
  },
  qc: (exp, state) => {
    state.style.align = 'center'
    return ''
  },
  ql: (exp, state) => {
    state.style.align = 'left'
    return ''
  },
  qr: (exp, state) => {
    state.style.align = 'right'
    return ''
  },
  qj: (exp, state) => {
    state.style.align = 'justify'
    return ''
  },
/*
  cf: exp => {
// foreground color
    return ''
  },
  cb: exp => {
// background color
    return ''
  },
  fonttbl: exp => '',
  colortbl: exp => '',
  expandedcolortbl: exp => ''
*/
}

function evaluate (exp, state, content) {
//  console.log('EVALUATE', exp.command, !!htmlExpression[exp.command])
  const result =  htmlExpression[exp.command] && htmlExpression[exp.command](exp, state, content)
  return result
}

class ToHTML extends Transform {
  constructor () {
    super({objectMode: true})
    this.inPara = null
    this.defaultStyle = {
      size: 10,
      align: 'left'
    }
    this.style = Object.assign({}, this.defaultStyle)
    this.lastStyle = this.styleStr()
  }
  styleStr () {
    let style = []
    if (this.style.size !== this.defaultStyle.size) {
      const em = this.style.size / this.defaultStyle.size
      style.push(`font-size: ${em}em;`)
    }
    if (this.style.align !== this.defaultStyle.align) {
      style.push(`text-align: ${this.style.align};`)
    }
    if (!style.length) return ''
    return ` style="${style.join(' ')}"`
  }
  _transform (exp, encoding, done) {
    const inPara = this.inPara
    const result = evaluate(exp, this)
    if (result) {
      let startP = ''
      if (!inPara) {
        this.inPara = true
        this.lastStyle = this.styleStr()
        startP = `<p${this.lastStyle}>`
      }
      if (/S/.test(result) && this.lastStyle !== this.styleStr()) {
        this.lastStyle = this.styleStr()
        this.push(`<span${this.lastStyle}>${startP}${result}</span>`)
      } else {
        this.push(`${startP}${result}`)
      }
    } else {
      if (result == null) process.emit('debug', exp)
    }
    done()
  }
  _flush (done) {
    if (this.inPara) this.push('
\n')
    done()
  }
}

module.exports = function (rtf) {
  return Bluebird.resolve(rtf).then(rtf => {
    var parser = new ParseRTF()
    return new Bluebird((resolve, reject) => {
      pumpCB(
        parser,
        new ToHTML(),
        concat(data => {
          const emptyInlineTags = /<(em|strong|span)[^>]*>(\s*)<[/]\1>/g
          const html = normalizeHtml(data).replace(emptyInlineTags, '$2')
          resolve(html)
        }),
        err => err && reject(err))
      parser.end(rtf)
    })
  })
}
<commit_msg>rtf-to-html: Fix a bug that resulted in inline tyles not always being included<commit_after>'use strict'
const Bluebird = require('bluebird')
const concat = require('concat-stream')
const iconv = require('iconv-lite')
const pumpCB = require('pump')
const Transform = require('readable-stream').Transform

const normalizeHtml = use('normalize-html')

class ParseRTF extends Transform {
  constructor () {
    super({objectMode: true})
    this.charset = 'ascii' // 'windows-1252'
    this.char = null
    this.path = []
    this.ctx = null
    this.buffer = ''
    this.cmd = ''
    this.arg = ''
    this.parserState = this.parserGeneral
    this.uc = 1
  }
  _transform (buf, encoding, done) {
    const text = buf.toString('ascii')
    for (let ii = 0; ii < text.length; ++ii) {
      this.parserState(text[ii])
    }
    done()
  }
  _flush (done) {
    this.endStr()
    this.ctx = this.path[0] || this.ctx
    if (this !== this.ctx && this.ctx) {
      this.push(this.ctx)
    }
    done()
  }
  parserGeneral (char) {
    if (char === '{') {
      this.endStr()
      this.path.push(this.ctx)
      if (this.ctx == null) {
        this.ctx = this
      } else {
        this.ctx = doesPushable({command: 'group', args: []})
        this.ctx.uc = this.uc
      }
    } else if (char === '}') {
      this.endStr()
      const finished = this.ctx
      this.ctx = this.path.pop()
      if (this.ctx) this.ctx.push(finished)
    } else if (char === '\\') {
      this.parserState = this.parserCmd0
    } else {
      if (char !== '\n') this.buffer += char
    }
  }
  parserCmd0 (char) {
    if (char === '\n') {
      this.endStr()
      this.ctx.push({command: 'fakeParagraph', args: []})
      this.parserState = this.parserGeneral
    } else if (char === '\'') {
      this.parserState = this.parserHexCharArgs
      this.arg = ''
    } else {
      this.cmd = char
      this.parserState = this.parserCmd1
    }
  }
  parserHexCharArgs (char) {
    this.arg += char
    if (this.arg.length === 2) {
      this.buffer += iconv.decode(new Buffer(this.arg, 'hex'), this.charset)
      this.parserState = this.parserGeneral
    }
  }
  parserCmd1 (char) {
    if (/^[a-zA-Z*]$/.test(char)) {
      this.cmd += char
    } else {
      this.arg = ''
      this.parserState = this.parserCmd2
      this.parserState(char)
    }
  }
  parserCmd2 (char) {
    if (/^[-0-9]$/.test(char)) {
      this.arg += char
    } else {
      if (this.cmd === 'ansicpg') {
        this.charset = 'windows-' + this.arg
      } else if (this.cmd === 'u') {
        var charBuf = Buffer.alloc ? Buffer.alloc(2) : new Buffer(2)
        charBuf.writeUInt16LE(Number(this.arg), 0)
        this.buffer += iconv.decode(charBuf, 'ucs2')
        if (this.ctx.uc) return this.skipChars(this.ctx.uc)
      } else {
        this.endStr()
        this.ctx.push({command: this.cmd, args: [this.arg]})
      }
      this.parserState = this.parserGeneral
      if (char !== ' ') return this.parserState(char)
    }
  }
  skipChars (num) {
    if (num > 1) {
      this.parserState = () => this.skipChars(num - 1)
    } else {
      this.parserState = this.parserGeneral
    }
  }

  endStr () {
    if (this.buffer === '') return
    (this.ctx || this).push({command: 'text', args: [this.buffer]})
    this.buffer = ''
  }
}

const Pushable = {
  push (value) {
    this.args.push(value)
  }
}

function doesPushable (obj) {
  return Object.assign(Object.create(Pushable), obj)
}

// We assume formatting expressions are paired as they have to be in HTML.
// They don't in RTF, so it's entirely possible for valid RTF to produce weird HTML.
// This will likely be cleaned up by the parse/serialize step, but still.
const htmlExpression = {
  group: (exp, state, content) => {
    let group = exp.args.shift()
    if (group.command === '*') group = exp.args.shift()
//    console.log('STARTING', group, exp.args)
    return evaluate(group, state, exp.args.map(arg => evaluate(arg, state)))
  },
  fakeParagraph: (exp, state) => {
    if (!state.inPara) return ''
    state.inPara = false
    return '
\n'
  },
  text: (exp) => {
    return exp.args[0]
  },
  b: (exp) => {
    if (exp.args[0] === '0') {
      return ''
    } else {
      return ''
    }
  },
  i: (exp) => {
    if (exp.args[0] === '0') {
      return ''
    } else {
      return ''
    }
  },
  ul: (exp) => {
    if (exp.args[0] === '0') {
      return ''
    } else {
      return ''
    }
  },
  ulnone: (exp) => {
    return ''
  },
  field: (exp, state, content) => {
    const args = []
    for (let item of content) {
      if (Array.isArray(item)) {
        args.push.apply(args, item)
      } else {
        args.push(item)
      }
    }
    const matchHyperlink = /HYPERLINK "(.*)"/
    const href = args[0].match(matchHyperlink)
    if (href) {
      return `<a href="${href[1]}">${args.slice(1).join('')}</a>`
    } else {
      process.emit('debug', 'FIELD', args)
      return args[1]
    }
  },
  '*': (exp, state, content) => {
    return content.filter(c => c !== '')
  },
  fldinst: (exp, state, content) => {
    return ''
  },
  fldrslt: (exp, state, content) => {
    return content.filter(c => c !== '')
  },
  fs: (exp, state) => {
    state.style.size = Number(exp.args[0]) / 2
    return ''
  },
  pard: (exp, state) => {
    state.style = Object.assign({}, state.defaultStyle)
    return ''
  },
  qc: (exp, state) => {
    state.style.align = 'center'
    return ''
  },
  ql: (exp, state) => {
    state.style.align = 'left'
    return ''
  },
  qr: (exp, state) => {
    state.style.align = 'right'
    return ''
  },
  qj: (exp, state) => {
    state.style.align = 'justify'
    return ''
  },
/*
  cf: exp => {
// foreground color
    return ''
  },
  cb: exp => {
// background color
    return ''
  },
  fonttbl: exp => '',
  colortbl: exp => '',
  expandedcolortbl: exp => ''
*/
}

function evaluate (exp, state, content) {
//  console.log('EVALUATE', exp.command, !!htmlExpression[exp.command])
  const result =  htmlExpression[exp.command] && htmlExpression[exp.command](exp, state, content)
  return result
}

class ToHTML extends Transform {
  constructor () {
    super({objectMode: true})
    this.inPara = null
    this.defaultStyle = {
      size: 10,
      align: 'left'
    }
    this.style = Object.assign({}, this.defaultStyle)
    this.lastStyle = this.styleStr()
  }
  styleStr () {
    let style = []
    if (this.style.size !== this.defaultStyle.size) {
      const em = this.style.size / this.defaultStyle.size
      style.push(`font-size: ${em}em;`)
    }
    if (this.style.align !== this.defaultStyle.align) {
      style.push(`text-align: ${this.style.align};`)
    }
    if (!style.length) return ''
    return ` style="${style.join(' ')}"`
  }
  _transform (exp, encoding, done) {
    const inPara = this.inPara
    const result = evaluate(exp, this)
    if (result) {
      let startP = ''
      if (!inPara) {
        this.inPara = true
        this.lastStyle = this.styleStr()
        startP = `<p${this.lastStyle}>`
      }
      if (/\S/.test(result) && this.lastStyle !== this.styleStr()) {
        this.lastStyle = this.styleStr()
        this.push(`<span${this.lastStyle}>${startP}${result}</span>`)
      } else {
        this.push(`${startP}${result}`)
      }
    } else {
      if (result == null) process.emit('debug', exp)
    }
    done()
  }
  _flush (done) {
    if (this.inPara) this.push('
\n')
    done()
  }
}

module.exports = function (rtf) {
  return Bluebird.resolve(rtf).then(rtf => {
    var parser = new ParseRTF()
    return new Bluebird((resolve, reject) => {
      pumpCB(
        parser,
        new ToHTML(),
        concat(data => {
          const emptyInlineTags = /<(em|strong|span)[^>]*>(\s*)<[/]\1>/g
          const html = normalizeHtml(data).replace(emptyInlineTags, '$2')
          resolve(html)
        }),
        err => err && reject(err))
      parser.end(rtf)
    })
  })
}
<|endoftext|>


<commit_before>describe('render', function(){
  var global = (function(){ return this; }());

  it('adds a bound() function to the global scope', function(){
    expect(global.bound).toEqual(jasmine.any(Function));
  });

  it('adds a .bound() method to jQuery objects', function(){
    expect($('

').bound).toEqual(jasmine.any(Function));
  });

  it('can add html to a node', function(){
    var $node = $('

');
    $node.bound({name: 'alice'});
    expect($node.html()).toEqual('alice');
  });

  it('falls back onto the global scope for keys that are not found on the input', function(){
    global.age = '30';
    var $node = $('

');
    $node.bound({name: 'alice'});
    expect($node.html()).toEqual('30');
    delete global.age;
  });

  it('does not remove a directive attribute after following it', function(){
    var $node = $('

');
    $node.bound({name: 'alice'});
    expect($node.attr('contents')).toEqual('name');
  });
});
<commit_msg>Changed jQuery function from .bound to .boundRender()<commit_after>describe('render', function(){
  var global = (function(){ return this; }());

  it('adds a bound() function to the global scope', function(){
    expect(global.bound).toEqual(jasmine.any(Function));
  });

  it('adds a .boundRender() method to jQuery objects', function(){
    expect($('

').bound).toEqual(jasmine.any(Function));
  });

  it('can add html to a node', function(){
    var $node = $('

');
    $node.bound({name: 'alice'});
    expect($node.html()).toEqual('alice');
  });

  it('falls back onto the global scope for keys that are not found on the input', function(){
    global.age = '30';
    var $node = $('

');
    $node.bound({name: 'alice'});
    expect($node.html()).toEqual('30');
    delete global.age;
  });

  it('does not remove a directive attribute after following it', function(){
    var $node = $('

');
    $node.bound({name: 'alice'});
    expect($node.attr('contents')).toEqual('name');
  });
});
<|endoftext|>