<commit_before>/**
 * Stack Exchange Chat Status
 * Copyright 2015 - Nathan Osman
 */

(function() {

    /**
     * Default values for global settings.
     */
    var DEFAULTS = {
        debug: false,
        self: true,
        server: 'sechat.quickmediasolutions.com'
    };

    /**
     * Store a value in localStorage.
     * @param key item to set
     * @param val new value
     */
    function set(key, val) {
        localStorage.setItem(key, JSON.stringify(val));
    }

    /**
     * Retrieve a setting from localStorage.
     * @param key item to retrieve
     *
     * The DEFAULTS variable will be used for setting the default value if the
     * key is not set.
     */
    function get(key) {
        var val = localStorage.getItem(key);
        if (val === null) {
            set(key, DEFAULTS[key]);
            return DEFAULTS[key];
        } else {
            return JSON.parse(val);
        }
    }

    /**
     * Log the specified message to the console for debug purposes.
     */
    function log(msg) {
        get('debug') && console.log('[ChatStatus] ' + msg);
    }

    /**
     * Return the current time as a unix timestamp (integer).
     */
    function now() {
        return parseInt(new Date().getTime() / 1000);
    }

    /**
     * Given a message, obtain its ID.
     * @param elem HTML element or jQuery object
     */
    function messageId(elem) {
        if ($(elem).attr('id'))
        return $(elem).attr('id').match(/message-(\d+)/)[1];
    }

    /**
     * Given a message, obtain the user's ID. This is made simpler by the fact
     * that SE stores the ID directly in one of the parent elements.
     * @param elem HTML element or jQuery object
     */
    function messageUserId(elem) {
        return $(elem).closest('.user-container').data('user');
    }

    /**
     * Display a notification at the top of the page.
     * @param msg HTML message to display
     */
    function notify(msg) {
        window.Notifier().notify(msg);
    }

    /**
     * Load the specified script into the page.
     * @param name filename of the script
     * @param callback function to execute after loading
     */
    function loadScript(name, callback) {
        var script = document.createElement('script');
        script.type = 'text/javascript';
        script.src = 'https://' + get('server') + '/js/' + name;
        script.onload = callback;
        script.onerror = function() {
            notify(
                "Unable to load " + name +
                " from " + get('server') + "."
            );
        };
        document.body.appendChild(script);
    }

    /**
     * Consider the following scenario - notification that a message was read
     * arrives before the message - therefore we need to store the message in a
     * "pending" container until it arrives.
     */
    var pendingContainers = {};

    /**
     * Obtain the container for a specific message, creating it if necessary.
     * @param msgId the ID of the message
     * @return the container for indicators
     */
    function getContainer(msgId) {
        if (msgId in pendingContainers) {
            return pendingContainers[msgId];
        }
        var $msg = $('#message-' + msgId),
            $container = $msg.next('.secs-container');
        if (!$container.length) {
            $container = $('
')
                .addClass('secs-container')
                .css('marginLeft', '18px');
            if ($msg.length) {
                $msg.after($container);
            } else {
                pendingContainers[msgId] = $container;
            }
        }
        return $container;
    }

    /**
     * Maintain a map of user IDs to their container.
     */
    var users = {};

    /**
     * Obtain the indicator element for a user, creating it if necessary.
     * @param userId the ID of the user
     * @return the indicator for the user
     */
    function getUser(userId) {
        var $user;
        if (userId in users) {
            $user = users[userId];
        } else {
            var $typing = $('')
                    .attr('src', '//cdn-chat.sstatic.net/chat/img/progress-dots.gif')
                    .css({
                        display: 'inline-block',
                        width: 0
                    });
            $user = $('')
                .css({
                    backgroundColor: 'rgba(0, 0, 0, 0.1)',
                    borderRadius: '12px',
                    display: 'inline-block',
                    margin: '4px 4px 4px 0',
                    padding: '4px'
                })
                .append($(CHAT.RoomUsers.createAvatarImage(userId, 16))
                    .css({
                        borderRadius: '9px',
                        padding: '1px'
                    }))
                .append($typing)
                .data('typing', $typing);
            CHAT.RoomUsers.allPresent().forEach(function(u) {
                if (u.id == userId) {
                    $user.attr('title', u.name + " has read this far");
                }
            });
            users[userId] = $user;
        }
        return $user;
    }

    /**
     * Update whether the current user is active or not.
     * @param userId the ID of the user to update
     * @param active whether the user is active or not
     */
    function updateUserActive(userId, active) {
        getUser(userId).css('opacity', active ? '1' : '0.5');
    }

    /**
     * Update the message that the user has last read.
     * @param userId the ID of the user to update
     * @param msgId the ID of the message or null for the indicator tray
     */
    function updateUserMessage(userId, msgId) {
        getContainer(msgId).append(getUser(userId).detach());
    }

    /**
     * Indicate that a user has stopped typing.
     * @param userId the ID of the user
     */
    function userStoppedTyping(userId) {
        var $user = getUser(userId),
            timeout = $user.data('timeout');
        if (timeout) {
            window.clearTimeout(timeout);
            $user.data('typing').animate({
                marginLeft: 0,
                marginRight: 0,
                width: 0
            });
            $user.removeData('timeout');
        }
    }

    /**
     * Indicate that a user has started typing.
     * @param userId the ID of the user to update
     *
     * The typing indicator will be displayed for four seconds or until it is
     * reset by another call to this function for the same user.
     */
    function userStartedTyping(userId) {
        var $user = getUser(userId),
            timeout = $user.data('timeout');
        if (timeout) {
            window.clearTimeout(timeout);
        } else {
            $user.data('typing').animate({
                marginLeft: '4px',
                marginRight: '4px',
                width: '18px'
            });
        }
        $user.data('timeout', window.setTimeout(userStoppedTyping, 4000, userId));
    }

    /**
     * Indicate that a user has quit.
     */
    function userQuit(userId) {
        getUser(userId).fadeOut(function() {
            $(this).remove();
            delete users[userId];
        });
    }

    var socket,
        lastTypingMsg = 0,
        lastMessageRead = 0,
        windowActive = true;

    /**
     * Determine if the socket is active.
     */
    function isActive() {
        return socket && socket.readyState == WebSocket.OPEN;
    }

    /**
     * Send a ping every 30 seconds to avoid having the socket timeout.
     */
    function ping() {
        if (isActive()) {
            log("ping");
            socket.send(JSON.stringify({
                type: 'ping'
            }));
        }
    }
    window.setInterval(ping, 30000);

    /**
     * Hook the input box in order to notify other users of typing. Only send
     * the messages once per two seconds to avoid excessive bandwidth.
     */
    $('#input').on('keypress', function() {
        if (isActive()) {
            var t = now();
            if (lastTypingMsg < (t - 2)) {
                log("last_char_entered: " + t);
                socket.send(JSON.stringify({
                    type: 'typing',
                    value: t
                }));
                lastTypingMsg = t;
            }
        }
    });

    /**
     * Broadcast the fact that the input box has gained or lost focus.
     * @param active whether the input box is active or not
     */
    function broadcastActive(active) {
        socket.send(JSON.stringify({
            type: 'active',
            value: active ? 1 : 0
        }));
    }

    /**
     * Broadcast the last message read to the other users.
     */
    function broadcastLastMessageRead() {
        if (isActive()) {
            var msgId = messageId($('.message').last());
            if (parseInt(msgId) > parseInt(lastMessageRead)) {
                lastMessageRead = parseInt(msgId);
                log("last_message_read: " + lastMessageRead);
                socket.send(JSON.stringify({
                    type: 'position',
                    value: lastMessageRead
                }));
            } else {
                broadcastActive(true);
            }
        }
    }

    /**
     * Load Live Query and watch for new messages coming in. When one comes in,
     * that user has stopped typing. Also, check for pending containers.
     */
    loadScript('jquery.livequery.min.js', function() {
        log("Live Query loaded")
        var firstLoad = true;
        $('#chat').livequery('.message', function(e) {
            if (!firstLoad) {
                var msgId = messageId(e),
                    userId = messageUserId(e);
                if (msgId in pendingContainers) {
                    $(e).after(pendingContainers[msgId]);
                    delete pendingContainers[msgId];
                }
                userStoppedTyping(userId);
                if (windowActive) {
                    broadcastLastMessageRead();
                }
            }
        });
        firstLoad = false;
    });

    /**
     * Keep track of whether the window (or tab) is active.
     */
    $(window).on({
        'blur': function() {
            log("Window has lost focus");
            windowActive = false;
            broadcastActive(false);
        },
        'focus': function() {
            log("Window has gained focus");
            windowActive = true;
            broadcastLastMessageRead();
        }
    });

    /**
     * Connect to the WebSocket endpoint and set the appropriate callbacks;
     */
    function connect() {
        socket = new WebSocket(
            'wss://' + get('server') + '/api/connect/' +
            CHAT.CURRENT_ROOM_ID + '/' + CHAT.CURRENT_USER_ID
        );
        socket.onopen = onopen;
        socket.onmessage = onmessage;
        socket.onerror = onerror;
        socket.onclose = onclose;
    }

    /**
     * Log the successful connection.
     */
    function onopen() {
        log("socket opened");
        broadcastLastMessageRead();
    }

    /**
     * Process a message received from another user.
     */
    function onmessage(e) {
        log("msg: " + e.data);
        var s = JSON.parse(e.data);
        if (s.type == 'active') {
            updateUserActive(s.user_id, s.value);
        } else if (s.type == 'position') {
            updateUserMessage(s.user_id, s.value);
            updateUserActive(s.user_id, s.value);
        } else if (s.type == 'typing' &&
                s.value > (now() - 4)) {
            userStartedTyping(s.user_id);
        } else if (s.type == 'quit') {
            userQuit(s.user_id);
        }
    }

    /**
     * Log an error that occurs.
     */
    function onerror(e) {
        log("socket error: " + e.message);
    }

    /**
     * Attempt to reconnect after one minute.
     */
    function onclose() {
        log("socket closed");
        socket = null;
        window.setTimeout(connect, 60 * 1000);
    }

    connect();

    /**
     * Update the visibility of the current user.
     */
    function updateSelf(val) {
        getUser(CHAT.CURRENT_USER_ID).toggle(val);
    }
    updateSelf(get('self'));

    /**
     * Preference dialog
     */
    var $prefCover = $('
')
            .css({
                backgroundColor: 'rgba(0, 0, 0, 0.4)',
                height: '100%',
                left: '0',
                position: 'fixed',
                top: '0',
                width: '100%',
                zIndex: '6'
            })
            .hide()
            .appendTo(document.body),
        $prefDialog = $('
')
            .css({
                backgroundColor: '#fff',
                boxShadow: '0 1px 15px #555555',
                boxSizing: 'border-box',
                height: '200px',
                left: '50%',
                marginLeft: '-150px',
                marginTop: '-100px',
                padding: '8px',
                position: 'fixed',
                top: '50%',
                width: '300px'
            })
            .append($('')
                .attr('href', '#')
                .css('float', 'right')
                .html('×')
                .click(function() {
                    $prefCover.fadeOut();
                    return false;
                }))
            .append($('
').text("Preferences"))
            .append('
')
            .appendTo($prefCover),
        $debugOption = $('
')
            .append($('<input>')
                .attr('type', 'checkbox')
                .prop('checked', get('debug'))
                .change(function() {
                    set('debug', this.checked);
                }))
            .append(" debug messages")
            .appendTo($prefDialog),
        $selfOption = $('
')
            .append($('<input>')
                .attr('type', 'checkbox')
                .prop('checked', get('self'))
                .change(function() {
                    set('self', this.checked);
                    updateSelf(this.checked);
                }))
            .append(" show me")
            .appendTo($prefDialog),
        $serverOption = $('
')
            .append("server: ")
            .append($('<input>')
                .attr('type', 'text')
                .val(get('server'))
                .change(function() {
                    set('server', $(this).val());
                }))
            .appendTo($prefDialog);
    $('input[type=checkbox]').css('verticalAlign', 'middle');

    /**
     * Add a link to the bottom of the page for changing preferences
     */
    $('#footer-legal')
        .prepend(' | ')
        .prepend($('')
            .attr('href', '#')
            .text('prefs')
            .click(function() {
                $prefCover.fadeIn();
                return false;
            }));

})();
<commit_msg>Completely rewrote script.<commit_after>/**
 * Stack Exchange Chat Status
 * Copyright 2015 - Nathan Osman
 */

(function() {

    /**
     * Display a notification at the top of the page.
     */
    function notify(msg) {
        window.Notifier().notify(msg);
    }

    /**
     * Return the current time as a unix timestamp (integer).
     */
    function now() {
        return parseInt(new Date().getTime() / 1000);
    }

    /**
     * Create a function that will only call the provided callback once.
     */
    function once(callback) {
        var firstCall = false;
        return function() {
            if (!firstCall) {
                callback.apply(null, arguments);
                firstCall = true;
            }
        };
    }

    /**
     * Create a function that ensures the provided callback is only called once
     * in the specified number of seconds.
     */
    function throttle(callback, seconds) {
        var lastCall = 0;
        return function() {
            var t = now();
            if (lastCall < (t - seconds)) {
                callback.apply(null, arguments);
                lastCall = t;
            }
        };
    }

    /**
     * Manage storage and retrieval of script preferences. localStorage is used
     * for keeping track of user preferences.
     */
    function Preferences() {

        /**
         * Default values for all settings.
         */
        var defaultValues = {
            // Show debug messages in the console
            debugMessages: false,
            // Show the user with everyone else
            showMe: true,
            // Show the user's active status
            showActive: true,
            // Show the user's typing status
            showTyping: true,
            // URI chat server to connect to
            server: 'sechat.quickmediasolutions.com',
            // Ping interval (in seconds)
            pingInterval: 30,
            // Connection retry interval (in seconds)
            retryInterval: 30
        };

        /**
         * Retrieve a value from localStorage. If the key has not been set, the
         * default value is returned.
         */
        this.get = function(key) {
            var val = localStorage.getItem(key);
            if (val === null) {
                return defaultValues[key];
            } else {
                return JSON.parse(val);
            }
        };

        /**
         * Store a value in localStorage. The stored value is JSON-serialized.
         */
        this.set = function(key, val) {
            localStorage.setItem(key, JSON.stringify(val));
        };
    }

    /**
     * Simplify the logging of messages to the console. Messages will only be
     * logged if debug messages are enabled.
     */
    function LogFactory(preferences) {

        // Only log messages if the appropriate preference is enabled
        var debugMessages = preferences.get('debugMessages');

        /**
         * Create a logger for the specified class.
         */
        this.create = function(class_) {
            return function(msg) {
                debugMessages && console.log("[" + class_ + "] " + msg);
            };
        };
    }

    /**
     * Interact with user containers, allowing user status and position to be
     * easily manipulated.
     */
    function UserManager() {

        /**
         * It is very possible that a packet indicating a user has read a
         * certain message arrives before the message. A container obviously
         * cannot be attached; therefore it must be added to a list of
         * "pending" containers.
         */
        var pendingContainers = {};

        /**
         * Retrieve the container for the specified message. First, a pending
         * container is returned if one exists. If the message exists in the
         * DOM, its container is optionally created and returned. If not, it is
         * added to the list of pending containers.
         */
        function getContainer(messageId) {
            if (messageId in pendingContainers) {
                return pendingContainers[messageId];
            }
            var $message = $('#message-' + messageId),
                $container = $message.next('.secs-container');
            if (!$container.length) {
                $container = $('
')
                    .addClass('secs-container')
                    .css('marginLeft', '18px')
                if ($message.length) {
                    $message.after($container);
                } else {
                    pendingContainers[messageId] = $container;
                }
            }
            return $container;
        }

        /**
         * New messages are inserted immediately after the previous one, which
         * unfortunately means that the container for a message is "bumped"
         * down when the next message is posted. We get around that by hooking
         * jQuery.insertAfter() and manually moving our container around. Yes,
         * it's a really nasty hack. Yes, it works.
         */
        var after = $.fn.after;
        $.fn.after = function(e) {
            var $container = $([]);
            if ($(e).hasClass('message')) {
                $container = this.next('.secs-container').detach();
            }
            var ret = after.apply(this, arguments);
            $container.insertBefore(e);
            return ret;
        };

        /**
         * Attach a pending container if one exists for the message.
         */
        this.attachPendingContainer = function(messageId) {
            if (messageId in pendingContainers) {
                $('#message-' + messageId).after(pendingContainers[messageId]);
                delete pendingContainers[messageId];
            }
        };

        /**
         * To speed things up, keep a map of user IDs to their indicator.
         */
        var users = {};

        /**
         * Display the current position and status of a user.
         */
        function User(userId) {

            // Create the indicator
            var $indicator = $('')
                    .css({
                        backgroundColor: 'rgba(0, 0, 0, 0.1)',
                        borderRadius: '12px',
                        display: 'inline-block',
                        margin: '4px 4px 4px 0',
                        opacity: '0.5',
                        padding: '2px'
                    })
                    .append($(CHAT.RoomUsers.createAvatarImage(userId, 16))
                        .css({
                            borderRadius: '9px',
                            padding: '1px'
                        })),
                $typing = $('')
                    .attr('src', '//cdn-chat.sstatic.net/chat/img/progress-dots.gif')
                    .css({
                        display: 'inline-block',
                        width: 0
                    })
                    .appendTo($indicator),
                timeout = null;

            // Lookup the user's name and display a tooltip
            CHAT.RoomUsers.allPresent().forEach(function(u) {
                if (u.id == userId) {
                    $indicator.attr('title', u.name + " has read this far");
                }
            });

            /**
             * Indicate whether the user is active (input has focus).
             */
            this.setActive = function(active) {
                $indicator.css('opacity', active ? '1' : '0.5');
            };

            /**
             * Move the indicator to the specified message.
             */
            this.moveTo = function(messageId) {
                getContainer(messageId).append($indicator.detach());
            };

            /**
             * Indicate whether the user is currently typing.
             */
            var setTyping = this.setTyping = function(typing) {
                if (timeout !== null) {
                    window.clearTimeout(timeout);
                    timeout = null;
                }
                $typing.animate({
                    marginLeft: typing ? '4px' : 0,
                    marginRight: typing ? '4px' : 0,
                    width: typing ? '18px' : 0
                });
                if (typing) {
                    timeout = window.setTimeout(setTyping, 4000, false);
                }
            };

            /**
             * Remove the indicator.
             */
            this.remove = function() {
                $indicator.fadeOut(function() {
                    $(this).remove();
                });
            };
        }

        /**
         * Retrieve the indicator element for a user, creating it if necessary.
         */
        var get = this.get = function(userId) {
            if (!(userId in users)) {
                users[userId] = new User(userId);
            }
            return users[userId];
        };

        /**
         * Remove the indicator for a user.
         */
        this.remove = function(userId) {
            get(userId).remove();
            delete users[userId];
        };
    }

    /**
     * Communicate with the central server via WebSocket.
     */
    function Socket(preferences, logFactory) {

        var self = this,
            log = logFactory.create('Socket'),
            server = preferences.get('server'),
            socket = null,
            timeout = null,
            pingInterval = preferences.get('pingInterval') * 1000,
            retryInterval = preferences.get('retryInterval') * 1000;

        /**
         * Send a message if the socket is open.
         */
        var send = this.send = function(type, value) {
            if (socket !== null && socket.readyState == WebSocket.OPEN) {
                var data = {
                    type: type
                };
                if (typeof value !== 'undefined') {
                    data.value = value;
                }
                socket.send(JSON.stringify(data));
            }
        }

        // TODO: use the /api/ping method to check if the connection is
        // still alive - if down, close the socket and notify the user

        /**
         * Ping the server to keep the connection alive.
         */
        function ping() {
            log("ping");
            send('ping');
            timeout = window.setTimeout(ping, pingInterval);
        }

        /**
         * Attempt to connect to the server.
         */
        function connect() {
            log("connecting to " + server);
            socket = new WebSocket(
                'wss://' + server + '/api/connect/' +
                CHAT.CURRENT_ROOM_ID + '/' + CHAT.CURRENT_USER_ID
            );
            socket.onopen = function() {
                log("connected");
                timeout = window.setTimeout(ping, pingInterval);
                self.onopen && self.onopen();
            };
            socket.onmessage = function(e) {
                log("received: " + e.data);
                self.onmessage && self.onmessage(e);
            };
            socket.onerror = function(e) {
                log("error: " + e.message);
            };
            socket.onclose = function() {
                log("connection closed; retrying in " + retryInterval + "ms");
                if (timeout !== null) {
                    window.clearTimeout(timeout);
                }
                window.setTimeout(connect, retryInterval);
            };
        }

        connect();
    }

    /**
     * Provides a set of methods and callbacks for interacting with the page.
     */
    function UI(preferences, logFactory) {

        var self = this,
            log = logFactory.create('UI');

        /**
         * Watch for the window gaining and losing focus.
         */
        $(window).on({
            blur: function() {
                log("window has lost focus");
                self.onactivechange && self.onactivechange(false);
            },
            focus: function() {
                log("window has gained focus");
                self.onactivechange && self.onactivechange(true);
            }
        });

        /**
         * Invoke the onmessage callback with the specified message.
         */
        function processMessage(elem) {
            var messageId = $(elem).messageId(),
                userId = $(elem).closest('.user-container').data('user');
            self.onnewmessage && self.onnewmessage(messageId, userId);
        }

        /**
         * In order to be notified of new messages, liveQuery is needed.
         * Therefore, it must be loaded from the remote URL. Once it is loaded,
         * the DOM is monitored for new messages.
         */
        var script = document.createElement('script'),
            server = preferences.get('server');
        script.type = 'text/javascript';
        script.src = 'https://' + server + '/js/jquery.livequery.min.js';
        script.onerror = function() {
            notify("Unable to load liveQuery from " + server + ".");
        };
        script.onload = function() {
            $('.message').livequery(once(function() {
                $('.message').expire();
                window.setTimeout(function() {
                    var firstMessages = true;
                    $('.message').livequery(function() {
                        if (!firstMessages) {
                            processMessage(this);
                        }
                    });
                    processMessage($('.message').last());
                    firstMessages = false;
                }, 0);
            }));
        };
        document.body.appendChild(script);

        /**
         * Invoke the typing callback when the user types.
         */
        $('#input').on('keypress', function() {
            self.ontyping && self.ontyping();
        });
    }

    /**
     * Allow user to manage preferences.
     */
    function PreferenceDialog(preferences) {

        /**
         * Create each of the elements.
         */
        var $prefCover = $('
')
                .css({
                    backgroundColor: 'rgba(0, 0, 0, 0.4)',
                    height: '100%',
                    left: '0',
                    position: 'fixed',
                    top: '0',
                    width: '100%',
                    zIndex: '6'
                })
                .hide()
                .appendTo('body'),
            $prefDialog = $('
')
                .css({
                    backgroundColor: '#fff',
                    boxShadow: '0 1px 15px #555555',
                    boxSizing: 'border-box',
                    height: '200px',
                    left: '50%',
                    marginLeft: '-150px',
                    marginTop: '-100px',
                    padding: '8px',
                    position: 'fixed',
                    top: '50%',
                    width: '300px'
                })
                .append($('')
                    .attr('href', '#')
                    .css('float', 'right')
                    .html('×')
                    .click(function() {
                        $prefCover.fadeOut();
                        return false;
                    }))
                .append($('
').text("Preferences"))
                .append($('
')
                    .css('fontSize', '8pt')
                    .text("Reload the page to apply changes."))
                .append('
')
                .appendTo($prefCover),
            $debugOption = $('
')
                .append($('<input>')
                    .attr('type', 'checkbox')
                    .prop('checked', preferences.get('debugMessages'))
                    .change(function() {
                        set('debug', this.checked);
                    }))
                .append(" debug messages")
                .appendTo($prefDialog),
            $serverOption = $('
')
                .append("server: ")
                .append($('<input>')
                    .attr('type', 'text')
                    .val(preferences.get('server'))
                    .change(function() {
                        set('server', $(this).val());
                    }))
                .appendTo($prefDialog);
        $('input[type=checkbox]').css('verticalAlign', 'middle');

        /**
         * Add a link to the footer.
         */
        $('#footer-legal')
            .prepend(' | ')
            .prepend($('')
                .attr('href', '#')
                .text('prefs')
                .click(function() {
                    $prefCover.fadeIn();
                    return false;
                }));
    }

    var preferences = new Preferences(),
        logFactory = new LogFactory(preferences),
        userManager = new UserManager(),
        socket = new Socket(preferences, logFactory);
        ui = new UI(preferences, logFactory),
        preferenceDialog = new PreferenceDialog(preferences);

    var windowActive = 1,
        lastMessageId = 0,
        lastMessageReadId = 0;

    /**
     * Broadcast the current status of the user each time the connection is
     * opened.
     */
    socket.onopen = function() {
        socket.send('active', windowActive ? 1 : 0);
        if (lastMessageReadId) {
            socket.send('position', lastMessageReadId);
        }
    };

    // TODO: no checking is done here, though the JSON is coming from a trusted
    // server over SSL, so fixing this isn't a huge priority

    /**
     * Process a message received from the socket.
     */
    socket.onmessage = function(e) {
        var json = JSON.parse(e.data),
            userId = json.user_id,
            type = json.type;
        if (type == 'quit') {
            userManager.remove(userId);
        } else {
            var user = userManager.get(json.user_id),
                value = json.value;
            if (type == 'active') {
                user.setActive(value);
            } else if (type == 'position') {
                user.moveTo(value);
            } else if (type == 'typing') {
                user.setTyping(value > (now() - 4));
            }
        }
    };

    /**
     * The window focus has changed. If focus is gained, either update the last
     * message read or indicate that focus was gained.
     */
    ui.onactivechange = function(active) {
        socket.send('active', windowActive = (active ? 1 : 0));
        if (windowActive && lastMessageReadId != lastMessageId) {
            socket.send('position', lastMessageReadId = lastMessageId);
        }
    };

    /**
     * Process a new message displayed in the UI.
     */
    ui.onnewmessage = function(messageId, userId) {
        userManager.attachPendingContainer(messageId);
        userManager.get(userId).setTyping(false);
        lastMessageId = messageId;
        if (windowActive) {
            lastMessageReadId = lastMessageId;
            socket.send('position', lastMessageReadId);
        }
    };

    /**
     * Indicate that the user has started typing - but throttle the calls to
     * avoid overwhelming the API (once every 2 seconds).
     */
    ui.ontyping = throttle(function() {
        socket.send('typing', now());
    }, 2);

})();
<|endoftext|>


<commit_before>//Kolla om inlogg redan existrerar
function kollainlogg(){
'use strict';
	if(localStorage.LoggedIn == 'true'){
		//hamta();
		loadtvatta('http://www.sgsstudentbostader.se/ext_gw.aspx?module=wwwash&lang=se#lblPanelName');
		console.log('Det verkar finnas en användare');
		console.log("Detta är användaren" + localStorage.LoggedIn);
	}
	else{
		inputfields();
		console.log('Detta är användaren i else ' + localStorage.LoggedIn);
		var testarstring = 'http://tvatta.sgsstudentbostader.se/wwwashcommand.aspx?command=cancel&PanelId=592&TypeId=5129&GroupId=5444&Date=2014-02-14&IntervalId=0&NextPagyye='
		if(String(testarstring).match('wwwashcalendar.aspx') != null || String(testarstring).match('NextPage') != null){
			console.log('Den är skild från null');
		}
	}
}

var historik = new Array();
//Hämtar inloggningsformet
function getFormData(){
localStorage.anv=document.getElementById("username").value;
localStorage.pass=document.getElementById("password").value;
console.log("Den skickar inloggningen");
hamta();
//byggnad('tvatta/byggnad.html');
//loadtvatta('tvatta/tvatta2.html');
}

//hämta marknad
function hamta() {
	$.getJSON("http://marknad.sgsstudentbostader.se/API/Service/AuthorizationServiceHandler.ashx?&syndicateNo=1&syndicateObjectMainGroupNo=1&username=" +localStorage.anv+ "&password=" +localStorage.pass+ "&Method=APILoginSGS&callback=?",
	  function(result) {
		console.log(result);
		$.each(result, function (newsItem,news)
		{
		localStorage[newsItem]=news
			//$("#newsfeed").append(newsItem + "=" + news + "
");
			console.log(newsItem + " = " + localStorage[newsItem])

		});

		if(localStorage.ReturnCode == 'NOMATCH'){
			inloggningfel();
		}
		else{

	  loggain()
	  }
	  });

};

//logga in
function loggain(){
var str1="_=1366895108402&customer_name=" + localStorage.SGS_CustomerName + "&customerid=" + localStorage.UserName + "&isresident=" + localStorage.SGS_LivesAtSgs + "&loggedin=" + localStorage.LoggedIn + "&token=" + localStorage.SecurityTokenId + "&tvattstuga=" + localStorage.SGS_Laundry;
localStorage.urlen="http://www.sgsstudentbostader.se/Assets/Handlers/Momentum.ashx?" + str1;
console.log("Logga in delen, nuvarande sgs.se URL  " + localStorage.urlen);
console.log("Användare sparad från form " + localStorage.anv);
console.log("Logga in delen " + localStorage.UserName);
hiddenbrowser();
}

//alt med hidden InAppBrowser
function hiddenbrowser(){
	console.log("Går in i funktionen hiddenbrowser");
	 var ref = window.open(localStorage.urlen, '_blank', 'hidden=yes');
 //Denna skall in i loadstop funktionen.
	 console.log(ref);
	 	ref.addEventListener('loadstart', function(event) {
	 		console.log('Inloggning hidden loadstart');
		 	show();
	 	})
		ref.addEventListener('loadstop', function(event) {
			 //alert('background window loaded');
			 byggnad('http://www.sgsstudentbostader.se/ext_gw.aspx?module=wwwash&lang=se#lblPanelName');
			 console.log("Inloggning hidden loadstop");

})

}

function loggaut(){
	var loggautmomentum = window.open('http://www.sgsstudentbostader.se/Assets/Handlers/MomentumLogout.ashx?_=1390854127881', '_blank', 'hidden=yes');
	var loggautmarknad = window.open('http://marknad.sgsstudentbostader.se/API/Service/AuthorizationServiceHandler.ashx?&Method=APILogout&callback=jsonp1390854117395', '_blank', 'hidden=yes');
	console.log("Loggar ut momentum " + loggautmomentum);
	console.log("Loggar ut marknad " + loggautmarknad);

	loggautmomentum.addEventListener('loadstop', function(momentumevent){
		console.log('Den har laddat klart logga ut momentum');
		console.log(momentumevent);
	})

	loggautmarknad.addEventListener('loadstop', function(marknadevent){
		console.log('Den har laddat klart logga ut marknad');
		console.log(marknadevent);
	})
	console.log('Sätter anv och pass till null');
	localStorage.LoggedIn = 'false';
	console.log(localStorage.LoggedIn);
	navigator.app.exitApp() ;
}

<commit_msg>öppnat hamta()<commit_after>//Kolla om inlogg redan existrerar
function kollainlogg(){
'use strict';
	if(localStorage.LoggedIn == 'true'){
		hamta();
		//loadtvatta('http://www.sgsstudentbostader.se/ext_gw.aspx?module=wwwash&lang=se#lblPanelName');
		console.log('Det verkar finnas en användare');
		console.log("Detta är användaren" + localStorage.LoggedIn);
	}
	else{
		inputfields();
		console.log('Detta är användaren i else ' + localStorage.LoggedIn);
		var testarstring = 'http://tvatta.sgsstudentbostader.se/wwwashcommand.aspx?command=cancel&PanelId=592&TypeId=5129&GroupId=5444&Date=2014-02-14&IntervalId=0&NextPagyye='
		if(String(testarstring).match('wwwashcalendar.aspx') != null || String(testarstring).match('NextPage') != null){
			console.log('Den är skild från null');
		}
	}
}

var historik = new Array();
//Hämtar inloggningsformet
function getFormData(){
localStorage.anv=document.getElementById("username").value;
localStorage.pass=document.getElementById("password").value;
console.log("Den skickar inloggningen");
hamta();
//byggnad('tvatta/byggnad.html');
//loadtvatta('tvatta/tvatta2.html');
}

//hämta marknad
function hamta() {
	$.getJSON("http://marknad.sgsstudentbostader.se/API/Service/AuthorizationServiceHandler.ashx?&syndicateNo=1&syndicateObjectMainGroupNo=1&username=" +localStorage.anv+ "&password=" +localStorage.pass+ "&Method=APILoginSGS&callback=?",
	  function(result) {
		console.log(result);
		$.each(result, function (newsItem,news)
		{
		localStorage[newsItem]=news
			//$("#newsfeed").append(newsItem + "=" + news + "
");
			console.log(newsItem + " = " + localStorage[newsItem])

		});

		if(localStorage.ReturnCode == 'NOMATCH'){
			inloggningfel();
		}
		else{

	  loggain()
	  }
	  });

};

//logga in
function loggain(){
var str1="_=1366895108402&customer_name=" + localStorage.SGS_CustomerName + "&customerid=" + localStorage.UserName + "&isresident=" + localStorage.SGS_LivesAtSgs + "&loggedin=" + localStorage.LoggedIn + "&token=" + localStorage.SecurityTokenId + "&tvattstuga=" + localStorage.SGS_Laundry;
localStorage.urlen="http://www.sgsstudentbostader.se/Assets/Handlers/Momentum.ashx?" + str1;
console.log("Logga in delen, nuvarande sgs.se URL  " + localStorage.urlen);
console.log("Användare sparad från form " + localStorage.anv);
console.log("Logga in delen " + localStorage.UserName);
hiddenbrowser();
}

//alt med hidden InAppBrowser
function hiddenbrowser(){
	console.log("Går in i funktionen hiddenbrowser");
	 var ref = window.open(localStorage.urlen, '_blank', 'hidden=yes');
 //Denna skall in i loadstop funktionen.
	 console.log(ref);
	 	ref.addEventListener('loadstart', function(event) {
	 		console.log('Inloggning hidden loadstart');
		 	show();
	 	})
		ref.addEventListener('loadstop', function(event) {
			 //alert('background window loaded');
			 byggnad('http://www.sgsstudentbostader.se/ext_gw.aspx?module=wwwash&lang=se#lblPanelName');
			 console.log("Inloggning hidden loadstop");

})

}

function loggaut(){
	var loggautmomentum = window.open('http://www.sgsstudentbostader.se/Assets/Handlers/MomentumLogout.ashx?_=1390854127881', '_blank', 'hidden=yes');
	var loggautmarknad = window.open('http://marknad.sgsstudentbostader.se/API/Service/AuthorizationServiceHandler.ashx?&Method=APILogout&callback=jsonp1390854117395', '_blank', 'hidden=yes');
	console.log("Loggar ut momentum " + loggautmomentum);
	console.log("Loggar ut marknad " + loggautmarknad);

	loggautmomentum.addEventListener('loadstop', function(momentumevent){
		console.log('Den har laddat klart logga ut momentum');
		console.log(momentumevent);
	})

	loggautmarknad.addEventListener('loadstop', function(marknadevent){
		console.log('Den har laddat klart logga ut marknad');
		console.log(marknadevent);
	})
	console.log('Sätter anv och pass till null');
	localStorage.LoggedIn = 'false';
	console.log(localStorage.LoggedIn);
	navigator.app.exitApp() ;
}

<|endoftext|>


<commit_before>/*
*/

var map = null;
var showWelcome = false;

// -----------------------------------------------------------------------------
/*
Spielplatzbestand Berlin
Karte, 1:1.000
nutzIII.pdf
http://fbinter.stadt-berlin.de/fb/gisbroker.do;jsessionid=8327EAA7A1442FD8EA33B1D195F90868?cmd=navigationShowResult&mid=K.gris_spielplatz%40senstadt

Spielplatzversorgung - öffentlich
Karte, 1:5.000
Nutzungsbedingungen noch nicht abschließend formuliert
http://fbinter.stadt-berlin.de/fb/gisbroker.do;jsessionid=8327EAA7A1442FD8EA33B1D195F90868?cmd=navigationShowResult&mid=K.spielver_oeff%40senstadt

Spielplatzversorgung - öffentlich und privat
Karte, 1:5.000
Nutzungsbedingungen noch nicht abschließend formuliert
http://fbinter.stadt-berlin.de/fb/gisbroker.do;jsessionid=8327EAA7A1442FD8EA33B1D195F90868?cmd=navigationShowResult&mid=K.spielver_oeffpriv%40senstadt

Spielplatzbestand Berlin
Sachdaten, 1845 Daten
nutzIII.pdf
http://fbinter.stadt-berlin.de/fb/gisbroker.do;jsessionid=8327EAA7A1442FD8EA33B1D195F90868?cmd=navigationShowResult&mid=S.s_spielplatz%40senstadt
http://fbinter.stadt-berlin.de/fb/wfs/geometry/senstadt/re_spielplatz?REQUEST=GetCapabilities&SERVICE=WFS&VERSION=1.1.0

Spielplatzversorgung - öffentlich / PLR
Sachdaten, 438 Daten
Nutzungsbedingungen noch nicht abschließend formuliert
http://fbinter.stadt-berlin.de/fb/gisbroker.do;jsessionid=8327EAA7A1442FD8EA33B1D195F90868?cmd=navigationShowResult&mid=S.s_spielver_oe_plr%40senstadt

Spielplatzversorgung - öffentlich / VE
Sachdaten, 1354 Daten
Nutzungsbedingungen noch nicht abschließend formuliert
http://fbinter.stadt-berlin.de/fb/gisbroker.do;jsessionid=8327EAA7A1442FD8EA33B1D195F90868?cmd=navigationShowResult&mid=S.s_spielver_oe_ve%40senstadt

Spielplatzversorgung - öffentlich und privat / PLR
Sachdaten, 438 Daten
Nutzungsbedingungen noch nicht abschließend formuliert
http://fbinter.stadt-berlin.de/fb/gisbroker.do;jsessionid=8327EAA7A1442FD8EA33B1D195F90868?cmd=navigationShowResult&mid=S.s_spielver_oepr_plr%40senstadt

Spielplatzversorgung - öffentlich und privat / VE
Sachdaten, 1354 Daten
Nutzungsbedingungen noch nicht abschließend formuliert
http://fbinter.stadt-berlin.de/fb/gisbroker.do;jsessionid=8327EAA7A1442FD8EA33B1D195F90868?cmd=navigationShowResult&mid=S.s_spielver_oepr_ve%40senstadt

Ärzte
http://daten.berlin.de/datensaetze/%C3%A4rzte-im-bezirk-lichtenberg
http://blog.zeit.de/open-data/2014/04/07/aerzte-berlin-ifg-open-data/
https://docs.google.com/spreadsheet/ccc?key=0AuAvveqPUgQ8dFZsbk5OeDUzUkIyUnRvN2pVT0paY0E&usp=sharing#gid=6

*/
// -----------------------------------------------------------------------------

function initNokiaMap( elementName, lat, lng, zoom)
{
	// test key for
	// url: http://tursics.de/sample/lichtenberg
	// until: 2014-06-27
	nokia.Settings.set( 'app_id', 'R69qfu8j1eZyNk50CekA');
	nokia.Settings.set( 'app_code', '6GwB7JeP2QyKOEUy911pZw');

	map = new nokia.maps.map.Display(
		document.getElementById( elementName), {
			components: [
				new nokia.maps.map.component.Behavior(),
				new nokia.maps.map.component.ZoomBar(),
//				new nokia.maps.map.component.TypeSelector(),
				// ScaleBar Overview ZoomRectangle Positioning ContextMenu InfoBubbles PublicTransport Traffic
			],
			zoomLevel: zoom,
			center: [lat, lng],
			baseMapType: nokia.maps.map.Display.NORMAL // NORMAL NORMAL_COMMUNITY SATELLITE SATELLITE_COMMUNITY  SMARTMAP SMART_PT TERRAIN TRAFFIC
	});
//	map.removeComponent( map.getComponentById( "zoom.MouseWheel"));
}

// -----------------------------------------------------------------------------

$( document).on( "pagecreate", "#pageMap", function()
{
	// center the city hall
	initNokiaMap( 'mapContainer', 52.515807, 13.479470, 16);

	$( '#displayBaby').on( 'click', function( e) {
		setAge( 0);
	});
	$( '#displayChild').on( 'click', function( e) {
		setAge( 6);
	});
	$( '#displayPregnant').on( 'click', function( e) {
		setAge( 18);
	});
	$( '#displayAdult').on( 'click', function( e) {
		setAge( 30);
	});
	$( '#displaySenior').on( 'click', function( e) {
		setAge( 65);
	});

	$( '#displayNormal').on( 'click', function( e) {
		map.setBaseMapType( map.NORMAL, 'default');
	});
	$( '#displaySatelite').on( 'click', function( e) {
		map.setBaseMapType( map.SATELLITE, 'default');
	});
	$( '#displayCar').on( 'click', function( e) {
		map.setBaseMapType( map.TRAFFIC, 'default');
	});
	$( '#displayBus').on( 'click', function( e) {
		map.setBaseMapType( map.SMART_PT, 'default');
	});

	initDragnDrop();

//	var marker1 = new nokia.maps.map.StandardMarker([52.515807, 13.479470], {
//		text: "Hi"
//	});
//	map.objects.add( marker1);

//	var marker2 = new nokia.maps.map.Marker(
//		new nokia.maps.geo.Coordinate(52.515, 13.481), {
//			title: "marker",
//			visibility: true,
//			icon: "images/pin_restaurant.png",
//			anchor: new nokia.maps.util.Point(16, 36)
//		}
//	);
//	map.objects.add( marker2);

/*	map.objects.add( new nokia.maps.map.Circle(
		[52.51, 13.49],
		500,
		{
			pen: {
				strokeColor: "#C22A",
				lineWidth: 2
			},
			brush: {
				color: "#0FF6"
			}
		}
	));*/

	setAge( 30);

//	showWelcome = true;
});

// -----------------------------------------------------------------------------

$( document).on( "pageshow", "#pageMap", function()
{
	if( showWelcome) {
		showWelcome = false;
		$( '#welcomeClose').on( 'click', function( e) {
			$( '#popupWelcome').popup( 'close');
		});
		$( '#popupWelcome').popup( 'open');
	}
});

// -----------------------------------------------------------------------------

/*function sample1()
{
	setTimeout( function () {
		function onTransitionFinished1() {
			map.removeListener( "transitionend", onTransitionFinished1);

			sample2();
		}

		map.addListener( "transitionend", onTransitionFinished1);
		map.setZoomLevel( 15, 'default');
	}, 1000);
}*/

// -----------------------------------------------------------------------------

/*function sample2()
{
	setTimeout( function () {
		function onTransitionFinished2() {
			map.removeListener( "basemapchangeend", onTransitionFinished2);

			sample3();
		}

		map.addListener( "basemapchangeend", onTransitionFinished2);
		map.setBaseMapType( map.SMART_PT, 'default');
	}, 1000);
}*/

// -----------------------------------------------------------------------------

/*function sample3()
{
	setTimeout( function () {
		function onTransitionFinished3() {
			map.removeListener( "basemapchangeend", onTransitionFinished3);

			sample4();
		}

		map.addListener( "basemapchangeend", onTransitionFinished3);
		map.setBaseMapType( map.SATELLITE, 'default', {
			latitude: 52.515,
			longitude: 13.481,
			zoom: 16
		});
	}, 1000);
}*/

// -----------------------------------------------------------------------------

/*function sample4()
{
	// http://developer.here.com/javascript-apis/documentation/maps/topics/overlays.html
	// http://heremaps.github.io/examples/explorer.html#find-user-address
}*/

// -----------------------------------------------------------------------------

/*function geocode( term)
{
	nokia.places.search.manager.geoCode({
		searchTerm: term,
		onComplete: geocodeResults
	});
}*/

// -----------------------------------------------------------------------------

/*var geocodeResultSet;
var geocodeResults = function( data, requestStatus, requestId)
{
	var i, len, locations, marker;

	if( requestStatus == "OK") {
		locations = data.results ? data.results.items : [data.location];
		if( locations.length > 0) {
			if( geocodeResultSet) {
				map.objects.remove( geocodeResultSet);
			}

			geocodeResultSet = new nokia.maps.map.Container();
			for( i = 0, len = locations.length; i < len; ++i) {
				marker = new nokia.maps.map.StandardMarker( locations[i].position, { text: i+1 });
				geocodeResultSet.objects.add( marker);
			}

			map.objects.add( geocodeResultSet);
//			map.zoomTo( geocodeResultSet.getBoundingBox(), false);
		} else {
			alert( "Your search produced no results!");
		}
	} else {
		alert( "The search request failed");
	}
};*/

// -----------------------------------------------------------------------------

function setAge( age)
{
	$( '#displayBaby').prop( 'checked', false);
	$( '#displayChild').prop( 'checked', false);
	$( '#displayPregnant').prop( 'checked', false);
	$( '#displayAdult').prop( 'checked', false);
	$( '#displaySenior').prop( 'checked', false);

	var str = '';
	str += '
';
	if( age < 6) {
		$( '#displayBaby').prop( 'checked', true).checkboxradio( 'refresh');
		str += 'Angebote für Babys und Kleinkinder
';
	} else if( age < 18) {
		$( '#displayChild').prop( 'checked', true).checkboxradio( 'refresh');
		str += 'Angebote für Kinder und Teenies
';
	} else if( age < 30) {
		$( '#displayPregnant').prop( 'checked', true).checkboxradio( 'refresh');
		str += 'Angebote für die Familienplanung
';
	} else if( age < 65) {
		$( '#displayAdult').prop( 'checked', true).checkboxradio( 'refresh');
		str += 'Angebote für Familien
';
	} else {
		$( '#displaySenior').prop( 'checked', true).checkboxradio( 'refresh');
		str += 'Angebote für ältere Bürger
';
	}
	$( '#displayBaby').checkboxradio( 'refresh');
	$( '#displayChild').checkboxradio( 'refresh');
	$( '#displayPregnant').checkboxradio( 'refresh');
	$( '#displayAdult').checkboxradio( 'refresh');
	$( '#displaySenior').checkboxradio( 'refresh');

	str += '
';
	str += '
';

	var ageStr = 'age' + age;
	for( var i = 0; i < dataVec.length; ++i) {
		if( dataVec[i][ageStr].length > 0) {
			str += '
 ' + dataVec[i][ageStr] + '
';
		}
	}

	str += '
';

	$( '#mapSelectInfo').html( str);
	$( '#ageList').listview();
}

// -----------------------------------------------------------------------------
//	map.addListener( "basemapchangestart", function () {});
//	map.addListener( "basemapchangeend", function () {});
// -----------------------------------------------------------------------------
<commit_msg>load and show data sets<commit_after>/*
*/

var map = null;
var showWelcome = false;

// -----------------------------------------------------------------------------
/*
Spielplatzbestand Berlin
Karte, 1:1.000
nutzIII.pdf
http://fbinter.stadt-berlin.de/fb/gisbroker.do;jsessionid=8327EAA7A1442FD8EA33B1D195F90868?cmd=navigationShowResult&mid=K.gris_spielplatz%40senstadt

Spielplatzversorgung - öffentlich
Karte, 1:5.000
Nutzungsbedingungen noch nicht abschließend formuliert
http://fbinter.stadt-berlin.de/fb/gisbroker.do;jsessionid=8327EAA7A1442FD8EA33B1D195F90868?cmd=navigationShowResult&mid=K.spielver_oeff%40senstadt

Spielplatzversorgung - öffentlich und privat
Karte, 1:5.000
Nutzungsbedingungen noch nicht abschließend formuliert
http://fbinter.stadt-berlin.de/fb/gisbroker.do;jsessionid=8327EAA7A1442FD8EA33B1D195F90868?cmd=navigationShowResult&mid=K.spielver_oeffpriv%40senstadt

Spielplatzbestand Berlin
Sachdaten, 1845 Daten
nutzIII.pdf
http://fbinter.stadt-berlin.de/fb/gisbroker.do;jsessionid=8327EAA7A1442FD8EA33B1D195F90868?cmd=navigationShowResult&mid=S.s_spielplatz%40senstadt
http://fbinter.stadt-berlin.de/fb/wfs/geometry/senstadt/re_spielplatz?REQUEST=GetCapabilities&SERVICE=WFS&VERSION=1.1.0

Spielplatzversorgung - öffentlich / PLR
Sachdaten, 438 Daten
Nutzungsbedingungen noch nicht abschließend formuliert
http://fbinter.stadt-berlin.de/fb/gisbroker.do;jsessionid=8327EAA7A1442FD8EA33B1D195F90868?cmd=navigationShowResult&mid=S.s_spielver_oe_plr%40senstadt

Spielplatzversorgung - öffentlich / VE
Sachdaten, 1354 Daten
Nutzungsbedingungen noch nicht abschließend formuliert
http://fbinter.stadt-berlin.de/fb/gisbroker.do;jsessionid=8327EAA7A1442FD8EA33B1D195F90868?cmd=navigationShowResult&mid=S.s_spielver_oe_ve%40senstadt

Spielplatzversorgung - öffentlich und privat / PLR
Sachdaten, 438 Daten
Nutzungsbedingungen noch nicht abschließend formuliert
http://fbinter.stadt-berlin.de/fb/gisbroker.do;jsessionid=8327EAA7A1442FD8EA33B1D195F90868?cmd=navigationShowResult&mid=S.s_spielver_oepr_plr%40senstadt

Spielplatzversorgung - öffentlich und privat / VE
Sachdaten, 1354 Daten
Nutzungsbedingungen noch nicht abschließend formuliert
http://fbinter.stadt-berlin.de/fb/gisbroker.do;jsessionid=8327EAA7A1442FD8EA33B1D195F90868?cmd=navigationShowResult&mid=S.s_spielver_oepr_ve%40senstadt

Ärzte
http://daten.berlin.de/datensaetze/%C3%A4rzte-im-bezirk-lichtenberg
http://blog.zeit.de/open-data/2014/04/07/aerzte-berlin-ifg-open-data/
https://docs.google.com/spreadsheet/ccc?key=0AuAvveqPUgQ8dFZsbk5OeDUzUkIyUnRvN2pVT0paY0E&usp=sharing#gid=6

*/
// -----------------------------------------------------------------------------

function initNokiaMap( elementName, lat, lng, zoom)
{
	// test key for
	// url: http://tursics.de/sample/lichtenberg
	// until: 2014-06-27
	nokia.Settings.set( 'app_id', 'R69qfu8j1eZyNk50CekA');
	nokia.Settings.set( 'app_code', '6GwB7JeP2QyKOEUy911pZw');

	map = new nokia.maps.map.Display(
		document.getElementById( elementName), {
			components: [
				new nokia.maps.map.component.Behavior(),
				new nokia.maps.map.component.ZoomBar(),
//				new nokia.maps.map.component.TypeSelector(),
				// ScaleBar Overview ZoomRectangle Positioning ContextMenu InfoBubbles PublicTransport Traffic
			],
			zoomLevel: zoom,
			center: [lat, lng],
			baseMapType: nokia.maps.map.Display.NORMAL // NORMAL NORMAL_COMMUNITY SATELLITE SATELLITE_COMMUNITY  SMARTMAP SMART_PT TERRAIN TRAFFIC
	});
//	map.removeComponent( map.getComponentById( "zoom.MouseWheel"));
}

// -----------------------------------------------------------------------------

$( document).on( "pagecreate", "#pageMap", function()
{
	// center the city hall
	initNokiaMap( 'mapContainer', 52.515807, 13.479470, 16);

	$( '#displayBaby').on( 'click', function( e) {
		setAge( 0);
	});
	$( '#displayChild').on( 'click', function( e) {
		setAge( 6);
	});
	$( '#displayPregnant').on( 'click', function( e) {
		setAge( 18);
	});
	$( '#displayAdult').on( 'click', function( e) {
		setAge( 30);
	});
	$( '#displaySenior').on( 'click', function( e) {
		setAge( 65);
	});

	$( '#displayNormal').on( 'click', function( e) {
		map.setBaseMapType( map.NORMAL, 'default');
	});
	$( '#displaySatelite').on( 'click', function( e) {
		map.setBaseMapType( map.SATELLITE, 'default');
	});
	$( '#displayCar').on( 'click', function( e) {
		map.setBaseMapType( map.TRAFFIC, 'default');
	});
	$( '#displayBus').on( 'click', function( e) {
		map.setBaseMapType( map.SMART_PT, 'default');
	});

	initDragnDrop();

//	var marker1 = new nokia.maps.map.StandardMarker([52.515807, 13.479470], {
//		text: "Hi"
//	});
//	map.objects.add( marker1);

//	var marker2 = new nokia.maps.map.Marker(
//		new nokia.maps.geo.Coordinate(52.515, 13.481), {
//			title: "marker",
//			visibility: true,
//			icon: "images/pin_restaurant.png",
//			anchor: new nokia.maps.util.Point(16, 36)
//		}
//	);
//	map.objects.add( marker2);

/*	map.objects.add( new nokia.maps.map.Circle(
		[52.51, 13.49],
		500,
		{
			pen: {
				strokeColor: "#C22A",
				lineWidth: 2
			},
			brush: {
				color: "#0FF6"
			}
		}
	));*/

	setAge( 30);

//	showWelcome = true;
});

// -----------------------------------------------------------------------------

$( document).on( "pageshow", "#pageMap", function()
{
	if( showWelcome) {
		showWelcome = false;
		$( '#welcomeClose').on( 'click', function( e) {
			$( '#popupWelcome').popup( 'close');
		});
		$( '#popupWelcome').popup( 'open');
	}
});

// -----------------------------------------------------------------------------

/*function sample1()
{
	setTimeout( function () {
		function onTransitionFinished1() {
			map.removeListener( "transitionend", onTransitionFinished1);

			sample2();
		}

		map.addListener( "transitionend", onTransitionFinished1);
		map.setZoomLevel( 15, 'default');
	}, 1000);
}*/

// -----------------------------------------------------------------------------

/*function sample2()
{
	setTimeout( function () {
		function onTransitionFinished2() {
			map.removeListener( "basemapchangeend", onTransitionFinished2);

			sample3();
		}

		map.addListener( "basemapchangeend", onTransitionFinished2);
		map.setBaseMapType( map.SMART_PT, 'default');
	}, 1000);
}*/

// -----------------------------------------------------------------------------

/*function sample3()
{
	setTimeout( function () {
		function onTransitionFinished3() {
			map.removeListener( "basemapchangeend", onTransitionFinished3);

			sample4();
		}

		map.addListener( "basemapchangeend", onTransitionFinished3);
		map.setBaseMapType( map.SATELLITE, 'default', {
			latitude: 52.515,
			longitude: 13.481,
			zoom: 16
		});
	}, 1000);
}*/

// -----------------------------------------------------------------------------

/*function sample4()
{
	// http://developer.here.com/javascript-apis/documentation/maps/topics/overlays.html
	// http://heremaps.github.io/examples/explorer.html#find-user-address
}*/

// -----------------------------------------------------------------------------

/*function geocode( term)
{
	nokia.places.search.manager.geoCode({
		searchTerm: term,
		onComplete: geocodeResults
	});
}*/

// -----------------------------------------------------------------------------

/*var geocodeResultSet;
var geocodeResults = function( data, requestStatus, requestId)
{
	var i, len, locations, marker;

	if( requestStatus == "OK") {
		locations = data.results ? data.results.items : [data.location];
		if( locations.length > 0) {
			if( geocodeResultSet) {
				map.objects.remove( geocodeResultSet);
			}

			geocodeResultSet = new nokia.maps.map.Container();
			for( i = 0, len = locations.length; i < len; ++i) {
				marker = new nokia.maps.map.StandardMarker( locations[i].position, { text: i+1 });
				geocodeResultSet.objects.add( marker);
			}

			map.objects.add( geocodeResultSet);
//			map.zoomTo( geocodeResultSet.getBoundingBox(), false);
		} else {
			alert( "Your search produced no results!");
		}
	} else {
		alert( "The search request failed");
	}
};*/

// -----------------------------------------------------------------------------

function setAge( age)
{
	$( '#displayBaby').prop( 'checked', false);
	$( '#displayChild').prop( 'checked', false);
	$( '#displayPregnant').prop( 'checked', false);
	$( '#displayAdult').prop( 'checked', false);
	$( '#displaySenior').prop( 'checked', false);

	var str = '';
	str += '
';
	if( age < 6) {
		$( '#displayBaby').prop( 'checked', true).checkboxradio( 'refresh');
		str += 'Angebote für Babys und Kleinkinder
';
	} else if( age < 18) {
		$( '#displayChild').prop( 'checked', true).checkboxradio( 'refresh');
		str += 'Angebote für Schulkinder
';
	} else if( age < 30) {
		$( '#displayPregnant').prop( 'checked', true).checkboxradio( 'refresh');
		str += 'Angebote für die Familienplanung
';
	} else if( age < 65) {
		$( '#displayAdult').prop( 'checked', true).checkboxradio( 'refresh');
		str += 'Angebote für Familien
';
	} else {
		$( '#displaySenior').prop( 'checked', true).checkboxradio( 'refresh');
		str += 'Angebote für ältere Bürger
';
	}
	$( '#displayBaby').checkboxradio( 'refresh');
	$( '#displayChild').checkboxradio( 'refresh');
	$( '#displayPregnant').checkboxradio( 'refresh');
	$( '#displayAdult').checkboxradio( 'refresh');
	$( '#displaySenior').checkboxradio( 'refresh');

	str += '
';
	str += '
';

	var ageStr = 'age' + age;
	for( var i = 0; i < dataVec.length; ++i) {
		if( dataVec[i][ageStr].length > 0) {
			str += '
 ' + dataVec[i][ageStr] + '
';
		}
	}

	str += '
';

	$( '#mapSelectInfo').html( str);
	$( '#ageList').listview();
}

// -----------------------------------------------------------------------------

var dataGeoSet = null;

function onShowData( dataId)
{
	if( dataId < dataVec.length) {
		if( dataGeoSet) {
			map.objects.remove( dataGeoSet);
		}
		dataGeoSet = new nokia.maps.map.Container();
		map.objects.add( dataGeoSet);

		$.getJSON( 'data/' + dataVec[ dataId].url, function( data) {
			try {
				$.each( data, function( key, val) {
					if((typeof val.lat != 'undefined') && (typeof val.lng != 'undefined')) {
						marker = new nokia.maps.map.StandardMarker([parseFloat( val.lat), parseFloat( val.lng)]);
						dataGeoSet.objects.add( marker);
					}
				});

				if( dataGeoSet.objects.getLength() > 0) {
					map.zoomTo( dataGeoSet.getBoundingBox(), false);
				}
			} catch( e) {
				alert( e);
			}
		});
	}
}

// -----------------------------------------------------------------------------
//	map.addListener( "basemapchangestart", function () {});
//	map.addListener( "basemapchangeend", function () {});
// -----------------------------------------------------------------------------
<|endoftext|>


<commit_before>/**
 * WXIDE r4 program logic
 * by Hongchan Choi (hoch, hongchan@ccrma.stanford.edu)
 */

(function() {
  /**
   * variables
   */
  var editor, interval;
  var editorToggleState = true;
  var editorAutoUpdateState = true;
  var errorLines = [];
  var editorDOM = $('#d-codemirror');
  var statusTextDOM = $('#s-status-text');
  var previewId = document.getElementById('d-preview');

  /**
   * example list
   */
  var exs = [
    {
      id: "a-menu-ex-0",
      text: "Hello WAAX!",
      url: "../examples/hello-waax.html"
    },
    {
      id: "a-menu-ex-1",
      text: "WAAX does THX",
      url: "../examples/waax-does-thx.html"
    },
    {
      id: "a-menu-ex-2",
      text: "Acidic Bassline",
      url: "../examples/acidic-bassline.html"
    },
    {
      id: "a-menu-ex-3",
      text: "Simple Drum Sampler",
      url: "../examples/simple-drum-sampler.html"
    },
    {
      id: "a-menu-ex-4",
      text: "Take the I train",
      url: "../examples/take-i-train.html"
    },
    {
      id: "a-menu-ex-5",
      text: "Custom Visualizer",
      url: "../examples/visualizer.html"
    },
    {
      id: "a-menu-ex-6",
      text: "WAAX GUI (pilot)",
      url: "../examples/ui-manager.html"
    },
    {
      id: "a-menu-ex-7",
      text: "Timeline",
      url: "../examples/timeline.html"
    },
    { id: "divider" },
    {
      id: "a-menu-ex-wx7",
      text: "WX7 FM Synth",
      url: null
    },
    {
      id: "a-menu-ex-wpc60",
      text: "WPC-60",
      url: null
    },
    {
      id: "a-menu-ex-winimoog",
      text: "Winimoog",
      url: null
    },
    {
      id: "a-menu-ex-wb303",
      text: "WB-303 Bassline",
      url: null
    },
    { id: "divider" },
    {
      id: "a-menu-ex-devmode",
      text: "DevMode",
      url: "../examples/devmode.html",
      active: true
    }
  ];

  /**
   * functions
   */
  // editor: resizeCodeArea
  function resizeCodeArea() {
    var h = window.innerHeight - 76;
    editorDOM.height(h);
  }
  // editor: toggleEditor
  function toggleEditor(bool) {
    editorToggleState = (bool === undefined) ? !editorToggleState : bool;
    if(editorToggleState) {
      $("#d-editor").show();
      $("#d-showEditor").hide();
    } else {
      $("#d-editor").hide();
      $("#d-showEditor").show();
    }
  }
  // import: handleFileImport
  // TODO: check file type (text/html)
  function handleFileImport(evt) {
    var file = evt.target.files[0];
    var reader = new FileReader();
    reader.onload = function(e) {
      editor.setValue(e.target.result);
    };
    reader.onerror = function(e) {
      console.log("error", e);
    };
    reader.readAsText(file);
    setStatusText("File imported.");
  }
  // export: createDownload
  function createDownload(fileName) {
    var userBuffer = editor.getValue() || "hello waax!";
    var exportFileName = fileName || "your-code.html";
    exportFileName += ".wx";
    var blob = new Blob([userBuffer], {type: "text/html"});
    var url = URL.createObjectURL(blob);
    $('#a-modal-save').attr({
      'href': url,
      'download': exportFileName
    });
    setStatusText("File exported.");
  }
  // export: destroyDownlaod
  function destroyDownload() {
    $('#a-modal-save').removeAttr('href');
    $('#a-modal-save').removeAttr('download');
  }
  // cache: loadFromCache
  function loadFromCache() {
    var data = null;
    if (localStorage.wxide !== undefined) {
      data = JSON.parse(localStorage.wxide);
    }
    if (data !== null) {
      editor.setValue(data);
      setStatusText("Loaded from cache.");
      return true;
    } else {
      setStatusText("Invalid content or empty cache.");
      return false;
    }
  }
  // cache: resetCache
  function resetCache() {
    localStorage.wxide = null;
    editor.setValue("");
    setStatusText("Cache/buffer cleared.");
  }
  // cache: saveToCache
  function saveToCache() {
    localStorage.wxide = JSON.stringify(editor.getValue());
    setStatusText("Buffer saved to cache.");
  }
  function injectCode(codes) {
    var iframe = document.createElement('iframe');
    iframe.style.width = '100%';
    iframe.style.height = '100%';
    iframe.style.border = '0';
    iframe.id = 'wxi-injection';
    previewId.appendChild(iframe);
    var content = iframe.contentDocument || iframe.contentWindow.document;
    content.open();
    content.write(codes);
    content.close();
  }
  // injection: update
  function update() {
    setStatusText("Updating...");
    killSpawned();
    var codes = editor.getValue();
    /* use esprima for code validation (disabled for performance reason)
    if (validate(codes)) {
      injectCode(codes);
    }*/
    injectCode(codes);
    setStatusText("Updated.");
  }
  // injection: validate through esprima
  function validate(value) {
    while (errorLines.length > 0) {
      editor.removeLineClass(errorLines.shift(), null, 'errorLine');
    }
    var string = '';
    var lines = value.split('\n');
    var lineCurrent = 0, lineTotal = lines.length;
    while (lineCurrent < lineTotal && lines[lineCurrent].indexOf('


<commit_before>'use strict';
import * as IDE from 'ide';
import Utils from '../utils';
import * as defaultVSCode from '../common/keybindings/vscode.json';

export default class VSCode extends IDE {

    constructor(platform) {
        super();
        this.name = "vscode";
        this.utils = new Utils(platform);
    }

    saveKeyBindings(userprefs) {
        // fs.save(createKeyBinding());
    }

    createKeyBindings(userprefs) {

        var json = userprefs.map((keybinding) => {
            keybinding['key'] = keybinding.keys;
            keybinding['command'] = keybinding.action;
            keybinding['when'] = defaultKeyBindings.;
        });

    }

    loadDefaultKeyBindings() {
        return defaultVSCode;
    }

};
<commit_msg>Almost finished vscode.js<commit_after>'use strict';
import * as IDE from 'ide';
import Utils from '../utils';
import * as defaultVSCode from '../common/keybindings/vscode.json';

export default class VSCode extends IDE {

    constructor(platform) {
        super();
        this.name = "vscode";
        this.utils = new Utils(platform);

        this.createVSCodeJSONPrefs = (keybindings) => {
            var defualtKeyBindings = loadDefaultKeybindings();

            return userprefs.map((keybinding) => {
                keybinding['key'] = keybinding.keys;
                keybinding['command'] = keybinding.action;
                keybinding['when'] = defualtKeyBindings.filter((defaultKeybinding) => {
                    if (defaultKeybinding.action === keybinding.action) {
                        return true;
                    }

                    return false;
                })[0].when;

                delete(keybinding.keys);
                delete(keybinding.action);
            });
        }

        this.createKeyBindings = (userprefs) => {
            var keybindings = this.utils.getNormalizedListByIDE(this.name, userprefs);
            return this.createVSCodeJSONPrefs(keybindings);
        };
    }

    saveKeyBindings(userprefs) {
        // fs.save(createKeyBinding());
    }

    loadDefaultKeybindings() {
        var defualtKeybindings = this.utils.getNormalizedListByIDE(this.name, defaultVSCode);
        return defualtKeybindings;
    }

};<|endoftext|>


<commit_before>'use strict';
var chalk = require('chalk');
var table = require('text-table');

module.exports = {
	reporter: function (result, config, options) {
		var total = result.length;
		var ret = '';
		var headers = [];
		var prevfile;

		options = options || {};

		ret += table(result.map(function (el, i) {
			var err = el.error;
			var line = [
				'',
				chalk.gray('line ' + err.line),
				chalk.gray('col ' + err.character),
				chalk.blue(err.reason)
			];

			if (el.file !== prevfile) {
				headers[i] = el.file;
			}

			if (options.verbose) {
				line.push(chalk.gray('(' + err.code + ')'));
			}

			prevfile = el.file;

			return line;
		})).split('\n').map(function (el, i) {
			return headers[i] ? '\n' + chalk.underline(headers[i]) + '\n' + el : el;
		}).join('\n') + '\n\n';

		if (total > 0) {
			ret += chalk.red.bold((process.platform !== 'win32' ? '✖ ' : '') + total + ' problem' + (total === 1 ? '' : 's'));
		} else {
			ret += chalk.green.bold((process.platform !== 'win32' ? '✔ ' : '') + 'No problems');
			ret = '\n' + ret.trim();
		}

		console.log(ret + '\n');
	}
};
<commit_msg>use stringLength method<commit_after>'use strict';
var chalk = require('chalk');
var table = require('text-table');

module.exports = {
	reporter: function (result, config, options) {
		var total = result.length;
		var ret = '';
		var headers = [];
		var prevfile;

		options = options || {};

		ret += table(result.map(function (el, i) {
			var err = el.error;
			var line = [
				'',
				chalk.gray('line ' + err.line),
				chalk.gray('col ' + err.character),
				chalk.blue(err.reason)
			];

			if (el.file !== prevfile) {
				headers[i] = el.file;
			}

			if (options.verbose) {
				line.push(chalk.gray('(' + err.code + ')'));
			}

			prevfile = el.file;

			return line;
		}), {
			stringLength: function (str) {
				return chalk.stripColor(str).length;
			}
		}).split('\n').map(function (el, i) {
			return headers[i] ? '\n' + chalk.underline(headers[i]) + '\n' + el : el;
		}).join('\n') + '\n\n';

		if (total > 0) {
			ret += chalk.red.bold((process.platform !== 'win32' ? '✖ ' : '') + total + ' problem' + (total === 1 ? '' : 's'));
		} else {
			ret += chalk.green.bold((process.platform !== 'win32' ? '✔ ' : '') + 'No problems');
			ret = '\n' + ret.trim();
		}

		console.log(ret + '\n');
	}
};
<|endoftext|>


<commit_before>/*!
 *  subpoly - a Web Audio subtractive polyphonic synthesizer
 *  (c) 2015 Benji Kay
 *  MIT License
 */

var Polysynth = function Polysynth(audioCtx, config) {

  var Synth = function Synth() {
    config = config || {};
    config.cutoff = config.cutoff || {};

    var synth = this;

    synth.audioCtx = audioCtx;
    synth.voices = [];

    for (var i = 0, ii = config.numVoices || 16; i < ii; i++) {
      synth.voices.push(new Monosynth(audioCtx, config));
    }

    synth.stereoWidth = config.stereoWidth  || 0.5; // out of 1
    synth.width(synth.stereoWidth);

    return synth;
  };

  // apply attack, decay, sustain envelope
  Synth.prototype.start = function startSynth() {
    this.voices.forEach(function(voice) {
      voice.start();
    });
  };

  // apply release envelope
  Synth.prototype.stop = function stopSynth() {
    this.voices.forEach(function(voice) {
      voice.stop();
    });
  };

  // get/set synth stereo width
  Synth.prototype.width = function width(newWidth) {
    var synth = this;
    if (synth.voices && newWidth) {
      synth.stereoWidth = newWidth;
      synth.voices.forEach(function(voice, i) {
        var spread = 1/(synth.voices.length - 1);
        var xPos = spread * i * synth.stereoWidth;
        var zPos = 1 - Math.abs(xPos);
        voice.pan.setPosition(xPos, 0, zPos);
      });
    }

    return synth.stereoWidth;
  };

  // convenience methods for changing values of all Monosynths' properties at once
  (function createSetters() {
    var monosynthProperties = ['maxGain', 'attack', 'decay', 'sustain', 'release'];
    var monosynthCutoffProperties = ['maxFrequency', 'attack', 'decay', 'sustain'];

    monosynthProperties.forEach(function(property) {
      Synth.prototype[property] = function(newValue) {
        synth.voices.forEach(function(voice) {
          voice[property] = newValue;
        });
      };
    });

    Synth.prototype.cutoff = {};
    monosynthCutoffProperties.forEach(function(property) {
      Synth.prototype.cutoff[property] = function(newValue) {
       synth.voices.forEach(function(voice) {
         voice.cutoff[property] = newValue;
       });
      };
    });

    Synth.prototype.waveform = function waveform(newWaveform) {
      synth.voices.forEach(function(voice) {
        voice.waveform(newWaveform);
      });
    };

    Synth.prototype.pitch = function pitch(newPitch) {
      synth.voices.forEach(function(voice) {
        voice.pitch(newPitch);
      });
    };
  })();

  return new Synth;
};
<commit_msg>fix cutoff setters<commit_after>/*!
 *  subpoly - a Web Audio subtractive polyphonic synthesizer
 *  (c) 2015 Benji Kay
 *  MIT License
 */

var Polysynth = function Polysynth(audioCtx, config) {
  var synth;
  var Synth = function Synth() {
    synth = this;
    synth.audioCtx = audioCtx;
    synth.voices = [];

    config = config || {};
    config.cutoff = config.cutoff || {};

    for (var i = 0, ii = config.numVoices || 16; i < ii; i++) {
      synth.voices.push(new Monosynth(audioCtx, config));
    }

    synth.stereoWidth = config.stereoWidth  || 0.5; // out of 1
    synth.width(synth.stereoWidth);

    return synth;
  };

  // apply attack, decay, sustain envelope
  Synth.prototype.start = function startSynth() {
    synth.voices.forEach(function startVoice(voice) {
      voice.start();
    });
  };

  // apply release envelope
  Synth.prototype.stop = function stopSynth() {
    synth.voices.forEach(function stopVoice(voice) {
      voice.stop();
    });
  };

  // get/set synth stereo width
  Synth.prototype.width = function width(newWidth) {
    if (synth.voices.length > 1 && newWidth) {
      synth.stereoWidth = newWidth;
      synth.voices.forEach(function panVoice(voice, i) {
        var spread = 1/(synth.voices.length - 1);
        var xPos = spread * i * synth.stereoWidth;
        var zPos = 1 - Math.abs(xPos);
        voice.pan.setPosition(xPos, 0, zPos);
      });
    }

    return synth.stereoWidth;
  };

  // convenience methods for changing values of all Monosynths' properties at once
  (function createSetters() {
    var monosynthProperties = ['maxGain', 'attack', 'decay', 'sustain', 'release'];
    var monosynthCutoffProperties = ['maxFrequency', 'attack', 'decay', 'sustain'];

    monosynthProperties.forEach(function createSetter(property) {
      Synth.prototype[property] = function setValues(newValue) {
        synth.voices.forEach(function setValue(voice) {
          voice[property] = newValue;
        });
      };
    });

    Synth.prototype.cutoff = {};
    monosynthCutoffProperties.forEach(function createSetter(property) {
      Synth.prototype.cutoff[property] = function setValues(newValue) {
        synth.voices.forEach(function setValue(voice) {
          voice.cutoff[property] = newValue;
        });
      };
    });

    Synth.prototype.waveform = function waveform(newWaveform) {
      synth.voices.forEach(function waveform(voice) {
        voice.waveform(newWaveform);
      });
    };

    Synth.prototype.pitch = function pitch(newPitch) {
      synth.voices.forEach(function pitch(voice) {
        voice.pitch(newPitch);
      });
    };
  })();

  return new Synth;
};
<|endoftext|>


<commit_before>var nodeUrl = require('url');
var request = require('request');
var serverSecrets;
try {
  // Everything that cannot be checked in but is useful server-side
  // is stored in this JSON data.
  serverSecrets = require('./secret.json');
} catch(e) { console.error('No secret data (secret.json, see server.js):', e); }

// data: {url}, JSON-serializable object.
// end: function(json), with json of the form:
//  - badges: list of objects of the form:
//    - link: target as a string URL.
//    - badge: shields image URL.
var suggest = function(data, end) {
  try {
    var url = nodeUrl.parse(data.url);
  } catch(e) { end({err:''+e}); return; }

  if (url.hostname === 'github.com') {
    github(url, end);
  }
};

var github = function(url, cb) {
  var badges = [];
  var userRepo = url.pathname.slice(1).split('/');
  var user = userRepo[0];
  var repo = userRepo[1];
  githubLicense(user, repo, function(err, badge) {
    if (err == null) {
      badges.push(badge);
    }
    cb({badges:badges});
  });
};

// user: eg, qubyte
// repo: eg, rubidium
// end: function(err, {link, badge})
var githubLicense = function(user, repo, end) {
  // Step 1: Get the repo's default branch.
  var apiUrl = 'https://api.github.com/repos/' + user + '/' + repo + '';
  // Using our OAuth App secret grants us 5000 req/hour
  // instead of the standard 60 req/hour.
  if (serverSecrets) {
    apiUrl += '?client_id=' + serverSecrets.gh_client_id
      + '&client_secret=' + serverSecrets.gh_client_secret;
  }
  var badgeData = {text:['license',''], colorscheme:'red'};
  // A special User-Agent is required:
  // http://developer.github.com/v3/#user-agent-required
  request(apiUrl, { headers: { 'User-Agent': 'Shields.io' } }, function(err, res, buffer) {
    if (err != null) { end(err); return; }
    try {
      if ((+res.headers['x-ratelimit-remaining']) === 0) { end(Error('rate limited')); return; }
      var data = JSON.parse(buffer);
      var defaultBranch = data.default_branch;
      // Step 2: Get the SHA-1 hash of the branch tip.
      var apiUrl = 'https://api.github.com/repos/' + user + '/' + repo + '/branches/' + defaultBranch;
      if (serverSecrets) {
        apiUrl += '?client_id=' + serverSecrets.gh_client_id
          + '&client_secret=' + serverSecrets.gh_client_secret;
      }
      request(apiUrl, { headers: { 'User-Agent': 'Shields.io' } }, function(err, res, buffer) {
        if (err != null) { end(err); return; }
        try {
          if ((+res.headers['x-ratelimit-remaining']) === 0) { end(Error('rate limited')); return; }
          var data = JSON.parse(buffer);
          var branchTip = data.commit.sha;
          // Step 3: Get the tree at the commit.
          var apiUrl = 'https://api.github.com/repos/' + user + '/' + repo + '/git/trees/' + branchTip;
          if (serverSecrets) {
            apiUrl += '?client_id=' + serverSecrets.gh_client_id
              + '&client_secret=' + serverSecrets.gh_client_secret;
          }
          request(apiUrl, { headers: { 'User-Agent': 'Shields.io' } }, function(err, res, buffer) {
            if (err != null) { end(err); return; }
            try {
              if ((+res.headers['x-ratelimit-remaining']) === 0) { end(Error('rate limited')); return; }
              var data = JSON.parse(buffer);
              var treeArray = data.tree;
              var licenseBlob;
              var licenseFilename;
              // Crawl each file in the root directory
              for (var i = 0; i < treeArray.length; i++) {
                if (treeArray[i].type != 'blob') {
                  continue;
                }
                if (treeArray[i].path.match(/(LICENSE|COPYING|COPYRIGHT).*/i)) {
                  licenseBlob = treeArray[i].sha;
                  licenseFilename = treeArray[i].path;
                  break;
                }
              }
              // Could not find license file
              if (!licenseBlob) { end(Error('no license file')); return; }

              // Step 4: Get the license blob.
              var apiUrl = 'https://api.github.com/repos/' + user + '/' + repo + '/git/blobs/' + licenseBlob;
              var link = 'https://raw.githubusercontent.com/' +
                [user, repo, defaultBranch, licenseFilename].join('/');

              if (serverSecrets) {
                apiUrl += '?client_id=' + serverSecrets.gh_client_id
                  + '&client_secret=' + serverSecrets.gh_client_secret;
              }
              // Get the raw blob instead of JSON
              // https://developer.github.com/v3/media/
              request(apiUrl, { headers: { 'User-Agent': 'Shields.io', 'Accept': 'appplication/vnd.github.raw' } },
              function(err, res, buffer) {
                if (err != null) { end(err); return; }
                try {
                  if ((+res.headers['x-ratelimit-remaining']) === 0) { end(Error('rate limited')); return; }
                  var license = guessLicense(buffer);
                  if (license) {
                    badgeData.text[1] = license;
                    end(null, {
                      link: link,
                      badge: shieldsBadge(badgeData),
                    });
                    return;
                  } else {
                    // Not a recognized license
                    end(Error('unknown license'));
                    return;
                  }
                } catch(e) { end(e); return; }
              });
            } catch(e) { end(e); return; }
          });
        } catch(e) { end(e); return; }
      });
    } catch(e) { end(e); return; }
  });
};

// Key phrases for common licenses
var licensePhrases = {
  'Apache 1.1': 'apache (software)? license,? (version)? 1\\.1',
  'Apache 2': 'apache (software)? license,? (version)? 2',
  'Original BSD': 'all advertising materials mentioning features or use of this software must display the following acknowledgement',
  'New BSD': 'may be used to endorse or promote products derived from this software without specific prior written permission',
  'BSD': 'redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met',
  'GPLv2': 'gnu general public license,? version 2',
  'GPLv3': 'gnu general public license,? version 3',
  'GPL': 'gnu general public license',
  'LGPLv2.0': 'gnu library general public license,? version 2',
  'LGPLv2.1': 'gnu lesser general public license,? version 2\\.1',
  'LGPLv3': 'gnu lesser general public license,? version 3',
  'LGPL': 'gnu (library|lesser) general public license',
  'MIT': '\\(?(mit|expat|x11)\\)? license|permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files',
  'MPL 1.1': 'mozilla public license,? (\\(MPL\\) )?(version |v|v\\.)?1\\.1',
  'MPL 2': 'mozilla public license,? (\\(MPL\\) )?(version |v|v\\.)?2',
  'MPL': 'mozilla public license',
  'CDDL': 'common development and distribution license',
  'Eclipse': 'eclipse public license',
  'Artistic': 'artistic license',
  'zlib': 'the origin of this software must not be misrepresented',
  'AGPLv1': 'affero general public license,? version 1',
  'AGPLv3': 'affero general public license,? version 3',
  'AGPL': 'affero general public license',
  'ISC': 'permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted',
  'CC0': 'cc0',
  'Unlicense': 'this is free and unencumbered software released into the public domain',
}
var licenseCodes = Object.keys(licensePhrases);
var spaceMetaRegex = new RegExp(' ', 'g');

// Spaces can be any whitespace
for (var i = 0; i < licenseCodes.length; i++) {
  licensePhrases[licenseCodes[i]] = licensePhrases[licenseCodes[i]].replace(spaceMetaRegex, '\\s+');
}

// Try to guess the license based on the text and return an abbreviated name (or null if not recognized).
var guessLicense = function(text) {
  var licenseRegex;
  for (var i = 0; i < licenseCodes.length; i++) {
    licenseRegex = licensePhrases[licenseCodes[i]];
    if (text.match(new RegExp(licenseRegex, 'i'))) {
      return licenseCodes[i];
    }
  }
  // Not a recognized license
  return null;
};

var shieldsBadge = function(badgeData) {
  return ('https://img.shields.io/badge/'
    + escapeField(badgeData.text[0])
    + '-' + escapeField(badgeData.text[1])
    + '-' + badgeData.colorscheme + '.svg');
};

var escapeField = function(s) {
  return encodeURIComponent(s.replace(/-/g, '--').replace(/_/g, '__'));
};

module.exports = suggest;
<commit_msg>Add suggest name information<commit_after>var nodeUrl = require('url');
var request = require('request');
var serverSecrets;
try {
  // Everything that cannot be checked in but is useful server-side
  // is stored in this JSON data.
  serverSecrets = require('./secret.json');
} catch(e) { console.error('No secret data (secret.json, see server.js):', e); }

// data: {url}, JSON-serializable object.
// end: function(json), with json of the form:
//  - badges: list of objects of the form:
//    - link: target as a string URL.
//    - badge: shields image URL.
//    - name: string
var suggest = function(data, end) {
  try {
    var url = nodeUrl.parse(data.url);
  } catch(e) { end({err:''+e}); return; }

  if (url.hostname === 'github.com') {
    github(url, end);
  }
};

var github = function(url, cb) {
  var badges = [];
  var userRepo = url.pathname.slice(1).split('/');
  var user = userRepo[0];
  var repo = userRepo[1];
  githubLicense(user, repo, function(err, badge) {
    if (err == null) {
      badges.push(badge);
    }
    cb({badges:badges});
  });
};

// user: eg, qubyte
// repo: eg, rubidium
// end: function(err, {link, badge})
var githubLicense = function(user, repo, end) {
  // Step 1: Get the repo's default branch.
  var apiUrl = 'https://api.github.com/repos/' + user + '/' + repo + '';
  // Using our OAuth App secret grants us 5000 req/hour
  // instead of the standard 60 req/hour.
  if (serverSecrets) {
    apiUrl += '?client_id=' + serverSecrets.gh_client_id
      + '&client_secret=' + serverSecrets.gh_client_secret;
  }
  var badgeData = {text:['license',''], colorscheme:'red'};
  // A special User-Agent is required:
  // http://developer.github.com/v3/#user-agent-required
  request(apiUrl, { headers: { 'User-Agent': 'Shields.io' } }, function(err, res, buffer) {
    if (err != null) { end(err); return; }
    try {
      if ((+res.headers['x-ratelimit-remaining']) === 0) { end(Error('rate limited')); return; }
      var data = JSON.parse(buffer);
      var defaultBranch = data.default_branch;
      // Step 2: Get the SHA-1 hash of the branch tip.
      var apiUrl = 'https://api.github.com/repos/' + user + '/' + repo + '/branches/' + defaultBranch;
      if (serverSecrets) {
        apiUrl += '?client_id=' + serverSecrets.gh_client_id
          + '&client_secret=' + serverSecrets.gh_client_secret;
      }
      request(apiUrl, { headers: { 'User-Agent': 'Shields.io' } }, function(err, res, buffer) {
        if (err != null) { end(err); return; }
        try {
          if ((+res.headers['x-ratelimit-remaining']) === 0) { end(Error('rate limited')); return; }
          var data = JSON.parse(buffer);
          var branchTip = data.commit.sha;
          // Step 3: Get the tree at the commit.
          var apiUrl = 'https://api.github.com/repos/' + user + '/' + repo + '/git/trees/' + branchTip;
          if (serverSecrets) {
            apiUrl += '?client_id=' + serverSecrets.gh_client_id
              + '&client_secret=' + serverSecrets.gh_client_secret;
          }
          request(apiUrl, { headers: { 'User-Agent': 'Shields.io' } }, function(err, res, buffer) {
            if (err != null) { end(err); return; }
            try {
              if ((+res.headers['x-ratelimit-remaining']) === 0) { end(Error('rate limited')); return; }
              var data = JSON.parse(buffer);
              var treeArray = data.tree;
              var licenseBlob;
              var licenseFilename;
              // Crawl each file in the root directory
              for (var i = 0; i < treeArray.length; i++) {
                if (treeArray[i].type != 'blob') {
                  continue;
                }
                if (treeArray[i].path.match(/(LICENSE|COPYING|COPYRIGHT).*/i)) {
                  licenseBlob = treeArray[i].sha;
                  licenseFilename = treeArray[i].path;
                  break;
                }
              }
              // Could not find license file
              if (!licenseBlob) { end(Error('no license file')); return; }

              // Step 4: Get the license blob.
              var apiUrl = 'https://api.github.com/repos/' + user + '/' + repo + '/git/blobs/' + licenseBlob;
              var link = 'https://raw.githubusercontent.com/' +
                [user, repo, defaultBranch, licenseFilename].join('/');

              if (serverSecrets) {
                apiUrl += '?client_id=' + serverSecrets.gh_client_id
                  + '&client_secret=' + serverSecrets.gh_client_secret;
              }
              // Get the raw blob instead of JSON
              // https://developer.github.com/v3/media/
              request(apiUrl, { headers: { 'User-Agent': 'Shields.io', 'Accept': 'appplication/vnd.github.raw' } },
              function(err, res, buffer) {
                if (err != null) { end(err); return; }
                try {
                  if ((+res.headers['x-ratelimit-remaining']) === 0) { end(Error('rate limited')); return; }
                  var license = guessLicense(buffer);
                  if (license) {
                    badgeData.text[1] = license;
                    end(null, {
                      link: link,
                      badge: shieldsBadge(badgeData),
                      name: 'GitHub license'
                    });
                    return;
                  } else {
                    // Not a recognized license
                    end(Error('unknown license'));
                    return;
                  }
                } catch(e) { end(e); return; }
              });
            } catch(e) { end(e); return; }
          });
        } catch(e) { end(e); return; }
      });
    } catch(e) { end(e); return; }
  });
};

// Key phrases for common licenses
var licensePhrases = {
  'Apache 1.1': 'apache (software)? license,? (version)? 1\\.1',
  'Apache 2': 'apache (software)? license,? (version)? 2',
  'Original BSD': 'all advertising materials mentioning features or use of this software must display the following acknowledgement',
  'New BSD': 'may be used to endorse or promote products derived from this software without specific prior written permission',
  'BSD': 'redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met',
  'GPLv2': 'gnu general public license,? version 2',
  'GPLv3': 'gnu general public license,? version 3',
  'GPL': 'gnu general public license',
  'LGPLv2.0': 'gnu library general public license,? version 2',
  'LGPLv2.1': 'gnu lesser general public license,? version 2\\.1',
  'LGPLv3': 'gnu lesser general public license,? version 3',
  'LGPL': 'gnu (library|lesser) general public license',
  'MIT': '\\(?(mit|expat|x11)\\)? license|permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files',
  'MPL 1.1': 'mozilla public license,? (\\(MPL\\) )?(version |v|v\\.)?1\\.1',
  'MPL 2': 'mozilla public license,? (\\(MPL\\) )?(version |v|v\\.)?2',
  'MPL': 'mozilla public license',
  'CDDL': 'common development and distribution license',
  'Eclipse': 'eclipse public license',
  'Artistic': 'artistic license',
  'zlib': 'the origin of this software must not be misrepresented',
  'AGPLv1': 'affero general public license,? version 1',
  'AGPLv3': 'affero general public license,? version 3',
  'AGPL': 'affero general public license',
  'ISC': 'permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted',
  'CC0': 'cc0',
  'Unlicense': 'this is free and unencumbered software released into the public domain',
}
var licenseCodes = Object.keys(licensePhrases);
var spaceMetaRegex = new RegExp(' ', 'g');

// Spaces can be any whitespace
for (var i = 0; i < licenseCodes.length; i++) {
  licensePhrases[licenseCodes[i]] = licensePhrases[licenseCodes[i]].replace(spaceMetaRegex, '\\s+');
}

// Try to guess the license based on the text and return an abbreviated name (or null if not recognized).
var guessLicense = function(text) {
  var licenseRegex;
  for (var i = 0; i < licenseCodes.length; i++) {
    licenseRegex = licensePhrases[licenseCodes[i]];
    if (text.match(new RegExp(licenseRegex, 'i'))) {
      return licenseCodes[i];
    }
  }
  // Not a recognized license
  return null;
};

var shieldsBadge = function(badgeData) {
  return ('https://img.shields.io/badge/'
    + escapeField(badgeData.text[0])
    + '-' + escapeField(badgeData.text[1])
    + '-' + badgeData.colorscheme + '.svg');
};

var escapeField = function(s) {
  return encodeURIComponent(s.replace(/-/g, '--').replace(/_/g, '__'));
};

module.exports = suggest;
<|endoftext|>