<commit_before>/*jslint node: true */

const assert = require('assert');
const fs = require('fs');
const path = require('path');
const util = require('util');
const jsonschema = require('jsonschema');
const debug = require('./lib/debug')('jubatus-node-client');
const rpc = require('msgpack-rpc-lite');

const isProduct = process.env.NODE_ENV && (/production/).test(process.env.NODE_ENV);

function toCamelCase(value) {
    return value.replace(/_([a-z])/g, (match, group1) => group1.toUpperCase());
}

function toSnakeCase(value) {
    return value.replace(/(^[A-Z])|([A-Z])/g, (match, g1, g2) => g1 ? g1.toLowerCase() : '_' + g2.toLowerCase());
}

function toOptions(args) {
    const first = args.shift() || {};
    const rpcClient = first instanceof rpc.Client ? first : first.rpcClient;
    const port = typeof first === 'number' ? first : first.port;
    const host = first.host || args[0];
    const name = first.name || args[1];
    const timeoutSeconds = first.timeoutSeconds || args[2];
    return { rpcClient, port, host, name, timeoutSeconds };
}

function createSuperConstructor() {
    const constructor = function constructor(...args) {
        const options = toOptions(args);
        const { port = 9199, host = 'localhost', timeoutSeconds = 0, rpcClient } = options;
        let { name = '' } = options ;
        const codecOptions = { encode: { useraw: true } };
        const client = rpcClient || rpc.createClient(port, host, timeoutSeconds * 1000, codecOptions);

        Object.defineProperty(this, 'client', {
            get() { return client; }
        });

        Object.defineProperty(this, 'name', {
            get() { return name; },
            set(value) { name = value; }
        });

        return this;
    };

    // This is not an RPC method.
    constructor.prototype.getClient = function () {
        return this.client;
    };

    // This is not an RPC method.
    constructor.prototype.getName = function () {
        return this.name;
    };

    // This is not an RPC method.
    constructor.prototype.setName = function (value) {
        this.name = value;
    };

    return constructor;
}

function buildMethods(schema, constructor, types) {
    return Object.keys(schema.properties)
        .map(method => ([ method, schema.properties[method] ]))
        .map(([ rpcName, { properties: { 'arguments': argumentsSchema, 'return': returnSchema } }]) => {
            argumentsSchema.definitions = returnSchema.definitions = schema.definitions;
            const validator = new jsonschema.Validator();
            const methodName = toCamelCase(rpcName),
                assertParams = isProduct ? () => {} : params => {
                    const result = validator.validate(params, argumentsSchema);
                    assert.ok(result.valid, util.format('%j', result.errors));
                },
                assertReturn = isProduct ? () => {} : returnValue => {
                    const result = validator.validate(returnValue, returnSchema);
                    assert.ok(result.valid, util.format('%j', result.errors));
                };
            const castTypeFunction = (value) => castType(value, returnSchema, types);
            return [ rpcName, methodName, assertParams, assertReturn, castTypeFunction ];
        })
        .reduce((constructor, [ rpcName, methodName, assertParams, assertReturn, castTypeFunction ]) => {
            constructor.prototype[methodName] = function (...args) {
                const self = this,
                    client = self.getClient(),
                    callback = (typeof args[args.length - 1] === 'function') && args.pop();
                const params = toTuple(args);
                assertParams(params);
                params.unshift(self.getName());
                if (callback) {
                    client.request.apply(client, [ rpcName ].concat(params, (error, result, msgid) => {
                        if (!error) { assertReturn(result); }
                        callback.call(self, error, error ? result : castTypeFunction(result), msgid);
                    }));
                } else {
                    return client.request.apply(client, [ rpcName ].concat(params))
                        .then(([result, msgid]) => ([castTypeFunction(result), msgid]));
                }
            };
            return constructor;
        }, constructor);
}

function createClientConstructor(className, schema, superConstructor, types) {
    const constructor = function constructor(...args) {
        assert(this instanceof constructor, `${className} is constructor.`);

        superConstructor.apply(this, args);
        return this;
    };
    util.inherits(constructor, superConstructor);
    return buildMethods(schema, constructor, types);
}

function toTuple(value) {
    return value && typeof value['toTuple'] === 'function' ? value.toTuple() : Array.isArray(value) ? value.map(toTuple) : value;
}

function castType(value, schema, types) {
    const typeName = schema.type || 'object';
    if ('$ref' in schema) {
        return resolveType(schema['$ref'], types).fromTuple(value);
    } else if (typeName === 'array') {
        if (Array.isArray(schema.items)) {
            return value.map((e, i) => castType(e, schema.items[i], types));
        } else {
            return value.map(e => castType(e, schema.items, types));
        }
    } else if (typeName === 'object' && ('properties' in schema || 'patternProperties' in schema)) {
        const properties = Object.keys(schema.properties || {}).map(propertyName => ({propertyName, schema: schema.properties[propertyName]}));
        const patternProperties = Object.keys(schema.patternProperties || {}).map(propertyName => ({propertyName, schema: schema.patternProperties[propertyName]}));
        return Object.keys(value)
            .map(key => ({ key, value: value[key] }))
            .map(({ key, value }) => {
                const schemas = []
                    .concat(properties.filter(({ propertyName }) => propertyName === key))
                    .concat(patternProperties.filter(({ pattern }) => (new RegExp(pattern).test(key))))
                    .map(({ schema }) => schema);
                const typedValue = schemas.reduce((value, schema) => castType(value, schema, types), value);
                return { [key]: typedValue };
            })
            .reduce((accumulator, current) => Object.assign(accumulator, current), {});
    } else {
        return value;
    }
}

function createTypes(definitions, ignoreKeys) {
    return Object.keys(definitions)
        .filter(key => !ignoreKeys.includes(key))
        .map(key => definitions[key])
        .map(definition => ([toCamelCase('_' + definition.title), definition.items]))
        .map(([name, items]) => {
            const keys = items.map(item => item.title).map(toCamelCase);
            const properties = keys
                .map(key => `${key}: { enumerable: true, value: ${key} }`)
                .concat(`[Symbol.toStringTag]: { value: '${name}' }`).join(',');
            const functionBody = `Object.defineProperties(this, {${properties}})`;
            const constructor = new Function(keys, functionBody);
            constructor.prototype.toTuple = function () {
                const self = this;
                return keys.map(key => self[key]).filter(value => value !== undefined).map(toTuple);
            };
            return ({ [name]: constructor });
        })
        .reduce((accumulator, current) => Object.assign(accumulator, current), {});
}

function resolveTypeName(pointer) {
    const p = pointer || '';
    return toCamelCase('_' + p.substring(p.lastIndexOf('/') + 1));
}

function resolveType(pointer, types) {
    return (types || {})[resolveTypeName(pointer)] || { fromTuple: (value) => value };
}

function defineFromTupleFunction(types, definitions, commonTypes) {
    return Object.keys(types).map(key => ([key, types[key]])).map(([typeName, constructor]) => {
        const definitionKey = toSnakeCase(typeName);
        const constractors = definitions[definitionKey].items.map(item => {
            return '$ref' in item ? types[resolveTypeName(item['$ref'])] || commonTypes[resolveTypeName(item['$ref'])] : null;
        });
        constructor.fromTuple = function (tuple) {
            const args = tuple.map((value, index) => constractors[index] ? constractors[index].fromTuple(value) : value);
            return new (Function.prototype.bind.apply(constructor, [null].concat(args)));
        };
        return { [typeName]: constructor };
    }).reduce((accumulator, current) => Object.assign(accumulator, current), {});
}

const dirname = path.resolve(__dirname, './api/');
const services = fs.readdirSync(dirname)
    .map(file => path.resolve(dirname, file))
    .filter(file => path.extname(file) === '.json')
    .filter(file => fs.statSync(file).isFile())
    .map(file => {
        const service = toCamelCase('_' + path.basename(file, '.json'));
        const schema = JSON.parse(fs.readFileSync(file));
        return ({ [service]: schema });
    })
    .reduce((accumulator, current) => Object.assign(accumulator, current));
const { Common: common } = services;
const schemas = Object.keys(services)
    .filter(serviceName => serviceName !== 'Common')
    .map(serviceName =>{
        const service = services[serviceName];
        service.definitions = Object.assign(service.definitions, common.definitions);
        return { [serviceName]: service };
    })
    .reduce((accumulator, current) => Object.assign(accumulator, current));
const commonConstructor = createClientConstructor('Common', common, createSuperConstructor());
const commonDefinitionKeys = Object.keys(common.definitions);
const commonTypes = defineFromTupleFunction(createTypes(common.definitions, []), common.definitions, {});
module.exports['common'] = { types: commonTypes };
Object.keys(schemas).forEach(function (className) {
    const schema = schemas[className];
    debug(className, schema);
    const definitions = schema.definitions;
    const types = defineFromTupleFunction(createTypes(definitions, commonDefinitionKeys), definitions, commonTypes);
    const clientConstructor = createClientConstructor(className, schema, commonConstructor, Object.assign(types, commonTypes));
    module.exports[className.toLowerCase()] = { client: { [className]: clientConstructor }, types };
});<commit_msg>This commit fix #44<commit_after>/*jslint node: true */

const assert = require('assert');
const fs = require('fs');
const path = require('path');
const util = require('util');
const jsonschema = require('jsonschema');
const debug = require('./lib/debug')('jubatus-node-client');
const rpc = require('msgpack-rpc-lite');

const isProduct = process.env.NODE_ENV && (/production/).test(process.env.NODE_ENV);

function toCamelCase(value) {
    return value.replace(/_([a-z])/g, (match, group1) => group1.toUpperCase());
}

function toSnakeCase(value) {
    return value.replace(/(^[A-Z])|([A-Z])/g, (match, g1, g2) => g1 ? g1.toLowerCase() : '_' + g2.toLowerCase());
}

function toOptions(args) {
    const first = args.shift() || {};
    const rpcClient = first instanceof rpc.Client ? first : first.rpcClient;
    const port = typeof first === 'number' ? first : first.port;
    const host = first.host || args[0];
    const name = first.name || args[1];
    const timeoutSeconds = first.timeoutSeconds || args[2];
    return { rpcClient, port, host, name, timeoutSeconds };
}

function createSuperConstructor() {
    const constructor = function constructor(...args) {
        const options = toOptions(args);
        const { port = 9199, host = 'localhost', timeoutSeconds = 0, rpcClient } = options;
        let { name = '' } = options ;
        const codecOptions = { encode: { useraw: true } };
        const client = rpcClient || rpc.createClient(port, host, timeoutSeconds * 1000, codecOptions);

        Object.defineProperty(this, 'client', {
            get() { return client; }
        });

        Object.defineProperty(this, 'name', {
            get() { return name; },
            set(value) { name = value; }
        });

        return this;
    };

    // This is not an RPC method.
    constructor.prototype.getClient = function () {
        return this.client;
    };

    // This is not an RPC method.
    constructor.prototype.getName = function () {
        return this.name;
    };

    // This is not an RPC method.
    constructor.prototype.setName = function (value) {
        this.name = value;
    };

    return constructor;
}

function buildMethods(schema, constructor, types) {
    return Object.keys(schema.properties)
        .map(method => ([ method, schema.properties[method] ]))
        .map(([ rpcName, { properties: { 'arguments': argumentsSchema, 'return': returnSchema } }]) => {
            argumentsSchema.definitions = returnSchema.definitions = schema.definitions;
            const validator = new jsonschema.Validator();
            const methodName = toCamelCase(rpcName),
                assertParams = isProduct ? () => {} : params => {
                    const result = validator.validate(params, argumentsSchema);
                    assert.ok(result.valid, util.format('%j', result.errors));
                },
                assertReturn = isProduct ? () => {} : returnValue => {
                    const result = validator.validate(returnValue, returnSchema);
                    assert.ok(result.valid, util.format('%j', result.errors));
                };
            const castTypeFunction = (value) => castType(value, returnSchema, types);
            return [ rpcName, methodName, assertParams, assertReturn, castTypeFunction ];
        })
        .reduce((constructor, [ rpcName, methodName, assertParams, assertReturn, castTypeFunction ]) => {
            constructor.prototype[methodName] = function (...args) {
                const self = this,
                    client = self.getClient(),
                    callback = (typeof args[args.length - 1] === 'function') && args.pop();
                const params = toTuple(args);
                assertParams(params);
                params.unshift(self.getName());
                if (callback) {
                    client.request.apply(client, [ rpcName ].concat(params, (error, result, msgid) => {
                        if (!error) { assertReturn(result); }
                        callback.call(self, error, error ? result : castTypeFunction(result), msgid);
                    }));
                } else {
                    return client.request.apply(client, [ rpcName ].concat(params))
                        .then(([result, msgid]) => ([castTypeFunction(result), msgid]));
                }
            };
            return constructor;
        }, constructor);
}

function createClientConstructor(className, schema, superConstructor, types) {
    const constructor = function constructor(...args) {
        assert(this instanceof constructor, `${className} is constructor.`);

        superConstructor.apply(this, args);
        return this;
    };
    util.inherits(constructor, superConstructor);
    return buildMethods(schema, constructor, types);
}

function toTuple(value) {
    return value && typeof value['toTuple'] === 'function' ? value.toTuple() : Array.isArray(value) ? value.map(toTuple) : value;
}

function castType(value, schema, types) {
    const typeName = schema.type || 'object';
    if ('$ref' in schema) {
        return resolveType(schema['$ref'], types).fromTuple(value);
    } else if (typeName === 'array') {
        if (Array.isArray(schema.items)) {
            return value.map((e, i) => castType(e, schema.items[i], types));
        } else {
            return value.map(e => castType(e, schema.items, types));
        }
    } else if (typeName === 'object' && ('properties' in schema || 'patternProperties' in schema)) {
        const properties = Object.keys(schema.properties || {}).map(propertyName => ({propertyName, schema: schema.properties[propertyName]}));
        const patternProperties = Object.keys(schema.patternProperties || {}).map(propertyName => ({propertyName, schema: schema.patternProperties[propertyName]}));
        return Object.keys(value)
            .map(key => ({ key, value: value[key] }))
            .map(({ key, value }) => {
                const schemas = []
                    .concat(properties.filter(({ propertyName }) => propertyName === key))
                    .concat(patternProperties.filter(({ pattern }) => (new RegExp(pattern).test(key))))
                    .map(({ schema }) => schema);
                const typedValue = schemas.reduce((value, schema) => castType(value, schema, types), value);
                return { [key]: typedValue };
            })
            .reduce((accumulator, current) => Object.assign(accumulator, current), {});
    } else {
        return value;
    }
}

function createTypes(definitions, ignoreKeys) {
    return Object.keys(definitions)
        .filter(key => !ignoreKeys.includes(key))
        .map(key => definitions[key])
        .map(definition => ([toCamelCase('_' + definition.title), definition.items]))
        .map(([name, items]) => {
            const keys = items.map(item => item.title).map(toCamelCase);
            const properties = keys
                .map(key => `${key}: { enumerable: true, value: ${key} }`)
                .concat(`[Symbol.toStringTag]: { value: '${name}' }`).join(',');
            const functionBody = `Object.defineProperties(this, {${properties}})`;
            const constructor = new Function(keys, functionBody);
            constructor.prototype.toTuple = function () {
                const self = this;
                return keys.map(key => self[key]).filter(value => value !== undefined).map(toTuple);
            };
            return ({ [name]: constructor });
        })
        .reduce((accumulator, current) => Object.assign(accumulator, current), {});
}

function resolveTypeName(pointer) {
    const p = pointer || '';
    return toCamelCase('_' + p.substring(p.lastIndexOf('/') + 1));
}

function resolveType(pointer, types) {
    return (types || {})[resolveTypeName(pointer)] || { fromTuple: (value) => value };
}

function defineFromTupleFunction(types, definitions, commonTypes) {
    return Object.keys(types).map(key => ([key, types[key]])).map(([typeName, constructor]) => {
        const definitionKey = toSnakeCase(typeName);
        const constractors = definitions[definitionKey].items.map(item => {
            return '$ref' in item ? types[resolveTypeName(item['$ref'])] || commonTypes[resolveTypeName(item['$ref'])] : null;
        });
        constructor.fromTuple = function (tuple) {
            const args = tuple.map((value, index) => constractors[index] ? constractors[index].fromTuple(value) : value);
            return new (Function.prototype.bind.apply(constructor, [null].concat(args)));
        };
        return { [typeName]: constructor };
    }).reduce((accumulator, current) => Object.assign(accumulator, current), {});
}

const dirname = path.resolve(__dirname, './api/');
const services = fs.readdirSync(dirname)
    .map(file => path.resolve(dirname, file))
    .filter(file => path.extname(file) === '.json')
    .filter(file => fs.statSync(file).isFile())
    .map(file => {
        const service = toCamelCase('_' + path.basename(file, '.json'));
        const schema = JSON.parse(fs.readFileSync(file));
        return ({ [service]: schema });
    })
    .reduce((accumulator, current) => Object.assign(accumulator, current), {});
const { Common: common } = services;
const schemas = Object.keys(services)
    .filter(serviceName => serviceName !== 'Common')
    .map(serviceName =>{
        const service = services[serviceName];
        Object.assign(service.definitions, common.definitions);
        return { [serviceName]: service };
    })
    .reduce((accumulator, current) => Object.assign(accumulator, current));
const commonConstructor = createClientConstructor('Common', common, createSuperConstructor());
const commonDefinitionKeys = Object.keys(common.definitions);
const commonTypes = defineFromTupleFunction(createTypes(common.definitions, []), common.definitions, {});
module.exports['common'] = { types: commonTypes, toTuple };
Object.keys(schemas).forEach(function (className) {
    const schema = schemas[className];
    debug(className, schema);
    const definitions = schema.definitions;
    const types = defineFromTupleFunction(createTypes(definitions, commonDefinitionKeys), definitions, commonTypes);
    const clientConstructor = createClientConstructor(className, schema, commonConstructor, Object.assign(Object.assign({}, commonTypes), types));
    module.exports[className.toLowerCase()] = { client: { [className]: clientConstructor }, types };
});<|endoftext|>


<commit_before>var LightSliderHomeInit = function() {

    //
    // Init
    //
    this.init();

};

LightSliderHomeInit.prototype.init = function() {

    var that = this;

    $('[data-slider-home]').lightSlider({
        item: 1,
        slideMargin: 0,
        pager: false,
        loop: true,
        auto: true,
        pauseOnHover: true,
        pause: 3000,
        onAfterSlide: function(slider) {
            var $pages = slider.parents('.slider-home').find('.slider-home__pager li');
            $pages.removeClass('active');
            $($pages[slider.getCurrentSlideCount()-1]).addClass('active');
        },
        onSliderLoad: function(slider) {
            var $pages = slider.parents('.slider-home').find('.slider-home__pager li');
            $pages.each(function(idx) {
                var that = $(this);
                that.click(function(e) {
                    e.preventDefault();
                    $pages.removeClass('active');
                    that.addClass('active');
                    slider.goToSlide(idx+1);
                    return false;
                });

            });
        }
    });

};

module.exports = LightSliderHomeInit;
<commit_msg>Increase pause time of home slider<commit_after>var LightSliderHomeInit = function() {

    //
    // Init
    //
    this.init();

};

LightSliderHomeInit.prototype.init = function() {

    var that = this;

    $('[data-slider-home]').lightSlider({
        item: 1,
        slideMargin: 0,
        pager: false,
        loop: true,
        auto: true,
        pauseOnHover: true,
        pause: 5000,
        onAfterSlide: function(slider) {
            var $pages = slider.parents('.slider-home').find('.slider-home__pager li');
            $pages.removeClass('active');
            $($pages[slider.getCurrentSlideCount()-1]).addClass('active');
        },
        onSliderLoad: function(slider) {
            var $pages = slider.parents('.slider-home').find('.slider-home__pager li');
            $pages.each(function(idx) {
                var that = $(this);
                that.click(function(e) {
                    e.preventDefault();
                    $pages.removeClass('active');
                    that.addClass('active');
                    slider.goToSlide(idx+1);
                    return false;
                });

            });
        }
    });

};

module.exports = LightSliderHomeInit;
<|endoftext|>


<commit_before>"use strict"

// Configure Cognito identity pool
var credentials = new AWS.CognitoIdentityCredentials({
    IdentityPoolId: 'us-west-2:7b7923c1-68de-4a32-8fe2-52840b399eb9',
});

//Getting AWS creds from Cognito is async, so we need to drive the rest of the mqtt client initialization in a callback
credentials.get(function(err) {
    if(err) {
        console.log(err);
        return;
    }
    var requestUrl = SigV4Utils.getSignedUrl('wss', 'data.iot.us-west-2.amazonaws.com', '/mqtt',
        'iotdevicegateway', 'us-west-2',
        credentials.accessKeyId, credentials.secretAccessKey, credentials.sessionToken);
    console.log('requestUrl:', requestUrl)
    initClient(requestUrl);
});

function init() {
    // console.log("Initializing the function")
}

// Connect the client, subscribe to the drawing topic, and publish a "hey I connected" message
function initClient(requestUrl) {
    var clientId = String(Math.random()).replace('.', '');
    var client = new Paho.MQTT.Client(requestUrl, clientId);
    var connectOptions = {
        onSuccess: function () {
            console.log('connected');

            // subscribe to the drawing
            client.subscribe("aws/things/test-iot/shadow");

            // publish a lifecycle event
            message = new Paho.MQTT.Message('{"id":"' + credentials.identityId + '"}');
            message.destinationName = 'aws/things/test-iot/shadow';
            console.log("I am here");
            console.log(message);
            client.send(message);
        },
        useSSL: true,
        timeout: 3,
        mqttVersion: 4,
        onFailure: function (err) {
            console.error('connect failed');
            console.error(err);
        }
    };

    client.connect(connectOptions);

    client.onMessageArrived = function (message) {
        try {
            console.log("msg arrived: " +  message.payloadString);
        } catch (e) {
            console.log("error! " + e);
        }

    };
}
<commit_msg>MQTT Fixes<commit_after>"use strict"

// Configure Cognito identity pool
var credentials = new AWS.CognitoIdentityCredentials({
    IdentityPoolId: 'us-west-2:7b7923c1-68de-4a32-8fe2-52840b399eb9',
});

//Getting AWS creds from Cognito is async, so we need to drive the rest of the mqtt client initialization in a callback
credentials.get(function(err) {
    if(err) {
        console.log(err);
        return;
    }
    var requestUrl = SigV4Utils.getSignedUrl('wss', 'data.iot.us-west-2.amazonaws.com', '/mqtt',
        'iotdevicegateway', 'us-west-2',
        credentials.accessKeyId, credentials.secretAccessKey, credentials.sessionToken);
    console.log('requestUrl:', requestUrl)
    initClient(requestUrl);
});

function init() {
    // console.log("Initializing the function")
}

// Connect the client, subscribe to the drawing topic, and publish a "hey I connected" message
function initClient(requestUrl) {
    var clientId = String(Math.random()).replace('.', '');
    var client = new Paho.MQTT.Client(requestUrl, clientId);
    var connectOptions = {
        onSuccess: function (data) {
            var topic = "test-iot/#";

            // subscribe to the drawing
            client.subscribe(topic);

            // publish a lifecycle event
            data = "Hey I connected"
            var message = new Paho.MQTT.Message("Hello");
            message.destinationName = topic;
            client.send(message);
            console.log("Message sent")
        },
        useSSL: true,
        timeout: 10,
        mqttVersion: 4,
        onFailure: function (err) {
            console.error('connect failed');
            console.error(err);
        },

    }

    client.onMessageArrived =  function (message) {
            console.log("Message arrived")
            try {
                console.log("msg arrived: ");
            } catch (e) {
                console.log("error! " + e);
            }
    }

    client.onConnectionLost =  function(responseObject) {
      console.log(responseObject)
      if (responseObject.errorCode !== 0) {
        console.log("onConnectionLost:"+responseObject.errorMessage);
      }
    }

    client.connect(connectOptions);

}
<|endoftext|>


<commit_before>var url = require('url'),
    fs = require('fs'),
    http = require('http'),
    bee = require('beeline'),
    SC_CLIENT_ID = '1288146c708a6fa789f74748fe960337';

var errorHandler = function (err, response) {
  console.log(err);
  response.writeHead(500);
  response.end();
};

var serveError = function (response) {
  return function (err) {
    errorHandler(err, response);
  };
};

var handleResponse = function (response, callback, errorStatusCode, errorHeaders) {
  var responseHandler = function (res) {
    if (!res.headers.location) {
      res.headers['Content-Length'] = 0;
      errorStatusCode = errorStatusCode || res.statusCode;
      errorHeaders = errorHeaders || res.headers;
      response.writeHead(errorStatusCode, errorHeaders);
      response.end();
    } else {
      callback && callback(res);
    }
  };
  return responseHandler;
};

var writeResponse = function (response) {
  var responseHandler = function (res) {
    response.writeHead(res.statusCode, res.headers);
    res.on('data', function (chunk) {
      response.write(chunk);
    });
    res.on('end', function () {
      response.end();
    });
  };
  return responseHandler;
};

var getRemoteContent = function (response, callback) {
  var responseHandler = function (res) {
    var reqOptions = url.parse(res.headers.location);
    reqOptions = {
      host: reqOptions.host,
      port: 80,
      path: reqOptions.pathname + reqOptions.search,
      headers: {
        'User-Agent': 'AudioJedit'
      }
    };

    var req = http.get(reqOptions, callback);

    req.on('error', serveError(response));

    return req;
  };
  return responseHandler;
};

var getJson = function (response, callbacks) {
  var responseHandler = function (res) {
    res.setEncoding('utf-8');
    var data = '';
    res.on('data', function (chunk) {
      data += chunk;
    });
    res.on('end', function () {
      callbacks[0](data, response, callbacks[1]);
    });
  };
  return responseHandler;
};

var getMp3 = function (track, response, callback) {
  track = JSON.parse(track);
  var reqOptions = url.parse(track.stream_url);
  reqOptions = {
    host: reqOptions.host,
    path: reqOptions.pathname + '?client_id=' + SC_CLIENT_ID,
    headers: {
      'User-Agent': 'AudioJedit'
    }
  };

  var req = http.get(reqOptions, handleResponse(response, callback, 404, { 'Content-Type': 'application/octet-stream' }));

  req.on('error', serveError(response));

  return req;
};

var serveIndex = function (response) {
  return fs.readFile('./public/index.html', function (err, data) {
    if (err) {
      console.log(err);
    } else {
      response.writeHead(200, { 'Content-Type': 'text/html' });
      response.end(data);
    }
  });
};

var serveRemoteContent = function (response, binary) {
  var responseHandler = function (res) {
    if (binary) {
      res.setEncoding('binary');
    }
    var reqOptions = url.parse(res.headers.location);
    reqOptions = {
      host: reqOptions.host,
      port: 80,
      path: reqOptions.pathname + reqOptions.search,
      headers: {
        'User-Agent': 'AudioJedit'
      }
    };

    var req = http.get(reqOptions, writeResponse(response));

    req.on('error', serveError(response));

    return req;
  };
  return responseHandler;
};

// resolves 'resource' from the SC API, and if found, passes it to a callback
var scResolve = function (resource, response, callback) {
  var reqOptions = {
    host: 'api.soundcloud.com',
    port: 80,
    path: '/resolve.json?client_id=' + SC_CLIENT_ID + '&url=http://soundcloud.com/' + resource,
    method: 'GET',
    headers: {
      'User-Agent': 'AudioJedit'
    }
  };

  var req = http.request(reqOptions, handleResponse(response, callback));

  req.on('error', serveError(response));

  req.end();

  return req;
};

var router = bee.route({

  'r`^/public/(.*)`': bee.staticDir('./public/', {
    '.html': 'text/html',
    '.css': 'text/css',
    '.gif': 'image/gif',
    '.png': 'image/png',
    '.ico': 'image/png',
    '.js': 'text/javascript'
  }),

  'r`^/(index(\\.html?)?)?(\\?.*)?$`': function (req, response, matches) {
    return serveIndex(response);
  },

  'r`^/([\\w-_]+)/([\\w-_]+)/audio`': function (req, response, matches) {
    var resource = matches.join('/');

    var callback = getJson(response, [getMp3, serveRemoteContent(response, true)]);

    scResolve(resource, response, getRemoteContent(response, callback));
  },

  'r`^/([\\w-_]+)/([\\w-_]+)(\\.\\w+)?`': function (req, response, matches) {
    var format = matches.filter(Boolean).length == 3 ? matches.pop().substring(1) : 'html';
    var resource = matches.join('/');

    // only json is permitted, else serve index
    if (format != 'json') {
      return serveIndex(response);
    }

    return scResolve(resource, response, serveRemoteContent(response));
  }
});

http.createServer(router).listen(process.env.PORT || 8181);

<commit_msg>remove unneeded binary branch<commit_after>var url = require('url'),
    fs = require('fs'),
    http = require('http'),
    bee = require('beeline'),
    SC_CLIENT_ID = '1288146c708a6fa789f74748fe960337';

var errorHandler = function (err, response) {
  console.log(err);
  response.writeHead(500);
  response.end();
};

var serveError = function (response) {
  return function (err) {
    errorHandler(err, response);
  };
};

var handleResponse = function (response, callback, errorStatusCode, errorHeaders) {
  var responseHandler = function (res) {
    if (!res.headers.location) {
      res.headers['Content-Length'] = 0;
      errorStatusCode = errorStatusCode || res.statusCode;
      errorHeaders = errorHeaders || res.headers;
      response.writeHead(errorStatusCode, errorHeaders);
      response.end();
    } else {
      callback && callback(res);
    }
  };
  return responseHandler;
};

var writeResponse = function (response) {
  var responseHandler = function (res) {
    response.writeHead(res.statusCode, res.headers);
    res.on('data', function (chunk) {
      response.write(chunk);
    });
    res.on('end', function () {
      response.end();
    });
  };
  return responseHandler;
};

var getRemoteContent = function (response, callback) {
  var responseHandler = function (res) {
    var reqOptions = url.parse(res.headers.location);
    reqOptions = {
      host: reqOptions.host,
      port: 80,
      path: reqOptions.pathname + reqOptions.search,
      headers: {
        'User-Agent': 'AudioJedit'
      }
    };

    var req = http.get(reqOptions, callback);

    req.on('error', serveError(response));

    return req;
  };
  return responseHandler;
};

var getJson = function (response, callbacks) {
  var responseHandler = function (res) {
    res.setEncoding('utf-8');
    var data = '';
    res.on('data', function (chunk) {
      data += chunk;
    });
    res.on('end', function () {
      callbacks[0](data, response, callbacks[1]);
    });
  };
  return responseHandler;
};

var getMp3 = function (track, response, callback) {
  track = JSON.parse(track);
  var reqOptions = url.parse(track.stream_url);
  reqOptions = {
    host: reqOptions.host,
    path: reqOptions.pathname + '?client_id=' + SC_CLIENT_ID,
    headers: {
      'User-Agent': 'AudioJedit'
    }
  };

  var req = http.get(reqOptions, handleResponse(response, callback, 404, { 'Content-Type': 'application/octet-stream' }));

  req.on('error', serveError(response));

  return req;
};

var serveIndex = function (response) {
  return fs.readFile('./public/index.html', function (err, data) {
    if (err) {
      console.log(err);
    } else {
      response.writeHead(200, { 'Content-Type': 'text/html' });
      response.end(data);
    }
  });
};

var serveRemoteContent = function (response) {
  var responseHandler = function (res) {
    var reqOptions = url.parse(res.headers.location);
    reqOptions = {
      host: reqOptions.host,
      port: 80,
      path: reqOptions.pathname + reqOptions.search,
      headers: {
        'User-Agent': 'AudioJedit'
      }
    };

    var req = http.get(reqOptions, writeResponse(response));

    req.on('error', serveError(response));

    return req;
  };
  return responseHandler;
};

// resolves 'resource' from the SC API, and if found, passes it to a callback
var scResolve = function (resource, response, callback) {
  var reqOptions = {
    host: 'api.soundcloud.com',
    port: 80,
    path: '/resolve.json?client_id=' + SC_CLIENT_ID + '&url=http://soundcloud.com/' + resource,
    method: 'GET',
    headers: {
      'User-Agent': 'AudioJedit'
    }
  };

  var req = http.request(reqOptions, handleResponse(response, callback));

  req.on('error', serveError(response));

  req.end();

  return req;
};

var router = bee.route({

  'r`^/public/(.*)`': bee.staticDir('./public/', {
    '.html': 'text/html',
    '.css': 'text/css',
    '.gif': 'image/gif',
    '.png': 'image/png',
    '.ico': 'image/png',
    '.js': 'text/javascript'
  }),

  'r`^/(index(\\.html?)?)?(\\?.*)?$`': function (req, response, matches) {
    return serveIndex(response);
  },

  'r`^/([\\w-_]+)/([\\w-_]+)/audio`': function (req, response, matches) {
    var resource = matches.join('/');

    var callback = getJson(response, [getMp3, serveRemoteContent(response)]);

    scResolve(resource, response, getRemoteContent(response, callback));
  },

  'r`^/([\\w-_]+)/([\\w-_]+)(\\.\\w+)?`': function (req, response, matches) {
    var format = matches.filter(Boolean).length == 3 ? matches.pop().substring(1) : 'html';
    var resource = matches.join('/');

    // only json is permitted, else serve index
    if (format != 'json') {
      return serveIndex(response);
    }

    return scResolve(resource, response, serveRemoteContent(response));
  }
});

http.createServer(router).listen(process.env.PORT || 8181);

<|endoftext|>


<commit_before>// Newrelic *must* be the first module loaded. Do not move this require module!
if ( process.env.NEW_RELIC_HOME ) {
  require( "newrelic" );
}

var express = require( "express" ),
    path = require( "path" ),
    helmet = require( "helmet" ),
    nunjucks = require( "nunjucks" ),
    nunjucksEnv = new nunjucks.Environment( new nunjucks.FileSystemLoader( __dirname + "/views" ) ),
    app = express(),
    lessMiddleware = require( "less-middleware" ),
    requirejsMiddleware = require( "requirejs-middleware" ),
    config = require( "./lib/config" ),
    Project,
    filter,
    middleware,
    APP_HOSTNAME = config.hostname,
    WWW_ROOT =  __dirname + "/public",
    i18n = require( "webmaker-i18n" ),
    emulate_s3 = config.S3_EMULATION || !config.S3_KEY,
    messina,
    logger;

nunjucksEnv.addFilter( "instantiate", function( input ) {
    var tmpl = new nunjucks.Template( input );
    return tmpl.render( this.getVariables() );
});

nunjucksEnv.express( app );

app.disable( "x-powered-by" );

app.configure( function() {
  var tmpDir = path.normalize( require( "os" ).tmpDir() + "/mozilla.butter/" );

  if ( config.ENABLE_GELF_LOGS ) {
    messina = require( "messina" );
    logger = messina( "popcorn.webmaker.org-" + config.NODE_ENV || "development" );
    logger.init();
    app.use( logger.middleware() );
  } else {
    app.use( express.logger( config.logger ) );
  }

  app.use( "/static/bower", express.static( path.join( __dirname, "/bower_components" ), {
    maxAge: "31556952000" // one year
  }));
  app.use(helmet.iexss());
  app.use(helmet.contentTypeOptions());
  if ( !!config.FORCE_SSL ) {
    app.use( helmet.hsts() );
    app.enable( "trust proxy" );
  }
  app.use( express.compress() )
    .use( lessMiddleware({
      once: config.OPTIMIZE_CSS,
      dest: tmpDir,
      src: WWW_ROOT,
      compress: config.OPTIMIZE_CSS,
      yuicompress: config.OPTIMIZE_CSS,
      optimization: config.OPTIMIZE_CSS ? 0 : 2
    }))
    .use( requirejsMiddleware({
      src: WWW_ROOT,
      dest: tmpDir,
      debug: config.DEBUG,
      once: config.OPTIMIZE_JS,
      modules: {
        "/src/butter.js": {
          include: [ "butter" ],
          mainConfigFile: WWW_ROOT + "/src/popcorn.js",
          paths: {
            "make-api": path.resolve( __dirname, "node_modules/makeapi-client/src/make-api" ),
            "sso-include": path.resolve( __dirname, "node_modules/webmaker-sso/include" )
          }
        },
        "/src/embed.js": {
          include: [ "embed" ],
          mainConfigFile: WWW_ROOT + "/src/popcorn.js"
        }
      },
      defaults: {
        name: "../external/require/require",
        baseUrl: WWW_ROOT + "/src/",
        findNestedDependencies: true,
        optimize: "none",
        preserveLicenseComments: false,
        paths: {
          "localized": path.resolve( __dirname, "bower_components/webmaker-i18n/localized" ),
          "jquery": path.resolve( __dirname, "bower_components/jquery/jquery.min" ),
          "jquery-ui": path.resolve( __dirname, "bower_components/jquery-ui/ui/jquery-ui" ),
          "farbtastic": path.resolve( __dirname, "bower_components/farbtastic/farbtastic" ),
          "WebmakerUI": path.resolve( __dirname, "bower_components/webmaker-ui/ui" ),
          "webmaker-ui-fragments": path.resolve( __dirname, "bower_components/webmaker-ui/webmaker-ui-fragments" )
        },
        wrap: {
          startFile: __dirname + "/tools/wrap.start",
          endFile: __dirname + "/tools/wrap.end"
        }
      }
    }))
    .use( express.static( tmpDir, JSON.parse( JSON.stringify( config.staticMiddleware ) ) ) )
    .use( express.static( WWW_ROOT, JSON.parse( JSON.stringify( config.staticMiddleware ) ) ) );

  // Setup locales with i18n
  app.use( i18n.middleware({
    supported_languages: config.SUPPORTED_LANGS,
    default_lang: "en-US",
    mappings: require("webmaker-locale-mapping"),
    translation_directory: path.resolve( __dirname, "locale" )
  }));

  app.locals({
    config: {
      app_hostname: APP_HOSTNAME,
      audience: config.AUDIENCE,
      ga_account: config.GA_ACCOUNT,
      ga_domain: config.GA_DOMAIN,
      jwplayer_key: config.JWPLAYER_KEY,
      make_endpoint: config.MAKE_ENDPOINT,
      node_hubble_endpoint: config.NODE_HUBBLE_ENDPOINT,
      user_bar: config.USER_BAR,
      sync_limit: config.SYNC_LIMIT
    },
    supportedLanguages: i18n.getLanguages(),
    listDropdownLang: i18n.getSupportLanguages()
  });

  app.use( express.json() )
    .use( express.urlencoded() )
    .use( express.cookieParser() )
    .use( express.cookieSession( config.session ) )
    .use( express.csrf() )
    .use( helmet.xframe() )
    /* Show Zeus who's boss
     * This only affects requests under /api and /persona, not static files
     * because the static file writes the response header before we hit this middleware
     */
    .use( function( req, res, next ) {
      res.header( "Cache-Control", "no-store" );
      return next();
    })
    .use( app.router )
    /*jslint unused: false */
    .use( function( err, req, res, next ) {
      middleware.errorHandler( err, req, res );
    })
    /*jslint unused: false */
    .use( function( req, res, next ) {
      var err = {
        message: req.gettext( "This page doesn't exist" ),
        status: 404
      };

      middleware.errorHandler( err, req, res );
    });

  Project = require( "./lib/project" )( config.database );
  filter = require( "./lib/filter" )( Project.isDBOnline );
});

require( "./lib/loginapi" )( app, {
  audience: config.AUDIENCE,
  loginURL: config.LOGIN_SERVER_URL_WITH_AUTH
});

require( "webmaker-mediasync" )( app, {
  serviceKeys: {
    soundcloud: config.SYNC_SOUNDCLOUD,
    flickr: config.SYNC_FLICKR,
    giphy: config.SYNC_GIPHY
  },
  limit: config.SYNC_LIMIT
});

middleware = require( "./lib/middleware" );

var routes = require( "./routes" );

app.param( "myproject", middleware.loadOwnProject( Project ));
app.param( "anyproject", middleware.loadAnyProject( Project ));

app.post( "/api/publish/:myproject",
  filter.isLoggedIn, filter.isStorageAvailable,
  routes.api.publish
);

app.get( "/dashboard/:lang", function( req, res ) {
  res.redirect( config.AUDIENCE + "/" + req.params.lang + "/me?app=popcorn" );
});

app.get( "/", routes.pages.editor );
app.get( "/index.html", routes.pages.editor );
app.get( "/editor", routes.pages.editor );
app.get( "/editor/:id", routes.pages.editor );
app.get( "/editor/:id/edit", routes.pages.editor );
app.get( "/editor/:id/remix", routes.pages.editor );
app.get( "/templates/basic", routes.pages.editor );
app.get( "/templates/basic/index.html", routes.pages.editor );

app.get( "/external/make-api.js", function( req, res ) {
  res.sendfile( path.resolve( __dirname, "node_modules/makeapi-client/src/make-api.js" ) );
});
app.get( "/external/sso-include.js", function( req, res ) {
  res.sendfile( path.resolve( __dirname, "node_modules/webmaker-sso/include.js" ) );
});
app.get( "/external/jwplayer.js", function( req, res ) {
  res.redirect( "//jwpsrv.com/library/" + app.locals.config.jwplayer_key + ".js" );
});

// Project Endpoints
app.post( "/api/project/:id?",
  filter.isLoggedIn,
  filter.isStorageAvailable,
  routes.api.synchronize( Project ),
  routes.make.synchronize
);

app.post( "/api/delete/:myproject", filter.isLoggedIn, filter.isStorageAvailable, routes.make.remove, routes.api.remove );
app.get( "/api/remix/:anyproject", filter.isStorageAvailable, routes.api.remix, routes.api.projectResponse( Project ) );
app.get( "/api/project/:anyproject", filter.isStorageAvailable, routes.api.find, routes.api.projectResponse( Project ) );

// Firehose Endpoints
//app.get( "/api/project/:id/remixes", filter.isStorageAvailable, filter.crossOriginAccessible, routes.firehose.remixes );
//app.get( "/api/projects/recentlyUpdated/:limit?", filter.isStorageAvailable, filter.crossOriginAccessible, routes.firehose.recentlyUpdated );
//app.get( "/api/projects/recentlyCreated/:limit?", filter.isStorageAvailable, filter.crossOriginAccessible, //routes.firehose.recentlyCreated );
//app.get( "/api/projects/recentlyRemixed/:limit?", filter.isStorageAvailable, filter.crossOriginAccessible, routes.firehose.recentlyRemixed );

app.post( "/crash", routes.api.crash );
app.post( "/feedback", routes.api.feedback );

app.get( "/healthcheck", routes.api.healthcheck );

// NOTE:
// This endpoint is publicly accessible with CORS enabled. Be careful of the information
// that is attached to it. IE, avoid putting API keys and other more sensitive information
// here.
app.get( "/api/butterconfig", middleware.crossOrigin, function( req, res ) {
  res.json({
    "audience": app.locals.config.audience,
    "make_endpoint": app.locals.config.make_endpoint,
    "node_hubble_endpoint": app.locals.config.node_hubble_endpoint,
    "user_bar": app.locals.config.user_bar,
    "sync_limit": app.locals.config.sync_limit
  });
});

// routes to be used in text!
app.get( "/layouts/header.html", function( req, res ) {
  res.render( "/layouts/header.html", {
    user_bar: app.locals.config.user_bar,
    audience: app.locals.config.audience,
    togetherjsEnabled: config.TOGETHERJS_ENABLED
  });
});

app.get( "/layouts/status-area.html", routes.path( "/layouts/status-area.html" ) );
app.get( "/layouts/media-editor.html", routes.path( "/layouts/media-editor.html" ) );
app.get( "/layouts/controls.html", routes.path( "/layouts/controls.html" ) );
app.get( "/layouts/media-instance.html", routes.path( "/layouts/media-instance.html" ) );
app.get( "/layouts/project-editor.html", routes.path( "/layouts/project-editor.html" ) );
app.get( "/layouts/editor-area.html", routes.path( "/layouts/editor-area.html" ) );
app.get( "/layouts/plugin-list-editor.html", routes.path( "/layouts/plugin-list-editor.html" ) );
app.get( "/layouts/sequencer-editor.html", routes.path( "/layouts/sequencer-editor.html" ) );
app.get( "/layouts/trackevent-editor-defaults.html", routes.path( "/layouts/trackevent-editor-defaults.html" ) );
app.get( "/dialog/dialogs/backup.html", routes.path( "/dialog/dialogs/backup.html" ) );
app.get( "/dialog/dialogs/crash.html", routes.path( "/dialog/dialogs/crash.html" ) );
app.get( "/dialog/dialogs/delete-track-events.html", routes.path( "/dialog/dialogs/delete-track-events.html" ) );
app.get( "/dialog/dialogs/delete-track.html", routes.path( "/dialog/dialogs/delete-track.html" ) );
app.get( "/dialog/dialogs/error-message.html", routes.path( "/dialog/dialogs/error-message.html" ) );
app.get( "/dialog/dialogs/feedback.html", routes.path( "/dialog/dialogs/feedback.html" ) );
app.get( "/dialog/dialogs/first-run.html", routes.path( "/dialog/dialogs/first-run.html" ) );
app.get( "/dialog/dialogs/track-data.html", routes.path( "/dialog/dialogs/track-data.html" ) );
app.get( "/dialog/dialogs/remove-project.html", routes.path( "/dialog/dialogs/remove-project.html" ) );
app.get( "/editors/default.html", routes.path( "/editor/default.html" ) );
app.get( "/templates/assets/editors/googlemap/googlemap-editor.html", routes.path( "/plugins/googlemap-editor.html" ) );
app.get( "/templates/assets/editors/popup/popup-editor.html", routes.path( "/plugins/popup-editor.html" ) );
app.get( "/templates/assets/editors/image/image-editor.html", routes.path( "/plugins/image-editor.html" ) );
app.get( "/templates/assets/editors/text/text-editor.html", routes.path( "/plugins/text-editor.html" ) );
app.get( "/templates/assets/editors/wikipedia/wikipedia-editor.html", routes.path( "/plugins/wikipedia-editor.html" ) );
app.get( "/templates/assets/editors/sketchfab/sketchfab-editor.html", routes.path( "/plugins/sketchfab-editor.html" ) );

// Localized Strings
app.get( "/strings/:lang?", middleware.crossOrigin, i18n.stringsRoute( "en-US" ) );

app.put( "/api/image", middleware.processForm, filter.isImage, routes.api.image );

app.listen( config.PORT, function() {
  console.log( "HTTP Server started on " + APP_HOSTNAME );
  console.log( "Press Ctrl+C to stop" );
});

// If we're in running in emulated S3 mode, run a mini
// server for serving up the "s3" published content.
if ( emulate_s3 ) {
  require( "mox-server" ).runServer( config.MOX_PORT || 12319 );
}
<commit_msg>[#934844] font awesome fonts are set to work cross domain.<commit_after>// Newrelic *must* be the first module loaded. Do not move this require module!
if ( process.env.NEW_RELIC_HOME ) {
  require( "newrelic" );
}

var express = require( "express" ),
    path = require( "path" ),
    helmet = require( "helmet" ),
    nunjucks = require( "nunjucks" ),
    nunjucksEnv = new nunjucks.Environment( new nunjucks.FileSystemLoader( __dirname + "/views" ) ),
    app = express(),
    lessMiddleware = require( "less-middleware" ),
    requirejsMiddleware = require( "requirejs-middleware" ),
    config = require( "./lib/config" ),
    Project,
    filter,
    middleware,
    APP_HOSTNAME = config.hostname,
    WWW_ROOT =  __dirname + "/public",
    i18n = require( "webmaker-i18n" ),
    emulate_s3 = config.S3_EMULATION || !config.S3_KEY,
    messina,
    logger;

nunjucksEnv.addFilter( "instantiate", function( input ) {
    var tmpl = new nunjucks.Template( input );
    return tmpl.render( this.getVariables() );
});

nunjucksEnv.express( app );

app.disable( "x-powered-by" );

app.configure( function() {
  var tmpDir = path.normalize( require( "os" ).tmpDir() + "/mozilla.butter/" );

  if ( config.ENABLE_GELF_LOGS ) {
    messina = require( "messina" );
    logger = messina( "popcorn.webmaker.org-" + config.NODE_ENV || "development" );
    logger.init();
    app.use( logger.middleware() );
  } else {
    app.use( express.logger( config.logger ) );
  }

  app.use( function( req, res, next ) {
    var allowed = [ "/static/bower/font-awesome/font/" ];
    for ( var i = 0; i < allowed.length; i++ ) {
      if ( req.url.substring( 0, allowed[ i ].length ) === allowed[ i ] ) {
        res.header( "Access-Control-Allow-Origin", "*" );
      }
    }
    next();
  });
  app.use( "/static/bower", express.static( path.join( __dirname, "/bower_components" ), {
    maxAge: "31556952000" // one year
  }));
  app.use(helmet.iexss());
  app.use(helmet.contentTypeOptions());
  if ( !!config.FORCE_SSL ) {
    app.use( helmet.hsts() );
    app.enable( "trust proxy" );
  }
  app.use( express.compress() )
    .use( lessMiddleware({
      once: config.OPTIMIZE_CSS,
      dest: tmpDir,
      src: WWW_ROOT,
      compress: config.OPTIMIZE_CSS,
      yuicompress: config.OPTIMIZE_CSS,
      optimization: config.OPTIMIZE_CSS ? 0 : 2
    }))
    .use( requirejsMiddleware({
      src: WWW_ROOT,
      dest: tmpDir,
      debug: config.DEBUG,
      once: config.OPTIMIZE_JS,
      modules: {
        "/src/butter.js": {
          include: [ "butter" ],
          mainConfigFile: WWW_ROOT + "/src/popcorn.js",
          paths: {
            "make-api": path.resolve( __dirname, "node_modules/makeapi-client/src/make-api" ),
            "sso-include": path.resolve( __dirname, "node_modules/webmaker-sso/include" )
          }
        },
        "/src/embed.js": {
          include: [ "embed" ],
          mainConfigFile: WWW_ROOT + "/src/popcorn.js"
        }
      },
      defaults: {
        name: "../external/require/require",
        baseUrl: WWW_ROOT + "/src/",
        findNestedDependencies: true,
        optimize: "none",
        preserveLicenseComments: false,
        paths: {
          "localized": path.resolve( __dirname, "bower_components/webmaker-i18n/localized" ),
          "jquery": path.resolve( __dirname, "bower_components/jquery/jquery.min" ),
          "jquery-ui": path.resolve( __dirname, "bower_components/jquery-ui/ui/jquery-ui" ),
          "farbtastic": path.resolve( __dirname, "bower_components/farbtastic/farbtastic" ),
          "WebmakerUI": path.resolve( __dirname, "bower_components/webmaker-ui/ui" ),
          "webmaker-ui-fragments": path.resolve( __dirname, "bower_components/webmaker-ui/webmaker-ui-fragments" )
        },
        wrap: {
          startFile: __dirname + "/tools/wrap.start",
          endFile: __dirname + "/tools/wrap.end"
        }
      }
    }))
    .use( express.static( tmpDir, JSON.parse( JSON.stringify( config.staticMiddleware ) ) ) )
    .use( express.static( WWW_ROOT, JSON.parse( JSON.stringify( config.staticMiddleware ) ) ) );

  // Setup locales with i18n
  app.use( i18n.middleware({
    supported_languages: config.SUPPORTED_LANGS,
    default_lang: "en-US",
    mappings: require("webmaker-locale-mapping"),
    translation_directory: path.resolve( __dirname, "locale" )
  }));

  app.locals({
    config: {
      app_hostname: APP_HOSTNAME,
      audience: config.AUDIENCE,
      ga_account: config.GA_ACCOUNT,
      ga_domain: config.GA_DOMAIN,
      jwplayer_key: config.JWPLAYER_KEY,
      make_endpoint: config.MAKE_ENDPOINT,
      node_hubble_endpoint: config.NODE_HUBBLE_ENDPOINT,
      user_bar: config.USER_BAR,
      sync_limit: config.SYNC_LIMIT
    },
    supportedLanguages: i18n.getLanguages(),
    listDropdownLang: i18n.getSupportLanguages()
  });

  app.use( express.json() )
    .use( express.urlencoded() )
    .use( express.cookieParser() )
    .use( express.cookieSession( config.session ) )
    .use( express.csrf() )
    .use( helmet.xframe() )
    /* Show Zeus who's boss
     * This only affects requests under /api and /persona, not static files
     * because the static file writes the response header before we hit this middleware
     */
    .use( function( req, res, next ) {
      res.header( "Cache-Control", "no-store" );
      return next();
    })
    .use( app.router )
    /*jslint unused: false */
    .use( function( err, req, res, next ) {
      middleware.errorHandler( err, req, res );
    })
    /*jslint unused: false */
    .use( function( req, res, next ) {
      var err = {
        message: req.gettext( "This page doesn't exist" ),
        status: 404
      };

      middleware.errorHandler( err, req, res );
    });

  Project = require( "./lib/project" )( config.database );
  filter = require( "./lib/filter" )( Project.isDBOnline );
});

require( "./lib/loginapi" )( app, {
  audience: config.AUDIENCE,
  loginURL: config.LOGIN_SERVER_URL_WITH_AUTH
});

require( "webmaker-mediasync" )( app, {
  serviceKeys: {
    soundcloud: config.SYNC_SOUNDCLOUD,
    flickr: config.SYNC_FLICKR,
    giphy: config.SYNC_GIPHY
  },
  limit: config.SYNC_LIMIT
});

middleware = require( "./lib/middleware" );

var routes = require( "./routes" );

app.param( "myproject", middleware.loadOwnProject( Project ));
app.param( "anyproject", middleware.loadAnyProject( Project ));

app.post( "/api/publish/:myproject",
  filter.isLoggedIn, filter.isStorageAvailable,
  routes.api.publish
);

app.get( "/dashboard/:lang", function( req, res ) {
  res.redirect( config.AUDIENCE + "/" + req.params.lang + "/me?app=popcorn" );
});

app.get( "/", routes.pages.editor );
app.get( "/index.html", routes.pages.editor );
app.get( "/editor", routes.pages.editor );
app.get( "/editor/:id", routes.pages.editor );
app.get( "/editor/:id/edit", routes.pages.editor );
app.get( "/editor/:id/remix", routes.pages.editor );
app.get( "/templates/basic", routes.pages.editor );
app.get( "/templates/basic/index.html", routes.pages.editor );

app.get( "/external/make-api.js", function( req, res ) {
  res.sendfile( path.resolve( __dirname, "node_modules/makeapi-client/src/make-api.js" ) );
});
app.get( "/external/sso-include.js", function( req, res ) {
  res.sendfile( path.resolve( __dirname, "node_modules/webmaker-sso/include.js" ) );
});
app.get( "/external/jwplayer.js", function( req, res ) {
  res.redirect( "//jwpsrv.com/library/" + app.locals.config.jwplayer_key + ".js" );
});

// Project Endpoints
app.post( "/api/project/:id?",
  filter.isLoggedIn,
  filter.isStorageAvailable,
  routes.api.synchronize( Project ),
  routes.make.synchronize
);

app.post( "/api/delete/:myproject", filter.isLoggedIn, filter.isStorageAvailable, routes.make.remove, routes.api.remove );
app.get( "/api/remix/:anyproject", filter.isStorageAvailable, routes.api.remix, routes.api.projectResponse( Project ) );
app.get( "/api/project/:anyproject", filter.isStorageAvailable, routes.api.find, routes.api.projectResponse( Project ) );

// Firehose Endpoints
//app.get( "/api/project/:id/remixes", filter.isStorageAvailable, filter.crossOriginAccessible, routes.firehose.remixes );
//app.get( "/api/projects/recentlyUpdated/:limit?", filter.isStorageAvailable, filter.crossOriginAccessible, routes.firehose.recentlyUpdated );
//app.get( "/api/projects/recentlyCreated/:limit?", filter.isStorageAvailable, filter.crossOriginAccessible, //routes.firehose.recentlyCreated );
//app.get( "/api/projects/recentlyRemixed/:limit?", filter.isStorageAvailable, filter.crossOriginAccessible, routes.firehose.recentlyRemixed );

app.post( "/crash", routes.api.crash );
app.post( "/feedback", routes.api.feedback );

app.get( "/healthcheck", routes.api.healthcheck );

// NOTE:
// This endpoint is publicly accessible with CORS enabled. Be careful of the information
// that is attached to it. IE, avoid putting API keys and other more sensitive information
// here.
app.get( "/api/butterconfig", middleware.crossOrigin, function( req, res ) {
  res.json({
    "audience": app.locals.config.audience,
    "make_endpoint": app.locals.config.make_endpoint,
    "node_hubble_endpoint": app.locals.config.node_hubble_endpoint,
    "user_bar": app.locals.config.user_bar,
    "sync_limit": app.locals.config.sync_limit
  });
});

// routes to be used in text!
app.get( "/layouts/header.html", function( req, res ) {
  res.render( "/layouts/header.html", {
    user_bar: app.locals.config.user_bar,
    audience: app.locals.config.audience,
    togetherjsEnabled: config.TOGETHERJS_ENABLED
  });
});

app.get( "/layouts/status-area.html", routes.path( "/layouts/status-area.html" ) );
app.get( "/layouts/media-editor.html", routes.path( "/layouts/media-editor.html" ) );
app.get( "/layouts/controls.html", routes.path( "/layouts/controls.html" ) );
app.get( "/layouts/media-instance.html", routes.path( "/layouts/media-instance.html" ) );
app.get( "/layouts/project-editor.html", routes.path( "/layouts/project-editor.html" ) );
app.get( "/layouts/editor-area.html", routes.path( "/layouts/editor-area.html" ) );
app.get( "/layouts/plugin-list-editor.html", routes.path( "/layouts/plugin-list-editor.html" ) );
app.get( "/layouts/sequencer-editor.html", routes.path( "/layouts/sequencer-editor.html" ) );
app.get( "/layouts/trackevent-editor-defaults.html", routes.path( "/layouts/trackevent-editor-defaults.html" ) );
app.get( "/dialog/dialogs/backup.html", routes.path( "/dialog/dialogs/backup.html" ) );
app.get( "/dialog/dialogs/crash.html", routes.path( "/dialog/dialogs/crash.html" ) );
app.get( "/dialog/dialogs/delete-track-events.html", routes.path( "/dialog/dialogs/delete-track-events.html" ) );
app.get( "/dialog/dialogs/delete-track.html", routes.path( "/dialog/dialogs/delete-track.html" ) );
app.get( "/dialog/dialogs/error-message.html", routes.path( "/dialog/dialogs/error-message.html" ) );
app.get( "/dialog/dialogs/feedback.html", routes.path( "/dialog/dialogs/feedback.html" ) );
app.get( "/dialog/dialogs/first-run.html", routes.path( "/dialog/dialogs/first-run.html" ) );
app.get( "/dialog/dialogs/track-data.html", routes.path( "/dialog/dialogs/track-data.html" ) );
app.get( "/dialog/dialogs/remove-project.html", routes.path( "/dialog/dialogs/remove-project.html" ) );
app.get( "/editors/default.html", routes.path( "/editor/default.html" ) );
app.get( "/templates/assets/editors/googlemap/googlemap-editor.html", routes.path( "/plugins/googlemap-editor.html" ) );
app.get( "/templates/assets/editors/popup/popup-editor.html", routes.path( "/plugins/popup-editor.html" ) );
app.get( "/templates/assets/editors/image/image-editor.html", routes.path( "/plugins/image-editor.html" ) );
app.get( "/templates/assets/editors/text/text-editor.html", routes.path( "/plugins/text-editor.html" ) );
app.get( "/templates/assets/editors/wikipedia/wikipedia-editor.html", routes.path( "/plugins/wikipedia-editor.html" ) );
app.get( "/templates/assets/editors/sketchfab/sketchfab-editor.html", routes.path( "/plugins/sketchfab-editor.html" ) );

// Localized Strings
app.get( "/strings/:lang?", middleware.crossOrigin, i18n.stringsRoute( "en-US" ) );

app.put( "/api/image", middleware.processForm, filter.isImage, routes.api.image );

app.listen( config.PORT, function() {
  console.log( "HTTP Server started on " + APP_HOSTNAME );
  console.log( "Press Ctrl+C to stop" );
});

// If we're in running in emulated S3 mode, run a mini
// server for serving up the "s3" published content.
if ( emulate_s3 ) {
  require( "mox-server" ).runServer( config.MOX_PORT || 12319 );
}
<|endoftext|>


<commit_before>module.exports = exports = {
    'affordance':require('./affordance.js'),
    'generator':require('./generator.js'),
    'method':require('./method.js'),
    'relations':require('./relations.js'),
    'resource':require('./resource.js'),
    'resources':require('./resources.js')
};
<commit_msg>Cleanup.<commit_after>/**
 * Instantiate my own in-line exports object. This may be redundant, but it makes things verbose.
 * @type {Object}
 */
module.exports = exports = {};

/**
 * Every returned exports object from the require invocations should return a convenience instantiation method for the
 * respective module-scoped constructor function.
 */
module.exports.affordance = exports.affordance = require('./affordance.js');
module.exports.generator = exports.generator = require('./generator.js');
module.exports.method = exports.method = require('./method.js');
module.exports.relations = exports.relations = require('./relations.js');
module.exports.resource = exports.resource = require('./resource.js');
module.exports.resources = exports.resources = require('./resources.js');
<|endoftext|>


<commit_before>import map from 'lodash.map'
import RawSource from 'webpack-sources/lib/RawSource'
import imagemin from 'imagemin'
import imageminPngquant from 'imagemin-pngquant'
import imageminOptipng from 'imagemin-optipng'
import imageminGifsicle from 'imagemin-gifsicle'
import imageminJpegtran from 'imagemin-jpegtran'
import imageminSvgo from 'imagemin-svgo'
import { cpus } from 'os'
import createThrottle from 'async-throttle'

function ImageminPlugin (options = {}) {
  // I love ES2015!
  const {
    disable = false,
    optipng = {
      optimizationLevel: 3
    },
    gifsicle = {
      optimizationLevel: 1
    },
    jpegtran = {
      progressive: false
    },
    svgo = {},
    pngquant = null,
    maxConcurrency = cpus().length,
    plugins = []
  } = options

  this.options = {
    disable,
    maxConcurrency,
    imageminOptions: {
      plugins: []
    }
  }

  // As long as the options aren't `null` then include the plugin. Let the destructuring above
  // control whether the plugin is included by default or not.
  for (let [plugin, pluginOptions] of [
    [imageminOptipng, optipng],
    [imageminGifsicle, gifsicle],
    [imageminJpegtran, jpegtran],
    [imageminSvgo, svgo],
    [imageminPngquant, pngquant]
  ]) {
    if (pluginOptions !== null) {
      this.options.imageminOptions.plugins.push(plugin(pluginOptions))
    }
  }

  // And finally, add any plugins that they pass in the options to the internal plugins array
  this.options.imageminOptions.plugins.push(...plugins)
}

ImageminPlugin.prototype.apply = function (compiler) {
  // If disabled, short-circuit here and just return
  if (this.options.disable === true) return null

  // Access the assets once they have been assembled
  compiler.plugin('emit', async (compilation, callback) => {
    const throttle = createThrottle(this.options.maxConcurrency)

    try {
      await Promise.all(map(compilation.assets, (asset, filename) => throttle(async () => {
        compilation.assets[filename] = await optimizeImage(asset, this.options.imageminOptions)
      })))

      // At this point everything is done, so call the callback without anything in it
      callback()
    } catch (err) {
      callback(err)
    }
  })
}

export default ImageminPlugin

async function optimizeImage (asset, imageminOptions) {
  // Grab the orig source and size
  const assetSource = asset.source()
  const assetOrigSize = asset.size()

  // Ensure that the contents i have are in the form of a buffer
  const assetContents = (Buffer.isBuffer(assetSource) ? assetSource : new Buffer(assetSource, 'utf8'))

  // Await for imagemin to do the compression
  const optimizedAssetContents = await imagemin.buffer(assetContents, imageminOptions)

  // If the optimization actually produced a smaller file, then return the optimized version
  if (optimizedAssetContents.length < assetOrigSize) {
    return new RawSource(optimizedAssetContents)
  } else {
    // otherwize return the orignal
    return asset
  }
}
<commit_msg>Performance: optimize only images.<commit_after>import map from 'lodash.map'
import RawSource from 'webpack-sources/lib/RawSource'
import imagemin from 'imagemin'
import imageminPngquant from 'imagemin-pngquant'
import imageminOptipng from 'imagemin-optipng'
import imageminGifsicle from 'imagemin-gifsicle'
import imageminJpegtran from 'imagemin-jpegtran'
import imageminSvgo from 'imagemin-svgo'
import path from 'path'
import { cpus } from 'os'
import createThrottle from 'async-throttle'

function ImageminPlugin (options = {}) {
  // I love ES2015!
  const {
    disable = false,
    optipng = {
      optimizationLevel: 3
    },
    gifsicle = {
      optimizationLevel: 1
    },
    jpegtran = {
      progressive: false
    },
    svgo = {},
    pngquant = null,
    maxConcurrency = cpus().length,
    plugins = []
  } = options

  this.options = {
    disable,
    maxConcurrency,
    imageminOptions: {
      plugins: []
    }
  }

  // As long as the options aren't `null` then include the plugin. Let the destructuring above
  // control whether the plugin is included by default or not.
  for (let [plugin, pluginOptions] of [
    [imageminOptipng, optipng],
    [imageminGifsicle, gifsicle],
    [imageminJpegtran, jpegtran],
    [imageminSvgo, svgo],
    [imageminPngquant, pngquant]
  ]) {
    if (pluginOptions !== null) {
      this.options.imageminOptions.plugins.push(plugin(pluginOptions))
    }
  }

  // And finally, add any plugins that they pass in the options to the internal plugins array
  this.options.imageminOptions.plugins.push(...plugins)
}

ImageminPlugin.prototype.apply = function (compiler) {
  // If disabled, short-circuit here and just return
  if (this.options.disable === true) return null

  const validExts = ['.jpg', '.jpeg', '.png', '.gif', '.svg']

  // Access the assets once they have been assembled
  compiler.plugin('emit', async (compilation, callback) => {
    const throttle = createThrottle(this.options.maxConcurrency)

    try {
      await Promise.all(map(compilation.assets, (asset, filename) => throttle(async () => {
        if (validExts.indexOf(path.extname(filename).toLowerCase()) === -1) return

        compilation.assets[filename] = await optimizeImage(asset, this.options.imageminOptions)
      })))

      // At this point everything is done, so call the callback without anything in it
      callback()
    } catch (err) {
      callback(err)
    }
  })
}

export default ImageminPlugin

async function optimizeImage (asset, imageminOptions) {
  // Grab the orig source and size
  const assetSource = asset.source()
  const assetOrigSize = asset.size()

  // Ensure that the contents i have are in the form of a buffer
  const assetContents = (Buffer.isBuffer(assetSource) ? assetSource : new Buffer(assetSource, 'utf8'))

  // Await for imagemin to do the compression
  const optimizedAssetContents = await imagemin.buffer(assetContents, imageminOptions)

  // If the optimization actually produced a smaller file, then return the optimized version
  if (optimizedAssetContents.length < assetOrigSize) {
    return new RawSource(optimizedAssetContents)
  } else {
    // otherwize return the orignal
    return asset
  }
}
<|endoftext|>


<commit_before>'use strict';

/*eslint no-script-url:0 */

import createReactClass from 'create-react-class';
import ReactDOM from 'react-dom';
import cx from 'classnames';

import TagTranslator from './tag-translator';
import _ from '@/src/undash';
import { keyCodes, ref } from '@/src/utils';
import HelperMixin from '@/src/mixins/helper';

/** @jsx jsx */
import jsx from '@/src/jsx';

const toString = function(value) {
  if (_.isUndefined(value) || _.isNull(value)) {
    return '';
  }
  return String(value);
};

/*
   Editor for tagged text. Renders text like "hello {{firstName}}"
   with replacement labels rendered in a pill box. Designed to load
   quickly when many separate instances of it are on the same
   page.

   Uses CodeMirror to edit text. To save memory the CodeMirror node is
   instantiated when the user moves the mouse into the edit area.
   Initially a read-only view using a simple div is shown.
 */
export default createReactClass({
  displayName: 'PrettyTextInput',

  mixins: [HelperMixin],

  componentDidMount: function() {
    this.createEditor();
  },

  componentDidUpdate: function(prevProps, prevState) {
    const hasReplaceChoicesChanged = !_.isEqual(
      prevProps.replaceChoices,
      this.props.replaceChoices
    );
    const hasCodeMirrorModeChanged =
      prevState.codeMirrorMode !== this.state.codeMirrorMode;
    if (hasCodeMirrorModeChanged || hasReplaceChoicesChanged) {
      // Changed from code mirror mode to read only mode or vice versa,
      // so setup the other editor.
      this.createEditor();
    }
    this.updateEditor();
  },

  componentWillUnmount: function() {
    if (this.state.codeMirrorMode) {
      this.removeCodeMirrorEditor();
    }
  },

  getInitialState: function() {
    const selectedChoices = this.props.selectedChoices;
    const replaceChoices = this.props.replaceChoices;
    const translator = TagTranslator(
      selectedChoices.concat(replaceChoices),
      this.props.config.humanize
    );

    return {
      // With number values, onFocus never fires, which means it stays read-only. So convert to string.
      value: toString(this.props.value),
      codeMirrorMode: false,
      isChoicesOpen: false,
      replaceChoices,
      translator,
      hasChanged: false,
    };
  },

  componentWillReceiveProps: function(nextProps) {
    // If we're debouncing a change, then we should just ignore this props change,
    // because there will be another when we hit the trailing edge of the debounce.
    if (this.isDebouncingCodeMirrorChange) {
      return;
    }

    const selectedChoices = nextProps.selectedChoices;
    const replaceChoices = nextProps.replaceChoices;
    const nextState = {
      replaceChoices,
      translator: TagTranslator(
        selectedChoices.concat(replaceChoices),
        this.props.config.humanize
      ),
    };

    // Not sure what the null/undefined checks are here for, but changed from falsey which was breaking.
    if (
      this.state.value !== nextProps.value &&
      !_.isUndefined(nextProps.value) &&
      nextProps.value !== null
    ) {
      nextState.value = toString(nextProps.value);
      if (this.state.hasChanged === false) {
        nextState.hasChanged = true;
      }
    }

    this.setState(nextState);
  },

  onChange: function(newValue) {
    this.props.onChange(newValue);
  },

  handleChoiceSelection: function(key, event) {
    const selectChoice = () => {
      const pos = this.state.selectedTagPos;
      const tag = '{{' + key + '}}';

      this.isInserting = true;
      if (pos) {
        this.codeMirror.replaceRange(
          tag,
          { line: pos.line, ch: pos.start },
          { line: pos.line, ch: pos.stop }
        );
      } else {
        this.codeMirror.replaceSelection(tag, 'end');
      }
      this.isInserting = false;
      this.codeMirror.focus();

      this.setState({ isChoicesOpen: false, selectedTagPos: null });
    };
    if (this.state.codeMirrorMode) {
      selectChoice();
    } else if (this.props.readOnly) {
      // hackety hack to stop dropdown choices from toggling
      event.stopPropagation();
      this.isInserting = true;
      this.onChange('{{' + key + '}}');
      this.isInserting = false;
      this.setState({ isChoicesOpen: false });
    } else {
      this.switchToCodeMirror(selectChoice);
    }
  },

  onFocusWrapper: function() {
    this.switchToCodeMirror(() => {
      this.codeMirror.focus();
      this.codeMirror.setCursor(this.codeMirror.lineCount(), 0);
    });
  },

  focus() {
    if (this.codeMirror) {
      this.focusCodeMirror();
    } else {
      this.switchToCodeMirror(() => {
        this.focusCodeMirror();
      });
    }
  },

  focusCodeMirror() {
    if (this.codeMirror) {
      this.codeMirror.focus();
    }
  },

  onFocusCodeMirror() {
    this.setState({ hasFocus: true });
    this.props.onFocus();
  },

  onBlur() {
    this.setState({ hasFocus: false }, this.props.onBlur);
  },

  insertBtn: function() {
    if (
      this.props.readOnly ||
      (this.isReadOnly() && !this.hasReadOnlyControls())
    ) {
      return null;
    }
    const onInsertClick = function() {
      this.setState({ selectedTagPos: null });
      this.onToggleChoices();
    };

    const props = {
      typeName: this.props.typeName,
      ref: ref(this, 'toggle'),
      onClick: onInsertClick.bind(this),
      readOnly: this.isReadOnly(),
      field: this.props.field,
    };
    return this.props.config.createElement('insert-button', props, 'Insert...');
  },

  choices: function() {
    if (this.isReadOnly()) {
      return null;
    }

    return this.props.config.createElement('choices', {
      typeName: this.props.typeName,
      ref: ref(this, 'choices'),
      onFocusSelect: this.focusCodeMirror,
      choices: this.state.replaceChoices,
      open: this.state.isChoicesOpen,
      ignoreCloseNodes: this.getCloseIgnoreNodes,
      onSelect: this.handleChoiceSelection,
      onClose: this.onCloseChoices,
      isAccordion: this.props.isAccordion,
      field: this.props.field,
      onChoiceAction: this.onChoiceAction,
    });
  },

  onChoiceAction: function(choice) {
    this.setState({
      isChoicesOpen: !!choice.isOpen,
    });
    this.onStartAction(choice.action, choice);
  },

  wrapperTabIndex() {
    if (this.props.readOnly || this.state.codeMirrorMode) {
      return null;
    }
    return this.props.tabIndex || 0;
  },

  onKeyDown(event) {
    if (!this.isReadOnly()) {
      if (event.keyCode === keyCodes.ESC) {
        event.preventDefault();
        event.stopPropagation();
        if (this.state.isChoicesOpen) {
          this.onToggleChoices();
          this.focusCodeMirror();
        }
      } else if (!this.state.isChoicesOpen) {
        // TODO: sane shortcut for opening choices
        // Below does not work yet. Ends up dumping { into search input.
        // if (this.codeMirror) {
        //   if (event.keyCode === keyCodes['['] && event.shiftKey) {
        //     const cursor = this.codeMirror.getCursor();
        //     const value = this.codeMirror.getValue();
        //     const lines = value.split('\n');
        //     const line = lines[cursor.line];
        //     if (line) {
        //       const prevChar = line[cursor.ch - 1];
        //       if (prevChar === '{') {
        //         this.onToggleChoices();
        //       }
        //     }
        //   }
        // }
      } else {
        if (this.choicesRef && this.choicesRef.onKeyDown) {
          this.choicesRef.onKeyDown(event);
        }
      }
    }
  },

  render: function() {
    return this.renderWithConfig();
  },

  renderDefault: function() {
    const textBoxClasses = cx(
      _.extend({}, this.props.classes, {
        'pretty-text-box': true,
        placeholder: this.hasPlaceholder(),
        'has-focus': this.state.hasFocus,
      })
    );

    // Render read-only version.
    return (

        {this.insertBtn()}
        {this.choices()}

    );
  },

  getCloseIgnoreNodes: function() {
    return this.toggleRef;
  },

  onToggleChoices: function() {
    this.setChoicesOpen(!this.state.isChoicesOpen);
  },

  setChoicesOpen: function(isOpen) {
    const action = isOpen ? 'open-replacements' : 'close-replacements';
    this.onStartAction(action);
    this.setState({ isChoicesOpen: isOpen });
  },

  onCloseChoices: function() {
    if (this.state.isChoicesOpen) {
      this.setChoicesOpen(false);
    }
  },

  createEditor: function() {
    if (this.state.codeMirrorMode) {
      this.createCodeMirrorEditor();
    } else {
      this.createReadonlyEditor();
    }
  },

  updateEditor: function() {
    if (this.state.codeMirrorMode) {
      const codeMirrorValue = this.codeMirror.getValue();
      if (!this.hasPlaceholder() && codeMirrorValue !== this.state.value) {
        // switch back to read-only mode to make it easier to render
        this.removeCodeMirrorEditor();
        this.createReadonlyEditor();
        this.setState({
          codeMirrorMode: false,
        });
      }
    } else {
      this.createReadonlyEditor();
    }
  },

  maybeSetCursorPosition(position) {
    if (position && this.codeMirror) {
      this.codeMirror.setCursor(position);
    }
  },

  maybeCodeMirrorOperation(ops) {
    if (this.codeMirror) {
      this.codeMirror.operation(ops);
    }
  },

  createCodeMirrorEditor: function() {
    const options = {
      tabindex: this.props.tabIndex || 0,
      lineWrapping: true,
      placeholder: toString(
        this.props.config.fieldPlaceholder(this.props.field)
      ),
      value: toString(this.state.value),
      readOnly: false,
      mode: null,
      extraKeys: {
        Tab: false,
        'Shift-Tab': false,
      },
    };

    const textBox = this.textBoxRef;
    textBox.innerHTML = ''; // release any previous read-only content so it can be GC'ed

    this.codeMirror = this.props.config.codeMirror()(textBox, options);
    this.codeMirror.on('change', this.onCodeMirrorChange);
    this.codeMirror.on('focus', this.onFocusCodeMirror);

    const editor = textBox.getElementsByTagName('textarea')[0];
    // Try to set aria-label on code block
    if (editor) {
      editor.setAttribute('aria-label', this.props.ariaLabel || 'code block');
    }

    this.debouncedOnChangeAndTagCodeMirror = _.debounce(() => {
      this.isDebouncingCodeMirrorChange = false;
      this.onChangeAndTagCodeMirror();
    }, 200);

    const pos = this.codeMirror ? this.codeMirror.getCursor() : null;
    this.tagCodeMirror(pos);
  },

  tagCodeMirror: function(cursorPosition) {
    const positions = this.state.translator.getTagPositions(
      this.codeMirror.getValue()
    );
    const self = this;

    const tagOps = function() {
      positions.forEach(function(pos) {
        const node = self.createTagNode(pos);
        self.codeMirror.markText(
          { line: pos.line, ch: pos.start },
          { line: pos.line, ch: pos.stop },
          { replacedWith: node, handleMouseEvents: true }
        );
      });
    };

    // Make sure we apply those operations after React has made its rendering pass.
    // As React 16 is asynchronous and uses rAF, it's safe to assume that this will
    // be called after React has patched the DOM.
    //
    // But if we are calling this function from CodeMirror itself, we want to stay
    // in sync with it's internal state.
    if (this.isDebouncingCodeMirrorChange) {
      this.maybeCodeMirrorOperation(tagOps);
      this.maybeSetCursorPosition(cursorPosition);
    } else {
      requestAnimationFrame(() => {
        this.maybeCodeMirrorOperation(tagOps);
        this.maybeSetCursorPosition(cursorPosition);
      });
    }
  },

  onChangeAndTagCodeMirror() {
    this.onChange(this.codeMirror.getValue());
    this.tagCodeMirror();
  },

  onCodeMirrorChange: function() {
    const newValue = this.codeMirror.getValue();
    this.setState({ value: newValue });

    // Immediately change and tag if inserting.
    if (this.isInserting) {
      this.onChangeAndTagCodeMirror();
    }
    // Otherwise, debounce so CodeMirror doesn't die.
    else {
      this.isDebouncingCodeMirrorChange = true;
      this.debouncedOnChangeAndTagCodeMirror();
    }
  },

  /* Return true if we should render the placeholder */
  hasPlaceholder: function() {
    return !this.state.value;
  },

  createReadonlyEditor: function() {
    const textBoxNode = this.textBoxRef;

    if (this.hasPlaceholder()) {
      return ReactDOM.render(

          {this.props.field.placeholder}
        ,
        textBoxNode
      );
    }

    const tokens = this.props.config.tokenize(this.state.value);
    const self = this;
    const nodes = tokens.map(function(part, i) {
      if (part.type === 'tag') {
        const label = self.state.translator.getLabel(part.value);
        const props = {
          typeName: self.props.typeName,
          key: i,
          tag: part.value,
          replaceChoices: self.state.replaceChoices,
          field: self.props.field,
        };
        return self.props.config.createElement('pretty-tag', props, label);
      }
      return {part.value};
    });

    return ReactDOM.render(

        {nodes}
      ,
      textBoxNode
    );
  },

  removeCodeMirrorEditor: function() {
    const textBoxNode = this.textBoxRef;

    const cmNode = textBoxNode.firstChild;
    textBoxNode.removeChild(cmNode);

    this.codeMirror.off('change', this.onCodeMirrorChange);
    this.codeMirror.off('focus', this.onFocusCodeMirror);

    if (this.debouncedOnChangeAndTagCodeMirror) {
      // Cancel any trailing invocation
      this.debouncedOnChangeAndTagCodeMirror.cancel();
      this.debouncedOnChangeAndTagCodeMirror = null;

      // Flush changes
      this.onChange(this.codeMirror.getValue());
    }

    this.codeMirror = null;
  },

  switchToCodeMirror: function(cb) {
    if (this.isReadOnly()) {
      return; // never render in code mirror if read-only
    }
    if (!this.props.readOnly) {
      if (!this.state.codeMirrorMode) {
        this.setState({ codeMirrorMode: true }, () => {
          if (this.codeMirror && _.isFunction(cb)) {
            cb();
          }
        });
      }
    }
  },

  onTagClick: function() {
    const cursor = this.codeMirror.getCursor();
    const pos = this.state.translator.getTrueTagPosition(
      this.state.value,
      cursor
    );

    this.setState({ selectedTagPos: pos });
    this.onToggleChoices();
  },

  createTagNode: function(pos) {
    const node = document.createElement('span');
    const label = this.state.translator.getLabel(pos.tag);
    const config = this.props.config;

    const props = {
      typeName: this.props.typeName,
      onClick: this.onTagClick,
      field: this.props.field,
      tag: pos.tag,
    };

    ReactDOM.render(config.createElement('pretty-tag', props, label), node);

    return node;
  },
});
<commit_msg>Do not fire the onChange event in pretty-text-input when no changes.<commit_after>'use strict';

/*eslint no-script-url:0 */

import createReactClass from 'create-react-class';
import ReactDOM from 'react-dom';
import cx from 'classnames';

import TagTranslator from './tag-translator';
import _ from '@/src/undash';
import { keyCodes, ref } from '@/src/utils';
import HelperMixin from '@/src/mixins/helper';

/** @jsx jsx */
import jsx from '@/src/jsx';

const toString = function(value) {
  if (_.isUndefined(value) || _.isNull(value)) {
    return '';
  }
  return String(value);
};

/*
   Editor for tagged text. Renders text like "hello {{firstName}}"
   with replacement labels rendered in a pill box. Designed to load
   quickly when many separate instances of it are on the same
   page.

   Uses CodeMirror to edit text. To save memory the CodeMirror node is
   instantiated when the user moves the mouse into the edit area.
   Initially a read-only view using a simple div is shown.
 */
export default createReactClass({
  displayName: 'PrettyTextInput',

  mixins: [HelperMixin],

  componentDidMount: function() {
    this.createEditor();
  },

  componentDidUpdate: function(prevProps, prevState) {
    const hasReplaceChoicesChanged = !_.isEqual(
      prevProps.replaceChoices,
      this.props.replaceChoices
    );
    const hasCodeMirrorModeChanged =
      prevState.codeMirrorMode !== this.state.codeMirrorMode;
    if (hasCodeMirrorModeChanged || hasReplaceChoicesChanged) {
      // Changed from code mirror mode to read only mode or vice versa,
      // so setup the other editor.
      this.createEditor();
    }
    this.updateEditor();
  },

  componentWillUnmount: function() {
    if (this.state.codeMirrorMode) {
      this.removeCodeMirrorEditor();
    }
  },

  getInitialState: function() {
    const selectedChoices = this.props.selectedChoices;
    const replaceChoices = this.props.replaceChoices;
    const translator = TagTranslator(
      selectedChoices.concat(replaceChoices),
      this.props.config.humanize
    );

    return {
      // With number values, onFocus never fires, which means it stays read-only. So convert to string.
      value: toString(this.props.value),
      codeMirrorMode: false,
      isChoicesOpen: false,
      replaceChoices,
      translator,
      hasChanged: false,
    };
  },

  componentWillReceiveProps: function(nextProps) {
    // If we're debouncing a change, then we should just ignore this props change,
    // because there will be another when we hit the trailing edge of the debounce.
    if (this.isDebouncingCodeMirrorChange) {
      return;
    }

    const selectedChoices = nextProps.selectedChoices;
    const replaceChoices = nextProps.replaceChoices;
    const nextState = {
      replaceChoices,
      translator: TagTranslator(
        selectedChoices.concat(replaceChoices),
        this.props.config.humanize
      ),
    };

    // Not sure what the null/undefined checks are here for, but changed from falsey which was breaking.
    if (
      this.state.value !== nextProps.value &&
      !_.isUndefined(nextProps.value) &&
      nextProps.value !== null
    ) {
      nextState.value = toString(nextProps.value);
      if (this.state.hasChanged === false) {
        nextState.hasChanged = true;
      }
    }

    this.setState(nextState);
  },

  onChange: function(newValue) {
    this.props.onChange(newValue);
  },

  handleChoiceSelection: function(key, event) {
    const selectChoice = () => {
      const pos = this.state.selectedTagPos;
      const tag = '{{' + key + '}}';

      this.isInserting = true;
      if (pos) {
        this.codeMirror.replaceRange(
          tag,
          { line: pos.line, ch: pos.start },
          { line: pos.line, ch: pos.stop }
        );
      } else {
        this.codeMirror.replaceSelection(tag, 'end');
      }
      this.isInserting = false;
      this.codeMirror.focus();

      this.setState({ isChoicesOpen: false, selectedTagPos: null });
    };
    if (this.state.codeMirrorMode) {
      selectChoice();
    } else if (this.props.readOnly) {
      // hackety hack to stop dropdown choices from toggling
      event.stopPropagation();
      this.isInserting = true;
      this.onChange('{{' + key + '}}');
      this.isInserting = false;
      this.setState({ isChoicesOpen: false });
    } else {
      this.switchToCodeMirror(selectChoice);
    }
  },

  onFocusWrapper: function() {
    this.switchToCodeMirror(() => {
      this.codeMirror.focus();
      this.codeMirror.setCursor(this.codeMirror.lineCount(), 0);
    });
  },

  focus() {
    if (this.codeMirror) {
      this.focusCodeMirror();
    } else {
      this.switchToCodeMirror(() => {
        this.focusCodeMirror();
      });
    }
  },

  focusCodeMirror() {
    if (this.codeMirror) {
      this.codeMirror.focus();
    }
  },

  onFocusCodeMirror() {
    this.setState({ hasFocus: true });
    this.props.onFocus();
  },

  onBlur() {
    this.setState({ hasFocus: false }, this.props.onBlur);
  },

  insertBtn: function() {
    if (
      this.props.readOnly ||
      (this.isReadOnly() && !this.hasReadOnlyControls())
    ) {
      return null;
    }
    const onInsertClick = function() {
      this.setState({ selectedTagPos: null });
      this.onToggleChoices();
    };

    const props = {
      typeName: this.props.typeName,
      ref: ref(this, 'toggle'),
      onClick: onInsertClick.bind(this),
      readOnly: this.isReadOnly(),
      field: this.props.field,
    };
    return this.props.config.createElement('insert-button', props, 'Insert...');
  },

  choices: function() {
    if (this.isReadOnly()) {
      return null;
    }

    return this.props.config.createElement('choices', {
      typeName: this.props.typeName,
      ref: ref(this, 'choices'),
      onFocusSelect: this.focusCodeMirror,
      choices: this.state.replaceChoices,
      open: this.state.isChoicesOpen,
      ignoreCloseNodes: this.getCloseIgnoreNodes,
      onSelect: this.handleChoiceSelection,
      onClose: this.onCloseChoices,
      isAccordion: this.props.isAccordion,
      field: this.props.field,
      onChoiceAction: this.onChoiceAction,
    });
  },

  onChoiceAction: function(choice) {
    this.setState({
      isChoicesOpen: !!choice.isOpen,
    });
    this.onStartAction(choice.action, choice);
  },

  wrapperTabIndex() {
    if (this.props.readOnly || this.state.codeMirrorMode) {
      return null;
    }
    return this.props.tabIndex || 0;
  },

  onKeyDown(event) {
    if (!this.isReadOnly()) {
      if (event.keyCode === keyCodes.ESC) {
        event.preventDefault();
        event.stopPropagation();
        if (this.state.isChoicesOpen) {
          this.onToggleChoices();
          this.focusCodeMirror();
        }
      } else if (!this.state.isChoicesOpen) {
        // TODO: sane shortcut for opening choices
        // Below does not work yet. Ends up dumping { into search input.
        // if (this.codeMirror) {
        //   if (event.keyCode === keyCodes['['] && event.shiftKey) {
        //     const cursor = this.codeMirror.getCursor();
        //     const value = this.codeMirror.getValue();
        //     const lines = value.split('\n');
        //     const line = lines[cursor.line];
        //     if (line) {
        //       const prevChar = line[cursor.ch - 1];
        //       if (prevChar === '{') {
        //         this.onToggleChoices();
        //       }
        //     }
        //   }
        // }
      } else {
        if (this.choicesRef && this.choicesRef.onKeyDown) {
          this.choicesRef.onKeyDown(event);
        }
      }
    }
  },

  render: function() {
    return this.renderWithConfig();
  },

  renderDefault: function() {
    const textBoxClasses = cx(
      _.extend({}, this.props.classes, {
        'pretty-text-box': true,
        placeholder: this.hasPlaceholder(),
        'has-focus': this.state.hasFocus,
      })
    );

    // Render read-only version.
    return (

        {this.insertBtn()}
        {this.choices()}

    );
  },

  getCloseIgnoreNodes: function() {
    return this.toggleRef;
  },

  onToggleChoices: function() {
    this.setChoicesOpen(!this.state.isChoicesOpen);
  },

  setChoicesOpen: function(isOpen) {
    const action = isOpen ? 'open-replacements' : 'close-replacements';
    this.onStartAction(action);
    this.setState({ isChoicesOpen: isOpen });
  },

  onCloseChoices: function() {
    if (this.state.isChoicesOpen) {
      this.setChoicesOpen(false);
    }
  },

  createEditor: function() {
    if (this.state.codeMirrorMode) {
      this.createCodeMirrorEditor();
    } else {
      this.createReadonlyEditor();
    }
  },

  updateEditor: function() {
    if (this.state.codeMirrorMode) {
      const codeMirrorValue = this.codeMirror.getValue();
      if (!this.hasPlaceholder() && codeMirrorValue !== this.state.value) {
        // switch back to read-only mode to make it easier to render
        this.removeCodeMirrorEditor();
        this.createReadonlyEditor();
        this.setState({
          codeMirrorMode: false,
        });
      }
    } else {
      this.createReadonlyEditor();
    }
  },

  maybeSetCursorPosition(position) {
    if (position && this.codeMirror) {
      this.codeMirror.setCursor(position);
    }
  },

  maybeCodeMirrorOperation(ops) {
    if (this.codeMirror) {
      this.codeMirror.operation(ops);
    }
  },

  createCodeMirrorEditor: function() {
    const options = {
      tabindex: this.props.tabIndex || 0,
      lineWrapping: true,
      placeholder: toString(
        this.props.config.fieldPlaceholder(this.props.field)
      ),
      value: toString(this.state.value),
      readOnly: false,
      mode: null,
      extraKeys: {
        Tab: false,
        'Shift-Tab': false,
      },
    };

    const textBox = this.textBoxRef;
    textBox.innerHTML = ''; // release any previous read-only content so it can be GC'ed

    this.codeMirror = this.props.config.codeMirror()(textBox, options);
    this.codeMirror.on('change', this.onCodeMirrorChange);
    this.codeMirror.on('focus', this.onFocusCodeMirror);

    const editor = textBox.getElementsByTagName('textarea')[0];
    // Try to set aria-label on code block
    if (editor) {
      editor.setAttribute('aria-label', this.props.ariaLabel || 'code block');
    }

    this.debouncedOnChangeAndTagCodeMirror = _.debounce(() => {
      this.isDebouncingCodeMirrorChange = false;
      this.onChangeAndTagCodeMirror();
    }, 200);

    const pos = this.codeMirror ? this.codeMirror.getCursor() : null;
    this.tagCodeMirror(pos);
  },

  tagCodeMirror: function(cursorPosition) {
    const positions = this.state.translator.getTagPositions(
      this.codeMirror.getValue()
    );
    const self = this;

    const tagOps = function() {
      positions.forEach(function(pos) {
        const node = self.createTagNode(pos);
        self.codeMirror.markText(
          { line: pos.line, ch: pos.start },
          { line: pos.line, ch: pos.stop },
          { replacedWith: node, handleMouseEvents: true }
        );
      });
    };

    // Make sure we apply those operations after React has made its rendering pass.
    // As React 16 is asynchronous and uses rAF, it's safe to assume that this will
    // be called after React has patched the DOM.
    //
    // But if we are calling this function from CodeMirror itself, we want to stay
    // in sync with it's internal state.
    if (this.isDebouncingCodeMirrorChange) {
      this.maybeCodeMirrorOperation(tagOps);
      this.maybeSetCursorPosition(cursorPosition);
    } else {
      requestAnimationFrame(() => {
        this.maybeCodeMirrorOperation(tagOps);
        this.maybeSetCursorPosition(cursorPosition);
      });
    }
  },

  onChangeAndTagCodeMirror() {
    this.onChange(this.codeMirror.getValue());
    this.tagCodeMirror();
  },

  onCodeMirrorChange: function() {
    const newValue = this.codeMirror.getValue();
    this.setState({ value: newValue });

    // Immediately change and tag if inserting.
    if (this.isInserting) {
      this.onChangeAndTagCodeMirror();
    }
    // Otherwise, debounce so CodeMirror doesn't die.
    else {
      this.isDebouncingCodeMirrorChange = true;
      this.debouncedOnChangeAndTagCodeMirror();
    }
  },

  /* Return true if we should render the placeholder */
  hasPlaceholder: function() {
    return !this.state.value;
  },

  createReadonlyEditor: function() {
    const textBoxNode = this.textBoxRef;

    if (this.hasPlaceholder()) {
      return ReactDOM.render(

          {this.props.field.placeholder}
        ,
        textBoxNode
      );
    }

    const tokens = this.props.config.tokenize(this.state.value);
    const self = this;
    const nodes = tokens.map(function(part, i) {
      if (part.type === 'tag') {
        const label = self.state.translator.getLabel(part.value);
        const props = {
          typeName: self.props.typeName,
          key: i,
          tag: part.value,
          replaceChoices: self.state.replaceChoices,
          field: self.props.field,
        };
        return self.props.config.createElement('pretty-tag', props, label);
      }
      return {part.value};
    });

    return ReactDOM.render(

        {nodes}
      ,
      textBoxNode
    );
  },

  removeCodeMirrorEditor: function() {
    const textBoxNode = this.textBoxRef;

    const cmNode = textBoxNode.firstChild;
    textBoxNode.removeChild(cmNode);

    this.codeMirror.off('change', this.onCodeMirrorChange);
    this.codeMirror.off('focus', this.onFocusCodeMirror);

    if (this.debouncedOnChangeAndTagCodeMirror) {
      // Cancel any trailing invocation
      this.debouncedOnChangeAndTagCodeMirror.cancel();
      this.debouncedOnChangeAndTagCodeMirror = null;

      // Flush changes
      if (this.isDebouncingCodeMirrorChange) {
        this.onChange(this.codeMirror.getValue());
      }
    }

    this.codeMirror = null;
  },

  switchToCodeMirror: function(cb) {
    if (this.isReadOnly()) {
      return; // never render in code mirror if read-only
    }
    if (!this.props.readOnly) {
      if (!this.state.codeMirrorMode) {
        this.setState({ codeMirrorMode: true }, () => {
          if (this.codeMirror && _.isFunction(cb)) {
            cb();
          }
        });
      }
    }
  },

  onTagClick: function() {
    const cursor = this.codeMirror.getCursor();
    const pos = this.state.translator.getTrueTagPosition(
      this.state.value,
      cursor
    );

    this.setState({ selectedTagPos: pos });
    this.onToggleChoices();
  },

  createTagNode: function(pos) {
    const node = document.createElement('span');
    const label = this.state.translator.getLabel(pos.tag);
    const config = this.props.config;

    const props = {
      typeName: this.props.typeName,
      onClick: this.onTagClick,
      field: this.props.field,
      tag: pos.tag,
    };

    ReactDOM.render(config.createElement('pretty-tag', props, label), node);

    return node;
  },
});
<|endoftext|>