<commit_before>import actionBuilder from 'action-builder'
import PropTypes from 'prop-types'
import React from 'react'
import {connect, select} from 'store'
import {msg, Msg} from 'translate'
import Icon from 'widget/icon'
import styles from 'widget/panelButtons.module.css'
import {PanelWizardContext} from './panel'

const mapStateToProps = (state, ownProps) => {
    const {statePath} = ownProps
    return {
        initialized: select([statePath, 'initialized']),
        selectedPanel: select([statePath, 'selectedPanel'])
    }
}

class PanelButtons extends React.Component {
    state = {
        selectedPanelIndex: 0,
        first: true,
        last: false,
        panels: []
    }

    static getDerivedStateFromProps(nextProps, prevState) {
        const {selectedPanel} = nextProps
        const panels = prevState.panels
        const selectedPanelIndex = panels.indexOf(selectedPanel)
        const first = selectedPanelIndex === 0
        const last = selectedPanelIndex === panels.length - 1
        return {...prevState, selectedPanelIndex, first, last}

    }

    apply() {
        const {form, onApply} = this.props
        onApply(form.values())
    }

    closePanel() {
        this.setModal(false)
        this.selectPanel()
    }

    ok() {
        this.apply()
        this.closePanel()
    }

    cancel() {
        const {onCancel} = this.props
        this.closePanel()
        onCancel && onCancel()
    }

    back() {
        const {selectedPanelIndex, first} = this.state
        if (!first) {
            this.apply()
            this.selectPanel(this.state.panels[selectedPanelIndex - 1])
        }
    }

    next() {
        const {selectedPanelIndex, last} = this.state
        if (!last) {
            this.apply()
            this.selectPanel(this.state.panels[selectedPanelIndex + 1])
        }
    }

    done() {
        this.apply()
        this.setInitialized()
        this.closePanel()
    }

    render() {
        const {initialized} = this.props
        return (<PanelWizardContext.Consumer>
                {panels => {
                    this.panels = panels || []
                    return (

                            {this.renderAdditionalButtons()}
                            {initialized ? this.renderFormButtons() : this.renderWizardButtons()}

                    )
                }}
            </PanelWizardContext.Consumer>
        )
    }

    componentDidMount() {
        const {initialized, form, modalOnDirty = true} = this.props
        if (modalOnDirty) {
            this.setModal(!initialized)
            if (initialized) {
                form.onDirty(() => this.setModal(true))
                form.onClean(() => this.setModal(false))
            }
        }
        this.setState(prevState => ({...prevState, panels: this.panels}))
    }

    setModal(modal) {
        const {statePath} = this.props
        actionBuilder('SET_MODAL', {modal})
            .set([statePath, 'modal'], modal)
            .dispatch()
    }

    selectPanel(panel) {
        const {statePath} = this.props
        actionBuilder('SELECT_PANEL', {panel})
            .set([statePath, 'selectedPanel'], panel)
            .dispatch()
    }

    setInitialized() {
        const {statePath} = this.props
        actionBuilder('SET_INITIALIZED')
            .set([statePath, 'initialized'], true)
            .dispatch()
    }

    renderAdditionalButtons() {
        const {additionalButtons = []} = this.props
        const renderButton = (button) =>
            <button
                type='button'
                key={button.key}
                onClick={(e) => {
                    e.preventDefault()
                    button.onClick(e)
                }}
                className={button.className || styles.default}>
                {button.label}
            </button>

        return (

                {additionalButtons.map(renderButton)}

        )
    }

    renderWizardButtons() {
        const {form} = this.props
        const {first, last} = this.state
        const back =
            <button
                type='button'
                onClick={(e) => {
                    e.preventDefault()
                    this.back()
                }}
                onMouseDown={(e) => e.preventDefault()} // Prevent onBlur validation before going back
                className={styles.default}>
                <Icon name={'chevron-left'}/>
                <Msg id='button.back'/>
            </button>
        const next =
            <button
                type='submit'
                onClick={(e) => {
                    e.preventDefault()
                    this.next()
                }}
                disabled={form.isInvalid()}
                className={styles.apply}>
                <Msg id='button.next'/>
                <Icon name={'chevron-right'}/>
            </button>

        const done =
            <button
                type='submit'
                onClick={(e) => {
                    e.preventDefault()
                    this.done()
                }}
                disabled={form.isInvalid()}
                className={styles.apply}>
                <Icon name={'check'}/>
                <Msg id='button.done'/>
            </button>

        return (

                {!first ? back : null}
                {!last ? next : done}

        )
    }

    renderFormButtons() {
        const {isActionForm, applyLabel = msg('button.ok'), cancelLabel = msg('button.cancel'), form} = this.props
        const dirty = form.isDirty()
        return (

                <button
                    type='button'
                    onClick={(e) => {
                        e.preventDefault()
                        this.cancel()
                    }}
                    disabled={!dirty && !isActionForm}
                    onMouseDown={(e) => e.preventDefault()} // Prevent onBlur validation before canceling
                    className={styles.cancel}
                    style={{opacity: isActionForm || dirty ? 1 : 0}}>
                    <Icon name={'undo-alt'}/>
                    {cancelLabel}
                </button>
                <button
                    type='submit'
                    onClick={(e) => {
                        e.preventDefault()
                        this.ok()
                    }}
                    disabled={form.isInvalid()}
                    className={styles.apply}>
                    <Icon name={'check'}/>
                    {applyLabel}
                </button>

        )
    }
}

PanelButtons.propTypes = {
    form: PropTypes.object.isRequired,
    statePath: PropTypes.string.isRequired
}

export default connect(mapStateToProps)(PanelButtons)<commit_msg>Only render wizard buttons if a PanelWizard provides panels.<commit_after>import actionBuilder from 'action-builder'
import PropTypes from 'prop-types'
import React from 'react'
import {connect, select} from 'store'
import {msg, Msg} from 'translate'
import Icon from 'widget/icon'
import styles from 'widget/panelButtons.module.css'
import {PanelWizardContext} from './panel'

const mapStateToProps = (state, ownProps) => {
    const {statePath} = ownProps
    return {
        initialized: select([statePath, 'initialized']),
        selectedPanel: select([statePath, 'selectedPanel'])
    }
}

class PanelButtons extends React.Component {
    state = {
        selectedPanelIndex: 0,
        first: true,
        last: false,
        panels: []
    }

    static getDerivedStateFromProps(nextProps, prevState) {
        const {selectedPanel} = nextProps
        const panels = prevState.panels
        const selectedPanelIndex = panels.indexOf(selectedPanel)
        const first = selectedPanelIndex === 0
        const last = selectedPanelIndex === panels.length - 1
        return {...prevState, selectedPanelIndex, first, last}

    }

    apply() {
        const {form, onApply} = this.props
        onApply(form.values())
    }

    closePanel() {
        this.setModal(false)
        this.selectPanel()
    }

    ok() {
        this.apply()
        this.closePanel()
    }

    cancel() {
        const {onCancel} = this.props
        this.closePanel()
        onCancel && onCancel()
    }

    back() {
        const {selectedPanelIndex, first} = this.state
        if (!first) {
            this.apply()
            this.selectPanel(this.state.panels[selectedPanelIndex - 1])
        }
    }

    next() {
        const {selectedPanelIndex, last} = this.state
        if (!last) {
            this.apply()
            this.selectPanel(this.state.panels[selectedPanelIndex + 1])
        }
    }

    done() {
        this.apply()
        this.setInitialized()
        this.closePanel()
    }

    render() {
        const {initialized} = this.props
        return (<PanelWizardContext.Consumer>
                {panels => {
                    this.panels = panels || []
                    return (

                            {this.renderAdditionalButtons()}
                            {!panels || initialized ? this.renderFormButtons() : this.renderWizardButtons()}

                    )
                }}
            </PanelWizardContext.Consumer>
        )
    }

    componentDidMount() {
        const {initialized, form, modalOnDirty = true} = this.props
        if (modalOnDirty) {
            this.setModal(!initialized)
            if (initialized) {
                form.onDirty(() => this.setModal(true))
                form.onClean(() => this.setModal(false))
            }
        }
        this.setState(prevState => ({...prevState, panels: this.panels}))
    }

    setModal(modal) {
        const {statePath} = this.props
        actionBuilder('SET_MODAL', {modal})
            .set([statePath, 'modal'], modal)
            .dispatch()
    }

    selectPanel(panel) {
        const {statePath} = this.props
        actionBuilder('SELECT_PANEL', {panel})
            .set([statePath, 'selectedPanel'], panel)
            .dispatch()
    }

    setInitialized() {
        const {statePath} = this.props
        actionBuilder('SET_INITIALIZED')
            .set([statePath, 'initialized'], true)
            .dispatch()
    }

    renderAdditionalButtons() {
        const {additionalButtons = []} = this.props
        const renderButton = (button) =>
            <button
                type='button'
                key={button.key}
                onClick={(e) => {
                    e.preventDefault()
                    button.onClick(e)
                }}
                className={button.className || styles.default}>
                {button.label}
            </button>

        return (

                {additionalButtons.map(renderButton)}

        )
    }

    renderWizardButtons() {
        const {form} = this.props
        const {first, last} = this.state
        const back =
            <button
                type='button'
                onClick={(e) => {
                    e.preventDefault()
                    this.back()
                }}
                onMouseDown={(e) => e.preventDefault()} // Prevent onBlur validation before going back
                className={styles.default}>
                <Icon name={'chevron-left'}/>
                <Msg id='button.back'/>
            </button>
        const next =
            <button
                type='submit'
                onClick={(e) => {
                    e.preventDefault()
                    this.next()
                }}
                disabled={form.isInvalid()}
                className={styles.apply}>
                <Msg id='button.next'/>
                <Icon name={'chevron-right'}/>
            </button>

        const done =
            <button
                type='submit'
                onClick={(e) => {
                    e.preventDefault()
                    this.done()
                }}
                disabled={form.isInvalid()}
                className={styles.apply}>
                <Icon name={'check'}/>
                <Msg id='button.done'/>
            </button>

        return (

                {!first ? back : null}
                {!last ? next : done}

        )
    }

    renderFormButtons() {
        const {isActionForm, applyLabel = msg('button.ok'), cancelLabel = msg('button.cancel'), form} = this.props
        const dirty = form.isDirty()
        return (

                <button
                    type='button'
                    onClick={(e) => {
                        e.preventDefault()
                        this.cancel()
                    }}
                    disabled={!dirty && !isActionForm}
                    onMouseDown={(e) => e.preventDefault()} // Prevent onBlur validation before canceling
                    className={styles.cancel}
                    style={{opacity: isActionForm || dirty ? 1 : 0}}>
                    <Icon name={'undo-alt'}/>
                    {cancelLabel}
                </button>
                <button
                    type='submit'
                    onClick={(e) => {
                        e.preventDefault()
                        this.ok()
                    }}
                    disabled={form.isInvalid()}
                    className={styles.apply}>
                    <Icon name={'check'}/>
                    {applyLabel}
                </button>

        )
    }
}

PanelButtons.propTypes = {
    form: PropTypes.object.isRequired,
    statePath: PropTypes.string.isRequired
}

export default connect(mapStateToProps)(PanelButtons)<|endoftext|>


<commit_before>/*
 * xhr.js - XMLHttpRequest helper class
 * (c) 2008-2010 Jo-Philipp Wich
 */

XHR = function()
{
	this.reinit = function()
	{
		if (window.XMLHttpRequest) {
			this._xmlHttp = new XMLHttpRequest();
		}
		else if (window.ActiveXObject) {
			this._xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
		}
		else {
			alert("xhr.js: XMLHttpRequest is not supported by this browser!");
		}
	}

	this.busy = function() {
		if (!this._xmlHttp)
			return false;

		switch (this._xmlHttp.readyState)
		{
			case 1:
			case 2:
			case 3:
				return true;

			default:
				return false;
		}
	}

	this.abort = function() {
		if (this.busy())
			this._xmlHttp.abort();
	}

	this.get = function(url,data,callback,timeout)
	{
		this.reinit();

		var xhr  = this._xmlHttp;
		var code = this._encode(data);

		url = location.protocol + '//' + location.host + url;

		if (code)
			if (url.substr(url.length-1,1) == '&')
				url += code;
			else
				url += '?' + code;

		if (!isNaN(timeout))
			xhr.timeout = timeout;

		xhr.open('GET', url, true);

		xhr.onreadystatechange = function()
		{
			if (xhr.readyState == 4) {
				var json = null;
				if (xhr.getResponseHeader("Content-Type") == "application/json") {
					try {
						json = eval('(' + xhr.responseText + ')');
					}
					catch(e) {
						json = null;
					}
				}

				callback(xhr, json);
			}
		}

		xhr.send(null);
	}

	this.post = function(url,data,callback,timeout)
	{
		this.reinit();

		var xhr  = this._xmlHttp;
		var code = this._encode(data);

		xhr.onreadystatechange = function()
		{
			if (xhr.readyState == 4)
				callback(xhr);
		}

		if (!isNaN(timeout))
			xhr.timeout = timeout;

		xhr.open('POST', url, true);
		xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
		xhr.send(code);
	}

	this.cancel = function()
	{
		this._xmlHttp.onreadystatechange = function(){};
		this._xmlHttp.abort();
	}

	this.send_form = function(form,callback,extra_values)
	{
		var code = '';

		for (var i = 0; i < form.elements.length; i++)
		{
			var e = form.elements[i];

			if (e.options)
			{
				code += (code ? '&' : '') +
					form.elements[i].name + '=' + encodeURIComponent(
						e.options[e.selectedIndex].value
					);
			}
			else if (e.length)
			{
				for (var j = 0; j < e.length; j++)
					if (e[j].name) {
						code += (code ? '&' : '') +
							e[j].name + '=' + encodeURIComponent(e[j].value);
					}
			}
			else
			{
				code += (code ? '&' : '') +
					e.name + '=' + encodeURIComponent(e.value);
			}
		}

		if (typeof extra_values == 'object')
			for (var key in extra_values)
				code += (code ? '&' : '') +
					key + '=' + encodeURIComponent(extra_values[key]);

		return(
			(form.method == 'get')
				? this.get(form.getAttribute('action'), code, callback)
				: this.post(form.getAttribute('action'), code, callback)
		);
	}

	this._encode = function(obj)
	{
		obj = obj ? obj : { };
		obj['_'] = Math.random();

		if (typeof obj == 'object')
		{
			var code = '';
			var self = this;

			for (var k in obj)
				code += (code ? '&' : '') +
					k + '=' + encodeURIComponent(obj[k]);

			return code;
		}

		return obj;
	}
}

XHR.get = function(url, data, callback)
{
	(new XHR()).get(url, data, callback);
}

XHR.poll = function(interval, url, data, callback, post)
{
	if (isNaN(interval) || interval < 1)
		interval = 5;

	if (!XHR._q)
	{
		XHR._t = 0;
		XHR._q = [ ];
		XHR._r = function() {
			for (var i = 0, e = XHR._q[0]; i < XHR._q.length; e = XHR._q[++i])
			{
				if (!(XHR._t % e.interval) && !e.xhr.busy())
					e.xhr[post ? 'post' : 'get'](e.url, e.data, e.callback, e.interval * 1000 - 5);
			}

			XHR._t++;
		};
	}

	var e = {
		interval: interval,
		callback: callback,
		url:      url,
		data:     data,
		xhr:      new XHR()
	};

	XHR._q.push(e);
	XHR.run();

	return e;
}

XHR.stop = function(e)
{
	for (var i = 0; XHR._q && XHR._q[i]; i++) {
		if (XHR._q[i] === e) {
			e.xhr.cancel();
			XHR._q.splice(i, 1);
			return true;
		}
	}

	return false;
}

XHR.halt = function()
{
	if (XHR._i)
	{
		/* show & set poll indicator */
		try {
			document.getElementById('xhr_poll_status').style.display = '';
			document.getElementById('xhr_poll_status_on').style.display = 'none';
			document.getElementById('xhr_poll_status_off').style.display = '';
		} catch(e) { }

		window.clearInterval(XHR._i);
		XHR._i = null;
	}
}

XHR.run = function()
{
	if (XHR._r && !XHR._i)
	{
		/* show & set poll indicator */
		try {
			document.getElementById('xhr_poll_status').style.display = '';
			document.getElementById('xhr_poll_status_on').style.display = '';
			document.getElementById('xhr_poll_status_off').style.display = 'none';
		} catch(e) { }

		/* kick first round manually to prevent one second lag when setting up
		 * the poll interval */
		XHR._r();
		XHR._i = window.setInterval(XHR._r, 1000);
	}
}

XHR.running = function()
{
	return !!(XHR._r && XHR._i);
}
<commit_msg>luci-base: xhr.js: fix timeout setting with IE 11<commit_after>/*
 * xhr.js - XMLHttpRequest helper class
 * (c) 2008-2010 Jo-Philipp Wich
 */

XHR = function()
{
	this.reinit = function()
	{
		if (window.XMLHttpRequest) {
			this._xmlHttp = new XMLHttpRequest();
		}
		else if (window.ActiveXObject) {
			this._xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
		}
		else {
			alert("xhr.js: XMLHttpRequest is not supported by this browser!");
		}
	}

	this.busy = function() {
		if (!this._xmlHttp)
			return false;

		switch (this._xmlHttp.readyState)
		{
			case 1:
			case 2:
			case 3:
				return true;

			default:
				return false;
		}
	}

	this.abort = function() {
		if (this.busy())
			this._xmlHttp.abort();
	}

	this.get = function(url,data,callback,timeout)
	{
		this.reinit();

		var xhr  = this._xmlHttp;
		var code = this._encode(data);

		url = location.protocol + '//' + location.host + url;

		if (code)
			if (url.substr(url.length-1,1) == '&')
				url += code;
			else
				url += '?' + code;

		xhr.open('GET', url, true);

		if (!isNaN(timeout))
			xhr.timeout = timeout;

		xhr.onreadystatechange = function()
		{
			if (xhr.readyState == 4) {
				var json = null;
				if (xhr.getResponseHeader("Content-Type") == "application/json") {
					try {
						json = eval('(' + xhr.responseText + ')');
					}
					catch(e) {
						json = null;
					}
				}

				callback(xhr, json);
			}
		}

		xhr.send(null);
	}

	this.post = function(url,data,callback,timeout)
	{
		this.reinit();

		var xhr  = this._xmlHttp;
		var code = this._encode(data);

		xhr.onreadystatechange = function()
		{
			if (xhr.readyState == 4)
				callback(xhr);
		}

		xhr.open('POST', url, true);

		if (!isNaN(timeout))
			xhr.timeout = timeout;

		xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
		xhr.send(code);
	}

	this.cancel = function()
	{
		this._xmlHttp.onreadystatechange = function(){};
		this._xmlHttp.abort();
	}

	this.send_form = function(form,callback,extra_values)
	{
		var code = '';

		for (var i = 0; i < form.elements.length; i++)
		{
			var e = form.elements[i];

			if (e.options)
			{
				code += (code ? '&' : '') +
					form.elements[i].name + '=' + encodeURIComponent(
						e.options[e.selectedIndex].value
					);
			}
			else if (e.length)
			{
				for (var j = 0; j < e.length; j++)
					if (e[j].name) {
						code += (code ? '&' : '') +
							e[j].name + '=' + encodeURIComponent(e[j].value);
					}
			}
			else
			{
				code += (code ? '&' : '') +
					e.name + '=' + encodeURIComponent(e.value);
			}
		}

		if (typeof extra_values == 'object')
			for (var key in extra_values)
				code += (code ? '&' : '') +
					key + '=' + encodeURIComponent(extra_values[key]);

		return(
			(form.method == 'get')
				? this.get(form.getAttribute('action'), code, callback)
				: this.post(form.getAttribute('action'), code, callback)
		);
	}

	this._encode = function(obj)
	{
		obj = obj ? obj : { };
		obj['_'] = Math.random();

		if (typeof obj == 'object')
		{
			var code = '';
			var self = this;

			for (var k in obj)
				code += (code ? '&' : '') +
					k + '=' + encodeURIComponent(obj[k]);

			return code;
		}

		return obj;
	}
}

XHR.get = function(url, data, callback)
{
	(new XHR()).get(url, data, callback);
}

XHR.poll = function(interval, url, data, callback, post)
{
	if (isNaN(interval) || interval < 1)
		interval = 5;

	if (!XHR._q)
	{
		XHR._t = 0;
		XHR._q = [ ];
		XHR._r = function() {
			for (var i = 0, e = XHR._q[0]; i < XHR._q.length; e = XHR._q[++i])
			{
				if (!(XHR._t % e.interval) && !e.xhr.busy())
					e.xhr[post ? 'post' : 'get'](e.url, e.data, e.callback, e.interval * 1000 - 5);
			}

			XHR._t++;
		};
	}

	var e = {
		interval: interval,
		callback: callback,
		url:      url,
		data:     data,
		xhr:      new XHR()
	};

	XHR._q.push(e);
	XHR.run();

	return e;
}

XHR.stop = function(e)
{
	for (var i = 0; XHR._q && XHR._q[i]; i++) {
		if (XHR._q[i] === e) {
			e.xhr.cancel();
			XHR._q.splice(i, 1);
			return true;
		}
	}

	return false;
}

XHR.halt = function()
{
	if (XHR._i)
	{
		/* show & set poll indicator */
		try {
			document.getElementById('xhr_poll_status').style.display = '';
			document.getElementById('xhr_poll_status_on').style.display = 'none';
			document.getElementById('xhr_poll_status_off').style.display = '';
		} catch(e) { }

		window.clearInterval(XHR._i);
		XHR._i = null;
	}
}

XHR.run = function()
{
	if (XHR._r && !XHR._i)
	{
		/* show & set poll indicator */
		try {
			document.getElementById('xhr_poll_status').style.display = '';
			document.getElementById('xhr_poll_status_on').style.display = '';
			document.getElementById('xhr_poll_status_off').style.display = 'none';
		} catch(e) { }

		/* kick first round manually to prevent one second lag when setting up
		 * the poll interval */
		XHR._r();
		XHR._i = window.setInterval(XHR._r, 1000);
	}
}

XHR.running = function()
{
	return !!(XHR._r && XHR._i);
}
<|endoftext|>


<commit_before>'use strict';
/**
 * Created by chregi on 2/28/15.
 */

 // parse a date in yyyy-mm-dd format
function parseDate(input) {
  var parts = input.match(/(\d+)/g);
  // new Date(year, month [, date [, hours[, minutes[, seconds[, ms]]]]])
  return new Date(parts[0], parts[1]-1, parts[2]); // months are 0-based
}

var getSchedule = function() {

	var id = $('#srs-schedule').attr('data-teamid');

	$.ajax({
	    url: "http://api.swissrugbystats.ch/teams/"+id+"/games.json",
	    success: function(data){

            /* build schedule table
             * TODO:
             * - kickoff vs result
             * - provided by rugbygear.ch claim
            */
            var tableStr = "";
            tableStr = "

Datum	Host	Guest	Kickoff / Result

";
            for (var i = 0; i < data.length; i++) {
                // parse the date
                var d = parseDate(data[i].date);
                var dStr = ("0" + d.getDate()).slice(-2) +"."+ ("0"+d.getMonth()).slice(-2) +"."+ d.getFullYear();
                tableStr += "
" + dStr + "	" + data[i].host.team.name + "	" + data[i].guest.team.name + "
";
            }

            tableStr += "

";
            $("#srs-schedule").html(tableStr);
	    }
	});

}

var getNextGame = function() {

	var id = $('#srs-nextGame').attr('data-teamid');

	$.ajax({
	    url: "http://api.swissrugbystats.ch/nextGameByTeamId/"+id+"/.json",
	    success: function(data){
	      $('#srs-nextGame').html("Host: " + data.host.team.name + "
Guest: " + data.guest.team.name);
	    }
	});
}

getSchedule();
getNextGame();<commit_msg>use jQuery instead of $ for jquery calls<commit_after>'use strict';
/**
 * Created by chregi on 2/28/15.
 */

 // parse a date in yyyy-mm-dd format
function parseDate(input) {
  var parts = input.match(/(\d+)/g);
  // new Date(year, month [, date [, hours[, minutes[, seconds[, ms]]]]])
  return new Date(parts[0], parts[1]-1, parts[2]); // months are 0-based
}

var getSchedule = function() {

	var id = jQuery('#srs-schedule').attr('data-teamid');

	$.ajax({
	    url: "http://api.swissrugbystats.ch/teams/"+id+"/games.json",
	    success: function(data){

            /* build schedule table
             * TODO:
             * - kickoff vs result
             * - provided by rugbygear.ch claim
            */
            var tableStr = "";
            tableStr = "

Datum	Host	Guest	Kickoff / Result

";
            for (var i = 0; i < data.length; i++) {
                // parse the date
                var d = parseDate(data[i].date);
                var dStr = ("0" + d.getDate()).slice(-2) +"."+ ("0"+d.getMonth()).slice(-2) +"."+ d.getFullYear();
                tableStr += "
" + dStr + "	" + data[i].host.team.name + "	" + data[i].guest.team.name + "
";
            }

            tableStr += "

";
            jQuery("#srs-schedule").html(tableStr);
	    }
	});

}

var getNextGame = function() {

	var id = jQuery('#srs-nextGame').attr('data-teamid');

	$.ajax({
	    url: "http://api.swissrugbystats.ch/nextGameByTeamId/"+id+"/.json",
	    success: function(data){
	      jQuery('#srs-nextGame').html("Host: " + data.host.team.name + "
Guest: " + data.guest.team.name);
	    }
	});
}

getSchedule();
getNextGame();<|endoftext|>


<commit_before>/** MIT License (c) copyright B Cavalier & J Hann */

/**
 * curl i18n plugin
 *
 * Licensed under the MIT License at:
 * 		http://www.opensource.org/licenses/mit-license.php
 *
 */

(function (global) {

define(/*=='curl/plugin/i18n',==*/ function () {

	function getLocale () {
		return (global.clientInformation || global.navigator).language;
	}

	return {
		load: function (absId, require, loaded, config) {
			//var locale = config.locale || getLocale();
			require([absId], loaded, loaded.error);
		}
	};

});

}());
<commit_msg>roughed-out a simple i18n plugin<commit_after>/** MIT License (c) copyright B Cavalier & J Hann */

/**
 * curl i18n plugin
 *
 * First, fetch default string bundle.
 *
 * If the default bundle has locale information in it, use that to define
 * locale-specific values or to fetch locale-specific files at the specified
 * location.
 *
 * If default bundle does not have local information in it, start scanning
 * for progressively specific files using a default naming scheme,
 * e.g. strings.js, strings/en.js, strings/en_US.js, etc.
 *
 * Browsers follow the language specifiers in the Language-Tags feature of
 * RFC 5646: http://tools.ietf.org/html/rfc5646
 *
 * Configuration options:
 *
 * locale {String|Function} the RFC 5646-compatible language specifier or
 *   a function that returns a language specifier.  The module id is passed
 *   as the only parameter to this function.  If this option is omitted,
 *   the browser's locale is sniffed and used.
 *
 * localeToModuleId {Function} a function that translates a locale string to a
 *   module id where an AMD-formatted string bundle may be found.  The default
 *   format is a module whose name is the locale located under the default
 *   (non-locale-specific) bundle.  For example, if the default bundle is
 *   "myview/strings.js", the en-us version will be "myview/strings/en-us.js".
 *   Parameters: moduleId {String}, locale {String}, returns {String}
 *
 */

(function (global) {

define(/*=='curl/plugin/i18n',==*/ function () {

	return {
		load: function (absId, require, loaded, config) {
			var locale, toFile, ids, bundles, fetched, i;

			locale = config.locale || getLocale;
			if (typeof locale == 'function') locale = locale(absId);

			toFile = config.localeToModuleId || localeToModuleId;

			ids = localeToModuleIds(absId, locale, toFile);

			bundles = [];

			fetched = 0;

			for (i = 0; i < ids.length; i++) {
				// save found bundles, just silently skip missing ones
				fetch(require, ids[i], i, got, countdown);
			}

			function got (bundle, i) {
				bundles[i] = bundle;
				countdown();
			}

			function countdown () {
				var base, bundle;
				if (++fetched == ids.length) {
					if (bundles.length == 0) {
						loaded.error(new Error('No bundles found for ' + absId + ' and locale ' + locale));
					}
					else {
						base = {};
						for (i = 0; i < bundles.length; i++) {
							base = mixin(base, bundles[i]);
						}
						loaded(base);
					}
				}
			}

		}
	};

	function fetch (require, id, i, cb, eb) {
		require([id], function (bundle) { cb(bundle, i); }, eb);
	}

	function localeToModuleIds (absId, locale, formatter) {
		var parts, specifier, ids, part;
		parts = locale.split(/-|_/);
		specifier = '';
		ids = [formatter(absId, '')];
		while ((part = parts.shift())) {
			specifier += part;
			ids.push(formatter(absId, specifier));
		}
		return ids;
	}

	function mixin (base, props) {
		if (props) {
			for (var p in props) {
				base[p] = props[p];
			}
		}
		return base;
	}

	function getLocale () {
		var ci = global.clientInformation || global.navigator;
		return ci.language || ci.userLanguage;
	}

	function localeToModuleId (absId, locale) {
		// TODO: is this regex robust enough?
		return absId.replace(/$|\.js/, locale ? '/' + locale  : '');
	}

});

}(this));
<|endoftext|>


<commit_before>// LocalStorage router
//
'use strict';

/* global document, window */
var Timer = require('./timer');
var LocalStorage = require('./local_storage');
var $$ = require('./utils');

// Max lifetime of router record in storage
var TIMEOUT = 4000;
// Update router record frequency
var UPDATE_INTERVAL = TIMEOUT / 4;

// Constructor
//
// options:
//
//  - namespace (String) - optional, localStorage keys prefix, default 'tabex_default_'
//
function Router(options) {
  var self = this;

  options = options || {};

  this.__namespace__ = options.namespace || 'tabex_default_';
  this.__node_id__ = Math.floor(Math.random() * 1e10) + 1;
  this.__last_message_cnt__ = 0;
  this.__handlers__ = [];
  this.__router_channels__ = {};

  // Constants for convenience
  this.__router_id_prefix__ = this.__namespace__ + 'router_';
  this.__router_channels_prefix__ = this.__namespace__ + 'subscribed_';
  this.__lock_prefix__ = this.__namespace__ + 'lock_';

  // IE broadcasts storage events also to the same window, we should filter that messages
  this.__storage_events_filter__ = [];

  for (var i = 0; i < 100; i++) {
    this.__storage_events_filter__.push('');
  }

  this.__ls__ = new LocalStorage();

  // Id of master tab
  this.__master_id__ = null;

  // Handle `localStorage` update
  $$.addEvent(window, 'storage', function (e) {
    // IE needs kludge because event fire before data was saved
    if ('onstoragecommit' in document) {
      setTimeout(function () {
        self.__on_changed__(e);
      }, 1);

      return;
    }

    self.__on_changed__(e);
  });

  // Handle page unload (listen `onbeforeunload` and `onunload` to ensure that data is stored successfully)
  // http://stackoverflow.com/questions/3775566/javascript-question-onbeforeunload-or-onunload
  //
  this.__destroyed__ = false;
  $$.addEvent(window, 'beforeunload', function () {
    self.__destroy__();
  });
  $$.addEvent(window, 'unload', function () {
    self.__destroy__();
  });

  // Update current tab info and check master alive
  this.__check_master__();
  Timer.setInterval(function () {
    self.__check_master__();
  }, UPDATE_INTERVAL);

  // Remove outdated lock records
  Timer.setInterval(function () {
    self.__locks_cleanup__();
  }, 1000);
}

// Broadcast message between all clients
//
// - channel (String) - channel name
// - message (Object) - message data
//
Router.prototype.broadcast = function (channel, message) {
  // If it is system subscribe message - update channels list
  if (channel === '!sys.channels.add') {
    this.__router_channels__[message.data.channel] = this.__router_channels__[message.data.channel] || 0;
    this.__router_channels__[message.data.channel]++;
    this.__update_channels_list__();

    return;
  }

  // If it is system unsubscribe message - update channels list
  if (channel === '!sys.channels.remove') {
    this.__router_channels__[message.data.channel] = this.__router_channels__[message.data.channel] || 0;
    this.__router_channels__[message.data.channel]--;
    this.__update_channels_list__();

    return;
  }

  // If it is system lock message - try acquire lock
  if (channel === '!sys.lock.request') {
    this.__lock__(message.data.id, message.id, message.data.timeout);

    return;
  }

  // If it is system unlock message - remove lock data
  if (channel === '!sys.lock.release') {
    this.__ls__.removeItem(this.__lock_prefix__ + message.data.id);

    return;
  }

  var serializedMessage = JSON.stringify({
    channel: channel,
    message: message,

    // Add random to be sure that `localStorage` sent event even new massage is same than previous
    random: Math.floor(Math.random() * 1e10)
  });

  // Add message to `localStorage` to distribute over Router instances
  this.__storage_events_filter__.shift();
  this.__storage_events_filter__.push(this.__namespace__ + 'broadcast' + '_' + serializedMessage);
  this.__ls__.setItem(this.__namespace__ + 'broadcast', serializedMessage);

  // Emit message for all clients and proxies registered on this router
  this.__handlers__.forEach(function (handler) {
    handler(channel, message);
  });
};

// Subscribe handler to all messages
//
Router.prototype.onmessage = function (handler) {
  var self = this;

  this.__handlers__.push(handler);

  // Delay sending events to next tick to allow client initialize handlers
  setTimeout(function () {
    // Sent master info for every new client
    handler('!sys.master', {
      data: {
        node_id: self.__node_id__,
        master_id: self.__master_id__
      },
      node_id: self.__node_id__,
      id: self.__node_id__ + '_' + (self.__last_message_cnt__++)
    });

    // Send channels info
    self.__on_channels_list_changed__();
  }, 0);
};

// Try acquire lock
//
// - lockId (String)
// - requestId (String)
// - timeout (Number)
//
Router.prototype.__lock__ = function (lockId, requestId, timeout) {
  var self = this;
  var lockKey = this.__lock_prefix__ + lockId;
  var lockValue = this.__ls__.getItem(lockKey);

  if (lockValue) {
    try {
      lockValue = JSON.parse(lockValue);
    } catch (__) {
      lockValue = null;
    }
  }

  // If `expire` not in past - lock already acquired, exit here
  if (lockValue && lockValue.expire > Date.now()) {
    return;
  }

  // Try acquire lock
  this.__ls__.setItem(lockKey, JSON.stringify({ expire: timeout + Date.now(), requestId: requestId }));

  // Read lock value again after 30 ms to check `requestId` (race condition
  // here - other tab may rewrite value in store). Delay needed to ensure that
  // localStorage's data synchronized
  setTimeout(function () {
    lockValue = self.__ls__.getItem(lockKey);

    if (lockValue) {
      try {
        lockValue = JSON.parse(lockValue);
      } catch (__) {
        lockValue = null;
      }
    }

    // If `requestId` is not same - other tab acquire lock, exit here
    if (!lockValue || lockValue.requestId !== requestId) {
      return;
    }

    // Here lock acquired - send message to clients
    self.__handlers__.forEach(function (handler) {
      handler('!sys.lock.acquired', {
        data: {
          request_id: requestId
        },
        node_id: self.__node_id__,
        id: self.__node_id__ + '_' + (self.__last_message_cnt__++)
      });
    });
  }, 30);
};

// Remove outdated lock records from storage
//
Router.prototype.__locks_cleanup__ = function () {
  for (var i = 0, key, val; i < this.__ls__.length; i++) {
    key = this.__ls__.key(i);

    // Filter localStorage records by prefix
    if (key.indexOf(this.__lock_prefix__) !== 0) {
      continue;
    }

    val = this.__ls__.getItem(key);

    try {
      val = JSON.parse(val);
    } catch (__) {
      val = null;
    }

    // If lock expire or record is broken - remove it
    if (!val || val.expire < Date.now()) {
      this.__ls__.removeItem(key);
    }
  }
};

// Update master id, if current tab is master - init connect and subscribe channels
//
Router.prototype.__on_master_changed__ = function (newMasterID) {
  var self = this;

  // If master tab closed
  if (!newMasterID) {
    // Select random master (tab with smallest ID becomes master)
    if (this.__get_alive_router_ids__().sort()[0] === this.__node_id__) {
      this.__storage_events_filter__.pop();
      this.__storage_events_filter__.push(this.__namespace__ + 'master' + '_' + this.__node_id__);
      this.__ls__.setItem(this.__namespace__ + 'master', this.__node_id__);
      this.__on_master_changed__(this.__node_id__);
    }
    return;
  }

  this.__master_id__ = +newMasterID;

  this.__handlers__.forEach(function (handler) {
    handler('!sys.master', {
      data: {
        node_id: self.__node_id__,
        master_id: self.__master_id__
      },
      node_id: self.__node_id__,
      id: self.__node_id__ + '_' + (self.__last_message_cnt__++)
    });
  });

  // Send channels info
  self.__on_channels_list_changed__();
};

// localStorage change handler. Updates master ID, receive subscribe requests
//
Router.prototype.__on_changed__ = function (e) {

  // IE broadcasts storage events also to the same window, we should filter that messages
  if (this.__storage_events_filter__.indexOf(e.key + '_' + e.newValue) !== -1) {
    return;
  }

  // Master changed
  if (e.key === this.__namespace__ + 'master') {
    this.__on_master_changed__(e.newValue);
  }

  // Channels list changed
  if (e.key.indexOf(this.__router_channels_prefix__) === 0) {
    this.__on_channels_list_changed__();
  }

  // Emit message for all clients and proxies registered on this router
  if (e.key === this.__namespace__ + 'broadcast') {
    var data = JSON.parse(e.newValue);

    this.__handlers__.forEach(function (handler) {
      handler(data.channel, data.message);
    });
  }
};

// Page unload handler. Remove tab data from store
//
Router.prototype.__destroy__ = function () {
  if (this.__destroyed__) {
    return;
  }

  this.__destroyed__ = true;

  this.__ls__.removeItem(this.__router_id_prefix__ + this.__node_id__);
  this.__ls__.removeItem(this.__router_channels_prefix__ + this.__node_id__);

  if (this.__master_id__ === this.__node_id__) {
    this.__ls__.removeItem(this.__namespace__ + 'master');
  }
};

// Get alive tabs IDs and remove timeouted tabs
//
Router.prototype.__get_alive_router_ids__ = function () {
  var maxTime = Date.now() - TIMEOUT;
  var id;
  var routersIDs = [];

  for (var i = 0, key; i < this.__ls__.length; i++) {
    key = this.__ls__.key(i);

    // Filter localStorage records by prefix
    if (key.indexOf(this.__router_id_prefix__) !== 0) {
      continue;
    }

    id = +key.substr(this.__router_id_prefix__.length);

    // Check router is alive and remove if not
    if (this.__ls__.getItem(key) < maxTime) {
      this.__ls__.removeItem(key);
      this.__ls__.removeItem(this.__router_channels_prefix__ + id);
      continue;
    }

    routersIDs.push(id);
  }

  return routersIDs;
};

// Update tab channels list
//
Router.prototype.__update_channels_list__ = function () {
  var self = this;
  var channels = [];

  Object.keys(this.__router_channels__).forEach(function (channel) {
    if (self.__router_channels__[channel] > 0) {
      channels.push(channel);
    }
  });

  var serializedChannels = JSON.stringify(channels.sort());

  // Update channels list if changed
  if (this.__ls__.getItem(this.__router_channels_prefix__ + this.__node_id__) !== serializedChannels) {
    this.__storage_events_filter__.pop();
    this.__storage_events_filter__.push(this.__router_channels_prefix__ + this.__node_id__ + '_' + serializedChannels);
    this.__ls__.setItem(this.__router_channels_prefix__ + this.__node_id__, serializedChannels);
    this.__on_channels_list_changed__();
  }
};

// Update subscribes if channels list changed (run only on master)
//
Router.prototype.__on_channels_list_changed__ = function () {
  var self = this;
  var channels = [];

  for (var i = 0, key; i < this.__ls__.length; i++) {
    key = this.__ls__.key(i);

    // Filter localStorage records by prefix
    if (key.indexOf(this.__router_channels_prefix__) !== 0) {
      continue;
    }

    channels = channels.concat(JSON.parse(this.__ls__.getItem(key)));
  }

  // Get unique channels names
  channels = channels.reduce(function (result, item) {
    if (result.indexOf(item) === -1) {
      result.push(item);
    }
    return result;
  }, []);

  this.__handlers__.forEach(function (handler) {
    handler('!sys.channels.refresh', {
      id: self.__node_id__ + '_' + (self.__last_message_cnt__++),
      node_id: self.__node_id__,
      data: {
        channels: channels
      }
    });
  });
};

// Update tab livetime and become master if not exists
//
Router.prototype.__check_master__ = function () {
  // Update current tab time
  this.__ls__.setItem(this.__router_id_prefix__ + this.__node_id__, Date.now());

  // Update local value of master ID
  this.__master_id__ = +this.__ls__.getItem(this.__namespace__ + 'master');

  // If master tab not found - become master
  if (this.__get_alive_router_ids__().indexOf(this.__master_id__) === -1) {
    this.__storage_events_filter__.pop();
    this.__storage_events_filter__.push(this.__namespace__ + 'master' + '_' + this.__node_id__);
    this.__ls__.setItem(this.__namespace__ + 'master', this.__node_id__);
    this.__on_master_changed__(this.__node_id__);
  }
};

module.exports = Router;
<commit_msg>Fixed processing of localstorage.clear() call, #10<commit_after>// LocalStorage router
//
'use strict';

/* global document, window */
var Timer = require('./timer');
var LocalStorage = require('./local_storage');
var $$ = require('./utils');

// Max lifetime of router record in storage
var TIMEOUT = 4000;
// Update router record frequency
var UPDATE_INTERVAL = TIMEOUT / 4;

// Constructor
//
// options:
//
//  - namespace (String) - optional, localStorage keys prefix, default 'tabex_default_'
//
function Router(options) {
  var self = this;

  options = options || {};

  this.__namespace__ = options.namespace || 'tabex_default_';
  this.__node_id__ = Math.floor(Math.random() * 1e10) + 1;
  this.__last_message_cnt__ = 0;
  this.__handlers__ = [];
  this.__router_channels__ = {};

  // Constants for convenience
  this.__router_id_prefix__ = this.__namespace__ + 'router_';
  this.__router_channels_prefix__ = this.__namespace__ + 'subscribed_';
  this.__lock_prefix__ = this.__namespace__ + 'lock_';

  // IE broadcasts storage events also to the same window, we should filter that messages
  this.__storage_events_filter__ = [];

  for (var i = 0; i < 100; i++) {
    this.__storage_events_filter__.push('');
  }

  this.__ls__ = new LocalStorage();

  // Id of master tab
  this.__master_id__ = null;

  // Handle `localStorage` update
  $$.addEvent(window, 'storage', function (e) {
    if (e.key === null) {
      // If event is caused by `localStorage.clear()` `key` will be null. Skip this
      // event and reestablish normal behaviour by `.__check_master__()` call.
      self.__check_master__();
      return;
    }

    // IE needs kludge because event fire before data was saved
    if ('onstoragecommit' in document) {
      setTimeout(function () {
        self.__on_changed__(e);
      }, 1);

      return;
    }

    self.__on_changed__(e);
  });

  // Handle page unload (listen `onbeforeunload` and `onunload` to ensure that data is stored successfully)
  // http://stackoverflow.com/questions/3775566/javascript-question-onbeforeunload-or-onunload
  //
  this.__destroyed__ = false;
  $$.addEvent(window, 'beforeunload', function () {
    self.__destroy__();
  });
  $$.addEvent(window, 'unload', function () {
    self.__destroy__();
  });

  // Update current tab info and check master alive
  this.__check_master__();
  Timer.setInterval(function () {
    self.__check_master__();
  }, UPDATE_INTERVAL);

  // Remove outdated lock records
  Timer.setInterval(function () {
    self.__locks_cleanup__();
  }, 1000);
}

// Broadcast message between all clients
//
// - channel (String) - channel name
// - message (Object) - message data
//
Router.prototype.broadcast = function (channel, message) {
  // If it is system subscribe message - update channels list
  if (channel === '!sys.channels.add') {
    this.__router_channels__[message.data.channel] = this.__router_channels__[message.data.channel] || 0;
    this.__router_channels__[message.data.channel]++;
    this.__update_channels_list__();

    return;
  }

  // If it is system unsubscribe message - update channels list
  if (channel === '!sys.channels.remove') {
    this.__router_channels__[message.data.channel] = this.__router_channels__[message.data.channel] || 0;
    this.__router_channels__[message.data.channel]--;
    this.__update_channels_list__();

    return;
  }

  // If it is system lock message - try acquire lock
  if (channel === '!sys.lock.request') {
    this.__lock__(message.data.id, message.id, message.data.timeout);

    return;
  }

  // If it is system unlock message - remove lock data
  if (channel === '!sys.lock.release') {
    this.__ls__.removeItem(this.__lock_prefix__ + message.data.id);

    return;
  }

  var serializedMessage = JSON.stringify({
    channel: channel,
    message: message,

    // Add random to be sure that `localStorage` sent event even new massage is same than previous
    random: Math.floor(Math.random() * 1e10)
  });

  // Add message to `localStorage` to distribute over Router instances
  this.__storage_events_filter__.shift();
  this.__storage_events_filter__.push(this.__namespace__ + 'broadcast' + '_' + serializedMessage);
  this.__ls__.setItem(this.__namespace__ + 'broadcast', serializedMessage);

  // Emit message for all clients and proxies registered on this router
  this.__handlers__.forEach(function (handler) {
    handler(channel, message);
  });
};

// Subscribe handler to all messages
//
Router.prototype.onmessage = function (handler) {
  var self = this;

  this.__handlers__.push(handler);

  // Delay sending events to next tick to allow client initialize handlers
  setTimeout(function () {
    // Sent master info for every new client
    handler('!sys.master', {
      data: {
        node_id: self.__node_id__,
        master_id: self.__master_id__
      },
      node_id: self.__node_id__,
      id: self.__node_id__ + '_' + (self.__last_message_cnt__++)
    });

    // Send channels info
    self.__on_channels_list_changed__();
  }, 0);
};

// Try acquire lock
//
// - lockId (String)
// - requestId (String)
// - timeout (Number)
//
Router.prototype.__lock__ = function (lockId, requestId, timeout) {
  var self = this;
  var lockKey = this.__lock_prefix__ + lockId;
  var lockValue = this.__ls__.getItem(lockKey);

  if (lockValue) {
    try {
      lockValue = JSON.parse(lockValue);
    } catch (__) {
      lockValue = null;
    }
  }

  // If `expire` not in past - lock already acquired, exit here
  if (lockValue && lockValue.expire > Date.now()) {
    return;
  }

  // Try acquire lock
  this.__ls__.setItem(lockKey, JSON.stringify({ expire: timeout + Date.now(), requestId: requestId }));

  // Read lock value again after 30 ms to check `requestId` (race condition
  // here - other tab may rewrite value in store). Delay needed to ensure that
  // localStorage's data synchronized
  setTimeout(function () {
    lockValue = self.__ls__.getItem(lockKey);

    if (lockValue) {
      try {
        lockValue = JSON.parse(lockValue);
      } catch (__) {
        lockValue = null;
      }
    }

    // If `requestId` is not same - other tab acquire lock, exit here
    if (!lockValue || lockValue.requestId !== requestId) {
      return;
    }

    // Here lock acquired - send message to clients
    self.__handlers__.forEach(function (handler) {
      handler('!sys.lock.acquired', {
        data: {
          request_id: requestId
        },
        node_id: self.__node_id__,
        id: self.__node_id__ + '_' + (self.__last_message_cnt__++)
      });
    });
  }, 30);
};

// Remove outdated lock records from storage
//
Router.prototype.__locks_cleanup__ = function () {
  for (var i = 0, key, val; i < this.__ls__.length; i++) {
    key = this.__ls__.key(i);

    // Filter localStorage records by prefix
    if (key.indexOf(this.__lock_prefix__) !== 0) {
      continue;
    }

    val = this.__ls__.getItem(key);

    try {
      val = JSON.parse(val);
    } catch (__) {
      val = null;
    }

    // If lock expire or record is broken - remove it
    if (!val || val.expire < Date.now()) {
      this.__ls__.removeItem(key);
    }
  }
};

// Update master id, if current tab is master - init connect and subscribe channels
//
Router.prototype.__on_master_changed__ = function (newMasterID) {
  var self = this;

  // If master tab closed
  if (!newMasterID) {
    // Select random master (tab with smallest ID becomes master)
    if (this.__get_alive_router_ids__().sort()[0] === this.__node_id__) {
      this.__storage_events_filter__.pop();
      this.__storage_events_filter__.push(this.__namespace__ + 'master' + '_' + this.__node_id__);
      this.__ls__.setItem(this.__namespace__ + 'master', this.__node_id__);
      this.__on_master_changed__(this.__node_id__);
    }
    return;
  }

  this.__master_id__ = +newMasterID;

  this.__handlers__.forEach(function (handler) {
    handler('!sys.master', {
      data: {
        node_id: self.__node_id__,
        master_id: self.__master_id__
      },
      node_id: self.__node_id__,
      id: self.__node_id__ + '_' + (self.__last_message_cnt__++)
    });
  });

  // Update channels list for this tab before sending channels info. Needed if
  // master changed by calling `localStorage.clear()`
  self.__update_channels_list__(true);

  // Send channels info
  self.__on_channels_list_changed__();
};

// localStorage change handler. Updates master ID, receive subscribe requests
//
Router.prototype.__on_changed__ = function (e) {

  // IE broadcasts storage events also to the same window, we should filter that messages
  if (this.__storage_events_filter__.indexOf(e.key + '_' + e.newValue) !== -1) {
    return;
  }

  // Master changed
  if (e.key === this.__namespace__ + 'master') {
    this.__on_master_changed__(e.newValue);
  }

  // Channels list changed
  if (e.key.indexOf(this.__router_channels_prefix__) === 0) {
    this.__on_channels_list_changed__();
  }

  // Emit message for all clients and proxies registered on this router
  if (e.key === this.__namespace__ + 'broadcast') {
    var data = JSON.parse(e.newValue);

    this.__handlers__.forEach(function (handler) {
      handler(data.channel, data.message);
    });
  }
};

// Page unload handler. Remove tab data from store
//
Router.prototype.__destroy__ = function () {
  if (this.__destroyed__) {
    return;
  }

  this.__destroyed__ = true;

  this.__ls__.removeItem(this.__router_id_prefix__ + this.__node_id__);
  this.__ls__.removeItem(this.__router_channels_prefix__ + this.__node_id__);

  if (this.__master_id__ === this.__node_id__) {
    this.__ls__.removeItem(this.__namespace__ + 'master');
  }
};

// Get alive tabs IDs and remove timeouted tabs
//
Router.prototype.__get_alive_router_ids__ = function () {
  var maxTime = Date.now() - TIMEOUT;
  var id;
  var routersIDs = [];

  for (var i = 0, key; i < this.__ls__.length; i++) {
    key = this.__ls__.key(i);

    // Filter localStorage records by prefix
    if (key.indexOf(this.__router_id_prefix__) !== 0) {
      continue;
    }

    id = +key.substr(this.__router_id_prefix__.length);

    // Check router is alive and remove if not
    if (this.__ls__.getItem(key) < maxTime) {
      this.__ls__.removeItem(key);
      this.__ls__.removeItem(this.__router_channels_prefix__ + id);
      continue;
    }

    routersIDs.push(id);
  }

  return routersIDs;
};

// Update tab channels list
//
Router.prototype.__update_channels_list__ = function (silent) {
  var self = this;
  var channels = [];

  Object.keys(this.__router_channels__).forEach(function (channel) {
    if (self.__router_channels__[channel] > 0) {
      channels.push(channel);
    }
  });

  var serializedChannels = JSON.stringify(channels.sort());

  // Update channels list if changed
  if (this.__ls__.getItem(this.__router_channels_prefix__ + this.__node_id__) !== serializedChannels) {
    this.__storage_events_filter__.pop();
    this.__storage_events_filter__.push(this.__router_channels_prefix__ + this.__node_id__ + '_' + serializedChannels);
    this.__ls__.setItem(this.__router_channels_prefix__ + this.__node_id__, serializedChannels);
    if (!silent) {
      this.__on_channels_list_changed__();
    }
  }
};

// Update subscribes if channels list changed (run only on master)
//
Router.prototype.__on_channels_list_changed__ = function () {
  var self = this;
  var channels = [];

  for (var i = 0, key; i < this.__ls__.length; i++) {
    key = this.__ls__.key(i);

    // Filter localStorage records by prefix
    if (key.indexOf(this.__router_channels_prefix__) !== 0) {
      continue;
    }

    channels = channels.concat(JSON.parse(this.__ls__.getItem(key)));
  }

  // Get unique channels names
  channels = channels.reduce(function (result, item) {
    if (result.indexOf(item) === -1) {
      result.push(item);
    }
    return result;
  }, []);

  this.__handlers__.forEach(function (handler) {
    handler('!sys.channels.refresh', {
      id: self.__node_id__ + '_' + (self.__last_message_cnt__++),
      node_id: self.__node_id__,
      data: {
        channels: channels
      }
    });
  });
};

// Update tab livetime and become master if not exists
//
Router.prototype.__check_master__ = function () {
  // Update current tab time
  this.__ls__.setItem(this.__router_id_prefix__ + this.__node_id__, Date.now());

  // Update local value of master ID
  this.__master_id__ = +this.__ls__.getItem(this.__namespace__ + 'master');

  // If master tab not found - become master
  if (this.__get_alive_router_ids__().indexOf(this.__master_id__) === -1) {
    this.__storage_events_filter__.pop();
    this.__storage_events_filter__.push(this.__namespace__ + 'master' + '_' + this.__node_id__);
    this.__ls__.setItem(this.__namespace__ + 'master', this.__node_id__);
    this.__on_master_changed__(this.__node_id__);
  }
};

module.exports = Router;
<|endoftext|>


<commit_before>import Reflux from 'reflux';
import actions from 'js/actions';
import {
  t,
  notify,
  assign
} from 'js/utils';

// TODO instead of this use `stateChanges` function from '/js/utils'
// after https://github.com/kobotoolbox/kpi/pull/1959 is merged
function stateChanges(orig_obj, new_obj) {
  var out = {},
      any = false;
  Object.keys(new_obj).forEach(function(key) {
    if (orig_obj[key] !== new_obj[key]) {
      out[key] = new_obj[key];
      any = true;
    }
  });
  if (!any) {
    return false;
  }
  return out;
}

// TODO: this data should come from backend
const MOCK_ASSIGNABLE_PERMISSIONS = {
  view_asset: {id: 'view_asset', label: 'View Form'},
  change_asset: {id: 'change_asset', label: 'Edit Form'},
  add_submissions: {id: 'add_submissions', label: 'Add Submissions'},
  view_submissions: {id: 'view_submissions', label: 'View Submissions'},
  partial_view_submissions: {id: 'partial_view_submissions', label: 'Restrict to submissions made by certain users'},
  change_submissions: {id: 'change_submissions', label: 'Edit Submissions'},
  validate_submissions: {id: 'validate_submissions', label: 'Validate Submissions'}
};
const MOCK_IMPLIED_PERMISSIONS = {
  change_asset: ['view_asset'],
  add_submissions: ['view_asset'],
  view_submissions: ['view_asset'],
  change_submissions: ['view_submissions'],
  validate_submissions: ['view_submissions']
};

const permConfig = Reflux.createStore({
  init() {
    this.isReady = false;
    this.state = {
      implied: MOCK_IMPLIED_PERMISSIONS,
      assignable: MOCK_ASSIGNABLE_PERMISSIONS
    };
    this.listenTo(actions.permissions.getConfig.completed, this.onGetConfigCompleted);
    this.listenTo(actions.permissions.getConfig.failed, this.onGetConfigFailed);
    actions.permissions.getConfig();
  },
  setState (change) {
    const changed = stateChanges(this.state, change);
    if (changed) {
      assign(this.state, changed);
      this.trigger(changed);
    }
  },
  onGetConfigCompleted(response) {
    console.debug('permConfig getConfig cmpleted', response);
    // this.setState(response);
    this.isReady = true;
  },
  onGetConfigFailed() {
    notify('Failed to get permissions config!', 'error');
  },
  getPermission(permissionId) {
    this.verifyReady();
    return this.state.assignable[permissionId];
  },
  getImpliedPermissions(permissionId) {
    this.verifyReady();
    return this.state.implied[permissionId];
  },
  getAssignablePermissions() {
    this.verifyReady();
    return this.state.assignable;
  },
  verifyReady() {
    if (!this.isReady) {
      throw new Error(t('Permission config is not ready or failed to initialize!'));
    }
  },
  getAvailablePermissions(assetType) {
    if (assetType === 'survey') {
      return [
        {value: 'view', label: t('View Form')},
        {value: 'change', label: t('Edit Form')},
        {value: 'view_submissions', label: t('View Submissions')},
        {value: 'add_submissions', label: t('Add Submissions')},
        {value: 'change_submissions', label: t('Edit Submissions')},
        {value: 'validate_submissions', label: t('Validate Submissions')}
      ];
    } else {
      return [
        {value: 'view', label: t('View')},
        {value: 'change', label: t('Edit')},
      ];
    }
  }
});

export default permConfig;
<commit_msg>up implied permissions mock<commit_after>import Reflux from 'reflux';
import actions from 'js/actions';
import {
  t,
  notify,
  assign
} from 'js/utils';

// TODO instead of this use `stateChanges` function from '/js/utils'
// after https://github.com/kobotoolbox/kpi/pull/1959 is merged
function stateChanges(orig_obj, new_obj) {
  var out = {},
      any = false;
  Object.keys(new_obj).forEach(function(key) {
    if (orig_obj[key] !== new_obj[key]) {
      out[key] = new_obj[key];
      any = true;
    }
  });
  if (!any) {
    return false;
  }
  return out;
}

// TODO: this data should come from backend
const MOCK_ASSIGNABLE_PERMISSIONS = {
  view_asset: {id: 'view_asset', label: 'View Form'},
  change_asset: {id: 'change_asset', label: 'Edit Form'},
  add_submissions: {id: 'add_submissions', label: 'Add Submissions'},
  view_submissions: {id: 'view_submissions', label: 'View Submissions'},
  partial_submissions: {id: 'partial_submissions', label: 'Restrict to submissions made by certain users'},
  change_submissions: {id: 'change_submissions', label: 'Edit Submissions'},
  validate_submissions: {id: 'validate_submissions', label: 'Validate Submissions'}
};
const MOCK_IMPLIED_PERMISSIONS = {
  view_asset: [],
  change_asset: ['view_asset'],
  add_submissions: ['view_asset'],
  view_submissions: ['view_asset'],
  partial_submissions: ['view_asset'],
  change_submissions: ['view_submissions'],
  validate_submissions: ['view_submissions']
};

const permConfig = Reflux.createStore({
  init() {
    this.isReady = false;
    this.state = {
      implied: MOCK_IMPLIED_PERMISSIONS,
      assignable: MOCK_ASSIGNABLE_PERMISSIONS
    };
    this.listenTo(actions.permissions.getConfig.completed, this.onGetConfigCompleted);
    this.listenTo(actions.permissions.getConfig.failed, this.onGetConfigFailed);
    actions.permissions.getConfig();
  },
  setState (change) {
    const changed = stateChanges(this.state, change);
    if (changed) {
      assign(this.state, changed);
      this.trigger(changed);
    }
  },
  onGetConfigCompleted(response) {
    console.debug('permConfig getConfig cmpleted', response);
    // this.setState(response);
    this.isReady = true;
  },
  onGetConfigFailed() {
    notify('Failed to get permissions config!', 'error');
  },
  getPermission(permissionId) {
    this.verifyReady();
    return this.state.assignable[permissionId];
  },
  getImpliedPermissions(permissionId) {
    this.verifyReady();
    return this.state.implied[permissionId];
  },
  getAssignablePermissions() {
    this.verifyReady();
    return this.state.assignable;
  },
  verifyReady() {
    if (!this.isReady) {
      throw new Error(t('Permission config is not ready or failed to initialize!'));
    }
  },
  getAvailablePermissions(assetType) {
    if (assetType === 'survey') {
      return [
        {value: 'view', label: t('View Form')},
        {value: 'change', label: t('Edit Form')},
        {value: 'view_submissions', label: t('View Submissions')},
        {value: 'add_submissions', label: t('Add Submissions')},
        {value: 'change_submissions', label: t('Edit Submissions')},
        {value: 'validate_submissions', label: t('Validate Submissions')}
      ];
    } else {
      return [
        {value: 'view', label: t('View')},
        {value: 'change', label: t('Edit')},
      ];
    }
  }
});

export default permConfig;
<|endoftext|>


<commit_before>/*jshint browser: true, jasmine: true */
/*global angular, inject, module, document */

describe('Popover', function () {
    'use strict';

    var $scope,
        $timeout,
        $window,
        body,
        el,
        popoverLink;

    beforeEach(module(
        'ngMock',
        'ui.bootstrap.popover',
        'sky.popover',
        'sky.templates'
    ));

    beforeEach(inject(function (_$window_, _$timeout_) {
        $window = _$window_;
        $timeout = _$timeout_;
    }));

    beforeEach(inject(function (_$templateCache_) {
        _$templateCache_.put('bbPopover/testpopover.html',
            '
' +
            '
{{message}}
' +
            'Close me' +
            '/
');
    }));

    beforeEach(inject(function (_$rootScope_, $compile) {
        $scope = _$rootScope_.$new();
        el = $compile('
{{message}}
')($scope);

        $scope.message = "Hello World";
        $scope.$digest();

        popoverLink = el.find('#popoverTrigger');

        body = $window.document.body;
        el.appendTo(body);
    }));

    afterEach(inject(function () {
        el.remove();
    }));

    function trigger(element, evt) {
        var myEvent = document.createEvent('Event');
        myEvent.initEvent(evt, true, false);
        element[0].dispatchEvent(myEvent);
    }

    describe('directive', function () {
        it('should bind scope data into the template', function () {
            trigger(popoverLink, 'click');
            $scope.$digest();
            expect(el.find('#messageWrapper')).toHaveText($scope.message);
        });

        it('should close when clicking outside of flyout', function () {
            expect(el.find('#messageWrapper').length).toBe(0);

            trigger(popoverLink, 'click');
            $scope.$digest();
            $timeout.flush();

            expect(el.find('#messageWrapper').length).toBe(1);

            angular.element(body).trigger('click');
            $scope.$digest();

            $timeout.flush();
            expect(el.find('#messageWrapper').length).toBe(0);
        });

        it('should not close when clicking inside of flyout', function () {
            expect(el.find('#messageWrapper').length).toBe(0);

            trigger(popoverLink, 'click');
            $scope.$digest();
            $timeout.flush();

            expect(el.find('#messageWrapper').length).toBe(1);

            trigger(el.find('#messageWrapper'), 'click');
            $scope.$digest();

            expect(function () {
                $timeout.flush();
            }).toThrow();

            expect(el.find('#messageWrapper').length).toBe(1);
        });

        it('should close when calling close function on scope', function () {
            expect(el.find('#messageWrapper').length).toBe(0);

            trigger(popoverLink, 'click');
            $scope.$digest();
            $timeout.flush();

            expect(el.find('#messageWrapper').length).toBe(1);

            trigger(el.find('#hidelink'), 'click');
            $scope.$digest();

            $timeout.flush();
            expect(el.find('#messageWrapper').length).toBe(0);
        });
    });
});
<commit_msg>start of popover tests<commit_after>/*jshint browser: true, jasmine: true */
/*global angular, inject, module, document */

describe('Popover', function () {
    'use strict';

    var $scope,
        $timeout,
        $window,
        body,
        el,
        popoverLink,
        $rootScope;

    beforeEach(module(
        'ngMock',
        'ui.bootstrap.popover',
        'sky.popover',
        'sky.templates'
    ));

    beforeEach(inject(function (_$window_, _$timeout_) {
        $window = _$window_;
        $timeout = _$timeout_;
    }));

    beforeEach(inject(function (_$templateCache_) {
        _$templateCache_.put('bbPopover/testpopover.html',
            '
' +
            '
{{message}}
' +
            'Close me' +
            '/
');
    }));

    beforeEach(inject(function (_$rootScope_, $compile) {
        $scope = _$rootScope_.$new();
        $rootScope = _$rootScope_;
        el = $compile('
{{message}}
')($scope);

        body = $window.document.body;
        el.appendTo(body);
        $scope.$digest();
        $scope.message = "Hello World";
        $scope.$digest();

        popoverLink = el.find('#popoverTrigger');

    }));
    afterEach(inject(function () {
        el.remove();
    }));

    function trigger(element, evt) {
        var myEvent = document.createEvent('Event');
        myEvent.initEvent(evt, true, false);
        element[0].dispatchEvent(myEvent);
        $rootScope.$digest();
    }

    describe('directive', function () {
        it('should bind scope data into the template', function () {
            trigger(popoverLink, 'focus');
            $scope.$digest();
            expect(el.find('#messageWrapper')).toHaveText($scope.message);

        });

        it('should close when clicking outside of flyout', function () {
            expect(el.find('#messageWrapper').length).toBe(0);

            trigger(popoverLink, 'focus');
            $scope.$digest();

            expect(el.find('#messageWrapper').length).toBe(1);
            trigger(popoverLink, 'blur');
            $scope.$digest();
            $timeout.flush();

            expect(el.find('#messageWrapper').length).toBe(0);
        });

        it('should close when calling close function on scope', function () {
            expect(el.find('#messageWrapper').length).toBe(0);

            trigger(popoverLink, 'focus');
            $scope.$digest();

            expect(el.find('#messageWrapper').length).toBe(1);

            trigger(el.find('#hidelink'), 'click');
            $scope.$digest();
            $timeout.flush();

            expect(el.find('#messageWrapper').length).toBe(0);
        });
    });
});
<|endoftext|>


<commit_before>'use strict';

const Promise = require('bluebird');
const fetch = require('node-fetch');
const ms = require('ms');
const NodeCache = require('node-cache');
const query = require('./db');

fetch.Promise = Promise;

const responseCache = new NodeCache({ stdTTL: 180 });

if (process.env.ICECAST_STATUS_URL) {
  const fetchStreamStats = () => {
    console.log('Fetching Stream Stats');

    return fetch(process.env.ICECAST_STATUS_URL)
    .then(res => {
      if (!res.ok) {
        return Promise.reject(new Error(`Could not fetch stream status. Response code ${res.status}`));
      }

      return res.json();
    })
    .then(json => query(
        'INSERT INTO whrb_stats_v2(stats) VALUES($1)',
        [json.icestats]
      )
    )
    .catch(err => console.error('Error fetching stream stats', err));
  };

  const intervalMs = ms(process.env.STATS_INTERVAL || '5m');
  const intervalMins = intervalMs / 60000;

  const now = new Date();
  const nextTime = new Date();
  const currentMinute = now.getMinutes();
  const nextMinute = currentMinute + intervalMins - (currentMinute % intervalMins);
  nextTime.setMinutes(nextMinute, 0, 0);

  const diff = nextTime - now;
  console.log(`Starting data fetch in ${diff / 1000} seconds`);
  setTimeout(() => {
    fetchStreamStats();
    setInterval(fetchStreamStats, intervalMs);
  }, diff);
}

// middleware for fetching stream stats
module.exports = (req, res) => {
  const cached = responseCache.get('stats');
  if (cached) {
    console.log('returning cached value');
    return res.json(cached);
  }

  return query('SELECT timestamp, stats FROM whrb_stats_v2 WHERE timestamp > current_date - interval \'8 days\' ORDER BY timestamp ASC')
  .spread((rows, result, error) => {
    if (error) {
      return Promise.reject(error);
    }

    const results = {
      Total: [],
    };

    rows.forEach(entry => {
      let total = 0;

      if (!entry.stats.source) {
        return;
      }

      entry.stats.source.forEach(statsRecord => {
        total += statsRecord.listeners;
        if (!Array.isArray(results[statsRecord.server_name])) {
          results[statsRecord.server_name] = [];
        }

        const item = { timestamp: entry.timestamp, listeners: statsRecord.listeners };
        results[statsRecord.server_name].push(item);
      });

      results.Total.push({ timestamp: entry.timestamp, listeners: total });
    });

    responseCache.set('stats', results);
    return res.json(results);
  })
  .catch(err => {
    console.error(err);
    res.status(500).json({ message: 'error getting stream stats' });
  });
};
<commit_msg>fix: drop TTL to 30s for stream stats.<commit_after>'use strict';

const Promise = require('bluebird');
const fetch = require('node-fetch');
const ms = require('ms');
const NodeCache = require('node-cache');
const query = require('./db');

fetch.Promise = Promise;

const responseCache = new NodeCache({ stdTTL: 30 });

if (process.env.ICECAST_STATUS_URL) {
  const fetchStreamStats = () => {
    console.log('Fetching Stream Stats');

    return fetch(process.env.ICECAST_STATUS_URL)
    .then(res => {
      if (!res.ok) {
        return Promise.reject(new Error(`Could not fetch stream status. Response code ${res.status}`));
      }

      return res.json();
    })
    .then(json => query(
        'INSERT INTO whrb_stats_v2(stats) VALUES($1)',
        [json.icestats]
      )
    )
    .catch(err => console.error('Error fetching stream stats', err));
  };

  const intervalMs = ms(process.env.STATS_INTERVAL || '5m');
  const intervalMins = intervalMs / 60000;

  const now = new Date();
  const nextTime = new Date();
  const currentMinute = now.getMinutes();
  const nextMinute = currentMinute + intervalMins - (currentMinute % intervalMins);
  nextTime.setMinutes(nextMinute, 0, 0);

  const diff = nextTime - now;
  console.log(`Starting data fetch in ${diff / 1000} seconds`);
  setTimeout(() => {
    fetchStreamStats();
    setInterval(fetchStreamStats, intervalMs);
  }, diff);
}

// middleware for fetching stream stats
module.exports = (req, res) => {
  const cached = responseCache.get('stats');
  if (cached) {
    console.log('returning cached value');
    return res.json(cached);
  }

  return query('SELECT timestamp, stats FROM whrb_stats_v2 WHERE timestamp > current_date - interval \'8 days\' ORDER BY timestamp ASC')
  .spread((rows, result, error) => {
    if (error) {
      return Promise.reject(error);
    }

    const results = {
      Total: [],
    };

    rows.forEach(entry => {
      let total = 0;

      if (!entry.stats.source) {
        return;
      }

      entry.stats.source.forEach(statsRecord => {
        total += statsRecord.listeners;
        if (!Array.isArray(results[statsRecord.server_name])) {
          results[statsRecord.server_name] = [];
        }

        const item = { timestamp: entry.timestamp, listeners: statsRecord.listeners };
        results[statsRecord.server_name].push(item);
      });

      results.Total.push({ timestamp: entry.timestamp, listeners: total });
    });

    responseCache.set('stats', results);
    return res.json(results);
  })
  .catch(err => {
    console.error(err);
    res.status(500).json({ message: 'error getting stream stats' });
  });
};
<|endoftext|>


<commit_before>var zmq = require('zmq');

var MAX64 = Math.pow(2, 64)-1;
function generate_secret() {
    return Math.floor(Math.random() * MAX64);
}

function Router(auth_cb) {

    var sock = this.sock = zmq.socket('router');
    sock.setsockopt(zmq.ZMQ_ROUTER_MANDATORY, 1);

    var SECRETS = {};

    sock.on('message', function(sender_id, target_id, data) {
        var out = undefined;
        try {
            var msg = JSON.parse(data);
            if (target_id == "") {
                if (msg.action == 'LOGIN') {
                    function auth_done(success) {
                        var out;
                        if (success) {
                            var secret = SECRETS[sender_id] =
                                generate_secret();
                            out = {
                                action: 'SECRET',
                                data: secret
                            };
                        } else {
                            out = {
                                err: 'authorization failed'
                            };
                        }
                        try {
                            sock.send([sender_id, sender_id,
                                       JSON.stringify(out)]);
                        } catch(err) {
                            console.log("Router errored sending secret.");
                        }
                    }

                    if (auth_cb) {
                        auth_cb(msg, auth_done);
                    } else {
                        auth_done(true);
                    }
                    return;
                } else {
                    target_id = sender_id;
                    out = {
                        err: 'invalid command'
                    };
                }
            } else {
                if ((msg.secret == undefined) ||
                    (SECRETS[sender_id] != msg.secret)) {
                    target_id = sender_id;
                    out = {
                        err: 'bad secret',
                        seq: msg.seq
                    };
                } else {
                    out = msg;
                    delete out.secret;
                }
            }

            if (out != undefined) {
                try {
                    sock.send([target_id, sender_id, JSON.stringify(out)]);
                } catch(err) {
                    out = {
                        err: 'scanner not connected',
                        seq: msg.seq
                    };
                    try {
                        sock.send([sender_id, sender_id, JSON.stringify(out)]);
                    } catch(err) {
                        console.log("Router errored sendering error back.");
                    }
                }
            } else {
                console.log("error in routing message.");
                return;
            }
        } catch(err) {
            console.log("QeueueRouter.message failed: "+err);
        }
    });
}

Router.prototype.start = function(listen_address) {
    this.listen_address = listen_address;
    this.sock.bindSync(listen_address);
};

Router.prototype.stop = function() {
    this.sock.unbind(this.listen_address);
    delete this.listen_address;
};

module.exports = Router;

<commit_msg>Pass sender_id in auth callback.<commit_after>var zmq = require('zmq');

var MAX64 = Math.pow(2, 64)-1;
function generate_secret() {
    return Math.floor(Math.random() * MAX64);
}

function Router(auth_cb) {

    var sock = this.sock = zmq.socket('router');
    sock.setsockopt(zmq.ZMQ_ROUTER_MANDATORY, 1);

    var SECRETS = {};

    sock.on('message', function(sender_id, target_id, data) {
        var out = undefined;
        try {
            var msg = JSON.parse(data);
            if (target_id == "") {
                if (msg.action == 'LOGIN') {
                    function auth_done(success) {
                        var out;
                        if (success) {
                            var secret = SECRETS[sender_id] =
                                generate_secret();
                            out = {
                                action: 'SECRET',
                                data: secret
                            };
                        } else {
                            out = {
                                err: 'authorization failed'
                            };
                        }
                        try {
                            sock.send([sender_id, sender_id,
                                       JSON.stringify(out)]);
                        } catch(err) {
                            console.log("Router errored sending secret.");
                        }
                    }

                    if (auth_cb) {
                        auth_cb(sender_id, msg, auth_done);
                    } else {
                        auth_done(true);
                    }
                    return;
                } else {
                    target_id = sender_id;
                    out = {
                        err: 'invalid command'
                    };
                }
            } else {
                if ((msg.secret == undefined) ||
                    (SECRETS[sender_id] != msg.secret)) {
                    target_id = sender_id;
                    out = {
                        err: 'bad secret',
                        seq: msg.seq
                    };
                } else {
                    out = msg;
                    delete out.secret;
                }
            }

            if (out != undefined) {
                try {
                    sock.send([target_id, sender_id, JSON.stringify(out)]);
                } catch(err) {
                    out = {
                        err: 'scanner not connected',
                        seq: msg.seq
                    };
                    try {
                        sock.send([sender_id, sender_id, JSON.stringify(out)]);
                    } catch(err) {
                        console.log("Router errored sendering error back.");
                    }
                }
            } else {
                console.log("error in routing message.");
                return;
            }
        } catch(err) {
            console.log("QeueueRouter.message failed: "+err);
        }
    });
}

Router.prototype.start = function(listen_address) {
    this.listen_address = listen_address;
    this.sock.bindSync(listen_address);
};

Router.prototype.stop = function() {
    this.sock.unbind(this.listen_address);
    delete this.listen_address;
};

module.exports = Router;

<|endoftext|>


<commit_before>import getCaretCoordinates from 'textarea-caret';

import { extend } from 'flarum/extend';
import ComposerBody from 'flarum/components/ComposerBody';
import TextEditor from 'flarum/components/TextEditor';
import TextEditorButton from 'flarum/components/TextEditorButton';
import avatar from 'flarum/helpers/avatar';
import usernameHelper from 'flarum/helpers/username';
import highlight from 'flarum/helpers/highlight';
import KeyboardNavigatable from 'flarum/utils/KeyboardNavigatable';
import { truncate } from 'flarum/utils/string';

import AutocompleteDropdown from './components/AutocompleteDropdown';

export default function addComposerAutocomplete() {
  extend(ComposerBody.prototype, 'config', function(original, isInitialized) {
    if (isInitialized) return;

    const composer = this;
    const $container = $('

');
    const dropdown = new AutocompleteDropdown({items: []});
    const $textarea = this.$('textarea').wrap('

');
    const searched = [];
    let mentionStart;
    let typed;
    let searchTimeout;

    const applySuggestion = function(replacement) {
      const insert = replacement + ' ';

      // When calling setValue(), mentionStart will be set back to 0 so we need to compute this beforehand
      const index = mentionStart - 1 + insert.length;

      const content = composer.content();
      composer.editor.setValue(content.substring(0, mentionStart - 1) + insert + content.substr($textarea[0].selectionStart));

      composer.editor.setSelectionRange(index, index);

      dropdown.hide();
    };

    this.navigator = new KeyboardNavigatable();
    this.navigator
      .when(() => dropdown.active)
      .onUp(() => dropdown.navigate(-1))
      .onDown(() => dropdown.navigate(1))
      .onSelect(dropdown.complete.bind(dropdown))
      .onCancel(dropdown.hide.bind(dropdown))
      .bindTo($textarea);

    $textarea
      .after($container)
      .on('click keyup input', function(e) {
        // Up, down, enter, tab, escape, left, right.
        if ([9, 13, 27, 40, 38, 37, 39].indexOf(e.which) !== -1) return;

        const cursor = this.selectionStart;

        if (this.selectionEnd - cursor > 0) return;

        // Search backwards from the cursor for an '@' symbol. If we find one,
        // we will want to show the autocomplete dropdown!
        const value = this.value;
        mentionStart = 0;
        for (let i = cursor - 1; i >= cursor - 30; i--) {
          const character = value.substr(i, 1);
          if (character === '@') {
            mentionStart = i + 1;
            break;
          }
        }

        dropdown.hide();
        dropdown.active = false;

        if (mentionStart) {
          typed = value.substring(mentionStart, cursor).toLowerCase();

          const makeSuggestion = function(user, replacement, content, className = '') {
            const username = usernameHelper(user);
            if (typed) {
              username.children[0] = highlight(username.children[0], typed);
            }

            return (
              <button className={'PostPreview ' + className}
                onclick={() => applySuggestion(replacement)}
                onmouseenter={function() {
                  dropdown.setIndex($(this).parent().index());
                }}>

                  {avatar(user)}
                  {username} {' '}
                  {content}

              </button>
            );
          };

          const userMatches = function(user) {
            const names = [
              user.username(),
              user.displayName()
            ];

            return names.some(value => value.toLowerCase().substr(0, typed.length) === typed);
          };

          const buildSuggestions = () => {
            const suggestions = [];

            // If the user has started to type a username, then suggest users
            // matching that username.
            if (typed) {
              app.store.all('users').forEach(user => {
                if (!userMatches(user)) return;

                suggestions.push(
                  makeSuggestion(user, '@' + user.username(), '', 'MentionsDropdown-user')
                );
              });
            }

            // If the user is replying to a discussion, or if they are editing a
            // post, then we can suggest other posts in the discussion to mention.
            // We will add the 5 most recent comments in the discussion which
            // match any username characters that have been typed.
            const composerPost = composer.props.post;
            const discussion = (composerPost && composerPost.discussion()) || composer.props.discussion;
            if (discussion) {
              discussion.posts()
                .filter(post => post && post.contentType() === 'comment' && (!composerPost || post.number() < composerPost.number()))
                .sort((a, b) => b.createdAt() - a.createdAt())
                .filter(post => {
                  const user = post.user();
                  return user && userMatches(user);
                })
                .splice(0, 5)
                .forEach(post => {
                  const user = post.user();
                  suggestions.push(
                    makeSuggestion(user, '@' + user.username() + '#' + post.id(), [
                      app.translator.trans('flarum-mentions.forum.composer.reply_to_post_text', {number: post.number()}), ' — ',
                      truncate(post.contentPlain(), 200)
                    ], 'MentionsDropdown-post')
                  );
                });
            }

            if (suggestions.length) {
              dropdown.props.items = suggestions;
              m.render($container[0], dropdown.render());

              dropdown.show();
              const coordinates = getCaretCoordinates(this, mentionStart);
              const width = dropdown.$().outerWidth();
              const height = dropdown.$().outerHeight();
              const parent = dropdown.$().offsetParent();
              let left = coordinates.left;
              let top = coordinates.top - this.scrollTop + 15;
              if (top + height > parent.height()) {
                top = coordinates.top - this.scrollTop - height - 15;
              }
              if (left + width > parent.width()) {
                left = parent.width() - width;
              }
              dropdown.show(left, top);
            } else {
              dropdown.active = false;
              dropdown.hide();
            }
          };

          dropdown.active = true;

          buildSuggestions();

          dropdown.setIndex(0);
          dropdown.$().scrollTop(0);

          clearTimeout(searchTimeout);
          if (typed) {
            searchTimeout = setTimeout(function() {
              const typedLower = typed.toLowerCase();
              if (searched.indexOf(typedLower) === -1) {
                app.store.find('users', {filter: {q: typed}, page: {limit: 5}}).then(() => {
                  if (dropdown.active) buildSuggestions();
                });
                searched.push(typedLower);
              }
            }, 250);
          }
        }
      });
  });

  extend(TextEditor.prototype, 'toolbarItems', function(items) {
    items.add('mention', (
      <TextEditorButton onclick={() => this.insertAtCursor('@')} icon="fas fa-at">
        {app.translator.trans('flarum-mentions.forum.composer.mention_tooltip')}
      </TextEditorButton>
    ));
  });
}
<commit_msg>Stop mentions dropdown from jumping around (#48)<commit_after>import getCaretCoordinates from 'textarea-caret';

import { extend } from 'flarum/extend';
import ComposerBody from 'flarum/components/ComposerBody';
import TextEditor from 'flarum/components/TextEditor';
import TextEditorButton from 'flarum/components/TextEditorButton';
import avatar from 'flarum/helpers/avatar';
import usernameHelper from 'flarum/helpers/username';
import highlight from 'flarum/helpers/highlight';
import KeyboardNavigatable from 'flarum/utils/KeyboardNavigatable';
import { truncate } from 'flarum/utils/string';

import AutocompleteDropdown from './components/AutocompleteDropdown';

export default function addComposerAutocomplete() {
  extend(ComposerBody.prototype, 'config', function(original, isInitialized) {
    if (isInitialized) return;

    const composer = this;
    const $container = $('

');
    const dropdown = new AutocompleteDropdown({items: []});
    const $textarea = this.$('textarea').wrap('

');
    const searched = [];
    let mentionStart;
    let typed;
    let searchTimeout;

    // We store users returned from an API here to preserve order in which they are returned
    // This prevents the user list jumping around while users are returned.
    // We also use a hashset for user IDs to provide O(1) lookup for the users already in the list.
    const returnedUsers = Array.from(app.store.all('users'));
    const returnedUserIds = new Set(returnedUsers.map(u => u.id()));

    const applySuggestion = function(replacement) {
      const insert = replacement + ' ';

      // When calling setValue(), mentionStart will be set back to 0 so we need to compute this beforehand
      const index = mentionStart - 1 + insert.length;

      const content = composer.content();
      composer.editor.setValue(content.substring(0, mentionStart - 1) + insert + content.substr($textarea[0].selectionStart));

      composer.editor.setSelectionRange(index, index);

      dropdown.hide();
    };

    this.navigator = new KeyboardNavigatable();
    this.navigator
      .when(() => dropdown.active)
      .onUp(() => dropdown.navigate(-1))
      .onDown(() => dropdown.navigate(1))
      .onSelect(dropdown.complete.bind(dropdown))
      .onCancel(dropdown.hide.bind(dropdown))
      .bindTo($textarea);

    $textarea
      .after($container)
      .on('click keyup input', function(e) {
        // Up, down, enter, tab, escape, left, right.
        if ([9, 13, 27, 40, 38, 37, 39].indexOf(e.which) !== -1) return;

        const cursor = this.selectionStart;

        if (this.selectionEnd - cursor > 0) return;

        // Search backwards from the cursor for an '@' symbol. If we find one,
        // we will want to show the autocomplete dropdown!
        const value = this.value;
        mentionStart = 0;
        for (let i = cursor - 1; i >= cursor - 30; i--) {
          const character = value.substr(i, 1);
          if (character === '@') {
            mentionStart = i + 1;
            break;
          }
        }

        dropdown.hide();
        dropdown.active = false;

        if (mentionStart) {
          typed = value.substring(mentionStart, cursor).toLowerCase();

          const makeSuggestion = function(user, replacement, content, className = '') {
            const username = usernameHelper(user);
            if (typed) {
              username.children[0] = highlight(username.children[0], typed);
            }

            return (
              <button className={'PostPreview ' + className}
                onclick={() => applySuggestion(replacement)}
                onmouseenter={function() {
                  dropdown.setIndex($(this).parent().index());
                }}>

                  {avatar(user)}
                  {username} {' '}
                  {content}

              </button>
            );
          };

          const userMatches = function(user) {
            const names = [
              user.username(),
              user.displayName()
            ];

            return names.some(value => value.toLowerCase().substr(0, typed.length) === typed);
          };

          const buildSuggestions = () => {
            const suggestions = [];

            // If the user has started to type a username, then suggest users
            // matching that username.
            if (typed) {
              returnedUsers.forEach(user => {
                if (!userMatches(user)) return;

                suggestions.push(
                  makeSuggestion(user, '@' + user.username(), '', 'MentionsDropdown-user')
                );
              });
            }

            // If the user is replying to a discussion, or if they are editing a
            // post, then we can suggest other posts in the discussion to mention.
            // We will add the 5 most recent comments in the discussion which
            // match any username characters that have been typed.
            const composerPost = composer.props.post;
            const discussion = (composerPost && composerPost.discussion()) || composer.props.discussion;
            if (discussion) {
              discussion.posts()
                .filter(post => post && post.contentType() === 'comment' && (!composerPost || post.number() < composerPost.number()))
                .sort((a, b) => b.createdAt() - a.createdAt())
                .filter(post => {
                  const user = post.user();
                  return user && userMatches(user);
                })
                .splice(0, 5)
                .forEach(post => {
                  const user = post.user();
                  suggestions.push(
                    makeSuggestion(user, '@' + user.username() + '#' + post.id(), [
                      app.translator.trans('flarum-mentions.forum.composer.reply_to_post_text', { number: post.number() }), ' — ',
                      truncate(post.contentPlain(), 200)
                    ], 'MentionsDropdown-post')
                  );
                });
            }

            if (suggestions.length) {
              dropdown.props.items = suggestions;
              m.render($container[0], dropdown.render());

              dropdown.show();
              const coordinates = getCaretCoordinates(this, mentionStart);
              const width = dropdown.$().outerWidth();
              const height = dropdown.$().outerHeight();
              const parent = dropdown.$().offsetParent();
              let left = coordinates.left;
              let top = coordinates.top - this.scrollTop + 15;
              if (top + height > parent.height()) {
                top = coordinates.top - this.scrollTop - height - 15;
              }
              if (left + width > parent.width()) {
                left = parent.width() - width;
              }
              dropdown.show(left, top);
            } else {
              dropdown.active = false;
              dropdown.hide();
            }
          };

          dropdown.active = true;

          buildSuggestions();

          dropdown.setIndex(0);
          dropdown.$().scrollTop(0);

          clearTimeout(searchTimeout);
          // Don't send API calls searching for users until at least 2 characters have been typed.
          // This focuses the mention results on users and posts in the discussion.
          if (typed.length > 1) {
            searchTimeout = setTimeout(function() {
              const typedLower = typed.toLowerCase();
              if (searched.indexOf(typedLower) === -1) {
                app.store.find('users', { filter: { q: typed }, page: { limit: 5 } }).then(results => {
                  results.forEach(u => {
                    if (!returnedUserIds.has(u.id())) {
                      returnedUserIds.add(u.id());
                      returnedUsers.push(u);
                    }
                  })
                  if (dropdown.active) buildSuggestions();
                });
                searched.push(typedLower);
              }
            }, 250);
          }
        }
      });
  });

  extend(TextEditor.prototype, 'toolbarItems', function(items) {
    items.add('mention', (
      <TextEditorButton onclick={() => this.insertAtCursor('@')} icon="fas fa-at">
        {app.translator.trans('flarum-mentions.forum.composer.mention_tooltip')}
      </TextEditorButton>
    ));
  });
}
<|endoftext|>