<commit_before>/*
 * Copyright 2014 Next Century Corporation
 * 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.
 *
 */

// Note: There is a single instance of an event bus shared by all messengers

/**
 * Creates the event bus to be used for communication between widgets. If running outside of OWF,
 * If in OWF, a {{#crossLink "neon.eventing.EventBus"}}{{/crossLink}} is returned. If running inside of OWF, a
 * {{#crossLink "neon.eventing.owf.OWFEventBus"}}{{/crossLink}} is returned.
 * @return {neon.eventing.EventBus | neon.eventing.owf.OWFEventBus}
 * @private
 * @method createEventBus_
 */
neon.eventing.createEventBus_ = function() {
    if(neon.util.owfUtils.isRunningInOWF()) {
        return new neon.eventing.owf.OWFEventBus();
    }
    return new neon.eventing.EventBus(this.id_);
};

/**
 * The event bus used for communication between widgets
 * @property eventBus_
 * @type {neon.eventing.EventBus|neon.eventing.owf.OWFEventBus}
 * @private
 */
neon.eventing.eventBus_ = neon.eventing.createEventBus_();

/**
 * A messenger is a widget's gateway to the event bus used for communication between widgets. Widgets publish and
 * subscribe to messages through a messenger. Each messenger has a unique id to identify which widget published the
 * message to ensure that widgets are not receiving their own messages to facilitate simpler widget code.
 * @class neon.eventing.Messenger
 * @constructor
 */

neon.eventing.Messenger = function() {
    this.id_ = uuid.v4();
};

/**
 * Publishes a message to a channel
 * @param {String} channel The channel to publish the message to
 * @param {Object} message The message to publish
 * @method publish
 */
neon.eventing.Messenger.prototype.publish = function(channel, message) {
    //var data = { payload: message, sender: this.id_ };
    neon.eventing.eventBus_.publish(channel, message, this.id_);
};

/**
 * Subscribes to the channel to receives any messages published to it. Note that messengers will not receive their
 * own messages even if they are subscribed to that channel.
 * @param {String} channel The channel to subscribe to
 * @param {Function} callback The callback to invoke when a message is received on the channel. The function takes
 * @param {Object|String} callback.message the message that was published
 * @method subscribe
 */
neon.eventing.Messenger.prototype.subscribe = function(channel, callback) {
    neon.eventing.eventBus_.subscribe(channel, callback, this.id_);
};

/**
 * Unsubscribes this messenger from the channel
 * @param {String} channel The channel to unsubscribe from
 * @method unsubscribe
 */
neon.eventing.Messenger.prototype.unsubscribe = function(channel) {
    neon.eventing.eventBus_.unsubscribe(channel, this.id_);
};

/**
 * Subscribe to events notifications
 * @param callbacks {object} An object containing callback functions. Each function takes one
 * parameter, the message that was published.
 *

 *
selectionChanged - function to execute when the selection has changed

 *
filtersChanged - function to execute when the filters have been changed

 *
activeDatasetChanged - function to execute when the active dataset has changed

 *

 * @method events
 */
neon.eventing.Messenger.prototype.events = function(callbacks) {
    var me = this;
    var globalChannelConfigs = this.createGlobalChannelSubscriptions_(callbacks);
    _.each(globalChannelConfigs, function(channelConfig) {
        me.subscribe(channelConfig.channel, function(payload) {
                if(channelConfig.callback && typeof channelConfig.callback === 'function') {
                    channelConfig.callback(payload);
                }
            }
        );
    });
};

/**
 * Unsubscribe from all events:  selection changes, filters changes, active dataset changed.
 * @method removeEvents
 */
neon.eventing.Messenger.prototype.removeEvents = function() {
    var me = this;
    var globalChannelConfigs = this.createGlobalChannelSubscriptions_({});
    _.each(globalChannelConfigs, function(channelConfig) {
        me.unsubscribe(channelConfig.channel);
    });
};

neon.eventing.Messenger.prototype.createGlobalChannelSubscriptions_ = function(neonCallbacks) {
    // some of the callbacks may be null/undefined, which is ok since they will be ignored
    return [
        {
            channel: neon.eventing.channels.SELECTION_CHANGED,
            callback: neonCallbacks.selectionChanged
        },
        {
            channel: neon.eventing.channels.FILTERS_CHANGED,
            callback: neonCallbacks.filtersChanged
        },
        {
            channel: neon.eventing.channels.CONNECT_TO_HOST,
            callback: neonCallbacks.connectToHost
        }
    ];
};

/**
 * Adds a filter to the data being viewed, so only data matching that filter is returned from queries. This will
 * fire a filter changed event to notify other widgets that the filters have changed. If a filter with this id
 * already exists, the filter is appended to that one with an AND clause. To replace the filter entirely, use
 * {{#crossLink "neon.eventing.Messenger/replaceFilter:method"}}{{/crossLink}}.
 * @param {String} id The id of the filter to apply. A typical value to use for the id is the return value of
 * {{#crossLink "neon.widget/getInstanceId:method"}}{{/crossLink}}
 * @param {neon.query.Filter} filter The filter to add
 * @param {Function} successCallback The callback to invoke when the filter is added
 * @param {Function} errorCallback The callback to invoke if an error occurs.
 * @method addFilter
 */
neon.eventing.Messenger.prototype.addFilter = function(id, filter, successCallback, errorCallback) {
    var filterKey = this.createFilterKey_(id, filter);
    return neon.util.ajaxUtils.doPostJSON(
        filterKey,
        neon.serviceUrl('filterservice', 'addfilter'),
        {
            success: this.createChannelCallback_(neon.eventing.channels.FILTERS_CHANGED, successCallback),
            error: errorCallback
        }
    );
};

/**
 * Removes a previously added filter. This will fire a filter changed event to notify other widgets that the filters
 * have changed.
 * @param {String} id The id of the filter to remove. This will be the same id used to create the filter.
 * @param {Function} successCallback The callback to invoke when the filter is removed
 * @param {Function} errorCallback The callback to invoke if an error occurs.
 * @method removeFilter
 */
neon.eventing.Messenger.prototype.removeFilter = function(id, successCallback, errorCallback) {
    return neon.util.ajaxUtils.doPost(
        neon.serviceUrl('filterservice', 'removefilter'),
        {
            success: this.createChannelCallback_(neon.eventing.channels.FILTERS_CHANGED, successCallback),
            error: errorCallback,
            data: id,
            contentType: 'text/plain',
            responseType: 'json'
        }
    );
};

/**
 * Replaces a previously added filter. This is a similar to calling remove and then add, but does so with one
 * notification event.
 * @param {String} id The id of the filter to replace.
 * @param {neon.query.Filter} filter The filter to replace the old one with
 * @param {Function} successCallback The callback to invoke when the filter is replaced
 * @param {Function} errorCallback The callback to invoke if an error occurs.
 * @method replaceFilter
 */
neon.eventing.Messenger.prototype.replaceFilter = function(id, filter, successCallback, errorCallback) {
    var filterKey = this.createFilterKey_(id, filter);
    return neon.util.ajaxUtils.doPostJSON(
        filterKey,
        neon.serviceUrl('filterservice', 'replacefilter'),
        {
            success: this.createChannelCallback_(neon.eventing.channels.FILTERS_CHANGED, successCallback),
            error: errorCallback
        }
    );
};

/**
 * Clears all filters. This will fire a filter changed event to notify other widgets that the filters have changed.
 * @method clearFilters
 */
neon.eventing.Messenger.prototype.clearFilters = function(successCallback, errorCallback) {
    return neon.util.ajaxUtils.doPost(
        neon.serviceUrl('filterservice', 'clearfilters'),
        {
            success: this.createChannelCallback_(neon.eventing.channels.FILTERS_CHANGED, successCallback),
            error: errorCallback,
            responseType: 'json'
        }
    );
};

/**
 * Clears all filters but does not notify other widgets that the filters have been changed. This is useful if this is
 * part of another event that may result in the widgets updating themselves
 * @method clearFiltersSilently
 */
neon.eventing.Messenger.prototype.clearFiltersSilently = function(successCallback, errorCallback) {
    return neon.util.ajaxUtils.doPost(
        neon.serviceUrl('filterservice', 'clearfilters'),
        {
            success: successCallback,
            error: errorCallback,
            responseType: 'json'
        }
    );
};

/**
 * Adds any elements matching the filter to the current selection. This will fire a selection changed event to notify
 * other widgets that the selection has changed.
 * @param {String} id The id of the selection to apply. A typical value to use for the id is the return value of
 * {{#crossLink "neon.widget/getInstanceId:method"}}{{/crossLink}}
 * @param {neon.query.Filter} filter Items matching this filter will be marked as selected
 * @param {Function} successCallback The callback to invoke when the selection is added
 * @param {Function} errorCallback The callback to invoke if an error occurs.
 * @method addSelection
 */
neon.eventing.Messenger.prototype.addSelection = function(id, filter, successCallback, errorCallback) {
    var filterKey = this.createFilterKey_(id, filter);
    return neon.util.ajaxUtils.doPostJSON(
        filterKey,
        neon.serviceUrl('selectionservice', 'addselection'),
        {
            success: this.createChannelCallback_(neon.eventing.channels.SELECTION_CHANGED, successCallback),
            error: errorCallback,
            global: false
        }
    );
};

/**
 * Removes this filter from the selected items. This will fire a selection changed event to notify
 * other widgets that the selection has changed.
 * @param {String} id The id of the selection to remove. This will be the same id used to create the selection.
 * @param {Function} successCallback The callback to invoke when the selection is removed
 * @param {Function} errorCallback The callback to invoke if an error occurs.
 * @method removeSelection
 */
neon.eventing.Messenger.prototype.removeSelection = function(id, successCallback, errorCallback) {
    return neon.util.ajaxUtils.doPost(
        neon.serviceUrl('selectionservice', 'removeselection'),
        {
            success:  this.createChannelCallback_(neon.eventing.channels.SELECTION_CHANGED, successCallback),
            error: errorCallback,
            global: false,
            data: id,
            contentType: 'text/plain',
            responseType: 'json'
        }
    );
};

/**
 * Replaces a previously added selection. This is a similar to calling remove and then add, but does so with one
 * notification event.
 * @param {String} id The id of the selection to replace.
 * @param {neon.query.Filter} filter The filter that defines the new selection
 * @param {Function} successCallback The callback to invoke when the selection is replaced
 * @param {Function} errorCallback The callback to invoke if an error occurs.
 * @method replaceSelection
 */

neon.eventing.Messenger.prototype.replaceSelection = function(id, filter, successCallback, errorCallback) {
    var filterKey = this.createFilterKey_(id, filter);
    return neon.util.ajaxUtils.doPostJSON(
        filterKey,
        neon.serviceUrl('selectionservice', 'replaceselection'),
        {
            success: this.createChannelCallback_(neon.eventing.channels.SELECTION_CHANGED, successCallback),
            error: errorCallback,
            global: false
        }
    );
};

/**
 * Clears the currently selected items. This will fire a selection changed event to notify
 * other widgets that the selection has changed.
 * @method clearSelection
 */
neon.eventing.Messenger.prototype.clearSelection = function(successCallback, errorCallback) {
    return neon.util.ajaxUtils.doPost(
        neon.serviceUrl('selectionservice', 'clearselection'),
        {
            success: this.createChannelCallback_(neon.eventing.channels.SELECTION_CHANGED, successCallback),
            error: errorCallback,
            global: false,
            responseType: 'json'
        }
    );
};

neon.eventing.Messenger.prototype.createFilterKey_ = function(id, filter) {
    return {
        id: id,
        filter: filter
    };
};

neon.eventing.Messenger.prototype.createChannelCallback_ = function(channelName, successCallback) {
    var me = this;
    var callback = function(results) {
        // pass the results in the callback and publish them to the channel so callers can listen
        // in either place
        if(successCallback && typeof successCallback === 'function') {
            successCallback(results);
        }
        me.publish(channelName, results || {});
    };
    return callback;
};
<commit_msg>NEON-1593 Fix bug where messenger channels weren't being removed for a visualization on removal<commit_after>/*
 * Copyright 2014 Next Century Corporation
 * 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.
 *
 */

// Note: There is a single instance of an event bus shared by all messengers

/**
 * Creates the event bus to be used for communication between widgets. If running outside of OWF,
 * If in OWF, a {{#crossLink "neon.eventing.EventBus"}}{{/crossLink}} is returned. If running inside of OWF, a
 * {{#crossLink "neon.eventing.owf.OWFEventBus"}}{{/crossLink}} is returned.
 * @return {neon.eventing.EventBus | neon.eventing.owf.OWFEventBus}
 * @private
 * @method createEventBus_
 */
neon.eventing.createEventBus_ = function() {
    if(neon.util.owfUtils.isRunningInOWF()) {
        return new neon.eventing.owf.OWFEventBus();
    }
    return new neon.eventing.EventBus(this.id_);
};

/**
 * The event bus used for communication between widgets
 * @property eventBus_
 * @type {neon.eventing.EventBus|neon.eventing.owf.OWFEventBus}
 * @private
 */
neon.eventing.eventBus_ = neon.eventing.createEventBus_();

/**
 * A messenger is a widget's gateway to the event bus used for communication between widgets. Widgets publish and
 * subscribe to messages through a messenger. Each messenger has a unique id to identify which widget published the
 * message to ensure that widgets are not receiving their own messages to facilitate simpler widget code.
 * @class neon.eventing.Messenger
 * @constructor
 */

neon.eventing.Messenger = function() {
    this.id_ = uuid.v4();
    this.channels = [];
};

/**
 * Publishes a message to a channel
 * @param {String} channel The channel to publish the message to
 * @param {Object} message The message to publish
 * @method publish
 */
neon.eventing.Messenger.prototype.publish = function(channel, message) {
    //var data = { payload: message, sender: this.id_ };
    neon.eventing.eventBus_.publish(channel, message, this.id_);
};

/**
 * Subscribes to the channel to receives any messages published to it. Note that messengers will not receive their
 * own messages even if they are subscribed to that channel.
 * @param {String} channel The channel to subscribe to
 * @param {Function} callback The callback to invoke when a message is received on the channel. The function takes
 * @param {Object|String} callback.message the message that was published
 * @method subscribe
 */
neon.eventing.Messenger.prototype.subscribe = function(channel, callback) {
    neon.eventing.eventBus_.subscribe(channel, callback, this.id_);
    if(this.channels.indexOf(channel) < 0) {
        this.channels.push(channel);
    }
};

/**
 * Unsubscribes this messenger from the channel
 * @param {String} channel The channel to unsubscribe from
 * @method unsubscribe
 */
neon.eventing.Messenger.prototype.unsubscribe = function(channel) {
    neon.eventing.eventBus_.unsubscribe(channel, this.id_);
    var index = this.channels.indexOf(channel);
    if(index >= 0) {
        this.channels.splice(index, 1);
    }
};

/**
 * Unsubscribes this messenger from all its channels, including events
 * @method unsubscribeAll
 */
neon.eventing.Messenger.prototype.unsubscribeAll = function() {
    var me = this;
    var allChannels = me.channels.slice();
    _.each(allChannels, function(channel) {
        me.unsubscribe(channel);
    });
};

/**
 * Subscribe to events notifications
 * @param callbacks {object} An object containing callback functions. Each function takes one
 * parameter, the message that was published.
 *

 *
selectionChanged - function to execute when the selection has changed

 *
filtersChanged - function to execute when the filters have been changed

 *
activeDatasetChanged - function to execute when the active dataset has changed

 *

 * @method events
 */
neon.eventing.Messenger.prototype.events = function(callbacks) {
    var me = this;
    var globalChannelConfigs = this.createGlobalChannelSubscriptions_(callbacks);
    _.each(globalChannelConfigs, function(channelConfig) {
        me.subscribe(channelConfig.channel, function(payload) {
                if(channelConfig.callback && typeof channelConfig.callback === 'function') {
                    channelConfig.callback(payload);
                }
            }
        );
    });
};

/**
 * Unsubscribe from all events:  selection changes, filters changes, active dataset changed.
 * @method removeEvents
 */
neon.eventing.Messenger.prototype.removeEvents = function() {
    var me = this;
    var globalChannelConfigs = this.createGlobalChannelSubscriptions_({});
    _.each(globalChannelConfigs, function(channelConfig) {
        me.unsubscribe(channelConfig.channel);
    });
};

neon.eventing.Messenger.prototype.createGlobalChannelSubscriptions_ = function(neonCallbacks) {
    // some of the callbacks may be null/undefined, which is ok since they will be ignored
    return [
        {
            channel: neon.eventing.channels.SELECTION_CHANGED,
            callback: neonCallbacks.selectionChanged
        },
        {
            channel: neon.eventing.channels.FILTERS_CHANGED,
            callback: neonCallbacks.filtersChanged
        },
        {
            channel: neon.eventing.channels.CONNECT_TO_HOST,
            callback: neonCallbacks.connectToHost
        }
    ];
};

/**
 * Adds a filter to the data being viewed, so only data matching that filter is returned from queries. This will
 * fire a filter changed event to notify other widgets that the filters have changed. If a filter with this id
 * already exists, the filter is appended to that one with an AND clause. To replace the filter entirely, use
 * {{#crossLink "neon.eventing.Messenger/replaceFilter:method"}}{{/crossLink}}.
 * @param {String} id The id of the filter to apply. A typical value to use for the id is the return value of
 * {{#crossLink "neon.widget/getInstanceId:method"}}{{/crossLink}}
 * @param {neon.query.Filter} filter The filter to add
 * @param {Function} successCallback The callback to invoke when the filter is added
 * @param {Function} errorCallback The callback to invoke if an error occurs.
 * @method addFilter
 */
neon.eventing.Messenger.prototype.addFilter = function(id, filter, successCallback, errorCallback) {
    var filterKey = this.createFilterKey_(id, filter);
    return neon.util.ajaxUtils.doPostJSON(
        filterKey,
        neon.serviceUrl('filterservice', 'addfilter'),
        {
            success: this.createChannelCallback_(neon.eventing.channels.FILTERS_CHANGED, successCallback),
            error: errorCallback
        }
    );
};

/**
 * Removes a previously added filter. This will fire a filter changed event to notify other widgets that the filters
 * have changed.
 * @param {String} id The id of the filter to remove. This will be the same id used to create the filter.
 * @param {Function} successCallback The callback to invoke when the filter is removed
 * @param {Function} errorCallback The callback to invoke if an error occurs.
 * @method removeFilter
 */
neon.eventing.Messenger.prototype.removeFilter = function(id, successCallback, errorCallback) {
    return neon.util.ajaxUtils.doPost(
        neon.serviceUrl('filterservice', 'removefilter'),
        {
            success: this.createChannelCallback_(neon.eventing.channels.FILTERS_CHANGED, successCallback),
            error: errorCallback,
            data: id,
            contentType: 'text/plain',
            responseType: 'json'
        }
    );
};

/**
 * Replaces a previously added filter. This is a similar to calling remove and then add, but does so with one
 * notification event.
 * @param {String} id The id of the filter to replace.
 * @param {neon.query.Filter} filter The filter to replace the old one with
 * @param {Function} successCallback The callback to invoke when the filter is replaced
 * @param {Function} errorCallback The callback to invoke if an error occurs.
 * @method replaceFilter
 */
neon.eventing.Messenger.prototype.replaceFilter = function(id, filter, successCallback, errorCallback) {
    var filterKey = this.createFilterKey_(id, filter);
    return neon.util.ajaxUtils.doPostJSON(
        filterKey,
        neon.serviceUrl('filterservice', 'replacefilter'),
        {
            success: this.createChannelCallback_(neon.eventing.channels.FILTERS_CHANGED, successCallback),
            error: errorCallback
        }
    );
};

/**
 * Clears all filters. This will fire a filter changed event to notify other widgets that the filters have changed.
 * @method clearFilters
 */
neon.eventing.Messenger.prototype.clearFilters = function(successCallback, errorCallback) {
    return neon.util.ajaxUtils.doPost(
        neon.serviceUrl('filterservice', 'clearfilters'),
        {
            success: this.createChannelCallback_(neon.eventing.channels.FILTERS_CHANGED, successCallback),
            error: errorCallback,
            responseType: 'json'
        }
    );
};

/**
 * Clears all filters but does not notify other widgets that the filters have been changed. This is useful if this is
 * part of another event that may result in the widgets updating themselves
 * @method clearFiltersSilently
 */
neon.eventing.Messenger.prototype.clearFiltersSilently = function(successCallback, errorCallback) {
    return neon.util.ajaxUtils.doPost(
        neon.serviceUrl('filterservice', 'clearfilters'),
        {
            success: successCallback,
            error: errorCallback,
            responseType: 'json'
        }
    );
};

/**
 * Adds any elements matching the filter to the current selection. This will fire a selection changed event to notify
 * other widgets that the selection has changed.
 * @param {String} id The id of the selection to apply. A typical value to use for the id is the return value of
 * {{#crossLink "neon.widget/getInstanceId:method"}}{{/crossLink}}
 * @param {neon.query.Filter} filter Items matching this filter will be marked as selected
 * @param {Function} successCallback The callback to invoke when the selection is added
 * @param {Function} errorCallback The callback to invoke if an error occurs.
 * @method addSelection
 */
neon.eventing.Messenger.prototype.addSelection = function(id, filter, successCallback, errorCallback) {
    var filterKey = this.createFilterKey_(id, filter);
    return neon.util.ajaxUtils.doPostJSON(
        filterKey,
        neon.serviceUrl('selectionservice', 'addselection'),
        {
            success: this.createChannelCallback_(neon.eventing.channels.SELECTION_CHANGED, successCallback),
            error: errorCallback,
            global: false
        }
    );
};

/**
 * Removes this filter from the selected items. This will fire a selection changed event to notify
 * other widgets that the selection has changed.
 * @param {String} id The id of the selection to remove. This will be the same id used to create the selection.
 * @param {Function} successCallback The callback to invoke when the selection is removed
 * @param {Function} errorCallback The callback to invoke if an error occurs.
 * @method removeSelection
 */
neon.eventing.Messenger.prototype.removeSelection = function(id, successCallback, errorCallback) {
    return neon.util.ajaxUtils.doPost(
        neon.serviceUrl('selectionservice', 'removeselection'),
        {
            success:  this.createChannelCallback_(neon.eventing.channels.SELECTION_CHANGED, successCallback),
            error: errorCallback,
            global: false,
            data: id,
            contentType: 'text/plain',
            responseType: 'json'
        }
    );
};

/**
 * Replaces a previously added selection. This is a similar to calling remove and then add, but does so with one
 * notification event.
 * @param {String} id The id of the selection to replace.
 * @param {neon.query.Filter} filter The filter that defines the new selection
 * @param {Function} successCallback The callback to invoke when the selection is replaced
 * @param {Function} errorCallback The callback to invoke if an error occurs.
 * @method replaceSelection
 */

neon.eventing.Messenger.prototype.replaceSelection = function(id, filter, successCallback, errorCallback) {
    var filterKey = this.createFilterKey_(id, filter);
    return neon.util.ajaxUtils.doPostJSON(
        filterKey,
        neon.serviceUrl('selectionservice', 'replaceselection'),
        {
            success: this.createChannelCallback_(neon.eventing.channels.SELECTION_CHANGED, successCallback),
            error: errorCallback,
            global: false
        }
    );
};

/**
 * Clears the currently selected items. This will fire a selection changed event to notify
 * other widgets that the selection has changed.
 * @method clearSelection
 */
neon.eventing.Messenger.prototype.clearSelection = function(successCallback, errorCallback) {
    return neon.util.ajaxUtils.doPost(
        neon.serviceUrl('selectionservice', 'clearselection'),
        {
            success: this.createChannelCallback_(neon.eventing.channels.SELECTION_CHANGED, successCallback),
            error: errorCallback,
            global: false,
            responseType: 'json'
        }
    );
};

neon.eventing.Messenger.prototype.createFilterKey_ = function(id, filter) {
    return {
        id: id,
        filter: filter
    };
};

neon.eventing.Messenger.prototype.createChannelCallback_ = function(channelName, successCallback) {
    var me = this;
    var callback = function(results) {
        // pass the results in the callback and publish them to the channel so callers can listen
        // in either place
        if(successCallback && typeof successCallback === 'function') {
            successCallback(results);
        }
        me.publish(channelName, results || {});
    };
    return callback;
};
<|endoftext|>


<commit_before>import {
  WebGLRenderTarget
} from 'three';

import {CopyShader} from '../../extras/shader/CopyShader';
import {ShaderPass} from '../../extras/pass/ShaderPass';
import {RenderPass} from '../../extras/pass/RenderPass';
import {MaskPass, ClearMaskPass} from '../../extras/pass/MaskPass';
import {Loop} from '../../core/Loop';

// TODO: Rewrite deprecated API
export class PostProcessorModule {
  renderTargetA = null;
  renderTargetB = null;

  writeBuffer = null;
  readBuffer = null;

  passes = [];
  copyPass = new ShaderPass(CopyShader);

  constructor() {
    this.params = {};
  }

  manager(manager) {
    this.renderer = manager.get('renderer');
    this.scene = manager.get('scene');
    this.camera = manager.get('camera');
    this.oldLoop = manager.get('renderer', true).renderLoop;
    this.renderLoop = new Loop(clock => this.render(clock.getDelta()));
    this.configure();
  }

  integrate(self) {
    self.oldLoop.stop(this);
    self.renderLoop.start(this);
  }

  configure() {
    const size = this.renderer.getSize();

    // TODO: Remove "THREE." declaration.
    this.renderTargetA = new WebGLRenderTarget(
      size.width,
      size.height,
      {
        minFilter: THREE.LinearFilter,
        magFilter: THREE.LinearFilter,
        format: THREE.RGBAFormat,
        stencilBuffer: false
      }
    );

    this.renderTargetB = this.renderTargetA.clone();
    this.writeBuffer = this.renderTargetA;
    this.readBuffer = this.renderTargetB;
  }

  /**
   * Swap drawing buffers
   */
  swapBuffers() {
    const tmp = this.readBuffer;
    this.readBuffer = this.writeBuffer;
    this.writeBuffer = tmp;
  }

  /**
   * Add a WHS.Pass to the composer, after all existing passes.
   * @param {WHS.Pass} pass : The pass to be added
   */
  addPass(pass) {
    if (!pass) return;
    const size = this.renderer ? this.renderer.getSize() : {width: 0, height: 0};
    pass.setSize(size.width, size.height);
    this.passes.push(pass);
  }

  /**
   * Get the pass of the same unique name, otherwise undefined.
   * @param  {String} name : the unique name of the pass to find.
   * @return {WHS.Pass} The found Pass or undefined.
   */
  getPass(name) {
    return this.passes.filter(v => v.name === name)[0];
  }

  /**
   * Get the pass index in the internal passes array.
   * @param  {String|WHS.Pass} passIndicator : The pass name or the pass instance.
   * @return {Number} The pass index or -1.
   */
  getPassIndex(passIndicator) {
    const passName = typeof passIndicator === 'string' ? passIndicator : passIndicator.name;
    return this.passes.indexOf(this.getPass(passName));
  }

  /**
   * Add a pass after another one. Add the pass in first position if previous is not found.
   * @param {String|WHS.Pass} previousPassIndicator : The previous pass name or the previous pass instance.
   */
  addPassAfter(previousPassIndicator, pass) {
    if (!pass) return;
    let index = this.getPassIndex(previousPassIndicator);
    index = index < 0 ? 0 : index + 1;
    this.insertPass(pass, index);
  }

  /**
   * Add a pass at the specified index.
   * @param  {WHS.Pass} pass : The pass instance to insert
   * @param  {Number} index : The index.
   */
  insertPass(pass, index) {
    if (pass) this.passes.splice(index, 0, pass);
  }

  /**
   * Remove a pass from this ser.
   * @param  {String|WHS.Pass} passIndicator : The pass name or the pass instance.
   */
  removePass(passIndicator) {
    const index = this.getPassIndex(passIndicator);
    if (index > -1) this.passes.splice(index, 1);
  }

  /**
   * A helper to create a render pass (WHS.RenderPass) that will draw your geometry in the PostProcessor first pass.
   * @param  {Boolean} renderToScreen : Should the renderpass be rendered directly to screen
   */
  createRenderPass(renderToScreen = false) {
    if (this.scene && this.camera) {
      const pass = new RenderPass('renderscene', this.scene, this.camera.native);
      pass.renderToScreen = renderToScreen;
      this.addPass(pass);
    }
  }

  /**
   * Render the EffectComposer, and all its passes.
   * @param  {Number} delta : The delta time since the last frame.
   */
  render(delta) {
    for (let i = 0, il = this.passes.length, pass, maskActive = false; i < il; i++) {
      pass = this.passes[i];

      if (pass.enabled === false) continue;

      pass.render(this.renderer, this.writeBuffer, this.readBuffer, delta, maskActive);

      if (pass.needsSwap) {
        if (maskActive) {
          const context = this.renderer.context;

          context.stencilFunc(context.NOTEQUAL, 1, 0xffffffff);
          this.copyPass.render(this.renderer, this.writeBuffer, this.readBuffer, delta);
          context.stencilFunc(context.EQUAL, 1, 0xffffffff);
        }

        this.swapBuffers();
      }

      if (MaskPass !== undefined) {
        if (pass instanceof MaskPass) maskActive = true;
        else if (pass instanceof ClearMaskPass) maskActive = false;
      }
    }
  }

  /**
   * Set another renderTarget.
   * @param  {THREE.WebGLRenderTarget} renderTarget : The new renderTarget to use.
   */
  reset(renderTarget) {
    if (renderTarget === undefined) {
      const size = this.renderer.getSize();

      renderTarget = this.renderTarget1.clone();
      renderTarget.setSize(size.width, size.height);
    }

    this.renderTarget1.dispose();
    this.renderTarget2.dispose();
    this.renderTarget1 = renderTarget;
    this.$renderTarget2 = renderTarget.clone();

    this.writeBuffer = this.renderTarget1;
    this.readBuffer = this.$renderTarget2;
  }

  /**
   * Resize the renderTarget and all the EffectComposer passes.
   * @param {Number} width : The width in pixels
   * @param {Number} height : The height in pixels
   */
  setSize(width, height) {
    this.renderTarget1.setSize(width, height);
    this.renderTarget2.setSize(width, height);

    for (let i = 0; i < this.passes.length; i++) this.passes[i].setSize(width, height);
  }
}
<commit_msg>Remove JSDoc comments<commit_after>import {
  WebGLRenderTarget
} from 'three';

import {CopyShader} from '../../extras/shader/CopyShader';
import {ShaderPass} from '../../extras/pass/ShaderPass';
import {RenderPass} from '../../extras/pass/RenderPass';
import {MaskPass, ClearMaskPass} from '../../extras/pass/MaskPass';
import {Loop} from '../../core/Loop';

// TODO: Rewrite deprecated API
export class PostProcessorModule {
  renderTargetA = null;
  renderTargetB = null;

  writeBuffer = null;
  readBuffer = null;

  passes = [];
  copyPass = new ShaderPass(CopyShader);

  constructor() {
    this.params = {};
  }

  manager(manager) {
    this.renderer = manager.get('renderer');
    this.scene = manager.get('scene');
    this.camera = manager.get('camera');
    this.oldLoop = manager.get('renderer', true).renderLoop;
    this.renderLoop = new Loop(clock => this.render(clock.getDelta()));
    this.configure();
  }

  integrate(self) {
    self.oldLoop.stop(this);
    self.renderLoop.start(this);
  }

  configure() {
    const size = this.renderer.getSize();

    // TODO: Remove "THREE." declaration.
    this.renderTargetA = new WebGLRenderTarget(
      size.width,
      size.height,
      {
        minFilter: THREE.LinearFilter,
        magFilter: THREE.LinearFilter,
        format: THREE.RGBAFormat,
        stencilBuffer: false
      }
    );

    this.renderTargetB = this.renderTargetA.clone();
    this.writeBuffer = this.renderTargetA;
    this.readBuffer = this.renderTargetB;
  }

  swapBuffers() {
    const tmp = this.readBuffer;
    this.readBuffer = this.writeBuffer;
    this.writeBuffer = tmp;
  }

  addPass(pass) {
    if (!pass) return;
    const size = this.renderer ? this.renderer.getSize() : {width: 0, height: 0};
    pass.setSize(size.width, size.height);
    this.passes.push(pass);
  }

  getPass(name) {
    return this.passes.filter(v => v.name === name)[0];
  }

  getPassIndex(passIndicator) {
    const passName = typeof passIndicator === 'string' ? passIndicator : passIndicator.name;
    return this.passes.indexOf(this.getPass(passName));
  }

  addPassAfter(previousPassIndicator, pass) {
    if (!pass) return;
    let index = this.getPassIndex(previousPassIndicator);
    index = index < 0 ? 0 : index + 1;
    this.insertPass(pass, index);
  }

  insertPass(pass, index) {
    if (pass) this.passes.splice(index, 0, pass);
  }

  removePass(passIndicator) {
    const index = this.getPassIndex(passIndicator);
    if (index > -1) this.passes.splice(index, 1);
  }

  createRenderPass(renderToScreen = false) {
    if (this.scene && this.camera) {
      const pass = new RenderPass('renderscene', this.scene, this.camera.native);
      pass.renderToScreen = renderToScreen;
      this.addPass(pass);
    }
  }

  render(delta) {
    for (let i = 0, il = this.passes.length, pass, maskActive = false; i < il; i++) {
      pass = this.passes[i];

      if (pass.enabled === false) continue;

      pass.render(this.renderer, this.writeBuffer, this.readBuffer, delta, maskActive);

      if (pass.needsSwap) {
        if (maskActive) {
          const context = this.renderer.context;

          context.stencilFunc(context.NOTEQUAL, 1, 0xffffffff);
          this.copyPass.render(this.renderer, this.writeBuffer, this.readBuffer, delta);
          context.stencilFunc(context.EQUAL, 1, 0xffffffff);
        }

        this.swapBuffers();
      }

      if (MaskPass !== undefined) {
        if (pass instanceof MaskPass) maskActive = true;
        else if (pass instanceof ClearMaskPass) maskActive = false;
      }
    }
  }

  reset(renderTarget) {
    if (renderTarget === undefined) {
      const size = this.renderer.getSize();

      renderTarget = this.renderTarget1.clone();
      renderTarget.setSize(size.width, size.height);
    }

    this.renderTarget1.dispose();
    this.renderTarget2.dispose();
    this.renderTarget1 = renderTarget;
    this.$renderTarget2 = renderTarget.clone();

    this.writeBuffer = this.renderTarget1;
    this.readBuffer = this.$renderTarget2;
  }

  setSize(width, height) {
    this.renderTarget1.setSize(width, height);
    this.renderTarget2.setSize(width, height);

    for (let i = 0; i < this.passes.length; i++) this.passes[i].setSize(width, height);
  }
}
<|endoftext|>


<commit_before>var config = require('./config')
var mongoOp = require('./model/mongo')
var stringProcessor = require('./stringprocessor')
var dates = require('./dates')

function addReminder (inputText, familyId, timeZone, callback) {
  var sendTo = stringProcessor.getFirstWord(inputText)
  var dateText = stringProcessor.removeFirstWord(inputText)
  var generalError = 'Error creating reminder list 😦'

  mongoOp.FamilyMembers.findOne({ 'name': sendTo, 'familyId': familyId }, function (err, familyMember) {
    if (familyMember == null || err) {
      callback('@' + sendTo + ' unkown sorry! 😕')
    } else {
      var sendToId = familyMember.userId

      // Check lists and create if necessary
      mongoOp.Lists.findOne({'listKey': config.remindersListKey, 'familyId': familyId}, function (err, list) {
        if (err) callback(generalError)
        if (list == null) {
          var newList = new mongoOp.Lists({
            'listKey': config.remindersListKey,
            'listDescription': config.remindersListKey,
            'familyId': familyId
          })
          newList.save(function (err, data) {
            if (err) callback(generalError)
          })
        }
        dates.processDateAndTitleFromText(dateText, timeZone, function (err, date, localDateText, title) {
          if (err) callback(err)

          if (title == null || title === '') {
            callback('Sorry need a reminder desciption 😦\nTry "remind @me tomorrow" followed by a description.')
          }

          // Create listItem
          var newItem = new mongoOp.ListItems({
            'listKey': config.remindersListKey,
            'listItemName': '@' + sendTo + ' ' + title + ' ' + localDateText,
            'familyId': familyId,
            'reminderWhen': date,
            'reminderUserId': sendToId,
            'reminderTitle': title
          })
          newItem.save(function (err, data) {
            if (err) callback('Error adding reminder 😦')
            else {
              console.log('----reminder saved: ' + inputText + ' Sherlocked:' + date + ' < ' + title)
              callback(null, 'Set for ' + localDateText + '.')
            }
          })
        })
      })
    }
  })
}

module.exports = {
  addReminder
}
<commit_msg>scheduler<commit_after>var config = require('./config')
var mongoOp = require('./model/mongo')
var stringProcessor = require('./stringprocessor')
var dates = require('./dates')

function addReminder (inputText, familyId, timeZone, callback) {
  var sendTo = stringProcessor.getFirstWord(inputText)
  var dateText = stringProcessor.removeFirstWord(inputText)
  dateText = dateText.replace('weekend', 'saturday')
  var generalError = 'Error creating reminder list 😦'

  mongoOp.FamilyMembers.findOne({ 'name': sendTo, 'familyId': familyId }, function (err, familyMember) {
    if (familyMember == null || err) {
      callback('@' + sendTo + ' unkown sorry! 😕')
    } else {
      var sendToId = familyMember.userId

      // Check lists and create if necessary
      mongoOp.Lists.findOne({'listKey': config.remindersListKey, 'familyId': familyId}, function (err, list) {
        if (err) callback(generalError)
        if (list == null) {
          var newList = new mongoOp.Lists({
            'listKey': config.remindersListKey,
            'listDescription': config.remindersListKey,
            'familyId': familyId
          })
          newList.save(function (err, data) {
            if (err) callback(generalError)
          })
        }
        dates.processDateAndTitleFromText(dateText, timeZone, function (err, date, localDateText, title) {
          if (err) callback(err)

          if (title == null || title === '') {
            callback('Sorry need a reminder desciption 😦\nJust add one to the end.')
            return
          }

          // Create listItem
          var newItem = new mongoOp.ListItems({
            'listKey': config.remindersListKey,
            'listItemName': '@' + sendTo + ' ' + title + ' ' + localDateText,
            'familyId': familyId,
            'reminderWhen': date,
            'reminderUserId': sendToId,
            'reminderTitle': title
          })
          newItem.save(function (err, data) {
            if (err) callback('Error adding reminder 😦')
            else {
              console.log('----reminder saved: ' + inputText + ' Sherlocked:' + date + ' < ' + title)
              callback(null, 'Set for ' + localDateText + '.')
            }
          })
        })
      })
    }
  })
}

module.exports = {
  addReminder
}
<|endoftext|>


<commit_before>import { createWidget } from 'discourse/widgets/widget'
import { showSelector } from "discourse/lib/emoji/emoji-toolbar"
import Babble from "../lib/babble"
import template from "../widgets/templates/babble-composer"

export default createWidget('babble-composer', {
  tagName: 'div.babble-post-composer',

  defaultState(attrs) {
    return {
      editing:         attrs.isEditing,
      submitDisabled:  attrs.submitDisabled,
      post:            attrs.post,
      topic:           attrs.topic,
      raw:             attrs.raw,
      lastInteraction: new Date(0)
    }
  },

  composerElement() {
    if (this.state.editing) {
      return $('.babble-post-container > .babble-post-composer textarea')
    } else {
      return $('.babble-chat > .babble-post-composer textarea')
    }
  },

  selectEmoji() {
    let $composer = this.composerElement()
    showSelector({
      container: this.container,
      onSelect: function(emoji) {
        $composer.val(`${$composer.val().trimRight()} :${emoji}:`)
        $composer.focus()
        $('.emoji-modal, .emoji-modal-wrapper').remove()
        return false
      }
    })

    $('.emoji-modal-wrapper').on('click', (e) => { e.stopPropagation() })
    $('body').on('keydown.emoji',         (e) => { e.stopPropagation() })
  },

  cancel() {
    Babble.editPost(null)
  },

  submit() {
    let $composer = this.composerElement(),
        text = $composer.val();
    $composer.val('')
    if (!text) { return; }

    if (this.state.editing) {
      this.update(text)
    } else {
      this.create(text)
    }
  },

  create(text) {
    var topic = this.state.topic
    this.state.submitDisabled = true
    Babble.stagePost(text)
    Discourse.ajax(`/babble/topics/${topic.id}/post`, {
      type: 'POST',
      data: { raw: text }
    }).then((data) => {
      Babble.handleNewPost(data)
    }).finally(() => {
      this.state.submitDisabled = false
    })
  },

  update(text) {
    var post = this.state.post
    Babble.editPost(null)
    if (post.raw.trim() === text.trim()) { return }
    Babble.set('loadingEditId', post.id)
    this.state.submitDisabled = true
    Discourse.ajax(`/babble/topics/${post.topic_id}/post/${post.id}`, {
      type: 'POST',
      data: { raw: text }
    }).then((data) => {
      Babble.handleNewPost(data)
    }).finally(() => {
      Babble.set('loadingEditId', null)
      this.state.submitDisabled = false
    })
  },

  keyUp(event) {
    this.state.showError = false
    this.checkInteraction()
    if (event.keyCode == 38 &&                               // key pressed is up key
        !this.state.editing &&                               // post is not being edited
        !$(event.target).siblings('.autocomplete').length) { // autocomplete is not active
      let myLastPost = _.last(_.select(this.state.topic.postStream.posts, function(post) {
        return post.user_id == Discourse.User.current().id
      }))
      if (myLastPost) { Babble.editPost(myLastPost) }
      return false
    }

    if (event.keyCode == 13 && !(event.ctrlKey || event.altKey || event.shiftKey)) {
      event.preventDefault()
      if (!this.state.submitDisabled) { // ignore if submit is disabled
        this.submit(this) // submit on enter
      }
      return false
    }
  },

  checkInteraction() {
    const topicId = this.state.topic.id
    const lastInteraction = this.state.lastInteraction
    const now = new Date
    if (now - lastInteraction > 5000) {
      this.state.lastInteraction = now
      Discourse.ajax(`/babble/topics/${topicId}/notification`, {
        type: 'POST',
        data: {state: 'editing'}
      })
    }
  },

  html() { return template.render(this) }
})
<commit_msg>Move submit on enter check to keyDown rather than keyUp<commit_after>import { createWidget } from 'discourse/widgets/widget'
import { showSelector } from "discourse/lib/emoji/emoji-toolbar"
import Babble from "../lib/babble"
import template from "../widgets/templates/babble-composer"

export default createWidget('babble-composer', {
  tagName: 'div.babble-post-composer',

  defaultState(attrs) {
    return {
      editing:         attrs.isEditing,
      submitDisabled:  attrs.submitDisabled,
      post:            attrs.post,
      topic:           attrs.topic,
      raw:             attrs.raw,
      lastInteraction: new Date(0)
    }
  },

  composerElement() {
    if (this.state.editing) {
      return $('.babble-post-container > .babble-post-composer textarea')
    } else {
      return $('.babble-chat > .babble-post-composer textarea')
    }
  },

  selectEmoji() {
    let $composer = this.composerElement()
    showSelector({
      container: this.container,
      onSelect: function(emoji) {
        $composer.val(`${$composer.val().trimRight()} :${emoji}:`)
        $composer.focus()
        $('.emoji-modal, .emoji-modal-wrapper').remove()
        return false
      }
    })

    $('.emoji-modal-wrapper').on('click', (e) => { e.stopPropagation() })
    $('body').on('keydown.emoji',         (e) => { e.stopPropagation() })
  },

  cancel() {
    Babble.editPost(null)
  },

  submit() {
    let $composer = this.composerElement(),
        text = $composer.val();
    $composer.val('')
    if (!text) { return; }

    if (this.state.editing) {
      this.update(text)
    } else {
      this.create(text)
    }
  },

  create(text) {
    var topic = this.state.topic
    this.state.submitDisabled = true
    Babble.stagePost(text)
    Discourse.ajax(`/babble/topics/${topic.id}/post`, {
      type: 'POST',
      data: { raw: text }
    }).then((data) => {
      Babble.handleNewPost(data)
    }).finally(() => {
      this.state.submitDisabled = false
    })
  },

  update(text) {
    var post = this.state.post
    Babble.editPost(null)
    if (post.raw.trim() === text.trim()) { return }
    Babble.set('loadingEditId', post.id)
    this.state.submitDisabled = true
    Discourse.ajax(`/babble/topics/${post.topic_id}/post/${post.id}`, {
      type: 'POST',
      data: { raw: text }
    }).then((data) => {
      Babble.handleNewPost(data)
    }).finally(() => {
      Babble.set('loadingEditId', null)
      this.state.submitDisabled = false
    })
  },

  keyDown(event) {
    if (event.keyCode == 13 && !(event.ctrlKey || event.altKey || event.shiftKey)) {
      event.preventDefault()
      if (!this.state.submitDisabled) { // ignore if submit is disabled
        this.submit(this) // submit on enter
      }
      return false
    }
  },

  keyUp(event) {
    this.state.showError = false
    this.checkInteraction()
    if (event.keyCode == 38 &&                               // key pressed is up key
        !this.state.editing &&                               // post is not being edited
        !$(event.target).siblings('.autocomplete').length) { // autocomplete is not active
      let myLastPost = _.last(_.select(this.state.topic.postStream.posts, function(post) {
        return post.user_id == Discourse.User.current().id
      }))
      if (myLastPost) { Babble.editPost(myLastPost) }
      return false
    }
  },

  checkInteraction() {
    const topicId = this.state.topic.id
    const lastInteraction = this.state.lastInteraction
    const now = new Date
    if (now - lastInteraction > 5000) {
      this.state.lastInteraction = now
      Discourse.ajax(`/babble/topics/${topicId}/notification`, {
        type: 'POST',
        data: {state: 'editing'}
      })
    }
  },

  html() { return template.render(this) }
})
<|endoftext|>


<commit_before>'use strict';
const md = require('markdown-it')();
const yaml = require('js-yaml');

const markdown_parser = function () {

  /**
   * Converts a markdown block with frontmatter (each is optional, technically) to a well-formed object.
   * @param block - the ".md" file, which can contain frontmatter or not, or only frontmatter.
   * @returns an object with any frontmatter keys, plus a .markdown key
     */
  function parseMarkdownBlock(block) {
    let returnObject = {};

    try {
      //for each block process the yaml frontmatter and markdown
      const frontmatterRE = /---\r?\n{1}([\s\S]*)---\r?\n{1}([\s\S]*)+/gm;
      const chunks = frontmatterRE.exec(block);

      if (chunks) {
        //we got some frontmatter
        if (chunks && chunks[1]) {
          //parse the yaml if we got it
          const frontmatter = chunks[1];
          returnObject = yaml.safeLoad(frontmatter);
        }

        if (chunks[2]) {
          //parse the actual markdown if it exists
          returnObject.markdown = md.render(chunks[2]);
        } else {
          returnObject.markdown = '';
        }
      } else {
        //assume the block was only markdown
        returnObject.markdown = md.render(block);
      }
    } catch (ex) {
      console.log(ex);
      console.log('error parsing markdown block', block);
      return undefined;
    }

    //return the frontmatter keys and markdown for a consumer to decide what to do with
    return returnObject;
  }

  return {
    parse: function (block) {
      return parseMarkdownBlock(block);
    }
  };

};

module.exports = markdown_parser;
<commit_msg>feat(logs): Refactor logs for markdown_parser<commit_after>'use strict';
const md = require('markdown-it')();
const yaml = require('js-yaml');
const logger = require('./log');

const markdown_parser = function () {

  /**
   * Converts a markdown block with frontmatter (each is optional, technically) to a well-formed object.
   * @param block - the ".md" file, which can contain frontmatter or not, or only frontmatter.
   * @returns an object with any frontmatter keys, plus a .markdown key
     */
  function parseMarkdownBlock(block) {
    let returnObject = {};

    try {
      //for each block process the yaml frontmatter and markdown
      const frontmatterRE = /---\r?\n{1}([\s\S]*)---\r?\n{1}([\s\S]*)+/gm;
      const chunks = frontmatterRE.exec(block);

      if (chunks) {
        //we got some frontmatter
        if (chunks && chunks[1]) {
          //parse the yaml if we got it
          const frontmatter = chunks[1];
          returnObject = yaml.safeLoad(frontmatter);
        }

        if (chunks[2]) {
          //parse the actual markdown if it exists
          returnObject.markdown = md.render(chunks[2]);
        } else {
          returnObject.markdown = '';
        }
      } else {
        //assume the block was only markdown
        returnObject.markdown = md.render(block);
      }
    } catch (ex) {
      logger.warning(ex);
      logger.warning(`error parsing markdown block ${block}`);
      return undefined;
    }

    //return the frontmatter keys and markdown for a consumer to decide what to do with
    return returnObject;
  }

  return {
    parse: function (block) {
      return parseMarkdownBlock(block);
    }
  };

};

module.exports = markdown_parser;
<|endoftext|>


<commit_before>var assert = require("assert");
var path = require("path");
var stream = require("stream");
var fs = require("fs");
var net = require("net");
var tty = require("tty");
var vm = require("vm");
var Fiber = require("fibers");
var _ = require("underscore");
var INFO_FILE_MODE = 0600; // Only the owner can read or write.
var EXITING_MESSAGE =
  // Exported so that ./client.js can know what to expect.
  exports.EXITING_MESSAGE = "Shell exiting...";

// Invoked by the server process to listen for incoming connections from
// shell clients. Each connection gets its own REPL instance.
exports.listen = function listen(shellDir) {
  function callback() {
    new Server(shellDir).listen();
  }

  // If the server is still in the very early stages of starting up,
  // Meteor.startup may not available yet.
  if (typeof Meteor === "object") {
    Meteor.startup(callback);
  } else if (typeof __meteor_bootstrap__ === "object") {
    var hooks = __meteor_bootstrap__.startupHooks;
    if (hooks) {
      hooks.push(callback);
    } else {
      // As a fallback, just call the callback asynchronously.
      process.nextTick(callback);
    }
  }
};

// Disabling the shell causes all attached clients to disconnect and exit.
exports.disable = function disable(shellDir) {
  try {
    // Replace info.json with a file that says the shell server is
    // disabled, so that any connected shell clients will fail to
    // reconnect after the server process closes their sockets.
    fs.writeFileSync(
      getInfoFile(shellDir),
      JSON.stringify({
        status: "disabled",
        reason: "Shell server has shut down."
      }) + "\n",
      { mode: INFO_FILE_MODE }
    );
  } catch (ignored) {}
};

function Server(shellDir) {
  var self = this;
  assert.ok(self instanceof Server);

  self.shellDir = shellDir;
  self.key = Math.random().toString(36).slice(2);

  self.server = net.createServer(function(socket) {
    self.onConnection(socket);
  }).on("error", function(err) {
    console.error(err.stack);
  });
}

var Sp = Server.prototype;

Sp.listen = function listen() {
  var self = this;
  var infoFile = getInfoFile(self.shellDir);

  fs.unlink(infoFile, function() {
    self.server.listen(0, "127.0.0.1", function() {
      fs.writeFileSync(infoFile, JSON.stringify({
        status: "enabled",
        port: self.server.address().port,
        key: self.key
      }) + "\n", {
        mode: INFO_FILE_MODE
      });
    });
  });
};

Sp.onConnection = function onConnection(socket) {
  var self = this;
  var dataSoFar = "";

  // Make sure this function doesn't try to write anything to the socket
  // after it has been closed.
  socket.on("close", function() {
    socket = null;
  });

  // If communication is not established within 1000ms of the first
  // connection, forcibly close the socket.
  var timeout = setTimeout(function() {
    if (socket) {
      socket.removeAllListeners("data");
      socket.end(EXITING_MESSAGE + "\n");
    }
  }, 1000);

  // Let connecting clients configure certain REPL options by sending a
  // JSON object over the socket. For example, only the client knows
  // whether it's running a TTY or an Emacs subshell or some other kind of
  // terminal, so the client must decide the value of options.terminal.
  socket.on("data", function onData(buffer) {
    // Just in case the options JSON comes in fragments.
    dataSoFar += buffer.toString("utf8");

    try {
      var options = JSON.parse(dataSoFar);
    } finally {
      if (! _.isObject(options)) {
        return; // Silence any parsing exceptions.
      }
    }

    if (socket) {
      socket.removeListener("data", onData);
    }

    if (options.key !== self.key) {
      if (socket) {
        socket.end(EXITING_MESSAGE + "\n");
      }
      return;
    }
    delete options.key;

    clearTimeout(timeout);

    // Immutable options.
    _.extend(options, {
      input: socket,
      output: socket,
      eval: evalCommand
    });

    // Overridable options.
    _.defaults(options, {
      prompt: "> ",
      terminal: true,
      useColors: true,
      useGlobal: true,
      ignoreUndefined: true,
    });

    self.startREPL(options);
  });
};

Sp.startREPL = function startREPL(options) {
  var self = this;

  if (! options.output.columns) {
    // The REPL's tab completion logic assumes process.stdout is a TTY,
    // and while that isn't technically true here, we can get tab
    // completion to behave correctly if we fake the .columns property.
    options.output.columns = getTerminalWidth();
  }

  // Make sure this function doesn't try to write anything to the output
  // stream after it has been closed.
  options.output.on("close", function() {
    options.output = null;
  });

  var repl = self.repl = require("repl").start(options);

  // History persists across shell sessions!
  self.initializeHistory();

  // Save the global `_` object in the server.  This is probably defined by the
  // underscore package.  It is unlikely to be the same object as the `var _ =
  // require('underscore')` in this file!
  var originalUnderscore = repl.context._;

  Object.defineProperty(repl.context, "_", {
    // Force the global _ variable to remain bound to underscore.
    get: function () { return originalUnderscore; },

    // Expose the last REPL result as __ instead of _.
    set: function(lastResult) {
      repl.context.__ = lastResult;
    },

    enumerable: true,

    // Allow this property to be (re)defined more than once (e.g. each
    // time the server restarts).
    configurable: true
  });

  // Use the same `require` function and `module` object visible to the
  // shell.js module.
  repl.context.require = require;
  repl.context.module = module;
  repl.context.repl = repl;

  // Some improvements to the existing help messages.
  repl.commands[".break"].help =
    "Terminate current command input and display new prompt";
  repl.commands[".exit"].help = "Disconnect from server and leave shell";
  repl.commands[".help"].help = "Show this help information";

  // When the REPL exits, signal the attached client to exit by sending it
  // the special EXITING_MESSAGE.
  repl.on("exit", function() {
    if (options.output) {
      options.output.write(EXITING_MESSAGE + "\n");
      options.output.end();
    }
  });

  // When the server process exits, end the output stream but do not
  // signal the attached client to exit.
  process.on("exit", function() {
    if (options.output) {
      options.output.end();
    }
  });

  // This Meteor-specific shell command rebuilds the application as if a
  // change was made to server code.
  repl.defineCommand("reload", {
    help: "Restart the server and the shell",
    action: function() {
      process.exit(0);
    }
  });
};

function getInfoFile(shellDir) {
  return path.join(shellDir, "info.json");
}
exports.getInfoFile = getInfoFile;

function getHistoryFile(shellDir) {
  return path.join(shellDir, "history");
}

function getTerminalWidth() {
  try {
    // Inspired by https://github.com/TooTallNate/ttys/blob/master/index.js
    var fd = fs.openSync("/dev/tty", "r");
    assert.ok(tty.isatty(fd));
    var ws = new tty.WriteStream(fd);
    ws.end();
    return ws.columns;
  } catch (fancyApproachWasTooFancy) {
    return 80;
  }
}

// Shell commands need to be executed in fibers in case they call into
// code that yields.
function evalCommand(command, context, filename, callback) {
  Fiber(function() {
    try {
      var result = vm.runInThisContext(command, filename);
    } catch (error) {
      if (process.domain) {
        process.domain.emit("error", error);
        process.domain.exit();
      } else {
        callback(error);
      }
      return;
    }
    callback(null, result);
  }).run();
}

// This function allows a persistent history of shell commands to be saved
// to and loaded from .meteor/local/shell-history.
Sp.initializeHistory = function initializeHistory() {
  var self = this;
  var rli = self.repl.rli;
  var historyFile = getHistoryFile(self.shellDir);
  var historyFd = fs.openSync(historyFile, "a+");
  var historyLines = fs.readFileSync(historyFile, "utf8").split("\n");
  var seenLines = Object.create(null);

  if (! rli.history) {
    rli.history = [];
    rli.historyIndex = -1;
  }

  while (rli.history && historyLines.length > 0) {
    var line = historyLines.pop();
    if (line && /\S/.test(line) && ! seenLines[line]) {
      rli.history.push(line);
      seenLines[line] = true;
    }
  }

  rli.addListener("line", function(line) {
    if (historyFd >= 0 && /\S/.test(line)) {
      fs.writeSync(historyFd, line + "\n");
    }
  });

  self.repl.on("exit", function() {
    fs.closeSync(historyFd);
    historyFd = -1;
  });
};
<commit_msg>Use a Promise to run shell commands using recycled Fibers.<commit_after>var assert = require("assert");
var path = require("path");
var stream = require("stream");
var fs = require("fs");
var net = require("net");
var tty = require("tty");
var vm = require("vm");
var _ = require("underscore");
var INFO_FILE_MODE = 0600; // Only the owner can read or write.
var EXITING_MESSAGE =
  // Exported so that ./client.js can know what to expect.
  exports.EXITING_MESSAGE = "Shell exiting...";

var Promise = require("meteor-promise");
// Only require("fibers") if somehow Promise.Fiber is not yet defined.
Promise.Fiber = Promise.Fiber || require("fibers");

// Invoked by the server process to listen for incoming connections from
// shell clients. Each connection gets its own REPL instance.
exports.listen = function listen(shellDir) {
  function callback() {
    new Server(shellDir).listen();
  }

  // If the server is still in the very early stages of starting up,
  // Meteor.startup may not available yet.
  if (typeof Meteor === "object") {
    Meteor.startup(callback);
  } else if (typeof __meteor_bootstrap__ === "object") {
    var hooks = __meteor_bootstrap__.startupHooks;
    if (hooks) {
      hooks.push(callback);
    } else {
      // As a fallback, just call the callback asynchronously.
      process.nextTick(callback);
    }
  }
};

// Disabling the shell causes all attached clients to disconnect and exit.
exports.disable = function disable(shellDir) {
  try {
    // Replace info.json with a file that says the shell server is
    // disabled, so that any connected shell clients will fail to
    // reconnect after the server process closes their sockets.
    fs.writeFileSync(
      getInfoFile(shellDir),
      JSON.stringify({
        status: "disabled",
        reason: "Shell server has shut down."
      }) + "\n",
      { mode: INFO_FILE_MODE }
    );
  } catch (ignored) {}
};

function Server(shellDir) {
  var self = this;
  assert.ok(self instanceof Server);

  self.shellDir = shellDir;
  self.key = Math.random().toString(36).slice(2);

  self.server = net.createServer(function(socket) {
    self.onConnection(socket);
  }).on("error", function(err) {
    console.error(err.stack);
  });
}

var Sp = Server.prototype;

Sp.listen = function listen() {
  var self = this;
  var infoFile = getInfoFile(self.shellDir);

  fs.unlink(infoFile, function() {
    self.server.listen(0, "127.0.0.1", function() {
      fs.writeFileSync(infoFile, JSON.stringify({
        status: "enabled",
        port: self.server.address().port,
        key: self.key
      }) + "\n", {
        mode: INFO_FILE_MODE
      });
    });
  });
};

Sp.onConnection = function onConnection(socket) {
  var self = this;
  var dataSoFar = "";

  // Make sure this function doesn't try to write anything to the socket
  // after it has been closed.
  socket.on("close", function() {
    socket = null;
  });

  // If communication is not established within 1000ms of the first
  // connection, forcibly close the socket.
  var timeout = setTimeout(function() {
    if (socket) {
      socket.removeAllListeners("data");
      socket.end(EXITING_MESSAGE + "\n");
    }
  }, 1000);

  // Let connecting clients configure certain REPL options by sending a
  // JSON object over the socket. For example, only the client knows
  // whether it's running a TTY or an Emacs subshell or some other kind of
  // terminal, so the client must decide the value of options.terminal.
  socket.on("data", function onData(buffer) {
    // Just in case the options JSON comes in fragments.
    dataSoFar += buffer.toString("utf8");

    try {
      var options = JSON.parse(dataSoFar);
    } finally {
      if (! _.isObject(options)) {
        return; // Silence any parsing exceptions.
      }
    }

    if (socket) {
      socket.removeListener("data", onData);
    }

    if (options.key !== self.key) {
      if (socket) {
        socket.end(EXITING_MESSAGE + "\n");
      }
      return;
    }
    delete options.key;

    clearTimeout(timeout);

    // Immutable options.
    _.extend(options, {
      input: socket,
      output: socket,
      eval: evalCommand
    });

    // Overridable options.
    _.defaults(options, {
      prompt: "> ",
      terminal: true,
      useColors: true,
      useGlobal: true,
      ignoreUndefined: true,
    });

    self.startREPL(options);
  });
};

Sp.startREPL = function startREPL(options) {
  var self = this;

  if (! options.output.columns) {
    // The REPL's tab completion logic assumes process.stdout is a TTY,
    // and while that isn't technically true here, we can get tab
    // completion to behave correctly if we fake the .columns property.
    options.output.columns = getTerminalWidth();
  }

  // Make sure this function doesn't try to write anything to the output
  // stream after it has been closed.
  options.output.on("close", function() {
    options.output = null;
  });

  var repl = self.repl = require("repl").start(options);

  // History persists across shell sessions!
  self.initializeHistory();

  // Save the global `_` object in the server.  This is probably defined by the
  // underscore package.  It is unlikely to be the same object as the `var _ =
  // require('underscore')` in this file!
  var originalUnderscore = repl.context._;

  Object.defineProperty(repl.context, "_", {
    // Force the global _ variable to remain bound to underscore.
    get: function () { return originalUnderscore; },

    // Expose the last REPL result as __ instead of _.
    set: function(lastResult) {
      repl.context.__ = lastResult;
    },

    enumerable: true,

    // Allow this property to be (re)defined more than once (e.g. each
    // time the server restarts).
    configurable: true
  });

  // Use the same `require` function and `module` object visible to the
  // shell.js module.
  repl.context.require = require;
  repl.context.module = module;
  repl.context.repl = repl;

  // Some improvements to the existing help messages.
  repl.commands[".break"].help =
    "Terminate current command input and display new prompt";
  repl.commands[".exit"].help = "Disconnect from server and leave shell";
  repl.commands[".help"].help = "Show this help information";

  // When the REPL exits, signal the attached client to exit by sending it
  // the special EXITING_MESSAGE.
  repl.on("exit", function() {
    if (options.output) {
      options.output.write(EXITING_MESSAGE + "\n");
      options.output.end();
    }
  });

  // When the server process exits, end the output stream but do not
  // signal the attached client to exit.
  process.on("exit", function() {
    if (options.output) {
      options.output.end();
    }
  });

  // This Meteor-specific shell command rebuilds the application as if a
  // change was made to server code.
  repl.defineCommand("reload", {
    help: "Restart the server and the shell",
    action: function() {
      process.exit(0);
    }
  });
};

function getInfoFile(shellDir) {
  return path.join(shellDir, "info.json");
}
exports.getInfoFile = getInfoFile;

function getHistoryFile(shellDir) {
  return path.join(shellDir, "history");
}

function getTerminalWidth() {
  try {
    // Inspired by https://github.com/TooTallNate/ttys/blob/master/index.js
    var fd = fs.openSync("/dev/tty", "r");
    assert.ok(tty.isatty(fd));
    var ws = new tty.WriteStream(fd);
    ws.end();
    return ws.columns;
  } catch (fancyApproachWasTooFancy) {
    return 80;
  }
}

// Shell commands need to be executed in a Fiber in case they call into
// code that yields. Using a Promise is an even better idea, since it runs
// its callbacks in Fibers drawn from a pool, so the Fibers are recycled.
var evalCommandPromise = Promise.resolve();

function evalCommand(command, context, filename, callback) {
  evalCommandPromise.then(function () {
    callback(null, vm.runInThisContext(command, filename));
  }).catch(callback);
}

// This function allows a persistent history of shell commands to be saved
// to and loaded from .meteor/local/shell-history.
Sp.initializeHistory = function initializeHistory() {
  var self = this;
  var rli = self.repl.rli;
  var historyFile = getHistoryFile(self.shellDir);
  var historyFd = fs.openSync(historyFile, "a+");
  var historyLines = fs.readFileSync(historyFile, "utf8").split("\n");
  var seenLines = Object.create(null);

  if (! rli.history) {
    rli.history = [];
    rli.historyIndex = -1;
  }

  while (rli.history && historyLines.length > 0) {
    var line = historyLines.pop();
    if (line && /\S/.test(line) && ! seenLines[line]) {
      rli.history.push(line);
      seenLines[line] = true;
    }
  }

  rli.addListener("line", function(line) {
    if (historyFd >= 0 && /\S/.test(line)) {
      fs.writeSync(historyFd, line + "\n");
    }
  });

  self.repl.on("exit", function() {
    fs.closeSync(historyFd);
    historyFd = -1;
  });
};
<|endoftext|>


<commit_before><commit_msg>Add SVG format detection<commit_after><|endoftext|>


<commit_before>var categories = require('./categories');

var PS = categories.PS;
var Com = categories.Com;
var Cre = categories.Cre;
var Col = categories.Col;

var weblitdata = {
  "WEB LITERACY" : {
    "Write": {
      "Design": [PS,Com,Cre],
      "Code": [PS,Cre],
      "Compose": [PS,Com],
      "Revise": [PS,Cre],
      "Remix": [PS,Cre,Com]
    },
    "Read": {
      "Evaluate": [PS],
      "Synthesize": [PS,Cre],
      "Navigate": [PS],
      "Search": [PS]
    },
    "Participate": {
      "Connect": [PS,Cre,Col],
      "Protect": [PS,Com],
      "Open Practice": [Com,Col],
      "Contribute": [PS,Cre,Col],
      "Share": [Com,Col]
    }
  }
};

module.exports = weblitdata;
<commit_msg>update some web lit data<commit_after>var categories = require('./categories');

var PS = categories.PS;
var Com = categories.Com;
var Cre = categories.Cre;
var Col = categories.Col;

var weblitdata = {
  "WEB LITERACY" : {
    "Write": {
      "Design": [PS,Com,Cre],
      "Code": [PS,Cre],
      "Compose": [PS,Com],
      "Revise": [PS,Cre],
      "Remix": [PS,Cre,Com]
    },
    "Read": {
      "Evaluate": [PS],
      "Synthesize": [PS,Com],
      "Navigate": [PS],
      "Search": [PS]
    },
    "Participate": {
      "Connect": [PS,Com,Col],
      "Protect": [PS,Com],
      "Open Practice": [Com,Col],
      "Contribute": [PS,Cre,Col],
      "Share": [Com,Cre]
    }
  }
};

module.exports = weblitdata;
<|endoftext|>


<commit_before>$((function () {
  displaySubHeader();

  const isMobile = $(window).width() <= 1230;

  $('#customers-main').slick({
    infinite: true,
    dots: true,
    draggable: isMobile,
    prevArrow: !isMobile ?
      '' : '',
    nextArrow: !isMobile ?
      '' : '',
  });

  const sliderArrow = $('.slider-arrow');
  sliderArrow.on("mouseenter", function() {
    $(this).addClass("pointer-cursor");
  });
  sliderArrow.on("mouseleave", function () {
    $(this).removeClass("pointer-cursor")
  });

  const subheaderA = $(".subheader-a");
  subheaderA.on("mouseenter", function () {
    $(this).addClass("selected-a")
    $(this).removeClass("non-selected-subheader")
  });
  subheaderA.on("mouseleave", function () {
    $(this).removeClass("selected-a");
    $(this).addClass("non-selected-subheader")
  })
}));

function displaySubHeader() {
  let inAboutSection = () =>
    $(window).scrollTop() + 101 >= $("#about-portfolio").offset().top &&
    $(window).scrollTop() + 160 < $("#events").offset().top;

  $(window).on("scroll", (function () {
    if (inAboutSection()) {
      setTimeout(function () {
        if (inAboutSection()) {
          $(".subheader-section").css({
            display: "block"
          });
        }
      }, 500);
    } else {
      $(".subheader-section").css({
        display: "none"
      });
    }

    if (
      $(window).scrollTop() + 120 >= $("#about-portfolio").offset().top &&
      $(window).scrollTop() + 200 < $("#about-hackathons").offset().top
    ) {
      $("#portfolio-a").addClass("selected-a");
      $("#portfolio-a-mobile").addClass("selected-a");
    } else {
      $("#portfolio-a")
        .removeClass("selected-a")
        .trigger("blur");
      $("#portfolio-a-mobile")
        .removeClass("selected-a")
        .trigger("blur");
    }

    if (
      $(window).scrollTop() + 120 >= $("#about-hackathons").offset().top &&
      $(window).scrollTop() + 200 < $("#about-customers").offset().top
    ) {
      $("#hackathon-a").addClass("selected-a");
      $("#hackathon-a-mobile").addClass("selected-a");
    } else {
      $("#hackathon-a")
        .removeClass("selected-a")
        .trigger("blur");
      $("#hackathon-a-mobile")
        .removeClass("selected-a")
        .trigger("blur");
    }

    if (
      $(window).scrollTop() + 180 >= $("#about-customers").offset().top &&
      $(window).scrollTop() + 200 < $("#team").offset().top
    ) {
      $("#customers-a").addClass("selected-a");
      $("#customers-a-mobile").addClass("selected-a");
    } else {
      $("#customers-a")
        .removeClass("selected-a")
        .trigger("blur");
      $("#customers-a-mobile")
        .removeClass("selected-a")
        .trigger("blur");
    }
  }));
}
<commit_msg>fix subheader<commit_after>$((function () {
  displaySubHeader();

  const isMobile = $(window).width() <= 1230;

  $('#customers-main').slick({
    infinite: true,
    dots: true,
    draggable: isMobile,
    prevArrow: !isMobile ?
      '' : '',
    nextArrow: !isMobile ?
      '' : '',
  });

  const sliderArrow = $('.slider-arrow');
  sliderArrow.on("mouseenter", function() {
    $(this).addClass("pointer-cursor");
  });
  sliderArrow.on("mouseleave", function () {
    $(this).removeClass("pointer-cursor")
  });

  const subheaderA = $(".subheader-a");
  subheaderA.on("mouseenter", function () {
    $(this).addClass("selected-a")
    $(this).removeClass("non-selected-subheader")
  });
  subheaderA.on("mouseleave", function () {
    $(this).removeClass("selected-a");
    $(this).addClass("non-selected-subheader")
  })
}));

function displaySubHeader() {
  let inAboutSection = () =>
    $(window).scrollTop() + 101 >= $("#about-portfolio").offset().top &&
    $(window).scrollTop() + 160 < $("#events").offset().top;

  $(window).on("scroll", (function () {
    if (inAboutSection()) {
      setTimeout(function () {
        if (inAboutSection()) {
          $(".subheader-section").css({
            display: "block"
          });
        }
      }, 500);
    } else {
      $(".subheader-section").css({
        display: "none"
      });
    }

    if (
      $(window).scrollTop() + 120 >= $("#about-portfolio").offset().top &&
      $(window).scrollTop() + 200 < $("#about-customers").offset().top
    ) {
      $("#portfolio-a").addClass("selected-a");
      $("#portfolio-a-mobile").addClass("selected-a");
    } else {
      $("#portfolio-a")
        .removeClass("selected-a")
        .trigger("blur");
      $("#portfolio-a-mobile")
        .removeClass("selected-a")
        .trigger("blur");
    }

    if (
      $(window).scrollTop() + 200 >= $("#about-customers").offset().top &&
      $(window).scrollTop() + 200 < $("#about-hackathons").offset().top
    ) {
      $("#customers-a").addClass("selected-a");
      $("#customers-a-mobile").addClass("selected-a");
    } else {
      $("#customers-a")
        .removeClass("selected-a")
        .trigger("blur");
      $("#customers-a-mobile")
        .removeClass("selected-a")
        .trigger("blur");
    }

    if (
      $(window).scrollTop() + 180 >= $("#about-hackathons").offset().top &&
      $(window).scrollTop() + 200 < $("#events").offset().top
    ) {
      $("#hackathon-a").addClass("selected-a");
      $("#hackathon-a-mobile").addClass("selected-a");
    } else {
      $("#hackathon-a")
        .removeClass("selected-a")
        .trigger("blur");
      $("#hackathon-a-mobile")
        .removeClass("selected-a")
        .trigger("blur");
    }
  }));
}
<|endoftext|>


<commit_before>import createReducer from './createReducer'
import Profiler from '../Profiler'
import parser from '../ast/parser'
import estraverse from '../../../lib/estraverse'
import escodegen from 'escodegen'

let id = 1

function addTextToNode (ast) {
    ast.text = escodegen.generate(ast)
}

function parseCode (text) {
    let stop = Profiler.start('code to ast')
    let ast
    try {
        ast = parser.parse(text, {
            plugins: {
                jsx: true
            },
            ecmaVersion: 6,
            sourceType: 'module',
            locations: true
        })
    } /* eslint-disable */ catch ( e ) /* eslint-enable */ {
        console.log(e)
        stop()
        return undefined
    }
    stop()
    return ast
}

function getFunctionsFromAst (content, ast) {
    let stop = Profiler.start('ast to functions')
    let functions = []

    estraverse.traverse(ast, {
        enter(node, parent) {
            if (node.type === 'JSXElement') {
                // unknown to estraverse
                return estraverse.VisitorOption.Skip
            }

            if (node.type === 'FunctionDeclaration' || node.type === 'ArrowFunctionExpression' || node.type === 'FunctionExpression') {
                if (!node.customId) {
                    node.customId = id++
                }
                if (!node.text) {
                    addTextToNode(node)
                }
                functions.push(node)
            }
        }
    })
    // sort descending by content length
    functions = functions.sort((a, b) => (a.end - a.start) - (b.end - b.start))
    stop()
    return functions
}

function createFileFromContent (path, content) {
    let file = {
        id: path,
        path
    }
    // create formatted code
    let ast = parseCode(content)
    if (ast) {
        file.content = escodegen.generate(ast)
        // refresh ast from formatted code
        file.ast = parseCode(file.content)
        file.functions = getFunctionsFromAst(file.content, file.ast).map(f => {
            f.fileId = file.id
            return f
        })
    } else {
        file.content = content
        file.functions = []
    }

    return file
}

const initialState = [
    createFileFromContent('foo/a.js', `
import editor from './editor'
import functionsView from './functionsView'
import { connect } from 'react-redux'

const MainSection = (React) => {
    const FunctionsView = functionsView(React)
    const Editor = editor(React)

    let selectState = ({fileStorage}) => {
        return {
            fileStorage
        }
    }

    let assignedFunction = function (a, b) {
        if (a) {
            return 'a'
        } else {
            return 'b'
        }
    }

    function functionWithName () {
        return 'simple'
    }

    return connect(selectState)(({fileStorage}) => {
        let fileContent = fileStorage[0].content
        let ast = fileStorage[0].ast
        let width = 500
        let height = 500
        let styleBase = {
            position: 'absolute',
            height: height,
            width: width,
            top: 0,
            left: 0
        }

        let styleRight = Object.assign({}, styleBase, {
            left: width
        })
        return 'no jsx because of escodegen'
    })
}

export default MainSection
`),

    createFileFromContent('foo/b.js', `export default function test (pA, pB) {
return pA+pB
}
`)

]

export const FUNCTION_CONTENT_UPDATED = 'FUNCTION_CONTENT_UPDATED '

export const updateFunctionContent = (params) => {
    return Object.assign({}, params, {
        type: FUNCTION_CONTENT_UPDATED
    })
}

let fileStorage = {
    [FUNCTION_CONTENT_UPDATED]: (state, action) => {
        const {newContent, oldFunction} = action

        let ast = parseCode(newContent)
        if (!ast) {
            // broken code, wait for working code
            return state
        }
        // ast parsing wraps content always in a "programm node"
        let newFunction
        estraverse.traverse(ast, {
            enter(node) {
                if (node.type === oldFunction.type) {
                    newFunction = node
                    this.break()
                }
            }
        })

        if (!newFunction) {
            // not found, uh oh?!
            throw new Error('old node type not found')
        }
        addTextToNode(newFunction)
        // save old id, because it is still the same function
        newFunction.customId = oldFunction.customId

        let file = state.find(file => file.id === oldFunction.fileId)
        // replace new ast in file
        file.ast = estraverse.replace(file.ast, {
            enter(node, parent) {
                if (node.type === 'JSXElement') {
                    // unknown to estraverse
                    return this.skip()
                }

                if (node.customId === newFunction.customId) {
                    return newFunction
                }
            }
        })

        // update file content with merged ast
        file.content = escodegen.generate(file.ast)
        file.functions = getFunctionsFromAst(file.content, file.ast).map(f => {
            f.fileId = file.id
            return f
        })

        // update state identity, so change is triggered in redux
        return state.map(f => {
            if (f.id === file.id) {
                return file
            } else {
                return f
            }
        })
    }
}

export default createReducer(initialState, fileStorage)
<commit_msg>skip function expression nodes<commit_after>import createReducer from './createReducer'
import Profiler from '../Profiler'
import parser from '../ast/parser'
import estraverse from '../../../lib/estraverse'
import escodegen from 'escodegen'

let id = 1

function addTextToNode (ast) {
    ast.text = escodegen.generate(ast)
}

function parseCode (text) {
    let stop = Profiler.start('code to ast')
    let ast
    try {
        ast = parser.parse(text, {
            plugins: {
                jsx: true
            },
            ecmaVersion: 6,
            sourceType: 'module',
            locations: true
        })
    } /* eslint-disable */ catch ( e ) /* eslint-enable */ {
        console.log(e)
        stop()
        return undefined
    }
    stop()
    return ast
}

function getFunctionsFromAst (ast) {
    let stop = Profiler.start('ast to functions')
    let functions = []

    estraverse.traverse(ast, {
        enter(node, parent) {
            if (node.type === 'JSXElement') {
                // unknown to estraverse
                return estraverse.VisitorOption.Skip
            }

            // TODO code generated from FunctionExpression are not parseable again, skip for now
            if (node.type === 'FunctionDeclaration' || node.type === 'ArrowFunctionExpression') {
                if (!node.customId) {
                    node.customId = id++
                }
                if (!node.text) {
                    addTextToNode(node)
                }
                functions.push(node)
            }
        }
    })
    // sort descending by content length
    functions = functions.sort((a, b) => (a.end - a.start) - (b.end - b.start))
    stop()
    return functions
}

function createFileFromContent (path, content) {
    let file = {
        id: path,
        path
    }
    // create formatted code
    let ast = parseCode(content)
    if (ast) {
        file.content = escodegen.generate(ast)
        // refresh ast from formatted code
        file.ast = parseCode(file.content)
        file.functions = getFunctionsFromAst(file.ast).map(f => {
            f.fileId = file.id
            return f
        })
    } else {
        file.content = content
        file.functions = []
    }

    return file
}

const initialState = [
    createFileFromContent('foo/a.js', `
import editor from './editor'
import functionsView from './functionsView'
import { connect } from 'react-redux'

const MainSection = (React) => {
    const FunctionsView = functionsView(React)
    const Editor = editor(React)

    let selectState = ({fileStorage}) => {
        return {
            fileStorage
        }
    }

    let assignedFunction = (a, b) => {
        if (a) {
            return 'a'
        } else {
            return 'b'
        }
    }

    function functionWithName () {
        return 'simple'
    }

    return connect(selectState)(({fileStorage}) => {
        let fileContent = fileStorage[0].content
        let ast = fileStorage[0].ast
        let width = 500
        let height = 500
        let styleBase = {
            position: 'absolute',
            height: height,
            width: width,
            top: 0,
            left: 0
        }

        let styleRight = Object.assign({}, styleBase, {
            left: width
        })
        return 'no jsx because of escodegen'
    })
}

export default MainSection
`),

    createFileFromContent('foo/b.js', `export default function test (pA, pB) {
return pA+pB
}
`)

]

export const FUNCTION_CONTENT_UPDATED = 'FUNCTION_CONTENT_UPDATED '

export const updateFunctionContent = (params) => {
    return Object.assign({}, params, {
        type: FUNCTION_CONTENT_UPDATED
    })
}

let fileStorage = {
    [FUNCTION_CONTENT_UPDATED]: (state, action) => {
        const {newContent, oldFunction} = action

        let ast = parseCode(newContent)
        if (!ast) {
            // broken code, wait for working code
            return state
        }
        // ast parsing wraps content always in a "programm node"
        let newFunction
        estraverse.traverse(ast, {
            enter(node) {
                if (node.type === oldFunction.type) {
                    newFunction = node
                    this.break()
                }
            }
        })

        if (!newFunction) {
            // not found, uh oh?!
            throw new Error('old node type not found')
        }
        addTextToNode(newFunction)
        // save old id, because it is still the same function
        newFunction.customId = oldFunction.customId

        let file = state.find(file => file.id === oldFunction.fileId)
        // replace new ast in file
        file.ast = estraverse.replace(file.ast, {
            enter(node, parent) {
                if (node.type === 'JSXElement') {
                    // unknown to estraverse
                    return this.skip()
                }

                if (node.customId === newFunction.customId) {
                    return newFunction
                }
            }
        })

        // update file content with merged ast
        file.content = escodegen.generate(file.ast)
        file.functions = getFunctionsFromAst(file.ast).map(f => {
            f.fileId = file.id
            return f
        })

        // update state identity, so change is triggered in redux
        return state.map(f => {
            if (f.id === file.id) {
                return file
            } else {
                return f
            }
        })
    }
}

export default createReducer(initialState, fileStorage)
<|endoftext|>


<commit_before>import { basename, relative, dirname } from 'path';
import { write } from './fs-promise';

const IMG_PLACEHOLDER = 'SPRITE.png';
const RETINA_IMG_PLACEHOLDER = 'SPRITE@2x.png';

/**
 * generate style
 * @param  {Array}   infoList                The images (merged to sprite) info list, generated by `generateSprite`
 * @param  {Object}  options                 options
           {String}  options.connector       className connector, default to "-"
           {String}  options.prefix          className prefix, default to "icon"
           {String}  options.suffix          className suffix
           {Boolean} options.retina          generate retina style
           {Boolean} options.writeToFile     whether write style to file
           {String}  options.stylePath       style path
           {String}  options.imagePath       image path
           {String}  options.retinaImagePath retina image path
           {Boolean} options.banner          whether write banner to style
 * @return {String}                          generated style text
 */
async function generateStyle(infoList, { connector = '-', prefix = 'icon', suffix = '', retina = true, writeToFile = true, stylePath, imagePath, retinaImagePath, banner = true } = {}) {
    if (!infoList || !infoList.length) return;
    const newLine = '\n';
    imagePath = !imagePath ? IMG_PLACEHOLDER : stylePath ? relative(dirname(stylePath), imagePath) : imagePath;
    retinaImagePath = !retinaImagePath ? RETINA_IMG_PLACEHOLDER :
        stylePath ? relative(dirname(stylePath), retinaImagePath) : retinaImagePath;
    let retinaBackgroundImage = retina ? `background-image: -webkit-image-set(url(${imagePath}) 1x, url(${retinaImagePath}) 2x);` : '';
    let bannerText = banner ? `/**${
        newLine}* Created at ${new Date().toLocaleString()}.${
        newLine}**/${
        newLine}` : '';
    let style = `${bannerText}.${prefix} {${
        newLine}    display: inline-block;${
        newLine}    background-repeat: no-repeat;${
        newLine}    background-image: url(${imagePath});${
        newLine}`;
    style += retinaBackgroundImage ? `    ${retinaBackgroundImage}${
        newLine}}` : '}';
    infoList.forEach(({ x, y, width, height, margin, path }) => {
        let name = basename(path);
        let css = `.${prefix + connector + name.slice(0, name.lastIndexOf('.')) + (suffix ? connector + suffix : '')} {${
            newLine}    background-position: ${-x - margin / 2}px ${-y - margin / 2}px;${
            newLine}    width: ${width - margin}px;${
            newLine}    height: ${height - margin}px;${
        newLine}}`;
        style += '\n' + css;
    });
    if (writeToFile && stylePath) {
        await write(stylePath, style, true);
    }
    return style;
}

export default generateStyle;
<commit_msg>throw error when arguments invalid<commit_after>import { basename, relative, dirname } from 'path';
import { write } from './fs-promise';

const IMG_PLACEHOLDER = 'SPRITE.png';
const RETINA_IMG_PLACEHOLDER = 'SPRITE@2x.png';

/**
 * generate style
 * @param  {Array}   infoList                The images (merged to sprite) info list, generated by `generateSprite`
 * @param  {Object}  options                 options
           {String}  options.connector       className connector, default to "-"
           {String}  options.prefix          className prefix, default to "icon"
           {String}  options.suffix          className suffix
           {Boolean} options.retina          generate retina style
           {Boolean} options.writeToFile     whether write style to file
           {String}  options.stylePath       style path
           {String}  options.imagePath       image path
           {String}  options.retinaImagePath retina image path
           {Boolean} options.banner          whether write banner to style
 * @return {String}                          generated style text
 */
async function generateStyle(infoList, { connector = '-', prefix = 'icon', suffix = '', retina = true, writeToFile = true, stylePath, imagePath, retinaImagePath, banner = true } = {}) {
    if (!infoList || !infoList.length) {
        throw new Error('invalid arguments');
    }
    const newLine = '\n';
    imagePath = !imagePath ? IMG_PLACEHOLDER : stylePath ? relative(dirname(stylePath), imagePath) : imagePath;
    retinaImagePath = !retinaImagePath ? RETINA_IMG_PLACEHOLDER :
        stylePath ? relative(dirname(stylePath), retinaImagePath) : retinaImagePath;
    let retinaBackgroundImage = retina ? `background-image: -webkit-image-set(url(${imagePath}) 1x, url(${retinaImagePath}) 2x);` : '';
    let bannerText = banner ? `/**${
        newLine}* Created at ${new Date().toLocaleString()}.${
        newLine}**/${
        newLine}` : '';
    let style = `${bannerText}.${prefix} {${
        newLine}    display: inline-block;${
        newLine}    background-repeat: no-repeat;${
        newLine}    background-image: url(${imagePath});${
        newLine}`;
    style += retinaBackgroundImage ? `    ${retinaBackgroundImage}${
        newLine}}` : '}';
    infoList.forEach(({ x, y, width, height, margin, path }) => {
        let name = basename(path);
        let css = `.${prefix + connector + name.slice(0, name.lastIndexOf('.')) + (suffix ? connector + suffix : '')} {${
            newLine}    background-position: ${-x - margin / 2}px ${-y - margin / 2}px;${
            newLine}    width: ${width - margin}px;${
            newLine}    height: ${height - margin}px;${
        newLine}}`;
        style += '\n' + css;
    });
    if (writeToFile && stylePath) {
        await write(stylePath, style, true);
    }
    return style;
}

export default generateStyle;
<|endoftext|>