<commit_before>/*
 * Component Name: RefreshableListView
 * Author: Simar Singh (github/iSimar)
 * Description: This component is used to render a listview that can be
 *              pulled down to refresh
 *
 * Dependencies:
 *  -> react-native-gifted-listview 0.0.6 (https://github.com/FaridSafi/react-native-gifted-listview)
 *
 * Properties:
 *  -> renderRow
 *      render function for rows or cells in the listview
 *  -> onRefresh
 *      used for filling the listview on ethier pull to refresh or pagination (load more),
 *      it is called with 2 arugments page number and callback. see react-native-gifted-listview docs.
 *  -> backgroundColor (optional)
 *      default = '#FFFFFF', background color of the listview
 *  -> loadMoreText (optional)
 *      default = '+', text used at the end of the listview - pagination
 *  -> renderHeader (optional)
 *      rendering not sticky header of the listview
 *
 * Example:
 *  <RefreshableListView renderRow={(row)=>this.renderListViewRow(row)}
 *                       renderHeader={this.renderListViewHeader}
 *                       onRefresh={(page, callback)=>this.listViewOnRefresh(page, callback)}
 *                       backgroundColor={'#F6F6EF'}
 *                       loadMoreText={'Load More...'}/>
 *
 */
var React = require('react-native');

var {
    StyleSheet,
    Text,
    View,
    TouchableOpacity,
    Platform
} = React;

var GiftedListView = require('react-native-gifted-listview');

module.exports = React.createClass({
    getInitialState: function(){
        return {
            renderRow: this.props.renderRow,
            backgroundColor: this.props.backgroundColor ? this.props.backgroundColor : '#FFFFFF',
            loadMoreText: this.props.loadMoreText ? this.props.loadMoreText : '+',
            renderHeader: this.props.renderHeader ? this.props.renderHeader : null,
        };
    },
    onRefresh: function(page=1, callback, options){
        this.props.onRefresh(page, callback);
    },
    renderRow: function(row){
        return this.state.renderRow(row);
    },
    render: function(){
        return(
            <View style={[styles.container, {backgroundColor: this.state.backgroundColor}]}>
                <View style={styles.navBarSpace} />
                <GiftedListView rowView={this.renderRow}
                                onFetch={this.onRefresh}
                                paginationAllLoadedView={this.renderPaginationAllLoadedView}
                                paginationWaitingView={this.renderPaginationWaitingView}
                                headerView={this.renderHeaderView}
                                refreshable={Platform.OS !== 'android'}
                                customStyles={{
                                                refreshableView: {
                                                    backgroundColor: this.state.backgroundColor,
                                                    justifyContent: 'flex-end',
                                                    paddingBottom: 12,
                                                },
                                                paginationView: {
                                                    backgroundColor: this.state.backgroundColor,
                                                    height: 60
                                                }
                                }}/>
            </View>
        );
    },
    renderPaginationAllLoadedView: function(){
        return(
            <View />
        );
    },
    renderPaginationWaitingView: function(paginateCallback) {
        return (
            <TouchableOpacity style={styles.paginationView}
                              onPress={paginateCallback}>
                <Text style={styles.loadMoreText}>
                    {this.state.loadMoreText}
                </Text>
           </TouchableOpacity>
        );
    },
    renderHeaderView: function(){
        if(this.state.renderHeader){
            return this.props.renderHeader();
        }
        return (null);
    }
});

var styles = StyleSheet.create({
    container: {
        flex: 1
    },
    navBarSpace: {
        height: (Platform.OS !== 'android') ? 64 : 0,
    },
    rowContainer: {
        paddingRight: 15,
        paddingLeft: 10,
        flexDirection: 'row'
    },
    paginationView: {
        justifyContent: 'center',
        alignItems: 'center',
        paddingTop: 20,
        paddingBottom: 20
    },
    loadMoreText: {
        fontSize: 15,
        color: 'gray',
    }
});<commit_msg>update RefreshableListView header comment - bumped gifted-listview version<commit_after>/*
 * Component Name: RefreshableListView
 * Author: Simar Singh (github/iSimar)
 * Description: This component is used to render a listview that can be
 *              pulled down to refresh
 *
 * Dependencies:
 *  -> react-native-gifted-listview 0.0.7 (https://github.com/FaridSafi/react-native-gifted-listview)
 *
 * Properties:
 *  -> renderRow
 *      render function for rows or cells in the listview
 *  -> onRefresh
 *      used for filling the listview on ethier pull to refresh or pagination (load more),
 *      it is called with 2 arugments page number and callback. see react-native-gifted-listview docs.
 *  -> backgroundColor (optional)
 *      default = '#FFFFFF', background color of the listview
 *  -> loadMoreText (optional)
 *      default = '+', text used at the end of the listview - pagination
 *  -> renderHeader (optional)
 *      rendering not sticky header of the listview
 *
 * Example:
 *  <RefreshableListView renderRow={(row)=>this.renderListViewRow(row)}
 *                       renderHeader={this.renderListViewHeader}
 *                       onRefresh={(page, callback)=>this.listViewOnRefresh(page, callback)}
 *                       backgroundColor={'#F6F6EF'}
 *                       loadMoreText={'Load More...'}/>
 *
 */
var React = require('react-native');

var {
    StyleSheet,
    Text,
    View,
    TouchableOpacity,
    Platform
} = React;

var GiftedListView = require('react-native-gifted-listview');

module.exports = React.createClass({
    getInitialState: function(){
        return {
            renderRow: this.props.renderRow,
            backgroundColor: this.props.backgroundColor ? this.props.backgroundColor : '#FFFFFF',
            loadMoreText: this.props.loadMoreText ? this.props.loadMoreText : '+',
            renderHeader: this.props.renderHeader ? this.props.renderHeader : null,
        };
    },
    onRefresh: function(page=1, callback, options){
        this.props.onRefresh(page, callback);
    },
    renderRow: function(row){
        return this.state.renderRow(row);
    },
    render: function(){
        return(
            <View style={[styles.container, {backgroundColor: this.state.backgroundColor}]}>
                <View style={styles.navBarSpace} />
                <GiftedListView rowView={this.renderRow}
                                onFetch={this.onRefresh}
                                paginationAllLoadedView={this.renderPaginationAllLoadedView}
                                paginationWaitingView={this.renderPaginationWaitingView}
                                headerView={this.renderHeaderView}
                                refreshable={Platform.OS !== 'android'}
                                customStyles={{
                                                refreshableView: {
                                                    backgroundColor: this.state.backgroundColor,
                                                    justifyContent: 'flex-end',
                                                    paddingBottom: 12,
                                                },
                                                paginationView: {
                                                    backgroundColor: this.state.backgroundColor,
                                                    height: 60
                                                }
                                }}/>
            </View>
        );
    },
    renderPaginationAllLoadedView: function(){
        return(
            <View />
        );
    },
    renderPaginationWaitingView: function(paginateCallback) {
        return (
            <TouchableOpacity style={styles.paginationView}
                              onPress={paginateCallback}>
                <Text style={styles.loadMoreText}>
                    {this.state.loadMoreText}
                </Text>
           </TouchableOpacity>
        );
    },
    renderHeaderView: function(){
        if(this.state.renderHeader){
            return this.props.renderHeader();
        }
        return (null);
    }
});

var styles = StyleSheet.create({
    container: {
        flex: 1
    },
    navBarSpace: {
        height: (Platform.OS !== 'android') ? 64 : 0,
    },
    rowContainer: {
        paddingRight: 15,
        paddingLeft: 10,
        flexDirection: 'row'
    },
    paginationView: {
        justifyContent: 'center',
        alignItems: 'center',
        paddingTop: 20,
        paddingBottom: 20
    },
    loadMoreText: {
        fontSize: 15,
        color: 'gray',
    }
});<|endoftext|>


<commit_before>define(["dojo/_base/declare", "dijit/_WidgetBase", "dojo/dom-style", "dojo/fx/Toggler", "dojo/fx"],
  function(declare, _WidgetBase, domStyle, FxToggler, fx) {

    return declare([_WidgetBase], {
      // summary:
      //   Widgetizes any domNode, to make it optional.
      //   If `displayed` is set to true, the widget appears. If `displayed` is set
      //   to false, it disappears. The default is not-displayed.
      // description:
      //   Changing the state is done with an animation. Although `set("displayed", value)`
      //   returns immediately, the operation takes a while.
      //   For now, there is no option to get a Promise on the animation.

      // displayed: boolean
      displayed: false,

      // reversed: boolean
      //   With a binding on `displayed`, certainly if it is several levels deep, the first
      //   actual `set("displayed")` happens quite late. `displayed` is initially false,
      //   so nothing is shown until the first `set("displayed")` happens in that case.
      //   With `reverse` true, we reverse the behavior: we show if `displayed` is false, and
      //   and hide if `displayed` is true. This also often makes the binding easier
      //   (no need for a format function).
      reversed: false,

      postCreate: function() {
        this.inherited(arguments);
        if (!this.shown()) {
          domStyle.set(this.domNode, "display", "none");
        }
      },

      shown: function() {
        return this.reversed ? !this.displayed : this.displayed;
      },

      _setDisplayedAttr: function(displayed) {
        // summary:
        //   Executes the change of being displayed. Since the visual
        //   effect takes time, this returns a Promise. Use it or not, as you like.
        //   Returns null if the value is not changed.

        var booleanDisplayed = !!displayed; // turn truthy or falsy into true or false
        if (booleanDisplayed !== this.displayed) {
          this._set("displayed", booleanDisplayed);
          if (this.shown()) {
            this._animation().show()
          }
          else {
            this._animation().hide();
          }
        }
      },

      _animation: function() {
        //noinspection JSUnresolvedFunction
        return new FxToggler({
          node: this.domNode,
          showDuration: 500,
          hideDuration: 500,
          showFunc: fx.wipeIn,
          hideFunc: fx.wipeOut
        });
      }

    });
  }
);
<commit_msg>this small change is absolutely necessary false IS NOT EQUAL TO null both are falsy, but that is not the same!!!!!<commit_after>define(["dojo/_base/declare", "dijit/_WidgetBase", "dojo/dom-style", "dojo/fx/Toggler", "dojo/fx"],
  function(declare, _WidgetBase, domStyle, FxToggler, fx) {

    return declare([_WidgetBase], {
      // summary:
      //   Widgetizes any domNode, to make it optional.
      //   If `displayed` is set to true, the widget appears. If `displayed` is set
      //   to false, it disappears. The default is not-displayed.
      // description:
      //   Changing the state is done with an animation. Although `set("displayed", value)`
      //   returns immediately, the operation takes a while.
      //   For now, there is no option to get a Promise on the animation.

      // displayed: boolean
      displayed: false,

      // reversed: boolean
      //   With a binding on `displayed`, certainly if it is several levels deep, the first
      //   actual `set("displayed")` happens quite late. `displayed` is initially false,
      //   so nothing is shown until the first `set("displayed")` happens in that case.
      //   With `reverse` true, we reverse the behavior: we show if `displayed` is false, and
      //   and hide if `displayed` is true. This also often makes the binding easier
      //   (no need for a format function).
      reversed: false,

      postCreate: function() {
        this.inherited(arguments);
        if (!this.shown()) {
          domStyle.set(this.domNode, "display", "none");
        }
      },

      shown: function() {
        return this.reversed ? !this.displayed : this.displayed;
      },

      _setDisplayedAttr: function(displayed) {
        // summary:
        //   Executes the change of being displayed. Since the visual
        //   effect takes time, this returns a Promise. Use it or not, as you like.
        //   Returns null if the value is not changed.

        var booleanDisplayed = !!displayed; // turn truthy or falsy into true or false
        if (booleanDisplayed != !!this.displayed) { // NOT !=== ; somebody DOES insert null into this.displayed directly;
          this._set("displayed", booleanDisplayed);
          if (this.shown()) {
            this._animation().show()
          }
          else {
            this._animation().hide();
          }
        }
      },

      _animation: function() {
        //noinspection JSUnresolvedFunction
        return new FxToggler({
          node: this.domNode,
          showDuration: 500,
          hideDuration: 500,
          showFunc: fx.wipeIn,
          hideFunc: fx.wipeOut
        });
      }

    });
  }
);
<|endoftext|>


<commit_before><commit_msg>Instantiate a new parser for each run<commit_after><|endoftext|>


<commit_before>var uuids = angular.module('uuids', []);
uuids.factory("rfc4122", function () {
    return {
        newuuid: function () {
            // http://www.ietf.org/rfc/rfc4122.txt
            var s = [];
            var hexDigits = "0123456789abcdef";
            for (var i = 0; i < 36; i++) {
                s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
            }
            s[14] = "4";  // bits 12-15 of the time_hi_and_version field to 0010
            s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1);  // bits 6-7 of the clock_seq_hi_and_reserved to 01
            s[8] = s[13] = s[18] = s[23] = "-";

            var uuid = s.join("");
            return uuid;
        }
    }
});

var services = angular.module('services', ['uuids']);
services.factory("t", function () {
    var someVar = "foo";
    return {
        start: function () {
            console.log("t started with someVar=" + someVar);
        }
    }
});
services.factory("u", function () {
    var someVar = "bar";
    return {
        start: function () {
            console.log("u started with someVar=" + someVar);
        }
    }
});
services.factory("websockets", function (rfc4122) {
    var listeners = [];
    var socket = null;
    return {
        start: function (url) {
            socket = new WebSocket(url);
            socket.onopen = function () {
            };
            socket.onclose = function () {
            };
            socket.onmessage = function (evt) {
                for (i = 0; i < listeners.length; ++i) {
                    listeners[i](evt);
                }
            }
        },
        stop: function () {
            socket.close();
        },
        addListener: function (t) {
            var uuid = rfc4122.newuuid();
            listeners.push(t);
            console.log("added listener " + uuid);
        }
    }
});

var app = angular.module('app', ['services'])
    .run(function (websockets) {
        websockets.start("ws://localhost:8080/events");
    });

app.controller('controller', function ($scope, websockets, t, u) {
    $scope.msg = "...";

    websockets.addListener(function (evt) {
        var obj = JSON.parse(evt.data);
        $scope.$apply(function () {
            $scope.msg = obj.message
        });
    });

    t.start();
    u.start();
});<commit_msg>migrate listeners to array of objects indexed by uuid<commit_after>var uuids = angular.module('uuids', []);
uuids.factory("rfc4122", function () {
    return {
        newuuid: function () {
            // http://www.ietf.org/rfc/rfc4122.txt
            var s = [];
            var hexDigits = "0123456789abcdef";
            for (var i = 0; i < 36; i++) {
                s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
            }
            s[14] = "4";  // bits 12-15 of the time_hi_and_version field to 0010
            s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1);  // bits 6-7 of the clock_seq_hi_and_reserved to 01
            s[8] = s[13] = s[18] = s[23] = "-";

            var uuid = s.join("");
            return uuid;
        }
    }
});

var services = angular.module('services', ['uuids']);
services.factory("t", function () {
    var someVar = "foo";
    return {
        start: function () {
            console.log("t started with someVar=" + someVar);
        }
    }
});
services.factory("u", function () {
    var someVar = "bar";
    return {
        start: function () {
            console.log("u started with someVar=" + someVar);
        }
    }
});
services.factory("websockets", function (rfc4122) {
    var listeners = [];
    var socket = null;
    return {
        start: function (url) {
            socket = new WebSocket(url);
            socket.onopen = function () {
            };
            socket.onclose = function () {
            };
            socket.onmessage = function (evt) {
                for (i = 0; i < listeners.length; ++i) {
                    listeners[i].fn(evt);
                }
            }
        },
        stop: function () {
            socket.close();
        },
        addListener: function (t) {
            var uuid = rfc4122.newuuid();
            var newListener = {uuid: uuid, fn: t}
            listeners.push(newListener);
            console.log("added listener " + uuid);
            return uuid;
        }
    }
});

var app = angular.module('app', ['services'])
    .run(function (websockets) {
        websockets.start("ws://localhost:8080/events");
    });

app.controller('controller', function ($scope, websockets, t, u) {
    $scope.msg = "...";

    var myUuid = websockets.addListener(function (evt) {
        var obj = JSON.parse(evt.data);
        $scope.$apply(function () {
            $scope.msg = obj.message
        });
    });
    console.log("my uuid: " + myUuid);

    t.start();
    u.start();
});<|endoftext|>


<commit_before>var echo = (function () {

var exports = {};

var waiting_for_id = {};
var waiting_for_ack = {};

function resend_message(message, row) {
    message.content = message.raw_content;
    var retry_spinner = row.find('.refresh-failed-message');
    retry_spinner.toggleClass('rotating', true);
    // Always re-set queue_id if we've gotten a new one
    // since the time when the message object was initially created
    message.queue_id = page_params.queue_id;
    var start_time = new Date();
    compose.transmit_message(message, function success(data) {
        retry_spinner.toggleClass('rotating', false);

        var message_id = data.id;

        retry_spinner.toggleClass('rotating', false);
        compose.send_message_success(message.local_id, message_id, start_time, true);

        // Resend succeeded, so mark as no longer failed
        message_store.get(message_id).failed_request = false;
        ui.show_failed_message_success(message_id);
    }, function error(response) {
        exports.message_send_error(message.local_id, response);
        retry_spinner.toggleClass('rotating', false);
        blueslip.log("Manual resend of message failed");
    });
}

function truncate_precision(float) {
    return parseFloat(float.toFixed(3));
}

function get_next_local_id() {
    var local_id_increment = 0.01;
    var latest = page_params.max_message_id;
    if (typeof message_list.all !== 'undefined' && message_list.all.last() !== undefined) {
        latest = message_list.all.last().id;
    }
    latest = Math.max(0, latest);
    return truncate_precision(latest + local_id_increment);
}

function insert_local_message(message_request, local_id) {
    // Shallow clone of message request object that is turned into something suitable
    // for zulip.js:add_message
    // Keep this in sync with changes to compose.create_message_object
    var message = $.extend({}, message_request);

    // Locally delivered messages cannot be unread (since we sent them), nor
    // can they alert the user.
    message.flags = ['read']; // we may add more flags later

    message.raw_content = message.content;

    // NOTE: This will parse synchronously. We're not using the async pipeline
    markdown.apply_markdown(message);

    message.content_type = 'text/html';
    message.sender_email = people.my_current_email();
    message.sender_full_name = people.my_full_name();
    message.avatar_url = page_params.avatar_url;
    message.timestamp = new XDate().getTime() / 1000;
    message.local_id = local_id;
    message.id = message.local_id;
    markdown.add_message_flags(message);
    markdown.add_subject_links(message);

    waiting_for_id[message.local_id] = message;
    waiting_for_ack[message.local_id] = message;

    if (message.type === 'stream') {
        message.display_recipient = message.stream;
    } else {
        // Build a display recipient with the full names of each
        // recipient.  Note that it's important that use
        // util.extract_pm_recipients, which filters out any spurious
        // ", " at the end of the recipient list
        var emails = util.extract_pm_recipients(message_request.private_message_recipient);
        message.display_recipient = _.map(emails, function (email) {
            email = email.trim();
            var person = people.get_by_email(email);
            if (person === undefined) {
                // For unknown users, we return a skeleton object.
                return {email: email, full_name: email,
                        unknown_local_echo_user: true};
            }
            // NORMAL PATH
            return person;
        });
    }

    // It is a little bit funny to go through the message_events
    // codepath, but it's sort of the idea behind local echo that
    // we are simulating server events before they actually arrive.
    message_events.insert_new_messages([message], local_id);
    return message.local_id;
}

exports.try_deliver_locally = function try_deliver_locally(message_request) {
    var next_local_id = get_next_local_id();
    if (next_local_id % 1 === 0) {
        blueslip.error("Incremented local id to next integer---100 local messages queued");
        return undefined;
    }

    if (markdown.contains_bugdown(message_request.content)) {
        return undefined;
    }

    if (narrow_state.active() && !narrow_state.filter().can_apply_locally()) {
        return undefined;
    }

    return insert_local_message(message_request, next_local_id);
};

exports.edit_locally = function edit_locally(message, raw_content, new_topic) {
    message.raw_content = raw_content;
    if (new_topic !== undefined) {
        stream_data.process_message_for_recent_topics(message, true);
        message.subject = new_topic;
        stream_data.process_message_for_recent_topics(message);
    }

    markdown.apply_markdown(message);

    // We don't handle unread counts since local messages must be sent by us

    home_msg_list.view.rerender_messages([message]);
    if (current_msg_list === message_list.narrowed) {
        message_list.narrowed.view.rerender_messages([message]);
    }
    stream_list.update_streams_sidebar();
    pm_list.update_private_messages();
};

exports.reify_message_id = function reify_message_id(local_id, server_id) {
    var message = waiting_for_id[local_id];
    delete waiting_for_id[local_id];

    // reify_message_id is called both on receiving a self-sent message
    // from the server, and on receiving the response to the send request
    // Reification is only needed the first time the server id is found
    if (message === undefined) {
        return;
    }

    message.id = server_id;
    delete message.local_id;

    // We have the real message ID  for this message
    $(document).trigger($.Event('message_id_changed', {old_id: local_id, new_id: server_id}));
};

exports.process_from_server = function process_from_server(messages) {
    var updated = false;
    var locally_processed_ids = [];
    var msgs_to_rerender = [];
    messages = _.filter(messages, function (message) {
        // In case we get the sent message before we get the send ACK, reify here
        exports.reify_message_id(message.local_id, message.id);

        var client_message = waiting_for_ack[message.local_id];
        if (client_message !== undefined) {
            if (client_message.content !== message.content) {
                client_message.content = message.content;
                updated = true;
                compose.mark_rendered_content_disparity(message.id, true);
            }
            msgs_to_rerender.push(client_message);
            locally_processed_ids.push(client_message.id);
            compose.report_as_received(client_message);
            delete waiting_for_ack[client_message.id];
            return false;
        }
        return true;
    });

    if (updated) {
        home_msg_list.view.rerender_messages(msgs_to_rerender);
        if (current_msg_list === message_list.narrowed) {
            message_list.narrowed.view.rerender_messages(msgs_to_rerender);
        }
    } else {
        _.each(locally_processed_ids, function (id) {
            ui.show_local_message_arrived(id);
        });
    }
    return messages;
};

exports.message_send_error = function message_send_error(local_id, error_response) {
    // Error sending message, show inline
    message_store.get(local_id).failed_request = true;
    ui.show_message_failed(local_id, error_response);
};

function abort_message(message) {
    // Remove in all lists in which it exists
    _.each([message_list.all, home_msg_list, current_msg_list], function (msg_list) {
        msg_list.remove_and_rerender([message]);
    });
}

function edit_failed_message(message) {
    message_edit.start_local_failed_edit(current_msg_list.get_row(message.local_id), message);
}

$(function () {
    function on_failed_action(action, callback) {
        $("#main_div").on("click", "." + action + "-failed-message", function (e) {
            e.stopPropagation();
            popovers.hide_all();
            var row = $(this).closest(".message_row");
            var message_id = rows.id(row);
            // Message should be waiting for ack and only have a local id,
            // otherwise send would not have failed
            var message = waiting_for_ack[message_id];
            if (message === undefined) {
                blueslip.warn("Got resend or retry on failure request but did not find message in ack list " + message_id);
                return;
            }
            callback(message, row);
        });
    }

    on_failed_action('remove', abort_message);
    on_failed_action('refresh', resend_message);
    on_failed_action('edit', edit_failed_message);
});

return exports;

}());
if (typeof module !== 'undefined') {
    module.exports = echo;
}
<commit_msg>local echo: Make get_next_local_id() less aggressive.<commit_after>var echo = (function () {

var exports = {};

var waiting_for_id = {};
var waiting_for_ack = {};

function resend_message(message, row) {
    message.content = message.raw_content;
    var retry_spinner = row.find('.refresh-failed-message');
    retry_spinner.toggleClass('rotating', true);
    // Always re-set queue_id if we've gotten a new one
    // since the time when the message object was initially created
    message.queue_id = page_params.queue_id;
    var start_time = new Date();
    compose.transmit_message(message, function success(data) {
        retry_spinner.toggleClass('rotating', false);

        var message_id = data.id;

        retry_spinner.toggleClass('rotating', false);
        compose.send_message_success(message.local_id, message_id, start_time, true);

        // Resend succeeded, so mark as no longer failed
        message_store.get(message_id).failed_request = false;
        ui.show_failed_message_success(message_id);
    }, function error(response) {
        exports.message_send_error(message.local_id, response);
        retry_spinner.toggleClass('rotating', false);
        blueslip.log("Manual resend of message failed");
    });
}

function truncate_precision(float) {
    return parseFloat(float.toFixed(3));
}

var get_next_local_id = (function () {

    var already_used = {};

    return function () {
        var local_id_increment = 0.01;
        var latest = page_params.max_message_id;
        if (typeof message_list.all !== 'undefined' && message_list.all.last() !== undefined) {
            latest = message_list.all.last().id;
        }
        latest = Math.max(0, latest);
        var next_local_id = truncate_precision(latest + local_id_increment);

        if (already_used[next_local_id]) {
            // If our id is already used, it is probably an edge case like we had
            // to abort a very recent message.
            blueslip.warn("We don't reuse ids for local echo.");
            return undefined;
        }

        if (next_local_id % 1 > local_id_increment * 5) {
            blueslip.warn("Turning off local echo for this message to let host catch up");
            return undefined;
        }

        if (next_local_id % 1 === 0) {
            // The logic to stop at 0.05 should prevent us from ever wrapping around
            // to the next integer.
            blueslip.error("Programming error");
            return undefined;
        }

        already_used[next_local_id] = true;

        return next_local_id;
    };
}());

function insert_local_message(message_request, local_id) {
    // Shallow clone of message request object that is turned into something suitable
    // for zulip.js:add_message
    // Keep this in sync with changes to compose.create_message_object
    var message = $.extend({}, message_request);

    // Locally delivered messages cannot be unread (since we sent them), nor
    // can they alert the user.
    message.flags = ['read']; // we may add more flags later

    message.raw_content = message.content;

    // NOTE: This will parse synchronously. We're not using the async pipeline
    markdown.apply_markdown(message);

    message.content_type = 'text/html';
    message.sender_email = people.my_current_email();
    message.sender_full_name = people.my_full_name();
    message.avatar_url = page_params.avatar_url;
    message.timestamp = new XDate().getTime() / 1000;
    message.local_id = local_id;
    message.id = message.local_id;
    markdown.add_message_flags(message);
    markdown.add_subject_links(message);

    waiting_for_id[message.local_id] = message;
    waiting_for_ack[message.local_id] = message;

    if (message.type === 'stream') {
        message.display_recipient = message.stream;
    } else {
        // Build a display recipient with the full names of each
        // recipient.  Note that it's important that use
        // util.extract_pm_recipients, which filters out any spurious
        // ", " at the end of the recipient list
        var emails = util.extract_pm_recipients(message_request.private_message_recipient);
        message.display_recipient = _.map(emails, function (email) {
            email = email.trim();
            var person = people.get_by_email(email);
            if (person === undefined) {
                // For unknown users, we return a skeleton object.
                return {email: email, full_name: email,
                        unknown_local_echo_user: true};
            }
            // NORMAL PATH
            return person;
        });
    }

    // It is a little bit funny to go through the message_events
    // codepath, but it's sort of the idea behind local echo that
    // we are simulating server events before they actually arrive.
    message_events.insert_new_messages([message], local_id);
    return message.local_id;
}

exports.try_deliver_locally = function try_deliver_locally(message_request) {
    if (markdown.contains_bugdown(message_request.content)) {
        return undefined;
    }

    if (narrow_state.active() && !narrow_state.filter().can_apply_locally()) {
        return undefined;
    }

    var next_local_id = get_next_local_id();

    if (!next_local_id) {
        // This can happen for legit reasons.
        return undefined;
    }

    return insert_local_message(message_request, next_local_id);
};

exports.edit_locally = function edit_locally(message, raw_content, new_topic) {
    message.raw_content = raw_content;
    if (new_topic !== undefined) {
        stream_data.process_message_for_recent_topics(message, true);
        message.subject = new_topic;
        stream_data.process_message_for_recent_topics(message);
    }

    markdown.apply_markdown(message);

    // We don't handle unread counts since local messages must be sent by us

    home_msg_list.view.rerender_messages([message]);
    if (current_msg_list === message_list.narrowed) {
        message_list.narrowed.view.rerender_messages([message]);
    }
    stream_list.update_streams_sidebar();
    pm_list.update_private_messages();
};

exports.reify_message_id = function reify_message_id(local_id, server_id) {
    var message = waiting_for_id[local_id];
    delete waiting_for_id[local_id];

    // reify_message_id is called both on receiving a self-sent message
    // from the server, and on receiving the response to the send request
    // Reification is only needed the first time the server id is found
    if (message === undefined) {
        return;
    }

    message.id = server_id;
    delete message.local_id;

    // We have the real message ID  for this message
    $(document).trigger($.Event('message_id_changed', {old_id: local_id, new_id: server_id}));
};

exports.process_from_server = function process_from_server(messages) {
    var updated = false;
    var locally_processed_ids = [];
    var msgs_to_rerender = [];
    messages = _.filter(messages, function (message) {
        // In case we get the sent message before we get the send ACK, reify here
        exports.reify_message_id(message.local_id, message.id);

        var client_message = waiting_for_ack[message.local_id];
        if (client_message !== undefined) {
            if (client_message.content !== message.content) {
                client_message.content = message.content;
                updated = true;
                compose.mark_rendered_content_disparity(message.id, true);
            }
            msgs_to_rerender.push(client_message);
            locally_processed_ids.push(client_message.id);
            compose.report_as_received(client_message);
            delete waiting_for_ack[client_message.id];
            return false;
        }
        return true;
    });

    if (updated) {
        home_msg_list.view.rerender_messages(msgs_to_rerender);
        if (current_msg_list === message_list.narrowed) {
            message_list.narrowed.view.rerender_messages(msgs_to_rerender);
        }
    } else {
        _.each(locally_processed_ids, function (id) {
            ui.show_local_message_arrived(id);
        });
    }
    return messages;
};

exports.message_send_error = function message_send_error(local_id, error_response) {
    // Error sending message, show inline
    message_store.get(local_id).failed_request = true;
    ui.show_message_failed(local_id, error_response);
};

function abort_message(message) {
    // Remove in all lists in which it exists
    _.each([message_list.all, home_msg_list, current_msg_list], function (msg_list) {
        msg_list.remove_and_rerender([message]);
    });
}

function edit_failed_message(message) {
    message_edit.start_local_failed_edit(current_msg_list.get_row(message.local_id), message);
}

$(function () {
    function on_failed_action(action, callback) {
        $("#main_div").on("click", "." + action + "-failed-message", function (e) {
            e.stopPropagation();
            popovers.hide_all();
            var row = $(this).closest(".message_row");
            var message_id = rows.id(row);
            // Message should be waiting for ack and only have a local id,
            // otherwise send would not have failed
            var message = waiting_for_ack[message_id];
            if (message === undefined) {
                blueslip.warn("Got resend or retry on failure request but did not find message in ack list " + message_id);
                return;
            }
            callback(message, row);
        });
    }

    on_failed_action('remove', abort_message);
    on_failed_action('refresh', resend_message);
    on_failed_action('edit', edit_failed_message);
});

return exports;

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


<commit_before>var echo = (function () {

var exports = {};

var waiting_for_id = {};
var waiting_for_ack = {};
var realm_filter_map = {};

// Regexes that match some of our common bugdown markup
var bugdown_re = [
                    // Inline image previews, check for contiguous chars ending in image suffix
                    // To keep the below regexes simple, split them out for the end-of-message case
                    /[^\s]*(?:\.bmp|\.gif|\.jpg|\.jpeg|\.png|\.webp)\s+/m,
                    /[^\s]*(?:\.bmp|\.gif|\.jpg|\.jpeg|\.png|\.webp)$/m,
                    // Twitter and youtube links are given previews
                    /[^\s]*(?:twitter|youtube).com\/[^\s]*/,
                    // Gravatars are inlined as well
                    /!avatar\([^)]+\)/,
                    /!gravatar\([^)]+\)/
                  ];

exports.contains_bugdown = function contains_bugdown(content) {
    // Try to guess whether or not a message has bugdown in it
    // If it doesn't, we can immediately render it client-side
    var markedup = _.find(bugdown_re, function (re) {
        return re.test(content);
    });
    return markedup !== undefined;
};

exports.apply_markdown = function apply_markdown(content) {
    return marked(content).trim();
};

function resend_message(message, row) {
    message.content = message.raw_content;
    var retry_spinner = row.find('.refresh-failed-message');
    retry_spinner.toggleClass('rotating', true);
    // Always re-set queue_id if we've gotten a new one
    // since the time when the message object was initially created
    message.queue_id = page_params.event_queue_id;
    var start_time = new Date();
    compose.transmit_message(message, function success(data) {
        retry_spinner.toggleClass('rotating', false);

        var message_id = data.id;

        retry_spinner.toggleClass('rotating', false);
        compose.send_message_success(message.local_id, message_id, start_time);

        // Resend succeeded, so mark as no longer failed
        all_msg_list.get(message_id).failed_request = false;
        ui.show_failed_message_success(message_id);
    }, function error(response) {
        exports.message_send_error(message.local_id, response);
        retry_spinner.toggleClass('rotating', false);
        blueslip.log("Manual resend of message failed");
    });
}

function truncate_precision(float) {
    return parseFloat(float.toFixed(3));
}

exports.try_deliver_locally = function try_deliver_locally(message_request) {
    var local_id_increment = 0.01;
    var next_local_id = truncate_precision(all_msg_list.last().id + local_id_increment);

    if (next_local_id % 1 === 0) {
        blueslip.error("Incremented local id to next integer---100 local messages queued");
    }

    if (exports.contains_bugdown(message_request.content)) {
        return undefined;
    }

    // Shallow clone of message request object that is turned into something suitable
    // for zulip.js:add_message
    // Keep this in sync with changes to compose.create_message_object
    var message = $.extend({}, message_request);
    message.raw_content = message.content;
    // NOTE: This will parse synchronously. We're not using the async pipeline
    message.content = exports.apply_markdown(message.content);
    message.content_type = 'text/html';
    // Locally delivered messages cannot be unread (since we sent them), nor
    // can they alert the user
    message.flags = ["read"];
    message.sender_email = page_params.email;
    message.sender_full_name = page_params.fullname;
    message.avatar_url = page_params.avatar_url;
    message.timestamp = new XDate().getTime() / 1000;
    message.local_id = next_local_id;
    message.id = message.local_id;

    waiting_for_id[message.local_id] = message;
    waiting_for_ack[message.local_id] = message;

    if (message.type === 'stream') {
        message.display_recipient = message.stream;
    } else {
        // Build a display recipient with the full names of each recipient
        var emails = message_request.private_message_recipient.split(',');
        message.display_recipient = _.map(emails, function (email) {
            email = email.trim();
            var person = people_dict.get(email);
            if (person !== undefined) {
                return person;
            }
            return {email: email, full_name: email};
        });
    }

    insert_new_messages([message]);

    blueslip.debug("Generated local id " + message.local_id);
    return message.local_id;
};

exports.edit_locally = function edit_locally(message, raw_content, new_topic) {
    message.raw_content = raw_content;
    if (new_topic !== undefined) {
        process_message_for_recent_subjects(message, true);
        message.subject = new_topic;
        process_message_for_recent_subjects(message);
    }

    message.content = exports.apply_markdown(raw_content);
    // We don't handle unread counts since local messages must be sent by us

    home_msg_list.rerender();
    if (current_msg_list === narrowed_msg_list) {
        narrowed_msg_list.rerender();
    }
    stream_list.update_streams_sidebar();
};

exports.reify_message_id = function reify_message_id(local_id, server_id) {
    var message = waiting_for_id[local_id];
    delete waiting_for_id[local_id];

    // reify_message_id is called both on receiving a self-sent message
    // from the server, and on receiving the response to the send request
    // Reification is only needed the first time the server id is found
    if (message === undefined) {
        return;
    }

    blueslip.debug("Reifying ID: " + local_id + " TO " + server_id);
    message.id = server_id;
    delete message.local_id;

    // We have the real message ID  for this message
    $(document).trigger($.Event('message_id_changed', {old_id: local_id, new_id: server_id}));
};

exports.process_from_server = function process_from_server(messages) {
    var updated = false;
    var locally_processed_ids = [];
    messages = _.filter(messages, function (message) {
        // In case we get the sent message before we get the send ACK, reify here
        exports.reify_message_id(message.local_id, message.id);

        var client_message = waiting_for_ack[message.local_id];
        if (client_message !== undefined) {
            if (client_message.content !== message.content) {
                client_message.content = message.content;
                updated = true;
            }
            // If a PM was sent to an out-of-realm address,
            // we didn't have the full person object originally,
            // so we might have to update the recipient bar and
            // internal data structures
            if (client_message.type === 'private') {
                var reply_to = get_private_message_recipient(message, 'full_name', 'email');
                if (client_message.display_reply_to !== reply_to) {
                    client_message.display_reply_to = reply_to;
                    _.each(message.display_recipient, function (person) {
                        if (people_dict.get(person.email).full_name !== person.full_name) {
                            reify_person(person);
                        }
                    });
                    updated = true;
                }
            }
            locally_processed_ids.push(client_message.id);
            delete waiting_for_ack[client_message.id];
            return false;
        }
        return true;
    });

    if (updated) {
        // TODO just rerender the message, not the whole list
        home_msg_list.rerender();
        if (current_msg_list === narrowed_msg_list) {
            narrowed_msg_list.rerender();
        }
    } else {
        _.each(locally_processed_ids, function (id) {
            ui.show_local_message_arrived(id);
        });
    }
    return messages;
};

exports.message_send_error = function message_send_error(local_id, error_response) {
    // Error sending message, show inline
    all_msg_list.get(local_id).failed_request = true;
    ui.show_message_failed(local_id, error_response);
};

function abort_message(message) {
    // Remove in all lists in which it exists
    _.each([all_msg_list, home_msg_list, current_msg_list], function (msg_list) {
        msg_list.remove_and_rerender([message]);
    });
}

function edit_failed_message(message) {
    message_edit.start_local_failed_edit(current_msg_list.get_row(message.local_id), message);
}

function escape(html, encode) {
  return html
    .replace(!encode ? /&(?!#?\w+;)/g : /&/g, '&')
    .replace(/</g, '<')
    .replace(/>/g, '>')
    .replace(/'/g, ''');
}

function handleEmoji(emoji_name) {
    var input_emoji = ':' + emoji_name + ":";
    if (emoji.emojis_by_name.hasOwnProperty(emoji_name)) {
        var emoji_url = emoji.emojis_by_name[emoji_name];
        return '';
    } else {
        return input_emoji;
    }
}

function handleUserMentions(username) {
    if (people_by_name_dict.get(username)) {
        var person = people_by_name_dict.get(username);
        return '' +
                '@' + person.full_name + '';
    } else {
        return undefined;
    }
}

function handleRealmFilter(pattern, matches) {
    var url = realm_filter_map[pattern];

    var current_group = 1;
    _.each(matches, function (match) {
        var back_ref = "\\" + current_group;
        url = url.replace(back_ref, match);
    });

    return url;
}

function python_to_js_filter(pattern, url) {
    // Converts a python named-group regex to a javascript-compatible numbered
    // group regex... with a regex!
    var named_group_re = /\(?P<([^>]+?)>/g;
    var match = named_group_re.exec(pattern);
    var current_group = 1;
    while (match) {
        var name = match[1];
        // Replace named group with regular matching group
        pattern = pattern.replace('(?P<' + name + '>', '(');
        // Replace named reference in url to numbered reference
        url = url.replace('%(' + name + ')s', '\\' + current_group);

        match = named_group_re.exec(pattern);
    }
    return [new RegExp(pattern, 'g'), url];
}

exports.set_realm_filters = function set_realm_filters(realm_filters) {
    // Update the marked parser with our particular set of realm filters
    realm_filter_map = {};

    var marked_rules = [];
    _.each(realm_filters, function (realm_filter) {
        var pattern = realm_filter[0];
        var url = realm_filter[1];
        var js_filters = python_to_js_filter(pattern, url);

        realm_filter_map[js_filters[0]] = js_filters[1];
        marked_rules.push(js_filters[0]);
    });

    marked.InlineLexer.rules.zulip.realm_filters = marked_rules;
};

$(function () {
    function disable_markdown_regex(rules, name) {
        rules[name] = {exec: function (_) {
                return false;
            }
        };
    }

    // Configure the marked markdown parser for our usage
    var r = new marked.Renderer();

    // No  around our code blocks instead a codehilite
, and disable class-specific highlighting
    // We special-case the 'quote' language and output a blockquote
    r.code = function (code, lang) {
        if (lang === 'quote') {
            return '

' + escape(code, true) + '

';
        }

        return '

'
          + escape(code, true)
          + '\n

';
    };

    // Disable ordered lists
    // We used GFM + tables, so replace the list start regex for that ruleset
    // We remove the |[\d+]\. that matches the numbering in a numbered list
    marked.Lexer.rules.tables.list = /^( *)((?:\*)) [\s\S]+?(?:\n+(?=(?: *[\-*_]){3,} *(?:\n+|$))|\n{2,}(?! )(?!\1(?:\*) )\n*|\s*$)/;

    // Disable headings
    disable_markdown_regex(marked.Lexer.rules.tables, 'heading');
    disable_markdown_regex(marked.Lexer.rules.tables, 'lheading');

    // Disable __strong__, all
    marked.InlineLexer.rules.zulip.strong = /^\*\*([\s\S]+?)\*\*(?!\*)/;
    disable_markdown_regex(marked.InlineLexer.rules.zulip, 'em');
    disable_markdown_regex(marked.InlineLexer.rules.zulip, 'del');
    // Disable autolink as (a) it is not used in our backend and (b) it interferes with @mentions
    disable_markdown_regex(marked.InlineLexer.rules.zulip, 'autolink');

    exports.set_realm_filters(page_params.realm_filters);

    marked.setOptions({
        gfm: true,
        tables: true,
        breaks: true,
        pedantic: false,
        sanitize: true,
        smartLists: true,
        smartypants: false,
        zulip: true,
        emojiHandler: handleEmoji,
        userMentionHandler: handleUserMentions,
        realmFilterHandler: handleRealmFilter,
        renderer: r
    });

    function on_failed_action(action, callback) {
        $("#main_div").on("click", "." + action + "-failed-message", function (e) {
            e.stopPropagation();
            popovers.hide_all();
            var row = $(this).closest(".message_row");
            var message_id = rows.id(row);
            // Message should be waiting for ack and only have a local id,
            // otherwise send would not have failed
            var message = waiting_for_ack[message_id];
            if (message === undefined) {
                blueslip.warning("Got resend or retry on failure request but did not find message in ack list " + message_id);
                return;
            }
            callback(message, row);
        });
    }

    on_failed_action('remove', abort_message);
    on_failed_action('refresh', resend_message);
    on_failed_action('edit', edit_failed_message);
});

return exports;

}());
if (typeof module !== 'undefined') {
    module.exports = echo;
}
<commit_msg>Render  with title and target attributes<commit_after>var echo = (function () {

var exports = {};

var waiting_for_id = {};
var waiting_for_ack = {};
var realm_filter_map = {};

// Regexes that match some of our common bugdown markup
var bugdown_re = [
                    // Inline image previews, check for contiguous chars ending in image suffix
                    // To keep the below regexes simple, split them out for the end-of-message case
                    /[^\s]*(?:\.bmp|\.gif|\.jpg|\.jpeg|\.png|\.webp)\s+/m,
                    /[^\s]*(?:\.bmp|\.gif|\.jpg|\.jpeg|\.png|\.webp)$/m,
                    // Twitter and youtube links are given previews
                    /[^\s]*(?:twitter|youtube).com\/[^\s]*/,
                    // Gravatars are inlined as well
                    /!avatar\([^)]+\)/,
                    /!gravatar\([^)]+\)/
                  ];

exports.contains_bugdown = function contains_bugdown(content) {
    // Try to guess whether or not a message has bugdown in it
    // If it doesn't, we can immediately render it client-side
    var markedup = _.find(bugdown_re, function (re) {
        return re.test(content);
    });
    return markedup !== undefined;
};

exports.apply_markdown = function apply_markdown(content) {
    return marked(content).trim();
};

function resend_message(message, row) {
    message.content = message.raw_content;
    var retry_spinner = row.find('.refresh-failed-message');
    retry_spinner.toggleClass('rotating', true);
    // Always re-set queue_id if we've gotten a new one
    // since the time when the message object was initially created
    message.queue_id = page_params.event_queue_id;
    var start_time = new Date();
    compose.transmit_message(message, function success(data) {
        retry_spinner.toggleClass('rotating', false);

        var message_id = data.id;

        retry_spinner.toggleClass('rotating', false);
        compose.send_message_success(message.local_id, message_id, start_time);

        // Resend succeeded, so mark as no longer failed
        all_msg_list.get(message_id).failed_request = false;
        ui.show_failed_message_success(message_id);
    }, function error(response) {
        exports.message_send_error(message.local_id, response);
        retry_spinner.toggleClass('rotating', false);
        blueslip.log("Manual resend of message failed");
    });
}

function truncate_precision(float) {
    return parseFloat(float.toFixed(3));
}

exports.try_deliver_locally = function try_deliver_locally(message_request) {
    var local_id_increment = 0.01;
    var next_local_id = truncate_precision(all_msg_list.last().id + local_id_increment);

    if (next_local_id % 1 === 0) {
        blueslip.error("Incremented local id to next integer---100 local messages queued");
    }

    if (exports.contains_bugdown(message_request.content)) {
        return undefined;
    }

    // Shallow clone of message request object that is turned into something suitable
    // for zulip.js:add_message
    // Keep this in sync with changes to compose.create_message_object
    var message = $.extend({}, message_request);
    message.raw_content = message.content;
    // NOTE: This will parse synchronously. We're not using the async pipeline
    message.content = exports.apply_markdown(message.content);
    message.content_type = 'text/html';
    // Locally delivered messages cannot be unread (since we sent them), nor
    // can they alert the user
    message.flags = ["read"];
    message.sender_email = page_params.email;
    message.sender_full_name = page_params.fullname;
    message.avatar_url = page_params.avatar_url;
    message.timestamp = new XDate().getTime() / 1000;
    message.local_id = next_local_id;
    message.id = message.local_id;

    waiting_for_id[message.local_id] = message;
    waiting_for_ack[message.local_id] = message;

    if (message.type === 'stream') {
        message.display_recipient = message.stream;
    } else {
        // Build a display recipient with the full names of each recipient
        var emails = message_request.private_message_recipient.split(',');
        message.display_recipient = _.map(emails, function (email) {
            email = email.trim();
            var person = people_dict.get(email);
            if (person !== undefined) {
                return person;
            }
            return {email: email, full_name: email};
        });
    }

    insert_new_messages([message]);

    blueslip.debug("Generated local id " + message.local_id);
    return message.local_id;
};

exports.edit_locally = function edit_locally(message, raw_content, new_topic) {
    message.raw_content = raw_content;
    if (new_topic !== undefined) {
        process_message_for_recent_subjects(message, true);
        message.subject = new_topic;
        process_message_for_recent_subjects(message);
    }

    message.content = exports.apply_markdown(raw_content);
    // We don't handle unread counts since local messages must be sent by us

    home_msg_list.rerender();
    if (current_msg_list === narrowed_msg_list) {
        narrowed_msg_list.rerender();
    }
    stream_list.update_streams_sidebar();
};

exports.reify_message_id = function reify_message_id(local_id, server_id) {
    var message = waiting_for_id[local_id];
    delete waiting_for_id[local_id];

    // reify_message_id is called both on receiving a self-sent message
    // from the server, and on receiving the response to the send request
    // Reification is only needed the first time the server id is found
    if (message === undefined) {
        return;
    }

    blueslip.debug("Reifying ID: " + local_id + " TO " + server_id);
    message.id = server_id;
    delete message.local_id;

    // We have the real message ID  for this message
    $(document).trigger($.Event('message_id_changed', {old_id: local_id, new_id: server_id}));
};

exports.process_from_server = function process_from_server(messages) {
    var updated = false;
    var locally_processed_ids = [];
    messages = _.filter(messages, function (message) {
        // In case we get the sent message before we get the send ACK, reify here
        exports.reify_message_id(message.local_id, message.id);

        var client_message = waiting_for_ack[message.local_id];
        if (client_message !== undefined) {
            if (client_message.content !== message.content) {
                client_message.content = message.content;
                updated = true;
            }
            // If a PM was sent to an out-of-realm address,
            // we didn't have the full person object originally,
            // so we might have to update the recipient bar and
            // internal data structures
            if (client_message.type === 'private') {
                var reply_to = get_private_message_recipient(message, 'full_name', 'email');
                if (client_message.display_reply_to !== reply_to) {
                    client_message.display_reply_to = reply_to;
                    _.each(message.display_recipient, function (person) {
                        if (people_dict.get(person.email).full_name !== person.full_name) {
                            reify_person(person);
                        }
                    });
                    updated = true;
                }
            }
            locally_processed_ids.push(client_message.id);
            delete waiting_for_ack[client_message.id];
            return false;
        }
        return true;
    });

    if (updated) {
        // TODO just rerender the message, not the whole list
        home_msg_list.rerender();
        if (current_msg_list === narrowed_msg_list) {
            narrowed_msg_list.rerender();
        }
    } else {
        _.each(locally_processed_ids, function (id) {
            ui.show_local_message_arrived(id);
        });
    }
    return messages;
};

exports.message_send_error = function message_send_error(local_id, error_response) {
    // Error sending message, show inline
    all_msg_list.get(local_id).failed_request = true;
    ui.show_message_failed(local_id, error_response);
};

function abort_message(message) {
    // Remove in all lists in which it exists
    _.each([all_msg_list, home_msg_list, current_msg_list], function (msg_list) {
        msg_list.remove_and_rerender([message]);
    });
}

function edit_failed_message(message) {
    message_edit.start_local_failed_edit(current_msg_list.get_row(message.local_id), message);
}

function escape(html, encode) {
  return html
    .replace(!encode ? /&(?!#?\w+;)/g : /&/g, '&')
    .replace(/</g, '<')
    .replace(/>/g, '>')
    .replace(/'/g, ''');
}

function handleEmoji(emoji_name) {
    var input_emoji = ':' + emoji_name + ":";
    if (emoji.emojis_by_name.hasOwnProperty(emoji_name)) {
        var emoji_url = emoji.emojis_by_name[emoji_name];
        return '';
    } else {
        return input_emoji;
    }
}

function handleUserMentions(username) {
    if (people_by_name_dict.get(username)) {
        var person = people_by_name_dict.get(username);
        return '' +
                '@' + person.full_name + '';
    } else {
        return undefined;
    }
}

function handleRealmFilter(pattern, matches) {
    var url = realm_filter_map[pattern];

    var current_group = 1;
    _.each(matches, function (match) {
        var back_ref = "\\" + current_group;
        url = url.replace(back_ref, match);
    });

    return url;
}

function python_to_js_filter(pattern, url) {
    // Converts a python named-group regex to a javascript-compatible numbered
    // group regex... with a regex!
    var named_group_re = /\(?P<([^>]+?)>/g;
    var match = named_group_re.exec(pattern);
    var current_group = 1;
    while (match) {
        var name = match[1];
        // Replace named group with regular matching group
        pattern = pattern.replace('(?P<' + name + '>', '(');
        // Replace named reference in url to numbered reference
        url = url.replace('%(' + name + ')s', '\\' + current_group);

        match = named_group_re.exec(pattern);
    }
    return [new RegExp(pattern, 'g'), url];
}

exports.set_realm_filters = function set_realm_filters(realm_filters) {
    // Update the marked parser with our particular set of realm filters
    realm_filter_map = {};

    var marked_rules = [];
    _.each(realm_filters, function (realm_filter) {
        var pattern = realm_filter[0];
        var url = realm_filter[1];
        var js_filters = python_to_js_filter(pattern, url);

        realm_filter_map[js_filters[0]] = js_filters[1];
        marked_rules.push(js_filters[0]);
    });

    marked.InlineLexer.rules.zulip.realm_filters = marked_rules;
};

$(function () {
    function disable_markdown_regex(rules, name) {
        rules[name] = {exec: function (_) {
                return false;
            }
        };
    }

    // Configure the marked markdown parser for our usage
    var r = new marked.Renderer();

    // No  around our code blocks instead a codehilite
, and disable class-specific highlighting
    // We special-case the 'quote' language and output a blockquote
    r.code = function (code, lang) {
        if (lang === 'quote') {
            return '

' + escape(code, true) + '

';
        }

        return '

'
          + escape(code, true)
          + '\n

';
    };

    // Our links have title= and target=_blank
    r.link = function (href, title, text) {
        title = title || href;
        var out = '' + text + '';
        return out;
    };

    // Disable ordered lists
    // We used GFM + tables, so replace the list start regex for that ruleset
    // We remove the |[\d+]\. that matches the numbering in a numbered list
    marked.Lexer.rules.tables.list = /^( *)((?:\*)) [\s\S]+?(?:\n+(?=(?: *[\-*_]){3,} *(?:\n+|$))|\n{2,}(?! )(?!\1(?:\*) )\n*|\s*$)/;

    // Disable headings
    disable_markdown_regex(marked.Lexer.rules.tables, 'heading');
    disable_markdown_regex(marked.Lexer.rules.tables, 'lheading');

    // Disable __strong__, all
    marked.InlineLexer.rules.zulip.strong = /^\*\*([\s\S]+?)\*\*(?!\*)/;
    disable_markdown_regex(marked.InlineLexer.rules.zulip, 'em');
    disable_markdown_regex(marked.InlineLexer.rules.zulip, 'del');
    // Disable autolink as (a) it is not used in our backend and (b) it interferes with @mentions
    disable_markdown_regex(marked.InlineLexer.rules.zulip, 'autolink');

    exports.set_realm_filters(page_params.realm_filters);

    marked.setOptions({
        gfm: true,
        tables: true,
        breaks: true,
        pedantic: false,
        sanitize: true,
        smartLists: true,
        smartypants: false,
        zulip: true,
        emojiHandler: handleEmoji,
        userMentionHandler: handleUserMentions,
        realmFilterHandler: handleRealmFilter,
        renderer: r
    });

    function on_failed_action(action, callback) {
        $("#main_div").on("click", "." + action + "-failed-message", function (e) {
            e.stopPropagation();
            popovers.hide_all();
            var row = $(this).closest(".message_row");
            var message_id = rows.id(row);
            // Message should be waiting for ack and only have a local id,
            // otherwise send would not have failed
            var message = waiting_for_ack[message_id];
            if (message === undefined) {
                blueslip.warning("Got resend or retry on failure request but did not find message in ack list " + message_id);
                return;
            }
            callback(message, row);
        });
    }

    on_failed_action('remove', abort_message);
    on_failed_action('refresh', resend_message);
    on_failed_action('edit', edit_failed_message);
});

return exports;

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


<commit_before>// don't 'use strict' since we're setting serverSettings globally
serverSettings = {
   "status":"unauthorized",
   "name":"Nightscout",
   "version":"unknown",
   "apiEnabled":false,
   "careportalEnabled":false,
   "boluscalcEnabled":false,
   "settings":{
     "enable":[],
     "thresholds":{}
   },
   "extendedSettings":{
   },
   "authorized":null
};

var params = {};
if (window.location.search) {
  window.location.search.substr(1).split('&').forEach(function(item) {
    params[item.split('=')[0]] = item.split('=')[1].replace(/[_\+]/g, ' ');
  });
}

var token = params.token;

var script = window.document.createElement('script');
var src = '/api/v1/status.js?t=' + new Date().getTime();
if (token) {
  src += '&token=' + token;
}
script.setAttribute('src', src);
window.document.body.appendChild(script);
<commit_msg>make codacy happier<commit_after>// don't 'use strict' since we're setting serverSettings globally
serverSettings = {
   'status':'unauthorized',
   'name':'Nightscout',
   'version':'unknown',
   'apiEnabled':false,
   'careportalEnabled':false,
   'boluscalcEnabled':false,
   'settings':{
     'enable':[],
     'thresholds':{}
   },
   'extendedSettings':{
   },
   'authorized':null
};

var params = {};
if (window.location.search) {
  window.location.search.substr(1).split('&').forEach(function(item) {
    params[item.split('=')[0]] = item.split('=')[1].replace(/[_\+]/g, ' ');
  });
}

var token = params.token;

var script = window.document.createElement('script');
var src = '/api/v1/status.js?t=' + new Date().getTime();
if (token) {
  src += '&token=' + token;
}
script.setAttribute('src', src);
window.document.body.appendChild(script);
<|endoftext|>


<commit_before>(function ($) {

$.hcn = $.hcn || {};

$.hcn.initReplyToReply = function () {

    /**
     * Click Handler for "Reply to Relpy".
     * ajax get the form for reply
     */
    $('a[id^=add-comment-]').click(function (x) {
        var target = $(x.currentTarget),
            replySection = target.parent().prev('.replyOfReply')
            form   = replySection.find('form');

        if (form.length > 0) {
            form.find('textarea').focus();
        } else {
            $.get(["/topic", target.attr("data-topic"), target.attr("data-reply"), "reply"].join("/"),
                  function (res) {
                      replySection.append(res);
                      replySection.find('form textarea').focus();
                  }
            );
        }
    });

    /**
     * Dynamicly bind submit to the "reply to reply" form.
     * Submit it in ajax way.
     */
    $('.topicMain').on("submit", 'form[id^=add-comment-form-]', function (event) {
        event.preventDefault();
        var t = $(event.currentTarget),
            parent = t.parent();

        $.post(t[0].action, t.serialize(), function (res) {
          t.remove();
          parent.append(res);
        })
    });

  }

})(jQuery)<commit_msg>fix js syntak error<commit_after>(function ($) {

$.hcn = $.hcn || {};

$.hcn.initReplyToReply = function () {

    /**
     * Click Handler for "Reply to Relpy".
     * ajax get the form for reply
     */
    $('a[id^=add-comment-]').click(function (x) {
        var target = $(x.currentTarget),
            replySection = target.parent().prev('.replyOfReply'),
            form   = replySection.find('form');

        if (form.length > 0) {
            form.find('textarea').focus();
        } else {
            $.get(["/topic", target.attr("data-topic"), target.attr("data-reply"), "reply"].join("/"),
                  function (res) {
                      replySection.append(res);
                      replySection.find('form textarea').focus();
                  }
            );
        }
    });

    /**
     * Dynamicly bind submit to the "reply to reply" form.
     * Submit it in ajax way.
     */
    $('.topicMain').on("submit", 'form[id^=add-comment-form-]', function (event) {
        event.preventDefault();
        var t = $(event.currentTarget),
            parent = t.parent();

        $.post(t[0].action, t.serialize(), function (res) {
          t.remove();
          parent.append(res);
        });
    });

  };

})(jQuery);
<|endoftext|>


<commit_before>Paloquiz.states.Main = function (game) {
    this.optionsPane;
    this.dialogPane;
    this.host;
    this.scoreText;
    this.loadText;
    this.messageText;
    this.questionText;
    this.optButtonsGroup;
    this.optButtons;
};

Paloquiz.states.Main.prototype = {

    QUESTION_IMAGE_MAX_HEIGHT: 0,
    QUESTION_IMAGE_MAX_WIDTH: 0,

    init: function () {
        QUESTION_IMAGE_MAX_HEIGHT = this.game.height * .32;
        QUESTION_IMAGE_MAX_WIDTH = this.game.width * .8;
    },

    preload: function () {
        this.load.baseURL = '/static/';
        this.load.image('background', 'assets/background.png');
        this.load.spritesheet('host', 'assets/palo.png', 30, 85);
        this.load.image('dialogPane', 'assets/dialog_pane.png');
        this.load.image('optionsPane', 'assets/options_pane.png');
        this.load.spritesheet('button', 'assets/button_sprite_sheet.png', 193, 71);
        //game.load.bitmapFont('carrier_command', 'assets/carrier_command.png', 'assets/carrier_command.xml');
        this.load.bitmapFont('desyrel', 'assets/desyrel.png', 'assets/desyrel.xml');

    },

    create: function () {

        var worldCenterX = this.game.world.centerX;
        var worldCenterY = this.game.world.centerY;
        var worldHeight = this.game.world.height;
        var worldWidth = this.game.world.width;

        this.game.stage.background = this.add.sprite(worldCenterX, worldCenterY, 'background');
        this.game.stage.background.anchor.setTo(0.5, 0.5);
        this.game.stage.background.width = this.game.width
        this.game.stage.background.height = this.game.height

        this.optionsPane = this.add.sprite(worldCenterX, worldHeight - 260, 'optionsPane');
        this.optionsPane.anchor.setTo(0.5, 0.0);

        this.dialogPane = this.add.sprite(worldCenterX, 220, 'dialogPane');
        this.dialogPane.anchor.setTo(0.5, 0.0);

        this.host = this.add.sprite(0, 0, 'host');
        // host.smoothed = false;  // does this do something?
        this.host.x = worldWidth - this.host.width / 2 - 55;
        this.host.y = worldHeight - 140;
        this.host.anchor.setTo(0.5, 0.5);
        this.host.scale.setTo(2, 2);

        //  You can listen for each of these events from Phaser.Loader
        this.load.onLoadStart.add(this.loadStart, this);
        this.load.onFileComplete.add(this.fileComplete, this);
        this.load.onLoadComplete.add(this.loadComplete, this);

        // Score text
        this.scoreText = this.add.text(worldCenterX + worldWidth / 2 - 70, 10, '0', {
            fill: '#ff8000'
        });

        //  Progress report
        this.loadText = this.add.text(worldCenterX, 50, 'Image', {
            fill: '#ffffff'
        });

        //  Bit map Text

        //messageText = game.add.bitmapText(200, 100, 'desyrel', 'Phaser & Pixi\nrocking!', 64);
        this.messageText = this.add.bitmapText(worldCenterX, worldCenterY, 'desyrel', 'Correct', 80);
        this.messageText.inputEnabled = true;
        this.messageText.anchor.setTo(0.5, 0.5);
        this.messageText.visible = false

        // Question text
        var qTextY = this.dialogPane.y + this.dialogPane.height / 2
        this.questionText = this.add.text(worldCenterX, qTextY, 'Question', {
            font: 'Pixel Art',
            fontSize: '26px',
            align: 'center',
            fill: '#ffffff'
        });
        this.questionText.anchor.setTo(0.5, 0.5);

        this.createOptionButtons();
        this.updateStatus();
    },

    createOptionButtons: function () {
        this.optButtonsGroup = this.add.group();
        this.optButtons = new Array();

        var bX = this.optionsPane.x - this.optionsPane.width * 0.10;
        var bY = this.optionsPane.y * 1.12;
        var bHeight = (this.optionsPane.height * 0.8) / 4;

        for (var i = 0; i < 4; i++) {
            this.optButtons[i] = new LabelButton(this, bX, bY + bHeight * i, 'button', 'buttont', this.actionOnClick, this, 2, 1, 0);
            this.optButtons[i].height = bHeight;
            this.optButtons[i].width = this.optionsPane.width * 0.72;
            this.optButtons[i].answerId = i;
            this.optButtonsGroup.add(this.optButtons[i]);
        }
    },

    answerCorrect: function (buttonId) {

        this.messageText.setText('Correct! :)');
        this.messageText.visible = true;
        this.enableInput(false);
        this.game.time.events.add(Phaser.Timer.SECOND * 2, function() {
            this.messageText.visible = false;
            this.enableInput(true);
            this.updateStatus();
        }, this);

    },

    answerWrong: function (buttonId) {
        this.messageText.setText('Wrong! >:(');
        this.messageText.visible = true;
        this.enableInput(false);
        this.game.time.events.add(Phaser.Timer.SECOND * 2, function() {
            this.messageText.visible = false;
            this.enableInput(true);
            this.updateStatus();
        }, this);
    },

    finishGame: function () {
        this.messageText.setText('Score = ' + this.scoreText.text);
        this.messageText.visible = true;
        this.enableInput(false);
    },

    enableInput: function (enable) {
        this.game.input.disabled = !enable;
    },

    actionOnClick: function (button) {
        getJSON('/answer/' + button.answerId, function(answerResponse) {

            if (answerResponse['correct']) {
                this.answerCorrect(button.answerId);
            } else {
                this.answerWrong(button.answerId);
            }
        });
    },

    loadQuestionImage: function (imageName, callback) {
        this.onQuestionImageLoaded = callback;
        this.load.image('questionImage', 'questions/' + imageName);
        this.load.start();
    },

    loadStart: function () {
        this.loadText.setText('Loading ...');
    },

    //  This callback is sent the following parameters:
    fileComplete: function (progress, cacheKey, success, totalLoaded, totalFiles) {
        this.loadText.setText('File Complete: ' + progress + '% - ' + totalLoaded + ' out of ' + totalFiles);
        if (cacheKey == 'questionImage') {
            replaceQuestionImage(cacheKey);
            if (this.onQuestionImageLoaded) {
                this.onQuestionImageLoaded();
            }
        }
    },

    loadComplete: function () {
        this.loadText.setText('Loaded');
    },

    replaceQuestionImage: function (cacheKey) {
        this.clearQuestionImage();

        var newImage = this.add.sprite(0, 0, cacheKey);

        newImage.anchor.setTo(0.5, 0.5);

        var resScaleH = QUESTION_IMAGE_MAX_HEIGHT / newImage.height;
        var resScaleW = QUESTION_IMAGE_MAX_WIDTH / newImage.width;
        var resScale = Math.min(resScaleH, resScaleW);
        newImage.height = newImage.height * resScale;
        newImage.width = newImage.width * resScale;

        newImage.x = game.world.centerX;
        newImage.y = newImage.height / 2 + game.height * .03;

        this.questionImage = newImage;
    },

    clearQuestionImage: function () {
        if (this.questionImage) {
            this.questionImage.kill();
            this.questionImage = undefined;
        }
    },

    loadQuestion: function (useHeader) {

        this.clearQuestionImage();

        if (useHeader) {
            getJSON('/questionHeader', function(questionHeader) {
                // questionText.setText(questionHeader['question']);
                this.loadQuestionImage(questionHeader['img'], function() {
                    getJSON('/question', function(question) {
                        this.questionText.setText(question['question']);
                        this.loadAnswers(question['answers']);
                    });
                });
            });
        } else {
            this.onQuestionImageReplaced = undefined;
            getJSON('/question', function(question) {
                this.questionText.setText(question['question']);
                this.loadAnswers(question['answers']);
                this.loadQuestionImage(question['img']);
            });
        }
    },

    loadAnswers: function (answers) {
        for (var i = 0; i < answers.length; i++) {
            this.optButtons[i].setLabel(answers[i])
        }
    },

    updateStatus: function () {
        getJSON('/status', function(gameStatus) {
            this.scoreText.setText(gameStatus['score']);
            if (gameStatus['status'] == 'start') {
                this.startGame();
            } else if (gameStatus['status'] == 'question') {
                this.loadQuestion(true);
            } else if (gameStatus['status'] == 'answer') {
                this.loadQuestion(false);
            } else if (gameStatus['status'] == 'finish') {
                this.finishGame();
            }
        });

    },

    startGame: function () {
        getJSON('/start', this.updateStatus);
    }

};

var LabelButton = function(game, x, y, key, label, callback, callbackContext, overFrame, outFrame, downFrame, upFrame) {
    Phaser.Button.call(this, game, x, y, key, callback, callbackContext, overFrame, outFrame, downFrame, upFrame);
    //Label style
    this.style = {
        font: 'Pixel Art',
        fontSize: '12px',
        align: 'center',
        fill: 'white'
    };

    this.anchor.setTo(0.5, 0.5);
    this.label = new Phaser.Text(Paloquiz.game, 0, 0, label, this.style);

    //puts the label in the center of the button
    this.label.anchor.setTo(0.5, 0.5);
    this.addChild(this.label);
    this.setLabel(label);

    //adds button to game
    game.add.existing(this);
};

LabelButton.prototype = Object.create(Phaser.Button.prototype);

LabelButton.prototype.constructor = LabelButton;

LabelButton.prototype.setLabel = function(label) {
    this.label.setText(label);
};

<commit_msg>Fixed paloquiz Main state bugs<commit_after>Paloquiz.states.Main = function (game) {
    this.optionsPane;
    this.dialogPane;
    this.host;
    this.scoreText;
    this.loadText;
    this.messageText;
    this.questionText;
    this.optButtonsGroup;
    this.optButtons;
};

Paloquiz.states.Main.prototype = {

    QUESTION_IMAGE_MAX_HEIGHT: 0,
    QUESTION_IMAGE_MAX_WIDTH: 0,

    init: function () {
        QUESTION_IMAGE_MAX_HEIGHT = this.game.height * .32;
        QUESTION_IMAGE_MAX_WIDTH = this.game.width * .8;
    },

    preload: function () {
        this.load.baseURL = '/static/';
        this.load.image('background', 'assets/background.png');
        this.load.spritesheet('host', 'assets/palo.png', 30, 85);
        this.load.image('dialogPane', 'assets/dialog_pane.png');
        this.load.image('optionsPane', 'assets/options_pane.png');
        this.load.spritesheet('button', 'assets/button_sprite_sheet.png', 193, 71);
        //game.load.bitmapFont('carrier_command', 'assets/carrier_command.png', 'assets/carrier_command.xml');
        this.load.bitmapFont('desyrel', 'assets/desyrel.png', 'assets/desyrel.xml');

    },

    create: function () {

        var worldCenterX = this.game.world.centerX;
        var worldCenterY = this.game.world.centerY;
        var worldHeight = this.game.world.height;
        var worldWidth = this.game.world.width;

        this.game.stage.background = this.add.sprite(worldCenterX, worldCenterY, 'background');
        this.game.stage.background.anchor.setTo(0.5, 0.5);
        this.game.stage.background.width = this.game.width
        this.game.stage.background.height = this.game.height

        this.optionsPane = this.add.sprite(worldCenterX, worldHeight - 260, 'optionsPane');
        this.optionsPane.anchor.setTo(0.5, 0.0);

        this.dialogPane = this.add.sprite(worldCenterX, 220, 'dialogPane');
        this.dialogPane.anchor.setTo(0.5, 0.0);

        this.host = this.add.sprite(0, 0, 'host');
        // host.smoothed = false;  // does this do something?
        this.host.x = worldWidth - this.host.width / 2 - 55;
        this.host.y = worldHeight - 140;
        this.host.anchor.setTo(0.5, 0.5);
        this.host.scale.setTo(2, 2);

        //  You can listen for each of these events from Phaser.Loader
        this.load.onLoadStart.add(this.loadStart, this);
        this.load.onFileComplete.add(this.fileComplete, this);
        this.load.onLoadComplete.add(this.loadComplete, this);

        // Score text
        this.scoreText = this.add.text(worldCenterX + worldWidth / 2 - 70, 10, '0', {
            fill: '#ff8000'
        });

        //  Progress report
        this.loadText = this.add.text(worldCenterX, 50, 'Image', {
            fill: '#ffffff'
        });

        //  Bit map Text

        //messageText = game.add.bitmapText(200, 100, 'desyrel', 'Phaser & Pixi\nrocking!', 64);
        this.messageText = this.add.bitmapText(worldCenterX, worldCenterY, 'desyrel', 'Correct', 80);
        this.messageText.inputEnabled = true;
        this.messageText.anchor.setTo(0.5, 0.5);
        this.messageText.visible = false

        // Question text
        var qTextY = this.dialogPane.y + this.dialogPane.height / 2
        this.questionText = this.add.text(worldCenterX, qTextY, 'Question', {
            font: 'Pixel Art',
            fontSize: '26px',
            align: 'center',
            fill: '#ffffff'
        });
        this.questionText.anchor.setTo(0.5, 0.5);

        this.createOptionButtons();
        this.updateStatus();
    },

    createOptionButtons: function () {
        this.optButtonsGroup = this.add.group();
        this.optButtons = new Array();

        var bX = this.optionsPane.x - this.optionsPane.width * 0.10;
        var bY = this.optionsPane.y * 1.12;
        var bHeight = (this.optionsPane.height * 0.8) / 4;

        for (var i = 0; i < 4; i++) {
            this.optButtons[i] = new LabelButton(this, bX, bY + bHeight * i, 'button', 'buttont', this.actionOnClick, this, 2, 1, 0);
            this.optButtons[i].height = bHeight;
            this.optButtons[i].width = this.optionsPane.width * 0.72;
            this.optButtons[i].answerId = i;
            this.optButtonsGroup.add(this.optButtons[i]);
        }
    },

    answerCorrect: function (buttonId) {

        this.messageText.setText('Correct! :)');
        this.messageText.visible = true;
        this.enableInput(false);
        this.game.time.events.add(Phaser.Timer.SECOND * 2, function() {
            this.messageText.visible = false;
            this.enableInput(true);
            this.updateStatus();
        }, this);

    },

    answerWrong: function (buttonId) {
        this.messageText.setText('Wrong! >:(');
        this.messageText.visible = true;
        this.enableInput(false);
        this.game.time.events.add(Phaser.Timer.SECOND * 2, function() {
            this.messageText.visible = false;
            this.enableInput(true);
            this.updateStatus();
        }, this);
    },

    finishGame: function () {
        this.messageText.setText('Score = ' + this.scoreText.text);
        this.messageText.visible = true;
        this.enableInput(false);
    },

    enableInput: function (enable) {
        this.game.input.disabled = !enable;
    },

    actionOnClick: function (button) {
        getJSON('/answer/' + button.answerId, function(answerResponse) {

            if (answerResponse['correct']) {
                this.answerCorrect(button.answerId);
            } else {
                this.answerWrong(button.answerId);
            }
        }, this);
    },

    loadQuestionImage: function (imageName, callback) {
        this.onQuestionImageLoaded = callback;
        this.load.image('questionImage', 'questions/' + imageName);
        this.load.start();
    },

    loadStart: function () {
        this.loadText.setText('Loading ...');
    },

    //  This callback is sent the following parameters:
    fileComplete: function (progress, cacheKey, success, totalLoaded, totalFiles) {
        this.loadText.setText('File Complete: ' + progress + '% - ' + totalLoaded + ' out of ' + totalFiles);
        if (cacheKey == 'questionImage') {
            this.replaceQuestionImage(cacheKey);
            if (this.onQuestionImageLoaded) {
                this.onQuestionImageLoaded();
            }
        }
    },

    loadComplete: function () {
        this.loadText.setText('Loaded');
    },

    replaceQuestionImage: function (cacheKey) {
        this.clearQuestionImage();

        var newImage = this.add.sprite(0, 0, cacheKey);

        newImage.anchor.setTo(0.5, 0.5);

        var resScaleH = QUESTION_IMAGE_MAX_HEIGHT / newImage.height;
        var resScaleW = QUESTION_IMAGE_MAX_WIDTH / newImage.width;
        var resScale = Math.min(resScaleH, resScaleW);
        newImage.height = newImage.height * resScale;
        newImage.width = newImage.width * resScale;

        newImage.x = this.game.world.centerX;
        newImage.y = newImage.height / 2 + this.game.height * .03;

        this.questionImage = newImage;
    },

    clearQuestionImage: function () {
        if (this.questionImage) {
            this.questionImage.kill();
            this.questionImage = undefined;
        }
    },

    loadQuestion: function (useHeader) {

        this.clearQuestionImage();

        if (useHeader) {
            getJSON('/questionHeader', function(questionHeader) {
                this.questionText.setText(questionHeader['question']);
                this.loadQuestionImage(questionHeader['img'], function() {
                    getJSON('/question', function(question) {
                        this.questionText.setText(question['question']);
                        this.loadAnswers(question['answers']);
                    }, this);
                });
            }, this);
        } else {
            this.onQuestionImageReplaced = undefined;
            getJSON('/question', function(question) {
                this.questionText.setText(question['question']);
                this.loadAnswers(question['answers']);
                this.loadQuestionImage(question['img']);
            }, this);
        }
    },

    loadAnswers: function (answers) {
        for (var i = 0; i < answers.length; i++) {
            this.optButtons[i].setLabel(answers[i])
        }
    },

    updateStatus: function () {
        getJSON('/status', function(gameStatus) {
            this.scoreText.setText(gameStatus['score']);
            if (gameStatus['status'] == 'start') {
                this.startGame();
            } else if (gameStatus['status'] == 'question') {
                this.loadQuestion(true);
            } else if (gameStatus['status'] == 'answer') {
                this.loadQuestion(false);
            } else if (gameStatus['status'] == 'finish') {
                this.finishGame();
            }
        }, this);

    },

    startGame: function () {
        getJSON('/start', this.updateStatus, this);
    }

};

var LabelButton = function(game, x, y, key, label, callback, callbackContext, overFrame, outFrame, downFrame, upFrame) {
    Phaser.Button.call(this, game.game, x, y, key, callback, callbackContext, overFrame, outFrame, downFrame, upFrame);
    //Label style
    this.style = {
        font: 'Pixel Art',
        fontSize: '12px',
        align: 'center',
        fill: 'white'
    };

    this.anchor.setTo(0.5, 0.5);
    this.label = game.add.text(0, 0, label, this.style);

    //puts the label in the center of the button
    this.label.anchor.setTo(0.5, 0.5);
    this.addChild(this.label);
    this.setLabel(label);

    //adds button to game
    game.add.existing(this);
};

LabelButton.prototype = Object.create(Phaser.Button.prototype);

LabelButton.prototype.constructor = LabelButton;

LabelButton.prototype.setLabel = function(label) {
    this.label.setText(label);
};

<|endoftext|>