<commit_before>var util = (function () {

var exports = {};

// From MDN: https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/random
exports.random_int = function random_int(min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
};

// Like C++'s std::lower_bound.  Returns the first index at which
// `value` could be inserted without changing the ordering.  Assumes
// the array is sorted.
//
// `first` and `last` are indices and `less` is an optionally-specified
// function that returns true if
//   array[i] < value
// for some i and false otherwise.
//
// Usage: lower_bound(array, value, [less])
//        lower_bound(array, first, last, value, [less])
exports.lower_bound = function (array, arg1, arg2, arg3, arg4) {
    var first;
    var last;
    var value;
    var less;
    if (arg3 === undefined) {
        first = 0;
        last = array.length;
        value = arg1;
        less = arg2;
    } else {
        first = arg1;
        last = arg2;
        value = arg3;
        less = arg4;
    }

    if (less === undefined) {
        less = function (a, b) { return a < b; };
    }

    var len = last - first;
    var middle;
    var step;
    while (len > 0) {
        step = Math.floor(len / 2);
        middle = first + step;
        if (less(array[middle], value, middle)) {
            first = middle;
            first += 1;
            len = len - step - 1;
        } else {
            len = step;
        }
    }
    return first;
};

exports.same_stream_and_topic = function util_same_stream_and_topic(a, b) {
    // Streams and topics are case-insensitive.
    return ((a.stream_id === b.stream_id) &&
            (a.subject.toLowerCase() === b.subject.toLowerCase()));
};

exports.is_pm_recipient = function (email, message) {
    var recipients = message.reply_to.toLowerCase().split(',');
    return recipients.indexOf(email.toLowerCase()) !== -1;
};

exports.extract_pm_recipients = function (recipients) {
    return _.filter(recipients.split(/\s*[,;]\s*/), function (recipient) {
        return recipient.trim() !== "";
    });
};

exports.same_recipient = function util_same_recipient(a, b) {
    if ((a === undefined) || (b === undefined)) {
        return false;
    }
    if (a.type !== b.type) {
        return false;
    }

    switch (a.type) {
    case 'private':
        if (a.to_user_ids === undefined) {
            return false;
        }
        return a.to_user_ids === b.to_user_ids;
    case 'stream':
        return exports.same_stream_and_topic(a, b);
    }

    // should never get here
    return false;
};

exports.same_sender = function util_same_sender(a, b) {
    return ((a !== undefined) && (b !== undefined) &&
            (a.sender_email.toLowerCase() === b.sender_email.toLowerCase()));
};

exports.normalize_recipients = function (recipients) {
    // Converts a string listing emails of message recipients
    // into a canonical formatting: emails sorted ASCIIbetically
    // with exactly one comma and no spaces between each.
    recipients = _.map(recipients.split(','), function (s) { return s.trim(); });
    recipients = _.map(recipients, function (s) { return s.toLowerCase(); });
    recipients = _.filter(recipients, function (s) { return s.length > 0; });
    recipients.sort();
    return recipients.join(',');
};

// Avoid URI decode errors by removing characters from the end
// one by one until the decode succeeds.  This makes sense if
// we are decoding input that the user is in the middle of
// typing.
exports.robust_uri_decode = function (str) {
    var end = str.length;
    while (end > 0) {
        try {
            return decodeURIComponent(str.substring(0, end));
        } catch (e) {
            if (!(e instanceof URIError)) {
                throw e;
            }
            end -= 1;
        }
    }
    return '';
};

exports.rtrim = function (str) {
    return str.replace(/\s+$/, '');
};

// If we can, use a locale-aware sorter.  However, if the browser
// doesn't support the ECMAScript Internationalization API
// Specification, do a dumb string comparison because
// String.localeCompare is really slow.
exports.strcmp = (function () {
    try {
        var collator = new Intl.Collator();
        return collator.compare;
    } catch (e) {
        // continue regardless of error
    }

    return function util_strcmp(a, b) {
        return (a < b ? -1 : (a > b ? 1 : 0));
    };
}());

exports.escape_regexp = function (string) {
    // code from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
    // Modified to escape the ^ to appease jslint. :/
    return string.replace(/([.*+?\^=!:${}()|\[\]\/\\])/g, "\\$1");
};

exports.array_compare = function util_array_compare(a, b) {
    if (a.length !== b.length) {
        return false;
    }
    var i;
    for (i = 0; i < a.length; i += 1) {
        if (a[i] !== b[i]) {
            return false;
        }
    }
    return true;
};

/* Represents a value that is expensive to compute and should be
 * computed on demand and then cached.  The value can be forcefully
 * recalculated on the next call to get() by calling reset().
 *
 * You must supply a option to the constructor called compute_value
 * which should be a function that computes the uncached value.
 */
var unassigned_value_sentinel = {};
exports.CachedValue = function (opts) {
    this._value = unassigned_value_sentinel;
    _.extend(this, opts);
};

exports.CachedValue.prototype = {
    get: function CachedValue_get() {
        if (this._value === unassigned_value_sentinel) {
            this._value = this.compute_value();
        }
        return this._value;
    },

    reset: function CachedValue_reset() {
        this._value = unassigned_value_sentinel;
    },
};

exports.execute_early = function (func) {
    if (page_params.test_suite) {
        $(document).one('phantom_page_loaded', func);
    } else {
        $(func);
    }
};

exports.is_all_or_everyone_mentioned = function (message_content) {
    var all_everyone_re = /(^|\s)(@\*{2}(all|everyone)\*{2})|(@(all|everyone))($|\s)/;
    return all_everyone_re.test(message_content);
};

exports.move_array_elements_to_front = function util_move_array_elements_to_front(array, selected) {
    var i;
    var selected_hash = {};
    for (i = 0; i < selected.length; i += 1) {
        selected_hash[selected[i]] = true;
    }
    var selected_elements = [];
    var unselected_elements = [];
    for (i = 0; i < array.length; i += 1) {
        if (selected_hash[array[i]]) {
            selected_elements.push(array[i]);
        } else {
            unselected_elements.push(array[i]);
        }
    }
    // Add the unselected elements after the selected ones
    return selected_elements.concat(unselected_elements);
};

// check by the userAgent string if a user's client is likely mobile.
exports.is_mobile = function () {
    var regex = "Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini";
    return new RegExp(regex, "i").test(window.navigator.userAgent);
};

return exports;
}());
if (typeof module !== 'undefined') {
    module.exports = util;
}
<commit_msg>Add preview_node functionality to util.js.<commit_after>var util = (function () {

var exports = {};

// From MDN: https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/random
exports.random_int = function random_int(min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
};

// Like C++'s std::lower_bound.  Returns the first index at which
// `value` could be inserted without changing the ordering.  Assumes
// the array is sorted.
//
// `first` and `last` are indices and `less` is an optionally-specified
// function that returns true if
//   array[i] < value
// for some i and false otherwise.
//
// Usage: lower_bound(array, value, [less])
//        lower_bound(array, first, last, value, [less])
exports.lower_bound = function (array, arg1, arg2, arg3, arg4) {
    var first;
    var last;
    var value;
    var less;
    if (arg3 === undefined) {
        first = 0;
        last = array.length;
        value = arg1;
        less = arg2;
    } else {
        first = arg1;
        last = arg2;
        value = arg3;
        less = arg4;
    }

    if (less === undefined) {
        less = function (a, b) { return a < b; };
    }

    var len = last - first;
    var middle;
    var step;
    while (len > 0) {
        step = Math.floor(len / 2);
        middle = first + step;
        if (less(array[middle], value, middle)) {
            first = middle;
            first += 1;
            len = len - step - 1;
        } else {
            len = step;
        }
    }
    return first;
};

// Produces an easy-to-read preview on an HTML element.  Currently
// only used for including in error report emails; be sure to discuss
// with other developers before using it in a user-facing context
// because it is not XSS-safe.
exports.preview_node = function (node) {
    if (node.constructor === jQuery) {
        node = node[0];
    }

    var tag = node.tagName.toLowerCase();
    var className = node.className.length ? node.className : false;
    var id = node.id.length ? node.id : false;

    var node_preview = "<" + tag +
       (id ? " id='" + id + "'" : "") +
       (className ? " class='" + className + "'" : "") +
       "></" + tag + ">";

      return node_preview;
};

exports.same_stream_and_topic = function util_same_stream_and_topic(a, b) {
    // Streams and topics are case-insensitive.
    return ((a.stream_id === b.stream_id) &&
            (a.subject.toLowerCase() === b.subject.toLowerCase()));
};

exports.is_pm_recipient = function (email, message) {
    var recipients = message.reply_to.toLowerCase().split(',');
    return recipients.indexOf(email.toLowerCase()) !== -1;
};

exports.extract_pm_recipients = function (recipients) {
    return _.filter(recipients.split(/\s*[,;]\s*/), function (recipient) {
        return recipient.trim() !== "";
    });
};

exports.same_recipient = function util_same_recipient(a, b) {
    if ((a === undefined) || (b === undefined)) {
        return false;
    }
    if (a.type !== b.type) {
        return false;
    }

    switch (a.type) {
    case 'private':
        if (a.to_user_ids === undefined) {
            return false;
        }
        return a.to_user_ids === b.to_user_ids;
    case 'stream':
        return exports.same_stream_and_topic(a, b);
    }

    // should never get here
    return false;
};

exports.same_sender = function util_same_sender(a, b) {
    return ((a !== undefined) && (b !== undefined) &&
            (a.sender_email.toLowerCase() === b.sender_email.toLowerCase()));
};

exports.normalize_recipients = function (recipients) {
    // Converts a string listing emails of message recipients
    // into a canonical formatting: emails sorted ASCIIbetically
    // with exactly one comma and no spaces between each.
    recipients = _.map(recipients.split(','), function (s) { return s.trim(); });
    recipients = _.map(recipients, function (s) { return s.toLowerCase(); });
    recipients = _.filter(recipients, function (s) { return s.length > 0; });
    recipients.sort();
    return recipients.join(',');
};

// Avoid URI decode errors by removing characters from the end
// one by one until the decode succeeds.  This makes sense if
// we are decoding input that the user is in the middle of
// typing.
exports.robust_uri_decode = function (str) {
    var end = str.length;
    while (end > 0) {
        try {
            return decodeURIComponent(str.substring(0, end));
        } catch (e) {
            if (!(e instanceof URIError)) {
                throw e;
            }
            end -= 1;
        }
    }
    return '';
};

exports.rtrim = function (str) {
    return str.replace(/\s+$/, '');
};

// If we can, use a locale-aware sorter.  However, if the browser
// doesn't support the ECMAScript Internationalization API
// Specification, do a dumb string comparison because
// String.localeCompare is really slow.
exports.strcmp = (function () {
    try {
        var collator = new Intl.Collator();
        return collator.compare;
    } catch (e) {
        // continue regardless of error
    }

    return function util_strcmp(a, b) {
        return (a < b ? -1 : (a > b ? 1 : 0));
    };
}());

exports.escape_regexp = function (string) {
    // code from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
    // Modified to escape the ^ to appease jslint. :/
    return string.replace(/([.*+?\^=!:${}()|\[\]\/\\])/g, "\\$1");
};

exports.array_compare = function util_array_compare(a, b) {
    if (a.length !== b.length) {
        return false;
    }
    var i;
    for (i = 0; i < a.length; i += 1) {
        if (a[i] !== b[i]) {
            return false;
        }
    }
    return true;
};

/* Represents a value that is expensive to compute and should be
 * computed on demand and then cached.  The value can be forcefully
 * recalculated on the next call to get() by calling reset().
 *
 * You must supply a option to the constructor called compute_value
 * which should be a function that computes the uncached value.
 */
var unassigned_value_sentinel = {};
exports.CachedValue = function (opts) {
    this._value = unassigned_value_sentinel;
    _.extend(this, opts);
};

exports.CachedValue.prototype = {
    get: function CachedValue_get() {
        if (this._value === unassigned_value_sentinel) {
            this._value = this.compute_value();
        }
        return this._value;
    },

    reset: function CachedValue_reset() {
        this._value = unassigned_value_sentinel;
    },
};

exports.execute_early = function (func) {
    if (page_params.test_suite) {
        $(document).one('phantom_page_loaded', func);
    } else {
        $(func);
    }
};

exports.is_all_or_everyone_mentioned = function (message_content) {
    var all_everyone_re = /(^|\s)(@\*{2}(all|everyone)\*{2})|(@(all|everyone))($|\s)/;
    return all_everyone_re.test(message_content);
};

exports.move_array_elements_to_front = function util_move_array_elements_to_front(array, selected) {
    var i;
    var selected_hash = {};
    for (i = 0; i < selected.length; i += 1) {
        selected_hash[selected[i]] = true;
    }
    var selected_elements = [];
    var unselected_elements = [];
    for (i = 0; i < array.length; i += 1) {
        if (selected_hash[array[i]]) {
            selected_elements.push(array[i]);
        } else {
            unselected_elements.push(array[i]);
        }
    }
    // Add the unselected elements after the selected ones
    return selected_elements.concat(unselected_elements);
};

// check by the userAgent string if a user's client is likely mobile.
exports.is_mobile = function () {
    var regex = "Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini";
    return new RegExp(regex, "i").test(window.navigator.userAgent);
};

return exports;
}());
if (typeof module !== 'undefined') {
    module.exports = util;
}
<|endoftext|>


<commit_before>"use strict";

const _ = require("lodash");

exports.eq_array = (a, b, eq) => {
    if (a === b) {
        // either both are undefined, or they
        // are referentially equal
        return true;
    }

    if (a === undefined || b === undefined) {
        return false;
    }

    if (a.length !== b.length) {
        return false;
    }

    return a.every((item, i) => eq(item, b[i]));
};

exports.ul = (opts) => ({
    tag_name: "ul",
    opts,
});

exports.render_tag = (tag) => {
    /*
        This renders a tag into a string.  It will
        automatically escape attributes, but it's your
        responsibility to make sure keyed_nodes provide
        a `render` method that escapes HTML properly.
        (One option is to use templates.)

        Do NOT call this method directly, except for
        testing.  The vdom scheme expects you to use
        the `update` method.
    */
    const opts = tag.opts;
    const tag_name = tag.tag_name;
    const attr_str = opts.attrs
        .map((attr) => " " + attr[0] + '="' + _.escape(attr[1]) + '"')
        .join("");

    const start_tag = "<" + tag_name + attr_str + ">";
    const end_tag = "</" + tag_name + ">";

    if (opts.keyed_nodes === undefined) {
        blueslip.error("We need keyed_nodes to render innards.");
        return undefined;
    }

    const innards = opts.keyed_nodes.map((node) => node.render()).join("\n");
    return start_tag + "\n" + innards + "\n" + end_tag;
};

exports.update = (replace_content, find, new_dom, old_dom) => {
    /*
        The update method allows you to continually
        update a "virtual" representation of your DOM,
        and then this method actually updates the
        real DOM using jQuery.  The caller will pass
        in a method called `replace_content` that will replace
        the entire html and a method called `find` to
        find the existing DOM for more surgical updates.

        The first "update" will be more like a create,
        because your `old_dom` should be undefined.
        After that initial call, it is important that
        you always pass in a correct value of `old_dom`;
        otherwise, things will be incredibly confusing.

        The basic scheme here is simple:

            1) If old_dom is undefined, we render
               everything for the first time.

            2) If the keys of your new children are no
               longer the same order as the old
               children, then we just render
               everything anew.
               (We may refine this in the future.)

            3) If your key structure remains the same,
               then we update your child nodes on
               a child-by-child basis, and we avoid
               updates where the data had remained
               the same.

        The key to making this all work is that
        `new_dom` should include a `keyed_nodes` option
        where each `keyed_node` has a `key` and supports
        these methods:

            eq - can compare itself to similar nodes
                 for data equality

            render - can create an HTML representation
                     of itself

        The `new_dom` should generally be created with
        something like `vdom.ul`, which will set a
        tag field internally and which will want options
        like `attrs` for attributes.

        For examples of creating vdom objects, look at
        `pm_list_dom.js`.
    */
    function do_full_update() {
        const rendered_dom = exports.render_tag(new_dom);
        replace_content(rendered_dom);
    }

    if (old_dom === undefined) {
        do_full_update();
        return;
    }

    const new_opts = new_dom.opts;
    const old_opts = old_dom.opts;

    if (new_opts.keyed_nodes === undefined) {
        // We generally want to use vdom on lists, and
        // adding keys for childrens lets us avoid unnecessary
        // redraws (or lets us know we should just rebuild
        // the dom).
        blueslip.error("We need keyed_nodes for updates.");
        return;
    }

    const same_structure = exports.eq_array(
        new_opts.keyed_nodes,
        old_opts.keyed_nodes,
        (a, b) => a.key === b.key,
    );

    if (!same_structure) {
        /* We could do something smarter like detecting row
           moves, but it's overkill for small lists.
        */
        do_full_update();
        return;
    }

    /*
        DO "QUICK" UPDATES:

        We've gotten this far, so we know we have the
        same overall structure for our parent tag, and
        the only thing left to do with our child nodes
        is to possibly update them in place (via jQuery).
        We will only update nodes whose data has changed.
    */

    const child_elems = find().children();

    for (const [i, new_node] of new_opts.keyed_nodes.entries()) {
        const old_node = old_opts.keyed_nodes[i];
        if (new_node.eq(old_node)) {
            continue;
        }
        const rendered_dom = new_node.render();
        child_elems.eq(i).replaceWith(rendered_dom);
    }

    exports.update_attrs(find(), new_opts.attrs, old_opts.attrs);
};

exports.update_attrs = (elem, new_attrs, old_attrs) => {
    const new_dict = new Map(new_attrs);
    const old_dict = new Map(old_attrs);

    for (const [k, v] of new_attrs) {
        if (v !== old_dict.get(k)) {
            elem.attr(k, v);
        }
    }

    for (const [k] of old_attrs) {
        if (!new_dict.has(k)) {
            elem.removeAttr(k);
        }
    }
};

window.vdom = exports;
<commit_msg>vdom: Move update_attrs definition before use.<commit_after>"use strict";

const _ = require("lodash");

exports.eq_array = (a, b, eq) => {
    if (a === b) {
        // either both are undefined, or they
        // are referentially equal
        return true;
    }

    if (a === undefined || b === undefined) {
        return false;
    }

    if (a.length !== b.length) {
        return false;
    }

    return a.every((item, i) => eq(item, b[i]));
};

exports.ul = (opts) => ({
    tag_name: "ul",
    opts,
});

exports.render_tag = (tag) => {
    /*
        This renders a tag into a string.  It will
        automatically escape attributes, but it's your
        responsibility to make sure keyed_nodes provide
        a `render` method that escapes HTML properly.
        (One option is to use templates.)

        Do NOT call this method directly, except for
        testing.  The vdom scheme expects you to use
        the `update` method.
    */
    const opts = tag.opts;
    const tag_name = tag.tag_name;
    const attr_str = opts.attrs
        .map((attr) => " " + attr[0] + '="' + _.escape(attr[1]) + '"')
        .join("");

    const start_tag = "<" + tag_name + attr_str + ">";
    const end_tag = "</" + tag_name + ">";

    if (opts.keyed_nodes === undefined) {
        blueslip.error("We need keyed_nodes to render innards.");
        return undefined;
    }

    const innards = opts.keyed_nodes.map((node) => node.render()).join("\n");
    return start_tag + "\n" + innards + "\n" + end_tag;
};

exports.update_attrs = (elem, new_attrs, old_attrs) => {
    const new_dict = new Map(new_attrs);
    const old_dict = new Map(old_attrs);

    for (const [k, v] of new_attrs) {
        if (v !== old_dict.get(k)) {
            elem.attr(k, v);
        }
    }

    for (const [k] of old_attrs) {
        if (!new_dict.has(k)) {
            elem.removeAttr(k);
        }
    }
};

exports.update = (replace_content, find, new_dom, old_dom) => {
    /*
        The update method allows you to continually
        update a "virtual" representation of your DOM,
        and then this method actually updates the
        real DOM using jQuery.  The caller will pass
        in a method called `replace_content` that will replace
        the entire html and a method called `find` to
        find the existing DOM for more surgical updates.

        The first "update" will be more like a create,
        because your `old_dom` should be undefined.
        After that initial call, it is important that
        you always pass in a correct value of `old_dom`;
        otherwise, things will be incredibly confusing.

        The basic scheme here is simple:

            1) If old_dom is undefined, we render
               everything for the first time.

            2) If the keys of your new children are no
               longer the same order as the old
               children, then we just render
               everything anew.
               (We may refine this in the future.)

            3) If your key structure remains the same,
               then we update your child nodes on
               a child-by-child basis, and we avoid
               updates where the data had remained
               the same.

        The key to making this all work is that
        `new_dom` should include a `keyed_nodes` option
        where each `keyed_node` has a `key` and supports
        these methods:

            eq - can compare itself to similar nodes
                 for data equality

            render - can create an HTML representation
                     of itself

        The `new_dom` should generally be created with
        something like `vdom.ul`, which will set a
        tag field internally and which will want options
        like `attrs` for attributes.

        For examples of creating vdom objects, look at
        `pm_list_dom.js`.
    */
    function do_full_update() {
        const rendered_dom = exports.render_tag(new_dom);
        replace_content(rendered_dom);
    }

    if (old_dom === undefined) {
        do_full_update();
        return;
    }

    const new_opts = new_dom.opts;
    const old_opts = old_dom.opts;

    if (new_opts.keyed_nodes === undefined) {
        // We generally want to use vdom on lists, and
        // adding keys for childrens lets us avoid unnecessary
        // redraws (or lets us know we should just rebuild
        // the dom).
        blueslip.error("We need keyed_nodes for updates.");
        return;
    }

    const same_structure = exports.eq_array(
        new_opts.keyed_nodes,
        old_opts.keyed_nodes,
        (a, b) => a.key === b.key,
    );

    if (!same_structure) {
        /* We could do something smarter like detecting row
           moves, but it's overkill for small lists.
        */
        do_full_update();
        return;
    }

    /*
        DO "QUICK" UPDATES:

        We've gotten this far, so we know we have the
        same overall structure for our parent tag, and
        the only thing left to do with our child nodes
        is to possibly update them in place (via jQuery).
        We will only update nodes whose data has changed.
    */

    const child_elems = find().children();

    for (const [i, new_node] of new_opts.keyed_nodes.entries()) {
        const old_node = old_opts.keyed_nodes[i];
        if (new_node.eq(old_node)) {
            continue;
        }
        const rendered_dom = new_node.render();
        child_elems.eq(i).replaceWith(rendered_dom);
    }

    exports.update_attrs(find(), new_opts.attrs, old_opts.attrs);
};

window.vdom = exports;
<|endoftext|>


<commit_before>(function ($, Ember) {
  var cl = console.log.bind(console);

  var App = Ember.Application.create({
    LOG_TRANSITIONS: true,
    rootElement: '#ember-container'
  });

  window.App = App;

  //-------------------------
  // Store
  //-------------------------
  App.Store = DS.Store.extend({
    revision: 12,
    adapter: DS.RESTAdapter
  });

  DS.RESTAdapter.reopen({
    namespace: 'api'
  });

  //-------------------------
  // Models
  //-------------------------
  App.Post = DS.Model.extend({
    title: DS.attr('string'),
    created: DS.attr('number'),
    userId: DS.attr('string'),
    userName: DS.attr('string'),
    userAvatar: DS.attr('string'),
    isMyPost: DS.attr('boolean')
  });

  App.User = DS.Model.extend({
    name: DS.attr('string'),
    avatar: DS.attr('string'),
    isMyUser: DS.attr('boolean'),
    email: DS.attr('string')
  });

  //-------------------------
  // Views
  //-------------------------
  App.PostsView = Ember.View.extend({
    templateName: 'posts'
  });

  //-------------------------
  // Controllers
  //-------------------------
  App.ApplicationController = Ember.Controller.extend({
    needs: ['posts', 'users']
  });

  App.IndexController = Ember.Controller.extend({
    needs: ['posts', 'users'],
    myUserBinding: 'controllers.users.myUser',
    newPostTitle: '',

    postBoxDisabled: function() {
      return !(this.get('myUser.isLoaded') && (this.get('myUser.length') > 0));
    }.property('myUser.isLoaded', 'myUser.length'),

    postButtonDisabled: function() {
      return !(!this.get('postBoxDisabled') && this.get('newPostTitle.length') > 0);
    }.property('postBoxDisabled', 'newPostTitle'),

    createNewPost: function() {
      var self = this;
      var myUser = this.get('controllers.users.myUser.firstObject.user');

      var newPost = App.Post.createRecord({
        user: myUser,
        title: this.get('newPostTitle')
      });
      newPost.on('didCreate', function() {
        self.set('newPostTitle', '');
      });
      newPost.get('transaction').commit();
    }
  });

  App.ProfileController = Ember.Controller.extend({
    needs: ['posts', 'users'],
    myUserBinding: 'controllers.users.myUser',
    filterByUserBinding: 'controllers.posts.filterByUser',
    newName: '',
    newEmail: '',

    // Helper to disable the button when the fields are not filled.
    createButtonDisabled: function() {
      return this.get('newName.length') === 0;
    }.property('newName'),

    createNew: function() {
      var self = this;

      var newUser = App.User.createRecord({
        name: this.get('newName'),
        email: this.get('newEmail'),
        isMyUser: true
      });

      newUser.on('didCreate', function() {
        self.set('myUser', App.User.filter(function(user) {
          return user.get('isMyUser') === true;
        }));
        self.set('filterByUser', self.get('myUser'));
      });

      newUser.get('transaction').commit();
    }
  });

  App.UsersController = Ember.ArrayController.extend({
    // This is set to a FilteredRecordArray by the router. Just use the
    // first object in the array.
    myUser: []
  });

  App.PostsController = Ember.ArrayController.extend({
    filterByUser: [],
    sortProperties: ['created'],
    sortAscending: false,

    filteredPosts: function() {
      if (this.get('filterByUser.length') > 0) {
        var filterUserId = this.get('filterByUser.firstObject.id');
        if (filterUserId) {
          return this.get('arrangedContent').filterProperty('userId', filterUserId);
        }
      }
      return this.get('arrangedContent');
    }.property('content.[]', 'filterByUser.[]'),

    deletePost: function(id) {
      if (confirm('Are you sure you want to delete this post?')) {
        var post = App.Post.find(id);
        post.deleteRecord();
        post.get('transaction').commit();
      }
    }
  });

  //-------------------------
  // Router
  //-------------------------
  App.Router.map(function() {
    this.resource('index', {path: '/'});
    this.resource('profile', {path: '/profile'});
  });

  App.ApplicationRoute = Ember.Route.extend({
    setupController: function(controller) {
      controller.set('controllers.posts.content', App.Post.find());
      controller.set('controllers.users.myUser', App.User.find({'is_my_user': true}));
      var refreshUI = function() {
        App.Post.find();
        setTimeout(refreshUI, 1000);
      };
      setTimeout(refreshUI, 5000);
    }
  });

  App.IndexRoute = Ember.Route.extend({
    setupController: function(controller) {
      var postsController = controller.get('controllers.posts');
      postsController.set('filterByUser', []);
    }
  });

  App.ProfileRoute = Ember.Route.extend({
    setupController: function(controller) {
      var postsController = controller.get('controllers.posts');
      var usersController = controller.get('controllers.users');
      postsController.set('filterByUser', usersController.get('myUser'));
    }
  });

  //-------------------------
  // Handlebars
  //-------------------------
  Ember.Handlebars.registerBoundHelper('nanoDate', function(value, options) {
    var escaped = Handlebars.Utils.escapeExpression(value);
    var ms = Math.round(escaped / Math.pow(10, 6));
    return new Handlebars.SafeString(moment(ms).fromNow());
  });

  Ember.Handlebars.registerBoundHelper('enrich', function(value, options) {
    var escaped = Handlebars.Utils.escapeExpression(value);
    var rUrl = /\(?\b(?:(http|https|ftp):\/\/)+((?:www.)?[a-zA-Z0-9\-\.]+[\.][a-zA-Z]{2,4}|localhost(?=\/)|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(?::(\d*))?(?=[\s\/,\.\)])([\/]{1}[^\s\?]*[\/]{1})*(?:\/?([^\s\n\?\[\]\{\}\#]*(?:(?=\.)){1}|[^\s\n\?\[\]\{\}\.\#]*)?([\.]{1}[^\s\?\#]*)?)?(?:\?{1}([^\s\n\#\[\]\(\)]*))?([\#][^\s\n]*)?\)?/ig;
    var matches = escaped.match(rUrl);

    if (matches) {
      var outer = $('<div />');
      var link = $('<a />', {
        href : matches[0],
        target : '_blank'
      });

      if (/\.(jpg|gif|png)$/.test(matches[0])) {
        var image = $('<img />', { src : matches[0] });
        image.addClass('img-rounded');
        link.html(image);
      }
      else {
        link.text(matches[0]);
      }

      escaped = escaped.replace(matches[0], outer.append(link).html());
    }

    return new Handlebars.SafeString(escaped);
  });

  $('.time').moment({ frequency: 5000 });

})(jQuery, Ember);
<commit_msg>Preserve post whitespace.<commit_after>(function ($, Ember) {
  var cl = console.log.bind(console);

  var App = Ember.Application.create({
    LOG_TRANSITIONS: true,
    rootElement: '#ember-container'
  });

  window.App = App;

  //-------------------------
  // Store
  //-------------------------
  App.Store = DS.Store.extend({
    revision: 12,
    adapter: DS.RESTAdapter
  });

  DS.RESTAdapter.reopen({
    namespace: 'api'
  });

  //-------------------------
  // Models
  //-------------------------
  App.Post = DS.Model.extend({
    title: DS.attr('string'),
    created: DS.attr('number'),
    userId: DS.attr('string'),
    userName: DS.attr('string'),
    userAvatar: DS.attr('string'),
    isMyPost: DS.attr('boolean')
  });

  App.User = DS.Model.extend({
    name: DS.attr('string'),
    avatar: DS.attr('string'),
    isMyUser: DS.attr('boolean'),
    email: DS.attr('string')
  });

  //-------------------------
  // Views
  //-------------------------
  App.PostsView = Ember.View.extend({
    templateName: 'posts'
  });

  //-------------------------
  // Controllers
  //-------------------------
  App.ApplicationController = Ember.Controller.extend({
    needs: ['posts', 'users']
  });

  App.IndexController = Ember.Controller.extend({
    needs: ['posts', 'users'],
    myUserBinding: 'controllers.users.myUser',
    newPostTitle: '',

    postBoxDisabled: function() {
      return !(this.get('myUser.isLoaded') && (this.get('myUser.length') > 0));
    }.property('myUser.isLoaded', 'myUser.length'),

    postButtonDisabled: function() {
      return !(!this.get('postBoxDisabled') && this.get('newPostTitle.length') > 0);
    }.property('postBoxDisabled', 'newPostTitle'),

    createNewPost: function() {
      var self = this;
      var myUser = this.get('controllers.users.myUser.firstObject.user');

      var newPost = App.Post.createRecord({
        user: myUser,
        title: this.get('newPostTitle')
      });
      newPost.on('didCreate', function() {
        self.set('newPostTitle', '');
      });
      newPost.get('transaction').commit();
    }
  });

  App.ProfileController = Ember.Controller.extend({
    needs: ['posts', 'users'],
    myUserBinding: 'controllers.users.myUser',
    filterByUserBinding: 'controllers.posts.filterByUser',
    newName: '',
    newEmail: '',

    // Helper to disable the button when the fields are not filled.
    createButtonDisabled: function() {
      return this.get('newName.length') === 0;
    }.property('newName'),

    createNew: function() {
      var self = this;

      var newUser = App.User.createRecord({
        name: this.get('newName'),
        email: this.get('newEmail'),
        isMyUser: true
      });

      newUser.on('didCreate', function() {
        self.set('myUser', App.User.filter(function(user) {
          return user.get('isMyUser') === true;
        }));
        self.set('filterByUser', self.get('myUser'));
      });

      newUser.get('transaction').commit();
    }
  });

  App.UsersController = Ember.ArrayController.extend({
    // This is set to a FilteredRecordArray by the router. Just use the
    // first object in the array.
    myUser: []
  });

  App.PostsController = Ember.ArrayController.extend({
    filterByUser: [],
    sortProperties: ['created'],
    sortAscending: false,

    filteredPosts: function() {
      if (this.get('filterByUser.length') > 0) {
        var filterUserId = this.get('filterByUser.firstObject.id');
        if (filterUserId) {
          return this.get('arrangedContent').filterProperty('userId', filterUserId);
        }
      }
      return this.get('arrangedContent');
    }.property('content.[]', 'filterByUser.[]'),

    deletePost: function(id) {
      if (confirm('Are you sure you want to delete this post?')) {
        var post = App.Post.find(id);
        post.deleteRecord();
        post.get('transaction').commit();
      }
    }
  });

  //-------------------------
  // Router
  //-------------------------
  App.Router.map(function() {
    this.resource('index', {path: '/'});
    this.resource('profile', {path: '/profile'});
  });

  App.ApplicationRoute = Ember.Route.extend({
    setupController: function(controller) {
      controller.set('controllers.posts.content', App.Post.find());
      controller.set('controllers.users.myUser', App.User.find({'is_my_user': true}));
      var refreshUI = function() {
        App.Post.find();
        setTimeout(refreshUI, 1000);
      };
      setTimeout(refreshUI, 5000);
    }
  });

  App.IndexRoute = Ember.Route.extend({
    setupController: function(controller) {
      var postsController = controller.get('controllers.posts');
      postsController.set('filterByUser', []);
    }
  });

  App.ProfileRoute = Ember.Route.extend({
    setupController: function(controller) {
      var postsController = controller.get('controllers.posts');
      var usersController = controller.get('controllers.users');
      postsController.set('filterByUser', usersController.get('myUser'));
    }
  });

  //-------------------------
  // Handlebars
  //-------------------------
  Ember.Handlebars.registerBoundHelper('nanoDate', function(value, options) {
    var escaped = Handlebars.Utils.escapeExpression(value);
    var ms = Math.round(escaped / Math.pow(10, 6));
    return new Handlebars.SafeString(moment(ms).fromNow());
  });

  Ember.Handlebars.registerBoundHelper('enrich', function(value, options) {
    var escaped = Handlebars.Utils.escapeExpression(value);
    var rUrl = /\(?\b(?:(http|https|ftp):\/\/)+((?:www.)?[a-zA-Z0-9\-\.]+[\.][a-zA-Z]{2,4}|localhost(?=\/)|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(?::(\d*))?(?=[\s\/,\.\)])([\/]{1}[^\s\?]*[\/]{1})*(?:\/?([^\s\n\?\[\]\{\}\#]*(?:(?=\.)){1}|[^\s\n\?\[\]\{\}\.\#]*)?([\.]{1}[^\s\?\#]*)?)?(?:\?{1}([^\s\n\#\[\]\(\)]*))?([\#][^\s\n]*)?\)?/ig;
    var matches = escaped.match(rUrl);

    if (matches) {
      var outer = $('<div />');
      var link = $('<a />', {
        href : matches[0],
        target : '_blank'
      });

      if (/\.(jpg|gif|png)$/.test(matches[0])) {
        var image = $('<img />', { src : matches[0] });
        image.addClass('img-rounded');
        link.html(image);
      }
      else {
        link.text(matches[0]);
      }

      escaped = escaped.replace(matches[0], outer.append(link).html());
    }

    return new Handlebars.SafeString(escaped.replace(/\n/g, '<br />'));
  });

  $('.time').moment({ frequency: 5000 });

})(jQuery, Ember);
<|endoftext|>


<commit_before>function normalizeMoney(value)
{
    var x = value.replace(/R\$ /g, '').replace(/\./g, '').replace(/,/g, '')
    return parseInt(x);
}

jQuery.fn.dataTableExt.oSort['money-asc'] = function(a, b) {
    return normalizeMoney(a) - normalizeMoney(b);
};

jQuery.fn.dataTableExt.oSort['money-desc'] = function(a, b) {
    return normalizeMoney(b) - normalizeMoney(a);
};

function handle_navigation (e)
{
    history.pushState(null, null, $(this)[0].href);
    view_from_url();
    e.preventDefault();
}

function setup_history_handling()
{
    // Gracefully degrade.
    if (typeof history == undefined || typeof history.pushState == undefined) {
        return;
    }

    window.addEventListener('popstate', function(e) {
        view_from_url();
    });
}

function setup_navigation_handling(query)
{
    // Gracefully degrade.
    if (typeof history == undefined || typeof history.pushState == undefined) {
        return;
    }

    $(query).click(handle_navigation);
}

function montanha_init()
{
    setup_history_handling();
    setup_navigation_handling('.navigation');

    view_from_url();
}

function cleanup() {
    var canvas = document.getElementById('graph');

    context = canvas.getContext('2d');
    context.clearRect(0, 0, canvas.width, canvas.height);

    var table_title = document.getElementById('tabletitle');
    table_title.innerHTML = '';

    var graph_title = document.getElementById('graphtitle');
    graph_title.innerHTML = '';
    graph_title.parentNode.style.display = 'none';

    // Remove the labels too.
    var graphparent = document.getElementById('graphparent');
    var divs = $(graphparent).find('div');
    for (var i = 0; i < divs.length; i++) {
        graphparent.removeChild(divs[i]);
    }

    // Remove any left over from FixedHeader.
    try {
        var div = document.evaluate("//div[contains(@class, 'FixedHeader_Cloned')]", document, null, XPathResult.ANY_TYPE, null).iterateNext();
        div.parentNode.removeChild(div);
    } catch(e) {
    }
}

function build_table_top(columns) {
    // Now let's start building the new data display.
    var table = document.createElement('table');
    table.setAttribute('id', 'resultstable');

    var thead = document.createElement('thead');
    table.appendChild(thead)

    var tr = document.createElement('tr');
    thead.appendChild(tr);

    // First of all, the titles.
    for (var i = 0; i < columns.length; i++) {
        var col_label = columns[i]['label'];

        var th = document.createElement('th');
        th.innerHTML = col_label;
        tr.appendChild(th);
    }

    var tbody = document.createElement('tbody');
    table.appendChild(tbody);

    // Place new table in the DOM, replacing the old one.
    var results_pane = document.getElementById('results');
    if (results_pane.firstChild != null) {
        results_pane.replaceChild(table, results_pane.firstChild);
    } else {
        results_pane.appendChild(table);
    }

    return [table, tbody];
}

function view_from_url() {
    var pathname = window.location.pathname;
    if (pathname == '' || pathname == '/') {
        view('per_nature');
        return;
    }

    var components = pathname.split('/');

    // Remove the empty string.
    components.shift();

    var section = components.shift();

    var id = components.shift();
    if (id == "") id = undefined;

    if (section == 'legislador') {
        if (id == undefined) {
            view('per_legislator');
            return;
        }

        detail_legislator(id);
        return;
    }

    if (section == 'partido') {
        view('per_party');
        return;
    }

    if (section == 'fornecedor') {
        view('per_supplier');
        return;
    }

    if (section == 'todos') {
        view_all();
        return;
    }

    view('per_nature');
}

function view_all() {
    cleanup();

    // Prepare columns we will display.
    var columns = []
    var n_columns = 0;

    var string_columns = ['Tipo de gasto', 'Deputad@', 'Partido',
                          'Empresa/Pessoa', 'CNPJ/CPF', 'N° do Doc.',
                          'Data']

    for (n_columns = 0; n_columns < string_columns.length; n_columns++) {
        var col = Object();
        col.label = string_columns[n_columns];
        col.type = 'string';
        columns[n_columns] = col;
    }

    var col = Object();
    col.label = 'Valor';
    col.type = 'money';
    columns[n_columns++] = col;

    // Build base table.
    var table_elements = build_table_top(columns);
    var table = table_elements[0];
    var tbody = table_elements[1];

    table.setAttribute('class', 'fullwidth');

    aoColumns = []
    for (var j = 0; j < columns.length; j++) {
        var coltype = columns[j]['type'];

        if (coltype == 'money') {
            aoColumns[j] = { sType: 'money' };
        } else {
            aoColumns[j] = null;
        }
    }

    var data_table = jQuery('#resultstable').dataTable({
        bPaginate: true,
	bProcessing: true,
	bServerSide: true,
	sAjaxSource: '/qserver/all',
        aoColumns: aoColumns,
        aaSorting: [[6, 'desc']]
    });

    new FixedHeader(data_table);
}

function detail_legislator(legid) {
    cleanup();

    $.getJSON('/qserver/legislator_info/' + legid, function(response) {
        var table_title = document.getElementById('tabletitle');
        table_title.innerHTML = response.legname + ' (' + response.legparty + ')';
    });

    // Prepare columns we will display.
    var columns = []
    var n_columns = 0;

    var string_columns = ['Tipo de gasto', 'Empresa/Pessoa',
                          'CNPJ/CPF', 'N° do Doc.', 'Data']

    for (n_columns = 0; n_columns < string_columns.length; n_columns++) {
        var col = Object();
        col.label = string_columns[n_columns];
        col.type = 'string';
        columns[n_columns] = col;
    }

    var col = Object();
    col.label = 'Valor';
    col.type = 'money';
    columns[n_columns++] = col;

    // Build base table.
    var table_elements = build_table_top(columns);
    var table = table_elements[0];
    var tbody = table_elements[1];

    table.setAttribute('class', 'fullwidth');

    aoColumns = []
    for (var j = 0; j < columns.length; j++) {
        var coltype = columns[j]['type'];

        if (coltype == 'money') {
            aoColumns[j] = { sType: 'money' };
        } else {
            aoColumns[j] = null;
        }
    }

    var data_table = jQuery('#resultstable').dataTable({
        bPaginate: true,
	bProcessing: true,
	bServerSide: true,
	sAjaxSource: '/qserver/legislator_all/' + legid,
        aoColumns: aoColumns,
        aaSorting: [[4, 'desc']]
    });

    new FixedHeader(data_table);
}

function view(url) {
    $.getJSON('/qserver/' + url, function(response) {
        var columns = response.columns
        var data = response.data;
        var show_graph = response.show_graph;
        var graph_column = response.graph_column;

        // First of all, cleanup the graph.
        cleanup();

        // Build base table.
        var table_elements = build_table_top(columns);
        var table = table_elements[0];
        var tbody = table_elements[1];

        // If we display a graph, we need to collect some information
        // to help us, and build the list of numbers to plot.
        if (show_graph) {
            // The last row is the total.
            var total = data[data.length - 1][graph_column];
            var graph_xticks = [];
            var graph_data = [];
            var graph_counter = 0;
            var other = 0;
        }

        // We leave the last one out, and deal with it afterwards!
        // This adds all of the data to the table.
        for (var i = 0; i < data.length - 1; i++) {
            tr = document.createElement('tr');
            tbody.appendChild(tr);

            if (i % 2 != 0) {
                tr.setAttribute('class', 'odd');
            }

            if (show_graph) {
                if ((data[i][graph_column] / total) > 0.05) {
                    graph_xticks[graph_counter] = {v: graph_counter, label: data[i][0]};
                    graph_data[graph_counter] = [graph_counter, data[i][graph_column]];
                    graph_counter++;
                } else { // Track too small to show slices.
                    other += data[i][graph_column];
                }
            }

            for (var j = 0; j < columns.length; j++) {
                var coltype = columns[j]['type'];
                var colindex = columns[j]['index'];

                td = document.createElement('td');

                if (coltype == 'money') {
                    td.setAttribute('class', 'right');
                    td.innerHTML = jQuery().number_format(data[i][colindex], { symbol: 'R$' });
                } else {
                    td.innerHTML = data[i][colindex];
                }

                tr.appendChild(td);
            }

        }

        var tfoot = document.createElement('tfoot');
        table.appendChild(tfoot);

        var tr = document.createElement('tr');
        tr.setAttribute('class', 'final');
        tfoot.appendChild(tr);

        for (var j = 0; j < columns.length; j++) {
            var last_line = data[data.length - 1];
            var skip_total = columns[j].skip_total;
            var coltype = columns[j]['type'];
            var colindex = columns[j]['index'];

            td = document.createElement('td');

            if (skip_total) {
                td.setAttribute('class', 'empty');
                tr.appendChild(td);
                continue;
            }

            if (coltype == 'money') {
                td.setAttribute('class', 'right');
                td.innerHTML = jQuery().number_format(data[i][colindex], { symbol: 'R$' });
            } else {
                td.innerHTML = data[i][colindex];
            }

            tr.appendChild(td);
        }

        // dataTable!
        aoColumns = []
        for (var j = 0; j < columns.length; j++) {
            var coltype = columns[j]['type'];

            if (coltype == 'money') {
                aoColumns[j] = { sType: 'money' };
            } else {
                aoColumns[j] = null;
            }
        }

        jQuery('#resultstable').dataTable({
            bPaginate: false,
            aoColumns: aoColumns,
            aaSorting: [[columns.length - 1, 'desc']]
        });

        setup_navigation_handling('#resultstable .navigation');

        // Graph.

        // No graph will be displayed - make sure the canvas is empty.
        if (!show_graph) {
            return;
        }

        var graph_title = document.getElementById('graphtitle');
        graph_title.parentNode.style.display = 'block';

        // Title the graph with the title of the graphed column.
        if (response.graph_title != undefined) {
            graph_title.innerHTML = response.graph_title;
        }

        // The "Other" slice of the pie.
        graph_xticks[graph_counter] = {v: graph_counter, label: 'Outros'};
        graph_data[graph_counter] = [graph_counter, other];

        var options = {
            'xTicks': graph_xticks,
            'drawBackground': false,
            'axisLabelWidth': '90',
            'axisLabelFontSize': 12,
        };

        var layout = new Layout('pie', options);
        layout.addDataset('expenses', graph_data);
        layout.evaluate();

        var canvas = document.getElementById('graph');
        var plotter = new PlotKit.SweetCanvasRenderer(canvas, layout, options);
        plotter.render();
    });
}
<commit_msg>Set more interesting titles depending on where on the page we are<commit_after>function normalizeMoney(value)
{
    var x = value.replace(/R\$ /g, '').replace(/\./g, '').replace(/,/g, '')
    return parseInt(x);
}

jQuery.fn.dataTableExt.oSort['money-asc'] = function(a, b) {
    return normalizeMoney(a) - normalizeMoney(b);
};

jQuery.fn.dataTableExt.oSort['money-desc'] = function(a, b) {
    return normalizeMoney(b) - normalizeMoney(a);
};

function handle_navigation (e)
{
    history.pushState(null, null, $(this)[0].href);
    view_from_url();
    e.preventDefault();
}

function setup_history_handling()
{
    // Gracefully degrade.
    if (typeof history == undefined || typeof history.pushState == undefined) {
        return;
    }

    window.addEventListener('popstate', function(e) {
        view_from_url();
    });
}

function setup_navigation_handling(query)
{
    // Gracefully degrade.
    if (typeof history == undefined || typeof history.pushState == undefined) {
        return;
    }

    $(query).click(handle_navigation);
}

function montanha_init()
{
    setup_history_handling();
    setup_navigation_handling('.navigation');

    view_from_url();
}

function cleanup() {
    var canvas = document.getElementById('graph');

    context = canvas.getContext('2d');
    context.clearRect(0, 0, canvas.width, canvas.height);

    var table_title = document.getElementById('tabletitle');
    table_title.innerHTML = '';

    var graph_title = document.getElementById('graphtitle');
    graph_title.innerHTML = '';
    graph_title.parentNode.style.display = 'none';

    // Remove the labels too.
    var graphparent = document.getElementById('graphparent');
    var divs = $(graphparent).find('div');
    for (var i = 0; i < divs.length; i++) {
        graphparent.removeChild(divs[i]);
    }

    // Remove any left over from FixedHeader.
    try {
        var div = document.evaluate("//div[contains(@class, 'FixedHeader_Cloned')]", document, null, XPathResult.ANY_TYPE, null).iterateNext();
        div.parentNode.removeChild(div);
    } catch(e) {
    }
}

function build_table_top(columns) {
    // Now let's start building the new data display.
    var table = document.createElement('table');
    table.setAttribute('id', 'resultstable');

    var thead = document.createElement('thead');
    table.appendChild(thead)

    var tr = document.createElement('tr');
    thead.appendChild(tr);

    // First of all, the titles.
    for (var i = 0; i < columns.length; i++) {
        var col_label = columns[i]['label'];

        var th = document.createElement('th');
        th.innerHTML = col_label;
        tr.appendChild(th);
    }

    var tbody = document.createElement('tbody');
    table.appendChild(tbody);

    // Place new table in the DOM, replacing the old one.
    var results_pane = document.getElementById('results');
    if (results_pane.firstChild != null) {
        results_pane.replaceChild(table, results_pane.firstChild);
    } else {
        results_pane.appendChild(table);
    }

    return [table, tbody];
}

var main_title = undefined;
function set_title(title)
{
    if (main_title == undefined) {
        main_title = document.title;
    }

    document.title = title + ' - ' + main_title;
}

function view_from_url()
{
    // Set a good fallback.
    set_title('Totais por tipo de gasto');

    var pathname = window.location.pathname;
    if (pathname == '' || pathname == '/') {
        view('per_nature');
        return;
    }

    var components = pathname.split('/');

    // Remove the empty string.
    components.shift();

    var section = components.shift();

    var id = components.shift();
    if (id == "") id = undefined;

    if (section == 'legislador') {
        set_title('Totais por legislador');

        if (id == undefined) {
            view('per_legislator');
            return;
        }

        detail_legislator(id);
        return;
    }

    if (section == 'partido') {
        set_title('Totais por partido');
        view('per_party');
        return;
    }

    if (section == 'fornecedor') {
        set_title('Totais por fornecedor');
        view('per_supplier');
        return;
    }

    if (section == 'todos') {
        set_title('Todos os gastos');
        view_all();
        return;
    }

    view('per_nature');
}

function view_all() {
    cleanup();

    // Prepare columns we will display.
    var columns = []
    var n_columns = 0;

    var string_columns = ['Tipo de gasto', 'Deputad@', 'Partido',
                          'Empresa/Pessoa', 'CNPJ/CPF', 'N° do Doc.',
                          'Data']

    for (n_columns = 0; n_columns < string_columns.length; n_columns++) {
        var col = Object();
        col.label = string_columns[n_columns];
        col.type = 'string';
        columns[n_columns] = col;
    }

    var col = Object();
    col.label = 'Valor';
    col.type = 'money';
    columns[n_columns++] = col;

    // Build base table.
    var table_elements = build_table_top(columns);
    var table = table_elements[0];
    var tbody = table_elements[1];

    table.setAttribute('class', 'fullwidth');

    aoColumns = []
    for (var j = 0; j < columns.length; j++) {
        var coltype = columns[j]['type'];

        if (coltype == 'money') {
            aoColumns[j] = { sType: 'money' };
        } else {
            aoColumns[j] = null;
        }
    }

    var data_table = jQuery('#resultstable').dataTable({
        bPaginate: true,
	bProcessing: true,
	bServerSide: true,
	sAjaxSource: '/qserver/all',
        aoColumns: aoColumns,
        aaSorting: [[6, 'desc']]
    });

    new FixedHeader(data_table);
}

function detail_legislator(legid) {
    cleanup();

    $.getJSON('/qserver/legislator_info/' + legid, function(response) {
        var full_title = response.legname + ' (' + response.legparty + ')';
        set_title(full_title);

        var table_title = document.getElementById('tabletitle');
        table_title.innerHTML = full_title;
    });

    // Prepare columns we will display.
    var columns = []
    var n_columns = 0;

    var string_columns = ['Tipo de gasto', 'Empresa/Pessoa',
                          'CNPJ/CPF', 'N° do Doc.', 'Data']

    for (n_columns = 0; n_columns < string_columns.length; n_columns++) {
        var col = Object();
        col.label = string_columns[n_columns];
        col.type = 'string';
        columns[n_columns] = col;
    }

    var col = Object();
    col.label = 'Valor';
    col.type = 'money';
    columns[n_columns++] = col;

    // Build base table.
    var table_elements = build_table_top(columns);
    var table = table_elements[0];
    var tbody = table_elements[1];

    table.setAttribute('class', 'fullwidth');

    aoColumns = []
    for (var j = 0; j < columns.length; j++) {
        var coltype = columns[j]['type'];

        if (coltype == 'money') {
            aoColumns[j] = { sType: 'money' };
        } else {
            aoColumns[j] = null;
        }
    }

    var data_table = jQuery('#resultstable').dataTable({
        bPaginate: true,
	bProcessing: true,
	bServerSide: true,
	sAjaxSource: '/qserver/legislator_all/' + legid,
        aoColumns: aoColumns,
        aaSorting: [[4, 'desc']]
    });

    new FixedHeader(data_table);
}

function view(url) {
    $.getJSON('/qserver/' + url, function(response) {
        var columns = response.columns
        var data = response.data;
        var show_graph = response.show_graph;
        var graph_column = response.graph_column;

        // First of all, cleanup the graph.
        cleanup();

        // Build base table.
        var table_elements = build_table_top(columns);
        var table = table_elements[0];
        var tbody = table_elements[1];

        // If we display a graph, we need to collect some information
        // to help us, and build the list of numbers to plot.
        if (show_graph) {
            // The last row is the total.
            var total = data[data.length - 1][graph_column];
            var graph_xticks = [];
            var graph_data = [];
            var graph_counter = 0;
            var other = 0;
        }

        // We leave the last one out, and deal with it afterwards!
        // This adds all of the data to the table.
        for (var i = 0; i < data.length - 1; i++) {
            tr = document.createElement('tr');
            tbody.appendChild(tr);

            if (i % 2 != 0) {
                tr.setAttribute('class', 'odd');
            }

            if (show_graph) {
                if ((data[i][graph_column] / total) > 0.05) {
                    graph_xticks[graph_counter] = {v: graph_counter, label: data[i][0]};
                    graph_data[graph_counter] = [graph_counter, data[i][graph_column]];
                    graph_counter++;
                } else { // Track too small to show slices.
                    other += data[i][graph_column];
                }
            }

            for (var j = 0; j < columns.length; j++) {
                var coltype = columns[j]['type'];
                var colindex = columns[j]['index'];

                td = document.createElement('td');

                if (coltype == 'money') {
                    td.setAttribute('class', 'right');
                    td.innerHTML = jQuery().number_format(data[i][colindex], { symbol: 'R$' });
                } else {
                    td.innerHTML = data[i][colindex];
                }

                tr.appendChild(td);
            }

        }

        var tfoot = document.createElement('tfoot');
        table.appendChild(tfoot);

        var tr = document.createElement('tr');
        tr.setAttribute('class', 'final');
        tfoot.appendChild(tr);

        for (var j = 0; j < columns.length; j++) {
            var last_line = data[data.length - 1];
            var skip_total = columns[j].skip_total;
            var coltype = columns[j]['type'];
            var colindex = columns[j]['index'];

            td = document.createElement('td');

            if (skip_total) {
                td.setAttribute('class', 'empty');
                tr.appendChild(td);
                continue;
            }

            if (coltype == 'money') {
                td.setAttribute('class', 'right');
                td.innerHTML = jQuery().number_format(data[i][colindex], { symbol: 'R$' });
            } else {
                td.innerHTML = data[i][colindex];
            }

            tr.appendChild(td);
        }

        // dataTable!
        aoColumns = []
        for (var j = 0; j < columns.length; j++) {
            var coltype = columns[j]['type'];

            if (coltype == 'money') {
                aoColumns[j] = { sType: 'money' };
            } else {
                aoColumns[j] = null;
            }
        }

        jQuery('#resultstable').dataTable({
            bPaginate: false,
            aoColumns: aoColumns,
            aaSorting: [[columns.length - 1, 'desc']]
        });

        setup_navigation_handling('#resultstable .navigation');

        // Graph.

        // No graph will be displayed - make sure the canvas is empty.
        if (!show_graph) {
            return;
        }

        var graph_title = document.getElementById('graphtitle');
        graph_title.parentNode.style.display = 'block';

        // Title the graph with the title of the graphed column.
        if (response.graph_title != undefined) {
            graph_title.innerHTML = response.graph_title;
        }

        // The "Other" slice of the pie.
        graph_xticks[graph_counter] = {v: graph_counter, label: 'Outros'};
        graph_data[graph_counter] = [graph_counter, other];

        var options = {
            'xTicks': graph_xticks,
            'drawBackground': false,
            'axisLabelWidth': '90',
            'axisLabelFontSize': 12,
        };

        var layout = new Layout('pie', options);
        layout.addDataset('expenses', graph_data);
        layout.evaluate();

        var canvas = document.getElementById('graph');
        var plotter = new PlotKit.SweetCanvasRenderer(canvas, layout, options);
        plotter.render();
    });
}
<|endoftext|>


<commit_before>const RichEmbed = require('discord.js').RichEmbed;
const got = require('got');

const randomSelection = choices => choices[Math.floor(Math.random() * choices.length)];

const randomColor = () => [
    Math.floor(Math.random() * 256),
    Math.floor(Math.random() * 256),
    Math.floor(Math.random() * 256)
];

const randomFooter = () => {
    return randomSelection([
        'just add water!',
        'Powered by squirrels!',
        'codeisluvcodeislife',
        'Where did you get that?',
        'WHAT DID YOU BREAK!?',
        'D-D-D-DROP THE BASS',
        'Eat, Sleep, Dubstep',
        '#BlameRayzr522'
    ]);
};

const formatNumber = number => isNaN(number) ? NaN : number.toLocaleString();

const quoteRegex = input => `${input}`.replace(/[.?*+^$[\]\\(){}|-]/g, '\\$&');

const now = () => {
    let now = process.hrtime();
    return now[0] * 1e3 + now[1] / 1e6;
};

const timestampToDate = timestamp => {
    if (timestamp === true) {
        return new Date();
    }
    if (typeof timestamp === 'number') {
        return new Date(timestamp);
    }
    return timestamp;
};

const parseArgs = (args, options) => {
    if (!options)
        return args;
    if (typeof options === 'string')
        options = [options];

    let optionValues = {};

    let i;
    for (i = 0; i < args.length; i++) {
        let arg = args[i];
        if (!arg.startsWith('-')) {
            break;
        }

        let label = arg.substr(1);

        if (options.indexOf(label + ':') > -1) {
            let leftover = args.slice(i + 1).join(' ');
            let matches = leftover.match(/^"(.+?)"/);
            if (matches) {
                optionValues[label] = matches[1];
                i += matches[0].split(' ').length;
            } else {
                i++;
                optionValues[label] = args[i];
            }
        } else if (options.indexOf(label) > -1) {
            optionValues[label] = true;
        } else {
            break;
        }
    }

    return {
        options: optionValues,
        leftover: args.slice(i)
    };
};

const embed = (title, description = '', fields = [], options = {}) => {
    let url = options.url || '';
    let color = options.color || randomColor();

    if (options.inline) {
        if (fields.length % 3 === 2) {
            fields.push({ name: '\u200b', value: '\u200b' });
        }
        fields.forEach(obj => {
            obj.inline = true;
        });
    }

    return new RichEmbed({ fields, video: options.video || url })
        .setTitle(title)
        .setColor(color)
        .setDescription(description)
        .setURL(url)
        .setImage(options.image)
        .setTimestamp(options.timestamp ? timestampToDate(options.timestamp) : null)
        .setFooter(options.footer === true ? randomFooter() : (options.footer ? options.footer : ''), options.footer ? global.bot.user.avatarURL : undefined)
        .setAuthor(options.author === undefined ? '' : options.author)
        .setThumbnail(options.thumbnail);
};

const multiSend = (channel, messages, delay) => {
    delay = delay || 100;
    messages.forEach((m, i) => {
        setTimeout(() => {
            channel.send(m);
        }, delay * i);
    });
};

const sendLarge = (channel, largeMessage, options = {}) => {
    let message = largeMessage;
    let messages = [];
    let prefix = options.prefix || '';
    let suffix = options.suffix || '';

    let max = 2000 - prefix.length - suffix.length;

    while (message.length >= max) {
        let part = message.substr(0, max);
        let cutTo = max;
        if (options.cutOn) {
            /*
             Prevent infinite loop where lastIndexOf(cutOn) is the first char in `part`
             Later, we will correct by +1 since we did lastIndexOf on all but the first char in `part`
             We *dont* correct immediately, since if cutOn is not found, cutTo will be -1, and we dont want that
             to become 0
             */
            cutTo = part.slice(1).lastIndexOf(options.cutOn);

            // Prevent infinite loop when cutOn isnt found in message
            if (cutTo === -1) {
                cutTo = max;
            } else {
                // Correction necessary from a few lines above
                cutTo += 1;

                if (options.cutAfter) {
                    cutTo += 1;
                }
                part = part.substr(0, cutTo);
            }
        }
        messages.push(prefix + part + suffix);
        message = message.substr(cutTo);
    }

    if (message.length > 1) {
        messages.push(prefix + message + suffix);
    }

    multiSend(channel, messages, options.delay);
};

const playAnimation = (msg, delay, list) => {
    if (list.length < 1)
        return;

    let next = list.shift();
    let start = now();

    msg.edit(next).then(() => {
        let elapsed = now() - start;

        setTimeout(() => {
            playAnimation(msg, delay, list);
        }, Math.max(50, delay - elapsed));
    });
};

const uploadMethods = {
    hastebin: 'hastebin',
    ix: 'ix.io'
};

const textUpload = (text, options) => {
    options = options || {};
    let method = (options.method || uploadMethods.hastebin).toLowerCase();

    if (method === uploadMethods.ix) {
        return ixUpload(text);
    } else if (method === uploadMethods.hastebin) {
        return hastebinUpload(text);
    }
};

const hastebinUpload = text => {
    return got('https://hastebin.com/documents', { body: { 'contents': text }, form: true })
        .then(res => {
            if (res && res.body && res.body.key) {
                const key = res.body.key;
                return {
                    key: key,
                    success: true,
                    url: `https://hastebin.com/${key}`,
                    rawUrl: `https://hastebin.com/raw/${key}`
                };
            } else {
                return {
                    success: false
                };
            }
        });
};

const ixUpload = text => {
    return got('http://ix.io', { body: { 'f:1': text }, form: true })
        .then(res => {
            if (res && res.body) {
                return {
                    success: true,
                    url: res.body,
                    rawUrl: res.body
                };
            } else {
                return {
                    success: false
                };
            }
        });
};

module.exports = {
    // Randomizers
    randomSelection,
    randomColor,
    randomFooter,
    // Formatter
    formatNumber,
    quoteRegex,
    // Times
    now,
    timestampToDate,
    // Message utilities
    parseArgs,
    embed,
    multiSend,
    sendLarge,
    playAnimation,
    // Services
    uploadMethods,
    textUpload,
    hastebinUpload,
    ixUpload
};
<commit_msg>Made ix.io the default uploader while hastebin is down<commit_after>const RichEmbed = require('discord.js').RichEmbed;
const got = require('got');

const randomSelection = choices => choices[Math.floor(Math.random() * choices.length)];

const randomColor = () => [
    Math.floor(Math.random() * 256),
    Math.floor(Math.random() * 256),
    Math.floor(Math.random() * 256)
];

const randomFooter = () => {
    return randomSelection([
        'just add water!',
        'Powered by squirrels!',
        'codeisluvcodeislife',
        'Where did you get that?',
        'WHAT DID YOU BREAK!?',
        'D-D-D-DROP THE BASS',
        'Eat, Sleep, Dubstep',
        '#BlameRayzr522'
    ]);
};

const formatNumber = number => isNaN(number) ? NaN : number.toLocaleString();

const quoteRegex = input => `${input}`.replace(/[.?*+^$[\]\\(){}|-]/g, '\\$&');

const now = () => {
    let now = process.hrtime();
    return now[0] * 1e3 + now[1] / 1e6;
};

const timestampToDate = timestamp => {
    if (timestamp === true) {
        return new Date();
    }
    if (typeof timestamp === 'number') {
        return new Date(timestamp);
    }
    return timestamp;
};

const parseArgs = (args, options) => {
    if (!options)
        return args;
    if (typeof options === 'string')
        options = [options];

    let optionValues = {};

    let i;
    for (i = 0; i < args.length; i++) {
        let arg = args[i];
        if (!arg.startsWith('-')) {
            break;
        }

        let label = arg.substr(1);

        if (options.indexOf(label + ':') > -1) {
            let leftover = args.slice(i + 1).join(' ');
            let matches = leftover.match(/^"(.+?)"/);
            if (matches) {
                optionValues[label] = matches[1];
                i += matches[0].split(' ').length;
            } else {
                i++;
                optionValues[label] = args[i];
            }
        } else if (options.indexOf(label) > -1) {
            optionValues[label] = true;
        } else {
            break;
        }
    }

    return {
        options: optionValues,
        leftover: args.slice(i)
    };
};

const embed = (title, description = '', fields = [], options = {}) => {
    let url = options.url || '';
    let color = options.color || randomColor();

    if (options.inline) {
        if (fields.length % 3 === 2) {
            fields.push({ name: '\u200b', value: '\u200b' });
        }
        fields.forEach(obj => {
            obj.inline = true;
        });
    }

    return new RichEmbed({ fields, video: options.video || url })
        .setTitle(title)
        .setColor(color)
        .setDescription(description)
        .setURL(url)
        .setImage(options.image)
        .setTimestamp(options.timestamp ? timestampToDate(options.timestamp) : null)
        .setFooter(options.footer === true ? randomFooter() : (options.footer ? options.footer : ''), options.footer ? global.bot.user.avatarURL : undefined)
        .setAuthor(options.author === undefined ? '' : options.author)
        .setThumbnail(options.thumbnail);
};

const multiSend = (channel, messages, delay) => {
    delay = delay || 100;
    messages.forEach((m, i) => {
        setTimeout(() => {
            channel.send(m);
        }, delay * i);
    });
};

const sendLarge = (channel, largeMessage, options = {}) => {
    let message = largeMessage;
    let messages = [];
    let prefix = options.prefix || '';
    let suffix = options.suffix || '';

    let max = 2000 - prefix.length - suffix.length;

    while (message.length >= max) {
        let part = message.substr(0, max);
        let cutTo = max;
        if (options.cutOn) {
            /*
             Prevent infinite loop where lastIndexOf(cutOn) is the first char in `part`
             Later, we will correct by +1 since we did lastIndexOf on all but the first char in `part`
             We *dont* correct immediately, since if cutOn is not found, cutTo will be -1, and we dont want that
             to become 0
             */
            cutTo = part.slice(1).lastIndexOf(options.cutOn);

            // Prevent infinite loop when cutOn isnt found in message
            if (cutTo === -1) {
                cutTo = max;
            } else {
                // Correction necessary from a few lines above
                cutTo += 1;

                if (options.cutAfter) {
                    cutTo += 1;
                }
                part = part.substr(0, cutTo);
            }
        }
        messages.push(prefix + part + suffix);
        message = message.substr(cutTo);
    }

    if (message.length > 1) {
        messages.push(prefix + message + suffix);
    }

    multiSend(channel, messages, options.delay);
};

const playAnimation = (msg, delay, list) => {
    if (list.length < 1)
        return;

    let next = list.shift();
    let start = now();

    msg.edit(next).then(() => {
        let elapsed = now() - start;

        setTimeout(() => {
            playAnimation(msg, delay, list);
        }, Math.max(50, delay - elapsed));
    });
};

const uploadMethods = {
    hastebin: 'hastebin',
    ix: 'ix.io'
};

const textUpload = (text, options) => {
    options = options || {};
    let method = (options.method || uploadMethods.ix).toLowerCase();

    if (method === uploadMethods.ix) {
        return ixUpload(text);
    } else if (method === uploadMethods.hastebin) {
        return hastebinUpload(text);
    }
};

const hastebinUpload = text => {
    return got('https://hastebin.com/documents', { body: { 'contents': text }, form: true })
        .then(res => {
            if (res && res.body && res.body.key) {
                const key = res.body.key;
                return {
                    key: key,
                    success: true,
                    url: `https://hastebin.com/${key}`,
                    rawUrl: `https://hastebin.com/raw/${key}`
                };
            } else {
                return {
                    success: false
                };
            }
        });
};

const ixUpload = text => {
    return got('http://ix.io', { body: { 'f:1': text }, form: true })
        .then(res => {
            if (res && res.body) {
                return {
                    success: true,
                    url: res.body,
                    rawUrl: res.body
                };
            } else {
                return {
                    success: false
                };
            }
        });
};

module.exports = {
    // Randomizers
    randomSelection,
    randomColor,
    randomFooter,
    // Formatter
    formatNumber,
    quoteRegex,
    // Times
    now,
    timestampToDate,
    // Message utilities
    parseArgs,
    embed,
    multiSend,
    sendLarge,
    playAnimation,
    // Services
    uploadMethods,
    textUpload,
    hastebinUpload,
    ixUpload
};
<|endoftext|>


<commit_before>const https = require('https')
const parseUrl = require('url').parse
const querystring = require('querystring')
const baseUrl = require('./constants').BASE_URL
const UA = require('./constants').UA
const APP_ID = require('./constants').APP_ID

const debug = {}
const ENV = process.env.NODE_ENV
Object.keys(console).forEach(method => {
  debug[method] = function() {
    if (ENV === 'debug' || ENV === 'test') {
      const args = [new Date()].concat([].slice.call(arguments))
      console[method].apply(console, args)
    }
  }
})

function sleep(second) {
  second = second || 1
  return new Promise(resolve => {
    setTimeout(() => {
      resolve()
    }, second * 1000)
  })
}

const token = (() => {
  let currentToken = ''
  return {
    get() {
      return currentToken
    },
    set(newToken) {
      currentToken = newToken
      return currentToken
    }
  }
})()

function r(url, options) {
  return new Promise((resolve, reject) => {
    const qs = options ? `?${querystring.stringify(options)}` : ''
    const finalUrl = `${url}${qs}`
    debug.log(`request url: ${finalUrl}`)
    let options = {
      headers: {
        'User-Agent': UA
      },
    };
    if(process.env.LOCAL_ADDRESS)
      options.localAddress = process.env.LOCAL_ADDRESS;
    const req = https.request(Object.assign(parseUrl(finalUrl), options), res => {
      let body = ''
      res.on('data', chunk => {
        body += chunk
      })
      res.on('end', () => {
        try {
          body = JSON.parse(body)
        } catch (e) {
          debug.error(`request error: ${e}`)
        }
        resolve({
          status: res.statusCode,
          headers: res.headers,
          body: body
        })
      })
    })

    req.on('error', err => {
      debug.error(`request failed: ${err}`)
      reject(err)
    })
    req.end()
  })
}

function getToken() {
  return r(baseUrl, {
    get_token: 'get_token',
    app_id: APP_ID
  }).then(res => token.set(res.body.token))
}

function request(url, options) {
  return Promise.resolve(token.get() || getToken()).then(currentToken => {
    options = options || {}
    options.token = currentToken
    return r(url, options)
  }).then(res => {
    if (res.body && res.body.error) {
      if (res.body.error_code == 4) {
        // token expired
        debug.warn(`token expired: ${token.get()}`)
        token.set(null)
        return request(url, options)
      } else if (res.body.error_code == 5) {
        // Too many requests per second
        debug.warn(`too many request`)
        return sleep(2).then(() => request(url, options))
      }
      return res
    } else if (!res.body) {
      // Too many requests per second
      debug.warn(`too many request`)
      return sleep(2).then(() => request(url, options))
    } else {
      return res
    }
  })
}

function proceedExtraParams(params) {
  params = params || {}
  const defaultParams = {
    category: null,
    limit: 25,
    sort: 'last',
    min_seeders: null,
    min_leechers: null,
    format: 'json_extended',
    ranked: null,
    app_id: APP_ID
  }

  const result = {}
  Object.keys(defaultParams).forEach(key => {
    const value = params[key] || defaultParams[key]
    if (!value) return
    result[key] = value
  })

  if (result.category) {
    result.category = result.category.join(';')
  }

  if (!~[25, 50, 100].indexOf(result.limit)) {
    result.limit =  25
  }

  if (!~['last', 'seeders', 'leechers'].indexOf(result.sort)) {
    result.sort = 'last'
  }

  if (typeof result.min_leechers !== 'number') {
    delete result.min_leechers
  }

  if (typeof result.min_seeders !== 'number') {
    delete result.min_seeders
  }

  if (!~['json', 'json_extended'].indexOf(result.format)) {
    result.format = 'json_extended'
  }

  if (result.ranked != 0) {
    delete result.ranked
  }

  return result
}

module.exports = {
  request,
  proceedExtraParams
}
<commit_msg>Rename Variable<commit_after>const https = require('https')
const parseUrl = require('url').parse
const querystring = require('querystring')
const baseUrl = require('./constants').BASE_URL
const UA = require('./constants').UA
const APP_ID = require('./constants').APP_ID

const debug = {}
const ENV = process.env.NODE_ENV
Object.keys(console).forEach(method => {
  debug[method] = function() {
    if (ENV === 'debug' || ENV === 'test') {
      const args = [new Date()].concat([].slice.call(arguments))
      console[method].apply(console, args)
    }
  }
})

function sleep(second) {
  second = second || 1
  return new Promise(resolve => {
    setTimeout(() => {
      resolve()
    }, second * 1000)
  })
}

const token = (() => {
  let currentToken = ''
  return {
    get() {
      return currentToken
    },
    set(newToken) {
      currentToken = newToken
      return currentToken
    }
  }
})()

function r(url, options) {
  return new Promise((resolve, reject) => {
    const qs = options ? `?${querystring.stringify(options)}` : ''
    const finalUrl = `${url}${qs}`
    debug.log(`request url: ${finalUrl}`)
    let requestOptions = {
      headers: {
        'User-Agent': UA
      },
    };
    if(process.env.LOCAL_ADDRESS)
      requestOptions.localAddress = process.env.LOCAL_ADDRESS;
    const req = https.request(Object.assign(parseUrl(finalUrl), requestOptions), res => {
      let body = ''
      res.on('data', chunk => {
        body += chunk
      })
      res.on('end', () => {
        try {
          body = JSON.parse(body)
        } catch (e) {
          debug.error(`request error: ${e}`)
        }
        resolve({
          status: res.statusCode,
          headers: res.headers,
          body: body
        })
      })
    })

    req.on('error', err => {
      debug.error(`request failed: ${err}`)
      reject(err)
    })
    req.end()
  })
}

function getToken() {
  return r(baseUrl, {
    get_token: 'get_token',
    app_id: APP_ID
  }).then(res => token.set(res.body.token))
}

function request(url, options) {
  return Promise.resolve(token.get() || getToken()).then(currentToken => {
    options = options || {}
    options.token = currentToken
    return r(url, options)
  }).then(res => {
    if (res.body && res.body.error) {
      if (res.body.error_code == 4) {
        // token expired
        debug.warn(`token expired: ${token.get()}`)
        token.set(null)
        return request(url, options)
      } else if (res.body.error_code == 5) {
        // Too many requests per second
        debug.warn(`too many request`)
        return sleep(2).then(() => request(url, options))
      }
      return res
    } else if (!res.body) {
      // Too many requests per second
      debug.warn(`too many request`)
      return sleep(2).then(() => request(url, options))
    } else {
      return res
    }
  })
}

function proceedExtraParams(params) {
  params = params || {}
  const defaultParams = {
    category: null,
    limit: 25,
    sort: 'last',
    min_seeders: null,
    min_leechers: null,
    format: 'json_extended',
    ranked: null,
    app_id: APP_ID
  }

  const result = {}
  Object.keys(defaultParams).forEach(key => {
    const value = params[key] || defaultParams[key]
    if (!value) return
    result[key] = value
  })

  if (result.category) {
    result.category = result.category.join(';')
  }

  if (!~[25, 50, 100].indexOf(result.limit)) {
    result.limit =  25
  }

  if (!~['last', 'seeders', 'leechers'].indexOf(result.sort)) {
    result.sort = 'last'
  }

  if (typeof result.min_leechers !== 'number') {
    delete result.min_leechers
  }

  if (typeof result.min_seeders !== 'number') {
    delete result.min_seeders
  }

  if (!~['json', 'json_extended'].indexOf(result.format)) {
    result.format = 'json_extended'
  }

  if (result.ranked != 0) {
    delete result.ranked
  }

  return result
}

module.exports = {
  request,
  proceedExtraParams
}
<|endoftext|>


<commit_before>import moment from 'moment';
import _ from 'underscore';
// ------------------------------------
// Help functions
// ------------------------------------

moment.locale('nb-no');

const easters = {
  '2016': moment({'year': 2016, 'month': 2, 'day': 27}),
  '2017': moment({'year': 2017, 'month': 3, 'day': 16}),
  '2018': moment({'year': 2018, 'month': 3, 'day': 1}),
  '2019': moment({'year': 2019, 'month': 3, 'day': 21}),
  '2020': moment({'year': 2020, 'month': 3, 'day': 12}),
  '2021': moment({'year': 2021, 'month': 3, 'day': 4}),
  '2022': moment({'year': 2022, 'month': 3, 'day': 17}),
  '2023': moment({'year': 2023, 'month': 3, 'day': 9}),
  '2024': moment({'year': 2024, 'month': 2, 'day': 31}),
  '2025': moment({'year': 2025, 'month': 3, 'day': 20}),
  '2026': moment({'year': 2026, 'month': 3, 'day': 5}),
};

const getNorwegianHolidays = (year) => {
  const easter = easters[year];
  return {
    '1. nyttårsdag': moment({'year': year, 'month': 0, 'day': 1}),
    'arbeidernes dag': moment({'year': year, 'month': 4, 'day': 1}),
    'grunnlovsdag': moment({'year': year, 'month': 4, 'day': 17}),
    '1. juledag': moment({'year': year, 'month': 11, 'day': 25}),
    '2. juledag': moment({'year': year, 'month': 11, 'day': 26}),
    'palmesøndag': moment(easter).subtract(7, 'days'),
    'skjærtorsdag': moment(easter).subtract(3, 'days'),
    'langfredag': moment(easter).subtract(2, 'days'),
    '2. påskedag': moment(easter).add(1, 'day'),
    'kristi himmelfartsdag': moment(easter).add(39, 'days'),
    '1. pinsedag': moment(easter).add(49, 'days'),
    '2. pinsedag': moment(easter).add(50, 'days')
  }
};

const getHolidaysOfYear = (year) => {
  return _.reduce(getNorwegianHolidays(year), (acc, day, index) => {
    acc[day.dayOfYear()] = {'date': day, 'name': index};
    return acc;
  }, {})
};

const getAllDaysOfYear = (year, holidays = getHolidaysOfYear(year), squeezeNumber = 3) => {
  const day = moment({'year': year, 'month': 0, 'day': 1});
  const result = {};
  while (day.get('year') === year) {
    let dayObject = {'date': moment(day)};
    dayObject.formattedDate = dayObject.date.format('dddd D MMM gggg');
    if (_.contains(Object.keys(holidays), day.dayOfYear().toString())) {
      dayObject['type'] = 'holiday';
      dayObject['name'] = holidays[day.dayOfYear().toString()].name;
    }
    else if (day.isoWeekday() === 6 || day.isoWeekday() === 7) {
      dayObject['type'] = 'weekend';
    }
    else {
      dayObject['type'] = 'workday';
    }
    result[day.dayOfYear()] = dayObject;
    day.add(1, 'day');
  }
  _.chain(result)
    .filter(day => day.type === 'workday')
    .each((day) => {
      let dayOfYear = day.date.dayOfYear();
      try {
        result[dayOfYear]['daysFromLast'] = (result[dayOfYear - 1].daysFromLast + 1 || 0);
      } catch (e) {
        result[dayOfYear]['daysFromLast'] = 1;
      }
    })
    .reverse()
    .each((day) => {
      let dayOfYear = day.date.dayOfYear();
      try {
        result[dayOfYear]['daysToNext'] = (result[dayOfYear + 1].daysToNext + 1 || 0);
      } catch (e) {
        result[dayOfYear]['daysToNext'] = 1;
      }
    }).each((day) => {
    let dayOfYear = day.date.dayOfYear();
    if (day.daysToNext + day.daysFromLast < squeezeNumber) {
      result[dayOfYear].type = 'inneklemt';
    }
  });
  return result;
};

export default getAllDaysOfYear
<commit_msg>Fix momentjs using Norwegian locale.<commit_after>import moment from 'moment';
import 'moment/locale/nb';
import _ from 'underscore';
// ------------------------------------
// Help functions
// ------------------------------------

moment.locale('nb-no');

const easters = {
  '2016': moment({'year': 2016, 'month': 2, 'day': 27}),
  '2017': moment({'year': 2017, 'month': 3, 'day': 16}),
  '2018': moment({'year': 2018, 'month': 3, 'day': 1}),
  '2019': moment({'year': 2019, 'month': 3, 'day': 21}),
  '2020': moment({'year': 2020, 'month': 3, 'day': 12}),
  '2021': moment({'year': 2021, 'month': 3, 'day': 4}),
  '2022': moment({'year': 2022, 'month': 3, 'day': 17}),
  '2023': moment({'year': 2023, 'month': 3, 'day': 9}),
  '2024': moment({'year': 2024, 'month': 2, 'day': 31}),
  '2025': moment({'year': 2025, 'month': 3, 'day': 20}),
  '2026': moment({'year': 2026, 'month': 3, 'day': 5}),
};

const getNorwegianHolidays = (year) => {
  const easter = easters[year];
  return {
    '1. nyttårsdag': moment({'year': year, 'month': 0, 'day': 1}),
    'arbeidernes dag': moment({'year': year, 'month': 4, 'day': 1}),
    'grunnlovsdag': moment({'year': year, 'month': 4, 'day': 17}),
    '1. juledag': moment({'year': year, 'month': 11, 'day': 25}),
    '2. juledag': moment({'year': year, 'month': 11, 'day': 26}),
    'palmesøndag': moment(easter).subtract(7, 'days'),
    'skjærtorsdag': moment(easter).subtract(3, 'days'),
    'langfredag': moment(easter).subtract(2, 'days'),
    '2. påskedag': moment(easter).add(1, 'day'),
    'kristi himmelfartsdag': moment(easter).add(39, 'days'),
    '1. pinsedag': moment(easter).add(49, 'days'),
    '2. pinsedag': moment(easter).add(50, 'days')
  }
};

const getHolidaysOfYear = (year) => {
  return _.reduce(getNorwegianHolidays(year), (acc, day, index) => {
    acc[day.dayOfYear()] = {'date': day, 'name': index};
    return acc;
  }, {})
};

const getAllDaysOfYear = (year, holidays = getHolidaysOfYear(year), squeezeNumber = 3) => {
  const day = moment({'year': year, 'month': 0, 'day': 1});
  const result = {};
  while (day.get('year') === year) {
    let dayObject = {'date': moment(day)};
    dayObject.formattedDate = dayObject.date.format('dddd D MMM gggg');
    if (_.contains(Object.keys(holidays), day.dayOfYear().toString())) {
      dayObject['type'] = 'holiday';
      dayObject['name'] = holidays[day.dayOfYear().toString()].name;
    }
    else if (day.isoWeekday() === 6 || day.isoWeekday() === 7) {
      dayObject['type'] = 'weekend';
    }
    else {
      dayObject['type'] = 'workday';
    }
    result[day.dayOfYear()] = dayObject;
    day.add(1, 'day');
  }
  _.chain(result)
    .filter(day => day.type === 'workday')
    .each((day) => {
      let dayOfYear = day.date.dayOfYear();
      try {
        result[dayOfYear]['daysFromLast'] = (result[dayOfYear - 1].daysFromLast + 1 || 0);
      } catch (e) {
        result[dayOfYear]['daysFromLast'] = 1;
      }
    })
    .reverse()
    .each((day) => {
      let dayOfYear = day.date.dayOfYear();
      try {
        result[dayOfYear]['daysToNext'] = (result[dayOfYear + 1].daysToNext + 1 || 0);
      } catch (e) {
        result[dayOfYear]['daysToNext'] = 1;
      }
    }).each((day) => {
    let dayOfYear = day.date.dayOfYear();
    if (day.daysToNext + day.daysFromLast < squeezeNumber) {
      result[dayOfYear].type = 'inneklemt';
    }
  });
  return result;
};

export default getAllDaysOfYear
<|endoftext|>


<commit_before>/*
MagJS v0.29.3
http://github.com/magnumjs/mag.js
(c) Michael Glazer
License: MIT
*/

(function(mag) {

  'use strict';

  var utils = {};

  //UTILITY
  utils.copy = obj => Object.assign({}, obj)

  utils.merge = function() {
    return Object.assign.apply({}, arguments);
  }

  utils.extend = function(target, source, deep) {
    //if the sources are undefined then don't add to target even if exists
    for (var k in source) {
      if (source[k] === undefined) {
        delete source[k];
      } else if (deep && utils.isObject(source[k])) {
        return utils.extend(target[k], source[k])
      }
    }
    return utils.merge(target, source)
  }

  //For Safari
  utils.isObject = obj => Object.prototype.toString.call(obj).substr(-7) == 'Object]'

  var funReplacer = (key, value) => typeof value == 'function' ? '' + value : value

  utils.toJson = obj => JSON.stringify(obj, funReplacer)

  utils.isEmpty = obj => {
    for (var k in obj) {
      if (obj.hasOwnProperty(k)) return 0
    }
    return 1
  }

  utils.isHTMLEle = (item) => item && item.nodeType === 1

  utils.callHook = function(hookins, key, name, i, data, before) {
    if (hookins[name][i].key == key) {
      before = {
        v: data.value,
        k: data.key
      }
      data.change = false;
      hookins[name][i].handler.call(hookins[name][i].context, data);
      //if any change
      if (before !== {
          v: data.value,
          k: data.key
        }) {
        data.change = true
      }
    }
  }

  //rAF:
  var queue = [],
    scheduler, scheduled=[], prev=[];

  utils.scheduleFlush = function(id, fun) {
    return new Promise(function(resolve) {
      if (mag.rafBounce || mag.rafBounceIds[id]) {
        if(scheduled[id]){
          prev[id]()
          cancelAnimationFrame(scheduled[id]);
        }
        prev[id] = resolve
        scheduled[id]=requestAnimationFrame(start => {
          fun();
          resolve();
        })
      } else {
        // queue[id] = queue[id] || [];
        //*
        if (fun) queue.push(fun);
        if (!scheduler && queue.length) {
          scheduler = requestAnimationFrame(start => {
            scheduler = 0;
            while (queue.length) {
              if (performance.now() - start > 16.6) break;
              queue.shift()()
            }
            if (!queue.length) {
              resolve();
            } else {
              //If the batch errored we may still have tasks queued
              utils.scheduleFlush(id)
                .then(() => resolve())
            }
          })
        } else {
          resolve()
        }
      }
    })
  }

  //Events:
  var handlers = []
  utils.onLCEvent = function(eventName, index, handler) {
    var eventer = eventName + '-' + index;
    handlers[eventer] = handlers[eventer] || []
    var size = handlers[eventer].push(handler);
    //Remove self:
    return function() {
      return handlers[eventer].splice(size - 1, 1)
    }
  }

  utils.callLCEvent = function(eventName, controller, node, index, once, extra) {
    var isPrevented;
    utils.runningEventInstance = index;
    var props = mag.mod.getProps(index);
    var instance = mag.mod.getMod(index);

    var obj = instance[eventName] ? instance : controller[eventName] && controller;
    if (obj) {
      isPrevented = obj[eventName].call(instance, node, props, index, extra)
      if (once) obj[eventName] = 0
    }

    // on Handlers
    var eventer = eventName + '-' + index;
    if (handlers[eventer]) {
      for (var handle of handlers[eventer]) {
        handle(controller, props);
      }
      if (once) handlers[eventer] = 0
    }
    utils.runningEventInstance = -1;
    if (isPrevented === false) return true;
  }

  //Collection:
  var a = {
    i: [],
    isItem: id => ~a.i.indexOf(id),
    setItem: id => a.i[a.i.length] = id,
    getItem: id => a.i.indexOf(id),
    getItemVal: index => a.i[index],
    removeItem: id => a.i.splice(a.i.indexOf(id), 1)
  }

  utils.items = a

  utils.getItemInstanceIdAll = () => a.i

  utils.getItemInstanceId = id => {
    if (a.isItem(id)) {
      return a.getItem(id)
    } else {
      a.setItem(id)
      return a.getItem(id)
    }
  }

  mag.utils = utils

}(mag));
<commit_msg>Update utils.js<commit_after>/*
MagJS v0.29.3
http://github.com/magnumjs/mag.js
(c) Michael Glazer
License: MIT
*/

(function(mag) {

  'use strict';

  var utils = {};

  //UTILITY
  utils.copy = obj => Object.assign({}, obj)

  utils.merge = function() {
    return Object.assign.apply({}, arguments);
  }

  utils.extend = function(target, source, deep) {
    //if the sources are undefined then don't add to target even if exists
    for (var k in source) {
      if (source[k] === undefined) {
        delete source[k];
      } else if (deep && utils.isObject(source[k])) {
        return utils.extend(target[k], source[k])
      }
    }
    return utils.merge(target, source)
  }

  //For Safari
  utils.isObject = obj => Object.prototype.toString.call(obj).substr(-7) == 'Object]'

  var funReplacer = (key, value) => typeof value == 'function' ? '' + value : value

  utils.toJson = obj => JSON.stringify(obj, funReplacer)

  utils.isEmpty = obj => {
    for (var k in obj) {
      if (obj.hasOwnProperty(k)) return 0
    }
    return 1
  }

  utils.isHTMLEle = (item) => item && item.nodeType === 1

  utils.callHook = function(hookins, key, name, i, data, before) {
    if (hookins[name][i].key == key) {
      before = {
        v: data.value,
        k: data.key
      }
      data.change = false;
      hookins[name][i].handler.call(hookins[name][i].context, data);
      //if any change
      if (before !== {
          v: data.value,
          k: data.key
        }) {
        data.change = true
      }
    }
  }

  //rAF:
  var queue = [],
    scheduler, scheduled=[], prev=[];

  utils.scheduleFlush = function(id, fun) {
    return new Promise(function(resolve) {
     if (mag.rafBounce || mag.rafBounceIds[id] && fun) {
        if(scheduled[id]){
          prev[id]()
          cancelAnimationFrame(scheduled[id]);
        }
        prev[id] = resolve
        scheduled[id]=requestAnimationFrame(start => {
          scheduled[id]=0
          fun();
          resolve();
        })
      } else {
        // queue[id] = queue[id] || [];
        //*
        if (fun) queue.push(fun);
        if (!scheduler && queue.length) {
          scheduler = requestAnimationFrame(start => {
            scheduler = 0;
            while (queue.length) {
              if (performance.now() - start > 16.6) break;
              queue.shift()()
            }
            if (!queue.length) {
              resolve();
            } else {
              //If the batch errored we may still have tasks queued
              utils.scheduleFlush(id)
                .then(() => resolve())
            }
          })
        } else {
          resolve()
        }
      }
    })
  }

  //Events:
  var handlers = []
  utils.onLCEvent = function(eventName, index, handler) {
    var eventer = eventName + '-' + index;
    handlers[eventer] = handlers[eventer] || []
    var size = handlers[eventer].push(handler);
    //Remove self:
    return function() {
      return handlers[eventer].splice(size - 1, 1)
    }
  }

  utils.callLCEvent = function(eventName, controller, node, index, once, extra) {
    var isPrevented;
    utils.runningEventInstance = index;
    var props = mag.mod.getProps(index);
    var instance = mag.mod.getMod(index);

    var obj = instance[eventName] ? instance : controller[eventName] && controller;
    if (obj) {
      isPrevented = obj[eventName].call(instance, node, props, index, extra)
      if (once) obj[eventName] = 0
    }

    // on Handlers
    var eventer = eventName + '-' + index;
    if (handlers[eventer]) {
      for (var handle of handlers[eventer]) {
        handle(controller, props);
      }
      if (once) handlers[eventer] = 0
    }
    utils.runningEventInstance = -1;
    if (isPrevented === false) return true;
  }

  //Collection:
  var a = {
    i: [],
    isItem: id => ~a.i.indexOf(id),
    setItem: id => a.i[a.i.length] = id,
    getItem: id => a.i.indexOf(id),
    getItemVal: index => a.i[index],
    removeItem: id => a.i.splice(a.i.indexOf(id), 1)
  }

  utils.items = a

  utils.getItemInstanceIdAll = () => a.i

  utils.getItemInstanceId = id => {
    if (a.isItem(id)) {
      return a.getItem(id)
    } else {
      a.setItem(id)
      return a.getItem(id)
    }
  }

  mag.utils = utils

}(mag));
<|endoftext|>