<commit_before>/*
 * A script to convert GraphHopper JSON to GPX with Locus navigation instructions.
 *
 * Copyright © 2015 Tomáš Janoušek.
 *
 * BOOKMARKLET:
 *
 *  javascript:$('body').append($("<script src='https://rawgit.com/liskin/locus-graphhopper-gpx/master/locus-graphhopper-gpx.js'></script>"))
 *
 * LICENSE:
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

function Ǥ( s ) {
	return document.createElementNS( 'http://www.topografix.com/GPX/1/1', s );
}

function Ł( s ) {
	return document.createElementNS( 'http://www.locusmap.eu', 'locus:' + s );
}

function Ŧ( s ) {
	return document.createTextNode( s );
}

function ǤŦ( s, t ) {
	var n = Ǥ( s );
	n.appendChild( Ŧ( t ) );
	return n;
}

function ŁŦ( s, t ) {
	var n = Ł( s );
	n.appendChild( Ŧ( t ) );
	return n;
}

function pointAction( i ) {
	switch ( i.sign ) {
		case -3: return 5;  // TURN_SHARP_LEFT
		case -2: return 4;  // TURN_LEFT
		case -1: return 3;  // TURN_SLIGHT_LEFT
		case  0: return 2;  // CONTINUE_ON_STREET
		case  1: return 6;  // TURN_SLIGHT_RIGHT
		case  2: return 7;  // TURN_RIGHT
		case  3: return 8;  // TURN_SHARP_RIGHT
		case  4: return 24; // FINISH
		case  5: return 50; // REACHED_VIA
		case  6: // USE_ROUNDABOUT
			if ( i.exit_number >= 1 && i.exit_number <= 8 ) {
				return 26 + i.exit_number;
			}
		default:
			throw "signToPointAction: " + i.sign + " unsupported";
	}
}

function jsonToLocusGPXDownload( json ) {
	var path = json.paths[0];
	var is = path.instructions;
	var ps = decodePathGraphHopper(path.points, true)

	var doc = document.implementation.createDocument('http://www.topografix.com/GPX/1/1', 'gpx', null);
	var gpx = doc.children[0];
	gpx.setAttributeNS( 'http://www.w3.org/2000/xmlns/', 'xmlns:locus', 'http://www.locusmap.eu' );
	gpx.setAttributeNS( 'http://www.w3.org/2000/xmlns/', 'xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance' );
	gpx.setAttributeNS( 'http://www.w3.org/2001/XMLSchema-instance', 'xsi:schemaLocation', 'http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd' );
	gpx.setAttribute( 'version', '1.1' );
	gpx.setAttribute( 'creator', 'locus-graphhopper-gpx' );

	for ( var j = 0; j < is.length; ++j ) {
		var i = is[ j ];
		var p = ps[ i.interval[ 0 ] ];

		var wpt = Ǥ( 'wpt' );
		{
			wpt.setAttribute( 'lat', p[ 1 ] );
			wpt.setAttribute( 'lon', p[ 0 ] );

			wpt.appendChild( ǤŦ( 'name', i.text ) );

			var ext = Ǥ( 'extensions' );
			{
				ext.appendChild( ŁŦ( 'rteDistance', Math.round( i.distance ) ) );
				if ( i.time != 0 ) {
					ext.appendChild( ŁŦ( 'rteTime', Math.round( i.time / 1000 ) ) );
					ext.appendChild( ŁŦ( 'rteSpeed', i.distance * 1000 / i.time ) );
				}
				ext.appendChild( ŁŦ( 'rtePointAction', pointAction( i ) ) );
			}
			wpt.appendChild( ext );
		}
		gpx.appendChild( wpt );
	}

	var trk = Ǥ( 'trk' );
	{
		trk.appendChild( ǤŦ( 'name', 'GraphHopper route' ) );

		var ext = Ǥ( 'extensions' );
		{
			ext.appendChild( ŁŦ( 'rteComputeType', '-1' ) );
		}
		trk.appendChild( ext );

		var trkseg = Ǥ( 'trkseg' );
		{
			for ( var j = 0; j < ps.length; ++j ) {
				var trkpt = Ǥ( 'trkpt' );
				{
					var p = ps[ j ];
					trkpt.setAttribute( 'lat', p[ 1 ] );
					trkpt.setAttribute( 'lon', p[ 0 ] );
				}
				trkseg.appendChild( trkpt );
			}
		}
		trk.appendChild( trkseg );
	}
	gpx.appendChild( trk );

	var data = '<?xml version="1.0" encoding="utf-8" standalone="yes" ?>' +
		new XMLSerializer().serializeToString( gpx );

	var blob = new Blob( [ data ], { type: "application/gpx+xml;charset=utf-8" } );
	saveAs( blob, "graphhopper_route.gpx" );
}

function jsonToLocusGPXMain() {
	if (!XMLHttpRequest.hasLocusSend) {
		XMLHttpRequest.hasLocusSend = 1;
		var oldSend = XMLHttpRequest.prototype.send;
		XMLHttpRequest.prototype.send = function () {
			this.addEventListener("load", function () {
				jsonToLocusGPXDownload(JSON.parse(this.responseText))
			})
			oldSend.apply(this, arguments);
		}
	}
	$('#searchButton')[0].click()
}

/*
 * FileSaver.js:
 *
 * Copyright © 2015 Eli Grey.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
/*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */
var saveAs=saveAs||"undefined"!=typeof navigator&&navigator.msSaveOrOpenBlob&&navigator.msSaveOrOpenBlob.bind(navigator)||function(e){"use strict";if("undefined"==typeof navigator||!/MSIE [1-9]\./.test(navigator.userAgent)){var t=e.document,n=function(){return e.URL||e.webkitURL||e},o=t.createElementNS("http://www.w3.org/1999/xhtml","a"),r="download"in o,i=function(n){var o=t.createEvent("MouseEvents");o.initMouseEvent("click",!0,!1,e,0,0,0,0,0,!1,!1,!1,!1,0,null),n.dispatchEvent(o)},a=e.webkitRequestFileSystem,c=e.requestFileSystem||a||e.mozRequestFileSystem,s=function(t){(e.setImmediate||e.setTimeout)(function(){throw t},0)},u="application/octet-stream",f=0,d=500,l=function(t){var o=function(){"string"==typeof t?n().revokeObjectURL(t):t.remove()};e.chrome?o():setTimeout(o,d)},v=function(e,t,n){t=[].concat(t);for(var o=t.length;o--;){var r=e["on"+t[o]];if("function"==typeof r)try{r.call(e,n||e)}catch(i){s(i)}}},p=function(t,s){var d,p,w,y=this,m=t.type,S=!1,h=function(){v(y,"writestart progress write writeend".split(" "))},O=function(){if((S||!d)&&(d=n().createObjectURL(t)),p)p.location.href=d;else{var o=e.open(d,"_blank");void 0==o&&"undefined"!=typeof safari&&(e.location.href=d)}y.readyState=y.DONE,h(),l(d)},b=function(e){return function(){return y.readyState!==y.DONE?e.apply(this,arguments):void 0}},g={create:!0,exclusive:!1};return y.readyState=y.INIT,s||(s="download"),r?(d=n().createObjectURL(t),o.href=d,o.download=s,i(o),y.readyState=y.DONE,h(),void l(d)):(/^\s*(?:text\/(?:plain|xml)|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(t.type)&&(t=new Blob(["",t],{type:t.type})),e.chrome&&m&&m!==u&&(w=t.slice||t.webkitSlice,t=w.call(t,0,t.size,u),S=!0),a&&"download"!==s&&(s+=".download"),(m===u||a)&&(p=e),c?(f+=t.size,void c(e.TEMPORARY,f,b(function(e){e.root.getDirectory("saved",g,b(function(e){var n=function(){e.getFile(s,g,b(function(e){e.createWriter(b(function(n){n.onwriteend=function(t){p.location.href=e.toURL(),y.readyState=y.DONE,v(y,"writeend",t),l(e)},n.onerror=function(){var e=n.error;e.code!==e.ABORT_ERR&&O()},"writestart progress write abort".split(" ").forEach(function(e){n["on"+e]=y["on"+e]}),n.write(t),y.abort=function(){n.abort(),y.readyState=y.DONE},y.readyState=y.WRITING}),O)}),O)};e.getFile(s,{create:!1},b(function(e){e.remove(),n()}),b(function(e){e.code===e.NOT_FOUND_ERR?n():O()}))}),O)}),O)):void O())},w=p.prototype,y=function(e,t){return new p(e,t)};return w.abort=function(){var e=this;e.readyState=e.DONE,v(e,"abort")},w.readyState=w.INIT=0,w.WRITING=1,w.DONE=2,w.error=w.onwritestart=w.onprogress=w.onwrite=w.onabort=w.onerror=w.onwriteend=null,y}}("undefined"!=typeof self&&self||"undefined"!=typeof window&&window||this.content);"undefined"!=typeof module&&module.exports?module.exports.saveAs=saveAs:"undefined"!=typeof define&&null!==define&&null!=define.amd&&define([],function(){return saveAs});
/* end of FileSaver.js */

/*
 * GHRequest.js:
 *
 * https://github.com/graphhopper/graphhopper/blob/c0105cdd3d367808b49d61314742742143ecde05/web/src/main/webapp/js/graphhopper/GHRequest.js
 * https://github.com/graphhopper/graphhopper/blob/c0105cdd3d367808b49d61314742742143ecde05/LICENSE.txt
 */
function decodePathGraphHopper(encoded, is3D) {
    var len = encoded.length;
    var index = 0;
    var array = [];
    var lat = 0;
    var lng = 0;
    var ele = 0;

    while (index < len) {
        var b;
        var shift = 0;
        var result = 0;
        do {
            b = encoded.charCodeAt(index++) - 63;
            result |= (b & 0x1f) << shift;
            shift += 5;
        } while (b >= 0x20);
        var deltaLat = ((result & 1) ? ~(result >> 1) : (result >> 1));
        lat += deltaLat;

        shift = 0;
        result = 0;
        do {
            b = encoded.charCodeAt(index++) - 63;
            result |= (b & 0x1f) << shift;
            shift += 5;
        } while (b >= 0x20);
        var deltaLon = ((result & 1) ? ~(result >> 1) : (result >> 1));
        lng += deltaLon;

        if (is3D) {
            // elevation
            shift = 0;
            result = 0;
            do
            {
                b = encoded.charCodeAt(index++) - 63;
                result |= (b & 0x1f) << shift;
                shift += 5;
            } while (b >= 0x20);
            var deltaEle = ((result & 1) ? ~(result >> 1) : (result >> 1));
            ele += deltaEle;
            array.push([lng * 1e-5, lat * 1e-5, ele / 100]);
        } else
            array.push([lng * 1e-5, lat * 1e-5]);
    }
    return array;
}
/* end of GHRequest.js */

jsonToLocusGPXMain();
<commit_msg>Force locale=en on ajax requests<commit_after>/*
 * A script to convert GraphHopper JSON to GPX with Locus navigation instructions.
 *
 * Copyright © 2015 Tomáš Janoušek.
 *
 * BOOKMARKLET:
 *
 *  javascript:$('body').append($("<script src='https://rawgit.com/liskin/locus-graphhopper-gpx/master/locus-graphhopper-gpx.js'></script>"))
 *
 * LICENSE:
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

function Ǥ( s ) {
	return document.createElementNS( 'http://www.topografix.com/GPX/1/1', s );
}

function Ł( s ) {
	return document.createElementNS( 'http://www.locusmap.eu', 'locus:' + s );
}

function Ŧ( s ) {
	return document.createTextNode( s );
}

function ǤŦ( s, t ) {
	var n = Ǥ( s );
	n.appendChild( Ŧ( t ) );
	return n;
}

function ŁŦ( s, t ) {
	var n = Ł( s );
	n.appendChild( Ŧ( t ) );
	return n;
}

function pointAction( i ) {
	switch ( i.sign ) {
		case -3: return 5;  // TURN_SHARP_LEFT
		case -2: return 4;  // TURN_LEFT
		case -1: return 3;  // TURN_SLIGHT_LEFT
		case  0: return 2;  // CONTINUE_ON_STREET
		case  1: return 6;  // TURN_SLIGHT_RIGHT
		case  2: return 7;  // TURN_RIGHT
		case  3: return 8;  // TURN_SHARP_RIGHT
		case  4: return 24; // FINISH
		case  5: return 50; // REACHED_VIA
		case  6: // USE_ROUNDABOUT
			if ( i.exit_number >= 1 && i.exit_number <= 8 ) {
				return 26 + i.exit_number;
			}
		default:
			throw "signToPointAction: " + i.sign + " unsupported";
	}
}

function jsonToLocusGPXDownload( json ) {
	var path = json.paths[0];
	var is = path.instructions;
	var ps = decodePathGraphHopper(path.points, true)

	var doc = document.implementation.createDocument('http://www.topografix.com/GPX/1/1', 'gpx', null);
	var gpx = doc.children[0];
	gpx.setAttributeNS( 'http://www.w3.org/2000/xmlns/', 'xmlns:locus', 'http://www.locusmap.eu' );
	gpx.setAttributeNS( 'http://www.w3.org/2000/xmlns/', 'xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance' );
	gpx.setAttributeNS( 'http://www.w3.org/2001/XMLSchema-instance', 'xsi:schemaLocation', 'http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd' );
	gpx.setAttribute( 'version', '1.1' );
	gpx.setAttribute( 'creator', 'locus-graphhopper-gpx' );

	for ( var j = 0; j < is.length; ++j ) {
		var i = is[ j ];
		var p = ps[ i.interval[ 0 ] ];

		var wpt = Ǥ( 'wpt' );
		{
			wpt.setAttribute( 'lat', p[ 1 ] );
			wpt.setAttribute( 'lon', p[ 0 ] );

			wpt.appendChild( ǤŦ( 'name', i.text ) );

			var ext = Ǥ( 'extensions' );
			{
				ext.appendChild( ŁŦ( 'rteDistance', Math.round( i.distance ) ) );
				if ( i.time != 0 ) {
					ext.appendChild( ŁŦ( 'rteTime', Math.round( i.time / 1000 ) ) );
					ext.appendChild( ŁŦ( 'rteSpeed', i.distance * 1000 / i.time ) );
				}
				ext.appendChild( ŁŦ( 'rtePointAction', pointAction( i ) ) );
			}
			wpt.appendChild( ext );
		}
		gpx.appendChild( wpt );
	}

	var trk = Ǥ( 'trk' );
	{
		trk.appendChild( ǤŦ( 'name', 'GraphHopper route' ) );

		var ext = Ǥ( 'extensions' );
		{
			ext.appendChild( ŁŦ( 'rteComputeType', '-1' ) );
		}
		trk.appendChild( ext );

		var trkseg = Ǥ( 'trkseg' );
		{
			for ( var j = 0; j < ps.length; ++j ) {
				var trkpt = Ǥ( 'trkpt' );
				{
					var p = ps[ j ];
					trkpt.setAttribute( 'lat', p[ 1 ] );
					trkpt.setAttribute( 'lon', p[ 0 ] );
				}
				trkseg.appendChild( trkpt );
			}
		}
		trk.appendChild( trkseg );
	}
	gpx.appendChild( trk );

	var data = '<?xml version="1.0" encoding="utf-8" standalone="yes" ?>' +
		new XMLSerializer().serializeToString( gpx );

	var blob = new Blob( [ data ], { type: "application/gpx+xml;charset=utf-8" } );
	saveAs( blob, "graphhopper_route.gpx" );
}

function jsonToLocusGPXMain() {
	if (!XMLHttpRequest.hasLocusHooks) {
		XMLHttpRequest.hasLocusHooks = 1;

		var oldSend = XMLHttpRequest.prototype.send;
		XMLHttpRequest.prototype.send = function () {
			this.addEventListener("load", function () {
				jsonToLocusGPXDownload(JSON.parse(this.responseText))
			})
			oldSend.apply(this, arguments);
		}

		var oldOpen = XMLHttpRequest.prototype.open;
		XMLHttpRequest.prototype.open = function () {
			var args = arguments;
			args[1] = args[1].replace(/\blocale=\w+/, "locale=en");
			oldOpen.apply(this, args);
		}
	}
	$('#searchButton')[0].click()
}

/*
 * FileSaver.js:
 *
 * Copyright © 2015 Eli Grey.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
/*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */
var saveAs=saveAs||"undefined"!=typeof navigator&&navigator.msSaveOrOpenBlob&&navigator.msSaveOrOpenBlob.bind(navigator)||function(e){"use strict";if("undefined"==typeof navigator||!/MSIE [1-9]\./.test(navigator.userAgent)){var t=e.document,n=function(){return e.URL||e.webkitURL||e},o=t.createElementNS("http://www.w3.org/1999/xhtml","a"),r="download"in o,i=function(n){var o=t.createEvent("MouseEvents");o.initMouseEvent("click",!0,!1,e,0,0,0,0,0,!1,!1,!1,!1,0,null),n.dispatchEvent(o)},a=e.webkitRequestFileSystem,c=e.requestFileSystem||a||e.mozRequestFileSystem,s=function(t){(e.setImmediate||e.setTimeout)(function(){throw t},0)},u="application/octet-stream",f=0,d=500,l=function(t){var o=function(){"string"==typeof t?n().revokeObjectURL(t):t.remove()};e.chrome?o():setTimeout(o,d)},v=function(e,t,n){t=[].concat(t);for(var o=t.length;o--;){var r=e["on"+t[o]];if("function"==typeof r)try{r.call(e,n||e)}catch(i){s(i)}}},p=function(t,s){var d,p,w,y=this,m=t.type,S=!1,h=function(){v(y,"writestart progress write writeend".split(" "))},O=function(){if((S||!d)&&(d=n().createObjectURL(t)),p)p.location.href=d;else{var o=e.open(d,"_blank");void 0==o&&"undefined"!=typeof safari&&(e.location.href=d)}y.readyState=y.DONE,h(),l(d)},b=function(e){return function(){return y.readyState!==y.DONE?e.apply(this,arguments):void 0}},g={create:!0,exclusive:!1};return y.readyState=y.INIT,s||(s="download"),r?(d=n().createObjectURL(t),o.href=d,o.download=s,i(o),y.readyState=y.DONE,h(),void l(d)):(/^\s*(?:text\/(?:plain|xml)|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(t.type)&&(t=new Blob(["",t],{type:t.type})),e.chrome&&m&&m!==u&&(w=t.slice||t.webkitSlice,t=w.call(t,0,t.size,u),S=!0),a&&"download"!==s&&(s+=".download"),(m===u||a)&&(p=e),c?(f+=t.size,void c(e.TEMPORARY,f,b(function(e){e.root.getDirectory("saved",g,b(function(e){var n=function(){e.getFile(s,g,b(function(e){e.createWriter(b(function(n){n.onwriteend=function(t){p.location.href=e.toURL(),y.readyState=y.DONE,v(y,"writeend",t),l(e)},n.onerror=function(){var e=n.error;e.code!==e.ABORT_ERR&&O()},"writestart progress write abort".split(" ").forEach(function(e){n["on"+e]=y["on"+e]}),n.write(t),y.abort=function(){n.abort(),y.readyState=y.DONE},y.readyState=y.WRITING}),O)}),O)};e.getFile(s,{create:!1},b(function(e){e.remove(),n()}),b(function(e){e.code===e.NOT_FOUND_ERR?n():O()}))}),O)}),O)):void O())},w=p.prototype,y=function(e,t){return new p(e,t)};return w.abort=function(){var e=this;e.readyState=e.DONE,v(e,"abort")},w.readyState=w.INIT=0,w.WRITING=1,w.DONE=2,w.error=w.onwritestart=w.onprogress=w.onwrite=w.onabort=w.onerror=w.onwriteend=null,y}}("undefined"!=typeof self&&self||"undefined"!=typeof window&&window||this.content);"undefined"!=typeof module&&module.exports?module.exports.saveAs=saveAs:"undefined"!=typeof define&&null!==define&&null!=define.amd&&define([],function(){return saveAs});
/* end of FileSaver.js */

/*
 * GHRequest.js:
 *
 * https://github.com/graphhopper/graphhopper/blob/c0105cdd3d367808b49d61314742742143ecde05/web/src/main/webapp/js/graphhopper/GHRequest.js
 * https://github.com/graphhopper/graphhopper/blob/c0105cdd3d367808b49d61314742742143ecde05/LICENSE.txt
 */
function decodePathGraphHopper(encoded, is3D) {
    var len = encoded.length;
    var index = 0;
    var array = [];
    var lat = 0;
    var lng = 0;
    var ele = 0;

    while (index < len) {
        var b;
        var shift = 0;
        var result = 0;
        do {
            b = encoded.charCodeAt(index++) - 63;
            result |= (b & 0x1f) << shift;
            shift += 5;
        } while (b >= 0x20);
        var deltaLat = ((result & 1) ? ~(result >> 1) : (result >> 1));
        lat += deltaLat;

        shift = 0;
        result = 0;
        do {
            b = encoded.charCodeAt(index++) - 63;
            result |= (b & 0x1f) << shift;
            shift += 5;
        } while (b >= 0x20);
        var deltaLon = ((result & 1) ? ~(result >> 1) : (result >> 1));
        lng += deltaLon;

        if (is3D) {
            // elevation
            shift = 0;
            result = 0;
            do
            {
                b = encoded.charCodeAt(index++) - 63;
                result |= (b & 0x1f) << shift;
                shift += 5;
            } while (b >= 0x20);
            var deltaEle = ((result & 1) ? ~(result >> 1) : (result >> 1));
            ele += deltaEle;
            array.push([lng * 1e-5, lat * 1e-5, ele / 100]);
        } else
            array.push([lng * 1e-5, lat * 1e-5]);
    }
    return array;
}
/* end of GHRequest.js */

jsonToLocusGPXMain();
<|endoftext|>


<commit_before>/* global process */
import Sequelize from 'sequelize';

const debug = require('debug')('overrustle:database');
let { DB_DB, DB_PATH, NODE_ENV } = process.env;

DB_DB = DB_DB || 'overrustle';
DB_PATH = DB_PATH || './overrustle.sqlite';

debug(`Using database ${DB_DB}:${DB_PATH}`);

export const sequelize = new Sequelize(DB_DB, null, null, {
  dialect: 'sqlite',
  storage: DB_PATH || ':memory:',

  logging: NODE_ENV === 'production' ? false : debug,

  define: {
    paranoid: false,
    underscored: true,
  },
});

// TODO
export const User = sequelize.define('user', {});

export const Stream = sequelize.define('stream', {
  id: {
    type: Sequelize.UUIDV4,
    defaultValue: Sequelize.UUIDV4,
    primaryKey: true,
    unique: true,
    allowNull: false,
    validate: {
      notEmpty: true,
    },
  },
  channel: {
    type: Sequelize.STRING,
    allowNull: false,
    unique: 'channel_service',
    validate: {
      notEmpty: true,
    },
  },
  service: {
    type: Sequelize.STRING,
    allowNull: false,
    unique: 'channel_service',
    validate: {
      notEmpty: true,
    },
  },
  // TODO - virtual getter for this
  overrustle: {
    type: Sequelize.STRING,
  },
  thumbnail: {
    type: Sequelize.STRING,
  },
  live: {
    type: Sequelize.BOOLEAN,
    defaultValue: false,
  },
  // special query for this
  // rustlers: {
  // },
  viewers: { // amount of people the service reports is watching this stream
    type: Sequelize.INTEGER,
    defaultValue: 1,
  },
}, {
  classMethods: {
    async findRustlersFor(id) {
      const [[{ rustlers }]] = await sequelize.query('SELECT COUNT(`rustlers`.`stream_id`) AS `rustlers` FROM `streams` AS `stream` LEFT OUTER JOIN `rustlers` AS `rustlers` ON `stream`.`id` = `rustlers`.`stream_id` WHERE `stream`.`id` = ? GROUP BY `stream`.`id` LIMIT 1;', {
        replacements: [id],
      });
      return rustlers;
    },
    async findAllWithRustlers() {
      const [ streams ] = await sequelize.query('SELECT `stream`.*, COUNT(`rustlers`.`stream_id`) AS `rustlers` FROM `streams` AS `stream` LEFT OUTER JOIN `rustlers` AS `rustlers` ON `stream`.`id` = `rustlers`.`stream_id` GROUP BY `stream`.`id`;');
      return streams;
    },
  },
});

export const Rustler = sequelize.define('rustler', {
  id: {
    type: Sequelize.UUIDV4,
    defaultValue: Sequelize.UUIDV4,
    primaryKey: true,
    unique: true,
    allowNull: false,
    validate: {
      notEmpty: true,
    },
  },
  stream_id: { // the Stream that the rustler is watching
    type: Sequelize.UUIDV4,
    references: {
      model: 'streams',
      key: 'id',
    },
    onUpdate: 'CASCADE',
    onDelete: 'SET NULL',
  },
});

User.sync();
Rustler.sync({ force: true });
Stream.sync();

Rustler.belongsTo(Stream, { as: 'stream' });
Stream.hasMany(Rustler);
<commit_msg>drop stream on reboot too<commit_after>/* global process */
import Sequelize from 'sequelize';

const debug = require('debug')('overrustle:database');
let { DB_DB, DB_PATH, NODE_ENV } = process.env;

DB_DB = DB_DB || 'overrustle';
DB_PATH = DB_PATH || './overrustle.sqlite';

debug(`Using database ${DB_DB}:${DB_PATH}`);

export const sequelize = new Sequelize(DB_DB, null, null, {
  dialect: 'sqlite',
  storage: DB_PATH || ':memory:',

  logging: NODE_ENV === 'production' ? false : debug,

  define: {
    paranoid: false,
    underscored: true,
  },
});

// TODO
export const User = sequelize.define('user', {});

export const Stream = sequelize.define('stream', {
  id: {
    type: Sequelize.UUIDV4,
    defaultValue: Sequelize.UUIDV4,
    primaryKey: true,
    unique: true,
    allowNull: false,
    validate: {
      notEmpty: true,
    },
  },
  channel: {
    type: Sequelize.STRING,
    allowNull: false,
    unique: 'channel_service',
    validate: {
      notEmpty: true,
    },
  },
  service: {
    type: Sequelize.STRING,
    allowNull: false,
    unique: 'channel_service',
    validate: {
      notEmpty: true,
    },
  },
  // TODO - virtual getter for this
  overrustle: {
    type: Sequelize.STRING,
  },
  thumbnail: {
    type: Sequelize.STRING,
  },
  live: {
    type: Sequelize.BOOLEAN,
    defaultValue: false,
  },
  // special query for this
  // rustlers: {
  // },
  viewers: { // amount of people the service reports is watching this stream
    type: Sequelize.INTEGER,
    defaultValue: 1,
  },
}, {
  classMethods: {
    async findRustlersFor(id) {
      const [[{ rustlers }]] = await sequelize.query('SELECT COUNT(`rustlers`.`stream_id`) AS `rustlers` FROM `streams` AS `stream` LEFT OUTER JOIN `rustlers` AS `rustlers` ON `stream`.`id` = `rustlers`.`stream_id` WHERE `stream`.`id` = ? GROUP BY `stream`.`id` LIMIT 1;', {
        replacements: [id],
      });
      return rustlers;
    },
    async findAllWithRustlers() {
      const [ streams ] = await sequelize.query('SELECT `stream`.*, COUNT(`rustlers`.`stream_id`) AS `rustlers` FROM `streams` AS `stream` LEFT OUTER JOIN `rustlers` AS `rustlers` ON `stream`.`id` = `rustlers`.`stream_id` GROUP BY `stream`.`id`;');
      return streams;
    },
  },
});

export const Rustler = sequelize.define('rustler', {
  id: {
    type: Sequelize.UUIDV4,
    defaultValue: Sequelize.UUIDV4,
    primaryKey: true,
    unique: true,
    allowNull: false,
    validate: {
      notEmpty: true,
    },
  },
  stream_id: { // the Stream that the rustler is watching
    type: Sequelize.UUIDV4,
    references: {
      model: 'streams',
      key: 'id',
    },
    onUpdate: 'CASCADE',
    onDelete: 'SET NULL',
  },
});

User.sync();
Rustler.sync({ force: true });
Stream.sync({ force: true });

Rustler.belongsTo(Stream, { as: 'stream' });
Stream.hasMany(Rustler);
<|endoftext|>


<commit_before>import React, { Component } from 'react';

class DropdownContainer extends Component {
  constructor(props) {
    super(props);
    this.state = {
      isClicked: false,
      isExpanded: false
    };
  }

  onClick = () => {
    this.setState({
      isClicked: true,
      isExpanded: !this.state.isExpanded
    });
  };

  handleBodyClick = e => {
    console.log('handleBodyClick', e.target.id);
    if (e.target.id !== 'trigger') {
      this.setState({ isExpanded: false, isClicked: false });
    }
  };

  componentDidMount() {
    document.addEventListener('click', this.handleBodyClick);
  }

  componentWillUnmount() {
    document.removeEventListener('click', this.handleBodyClick);
  }

  render() {
    return (

        <DropdownTrigger
          isExpanded={this.state.isExpanded}
          isClicked={this.state.isClicked}
          onSelect={() => this.onClick()}
        />
        <DropdownItems
          isExpanded={this.state.isExpanded}
          visible={this.state.isClicked}
        />

    );
  }
}

class DropdownTrigger extends Component {
  render() {
    let triggerStyle = this.props.isClicked && this.props.isExpanded
      ? {
          backgroundColor: '#EEEEEE',
          width: '78px',
          cursor: 'pointer',
          display: 'flex',
          wordWrap: 'breakWord',
          justifyContent: 'center',
          paddingTop: '10.5px'
        }
      : { cursor: 'pointer', justifyContent: 'center', width: '78px' };
    let caretStyle = this.props.isClicked && this.props.isExpanded
      ? {}
      : { paddingTop: '12px' };
    return (

          Dropdown

    );
  }
}
class DropdownItems extends Component {
  render() {
    const itemContent = ['Action', 'Another action', 'Something else here'];
    const itemStyle = {
      margin: '0px 4px',
      paddingLeft: '15px',
      verticalAlign: 'middle',
      paddingTop: '4px',
      paddingBottom: '4px'
    };
    if (this.props.visible && this.props.isExpanded) {
      return (

          {itemContent.map((value, key) => {
            return (

{value}

            );
          })}

Separated link

      );
    } else {
      return
;
    }
  }
}

export default DropdownContainer;
<commit_msg>style consts created<commit_after>import React, { Component } from 'react';

class DropdownContainer extends Component {
  constructor(props) {
    super(props);
    this.state = {
      isClicked: false,
      isExpanded: false
    };
  }

  onClick = () => {
    this.setState({
      isClicked: true,
      isExpanded: !this.state.isExpanded
    });
  };

  handleBodyClick = e => {
    console.log('handleBodyClick', e.target.id);
    if (e.target.id !== 'trigger') {
      this.setState({ isExpanded: false, isClicked: false });
    }
  };

  componentDidMount() {
    document.addEventListener('click', this.handleBodyClick);
  }

  componentWillUnmount() {
    document.removeEventListener('click', this.handleBodyClick);
  }

  render() {
    const dropdownContainerStyle = {
      display: 'block',
      position: 'absolute',
      left: '202px',
      backgroundColor: '#fff'
    };
    return (

        <DropdownTrigger
          isExpanded={this.state.isExpanded}
          isClicked={this.state.isClicked}
          onSelect={() => this.onClick()}
        />
        <DropdownItems
          isExpanded={this.state.isExpanded}
          visible={this.state.isClicked}
        />

    );
  }
}

class DropdownTrigger extends Component {
  render() {
    let triggerStyle = this.props.isClicked && this.props.isExpanded
      ? {
          backgroundColor: '#EEEEEE',
          width: '78px',
          cursor: 'pointer',
          display: 'flex',
          wordWrap: 'breakWord',
          justifyContent: 'center',
          paddingTop: '10.5px'
        }
      : { cursor: 'pointer', justifyContent: 'center', width: '78px' };
    let caretStyle = this.props.isClicked && this.props.isExpanded
      ? {}
      : { paddingTop: '12px' };
    return (

          Dropdown

    );
  }
}
class DropdownItems extends Component {
  render() {
    const itemContent = ['Action', 'Another action', 'Something else here'];
    const itemStyle = {
      margin: '0px 4px',
      paddingLeft: '15px',
      verticalAlign: 'middle',
      paddingTop: '4px',
      paddingBottom: '4px'
    };
    const divItemStyle = {
      border: '1px solid rgba(0,0,0,.15)',
      borderRadius: '4px',
      width: '170px',
      color: '#333',
      cursor: 'pointer',
      fontFamily: 'Arial',
      marginTop: '3px',
      boxShadow: '0 6px 12px rgba(0,0,0,.175)'
    };

    if (this.props.visible && this.props.isExpanded) {
      return (

          {itemContent.map((value, key) => {
            return (

{value}

            );
          })}

Separated link

      );
    } else {
      return
;
    }
  }
}

export default DropdownContainer;
<|endoftext|>


<commit_before>/*!
 * froala_editor v1.2.4 (http://editor.froala.com)
 * Copyright 2014-2014 Froala
 */
Array.prototype.indexOf||(Array.prototype.indexOf=function(a){if(void 0===this||null===this)throw TypeError();var b=Object(this),c=b.length>>>0;if(0===c)return-1;var d=0;if(arguments.length>0&&(d=Number(arguments[1]),isNaN(d)?d=0:0!==d&&d!==1/0&&d!==-(1/0)&&(d=(d>0||-1)*Math.floor(Math.abs(d)))),d>=c)return-1;for(var e=d>=0?d:Math.max(c-Math.abs(d),0);c>e;e++)if(e in b&&b[e]===a)return e;return-1}),String.prototype.trim||(String.prototype.trim=function(){return String(this).replace(/^\s+/,"").replace(/\s+$/,"")}),Object.keys||(Object.keys=function(a){var b=[];for(var c in a)a.hasOwnProperty(c)&&b.push(c);return b});var Node=Node||{ELEMENT_NODE:1,ATTRIBUTE_NODE:2,TEXT_NODE:3};$.Editable.prototype.saveSelection=function(){var a=window.document.selection;this.savedSelection="None"!=a.type?a.createRange():null},$.Editable.prototype.restoreSelection=function(){this.selectionDisabled||this.savedSelection&&this.savedSelection.select()},$.Editable.prototype.getSelectionTextInfo=function(){return!1},$.Editable.DEFAULTS.blockTags={n:"Normal",h1:"Heading 1",h2:"Heading 2",h3:"Heading 3",h4:"Heading 4",h5:"Heading 5",h6:"Heading 6"};<commit_msg>Apply IE fix only to Internet Explorer version 8 or older.<commit_after>/*!
 * froala_editor v1.2.4 (http://editor.froala.com)
 * Copyright 2014-2014 Froala
 */

(function() {

  var fixFroalaOnIE = function() {
    Array.prototype.indexOf||(Array.prototype.indexOf=function(a){if(void 0===this||null===this)throw TypeError();var b=Object(this),c=b.length>>>0;if(0===c)return-1;var d=0;if(arguments.length>0&&(d=Number(arguments[1]),isNaN(d)?d=0:0!==d&&d!==1/0&&d!==-(1/0)&&(d=(d>0||-1)*Math.floor(Math.abs(d)))),d>=c)return-1;for(var e=d>=0?d:Math.max(c-Math.abs(d),0);c>e;e++)if(e in b&&b[e]===a)return e;return-1}),String.prototype.trim||(String.prototype.trim=function(){return String(this).replace(/^\s+/,"").replace(/\s+$/,"")}),Object.keys||(Object.keys=function(a){var b=[];for(var c in a)a.hasOwnProperty(c)&&b.push(c);return b});var Node=Node||{ELEMENT_NODE:1,ATTRIBUTE_NODE:2,TEXT_NODE:3};$.Editable.prototype.saveSelection=function(){var a=window.document.selection;this.savedSelection="None"!=a.type?a.createRange():null},$.Editable.prototype.restoreSelection=function(){this.selectionDisabled||this.savedSelection&&this.savedSelection.select()},$.Editable.prototype.getSelectionTextInfo=function(){return!1},$.Editable.DEFAULTS.blockTags={n:"Normal",h1:"Heading 1",h2:"Heading 2",h3:"Heading 3",h4:"Heading 4",h5:"Heading 5",h6:"Heading 6"};
  };

  // Returns the version of Internet Explorer or a -1
  // (indicating the use of another browser).
  var getInternetExplorerVersion = function() {
    var rv = -1; // Return value assumes failure.
    if (navigator.appName == 'Microsoft Internet Explorer') {
      var ua = navigator.userAgent;
      var re  = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})");
      if (re.exec(ua) != null) {
        rv = parseFloat(RegExp.$1);
      }
    }
    return rv;
  };

  var currIEVersion = getInternetExplorerVersion();
  if(currIEVersion > -1 && currIEVersion <= 8.0) {
    fixFroalaOnIE();
  }
})();
<|endoftext|>


<commit_before>NANO.plugin(function (NANO) {
  var search = NANO.search = {

    _each: {},
    _collection: {},
    _ready_callbacks: [],

    _init_sequence: [
      "airlines",
      "airports",
      "currency_rates",
      "closest_cities",
      "gates_info",
      "metadata",
      "minimal_prices",
      "params_attributes",
      "tickets"
    ],

    new: function(search_data){
      var self = this;
      var ajax_params = {
        url: NANO.routes.search_path,
        type: NANO.routes.search_method,
        dataType: 'json',
        data: search_data.params,
        success: function(data){
          self.create(data, search_data.success);
        },
        error: search_data.error
      };
      NANO.ajax(ajax_params);
    },

    create: function (json, complete) {
      var search_results = _.extend({}, json);
      var self = this;
      var worker = null;

      _.each(this._init_sequence, function(field_name){
        var Collection = self._collection[field_name];
        var params = {
          data: search_results[field_name] || [],
          after: function(results){
            if(Collection){
              return new Collection(results);
            }
            return results;
          },
          complete: function(results){
            search_results[field_name] = results;
          }
        };
        if(self._each[field_name]){
          var Item = self._each[field_name];
          params.iterator = function(data){
            return new Item(data, search_results);
          };
        }
        if(!worker){
          worker = NANO.asyncWorker(params);
        }else{
          worker = worker.then(params);
        }
      });

      worker.then({
        data: search_results,
        complete: function(search_results){
          _.each(self._ready_callbacks, function(callback){
            callback(search_results);
          });
          complete(search_results);
        }
      });

      return worker;
    },

    each: function(field, constructor){
      this._each[field] = constructor;
    },

    collection: function (field, constructor){
      this._collection[field] = constructor;
    },

    customize: function (process_type, name, handler) {
      var constructor = this["_" + process_type][name];
      handler.call(constructor.prototype, constructor.prototype);
    },
    ready: function(callback){
      this._ready_callbacks.push(callback);
    }
  };

  var Data = function(json){
    this._data = json;
  };
  Data.prototype = {
    get: function(id){
      return id ? this._data[id] : this._data;
    }
  };

  search.collection("airlines", Data);
  search.collection("airports", Data);
  search.collection("closest_cities", Data);
  search.collection("minimal_prices", Data);
  search.collection("params_attributes", Data);

  search.collection("currency_rates", function(rates){
    NANO.currency.update_rates(rates);
    return rates;
  });

  var Gates = function(json_data){
    this._data = {};
    var self = this;
    _.each(json_data, function(gate_data){
      self._data[gate_data.id] = gate_data;
    });
  };
  Gates.prototype = {
    get: function(id){
      return id ? this._data[id] : this._data;
    }
  };
  search.collection("gates_info", Gates);

  var memoize = function(this_object){
    _.each(this_object.memoize, function(method_name){
      this_object[method_name] = _.memoize(this_object[method_name]);
    });
  };

  var Flights = function(flights_array){
    this.flights = _.map(flights_array, function(flight_json){
      return new Flight(flight_json);
    });
    memoize(this);
  };
  Flights.prototype = {
    memoize: ['airlines', 'aircompany', 'duration', 'stops_count', 'stops_durations', 'stops_data'],
    get_flights_values: function(field_name, is_function, params){
      return _.map(this.flights, function(flight){
        return is_function ? flight[field_name].apply(flight, params) : flight.data[field_name];
      });
    },
    list: function(){
      return this.flights;
    },
    origin: function(){
      return this.flights[0].origin();
    },
    destination: function(){
      return _.last(this.flights).destination();
    },
    duration: function(){
      return _.reduce(this.flights, function(duration, flight){
        return duration + flight.data.duration + flight.data.delay;
      }, 0);
    },
    numbers: function(){
      return this.get_flights_values("number");
    },
    airlines: function(){
      return _.uniq(this.get_flights_values("airline"));
    },

    main_airline: function(){
      return _(this.list()).chain().
        reduce(function(hash, flight){
          var airc = flight.airline;
          hash[airc] = hash.hasOwnProperty(airc) ? hash[airc] + flight.duration : flight.duration;
          return hash;
        }, {}).
        map(function(flight_duration, aircompany_iata){
          return {
            duration: flight_duration,
            iata: aircompany_iata
          };
        }).
        max(function(info){
          return info.duration;
        }).
        value();
    },
    depart_timestamp: function(){
      return this.flights[0].depart_timestamp();
    },
    arrival_timestamp: function(){
      return this.flights[0].arrival_timestamp();
    },
    stops_iatas: function(){
      return _.map(this.stops(), function(stop_data){
        return stop_data.iata;
      });
    },
    stops: function(){
      return _.compact(this.get_flights_values("stop_data", true));
    },
    stops_count: function(){
      return Math.max(this.flights.length - 1, 0);
    },
    stops_durations: function(){
      return _.compact(_.uniq(this.get_flights_values("delay")));
    }
  };

  var Flight = function(flight_json){
    this.data = flight_json;
  };
  Flight.prototype = {
    duration: function(){
      return this.data.duration;
    },
    number: function(){
      return this.data.number;
    },
    airline: function(){
      return this.data.airline;
    },
    origin: function(){
      return this.data.origin;
    },
    destination: function(){
      return this.data.destination;
    },
    depart_timestamp: function(){
      return this.data.departure * 1000;
    },
    arrival_timestamp: function(){
      return this.data.arrival * 1000;
    },
    stop_data: function(){
      if(this.data.delay && 0 < this.data.delay){
        return {
          iata: this.data.origin,
          delay: this.data.delay
        };
      }else{
        return null;
      }
    }
  };
  //добавляем сами билеты
  var Ticket = function(ticket_json, search){
    this.search = search;
    this.id = _.uniqueId("ticket_");
    this._data = ticket_json;
    this.direct_flights = new Flights(ticket_json.direct_flights);
    this.return_flights = ticket_json.return_flights ? new Flights(ticket_json.return_flights) : false;
    var self = this;
    _.each(this.init_stack, function(method_name){
      self[method_name]();
    });
    memoize(this);
  };
  Ticket.prototype = {
    fn: Ticket.prototype,
    init_stack: [],
    memoize: [],
    _default_url: function(url_id){
      return '/searches/' + this.search.search_id + '/order_urls/' + url_id;
    },
    send_gate_data: function(gate_id){
      if(!urls[id]){
        return;
      }
      var gate_data = _.detect(gates, function(gate){
        return gate.id === gate_id;
      });
      NANO.ajax({
        url: this._default_url(gate_data.url_id) + '.js',
        type: "get",
        data: {
          click: {
            gate_id: gate_data.id,
            proposal_price: gate_data.price.default_currency_value
          }
        }
      });
    },
    gates: function(){
      var ticket_gates = [];
      var self = this;
      var prices = this._data.native_prices;
      var gates = this.search.gates_info;
      var search = this.search;
      var urls = this.direct_order_urls;

      _.each(this._data.order_urls, function(url_id, id){
        var gate = gates.get(id);
        var gate_data = {
          ticket_id: self.id,
          id: id,
          name: gate.label,
          url_id: url_id,
          price: {
            currency: gate.currency_code,
            value: prices[id],
            default_currency_value: NANO.currency.to_default(prices[id], gate.currency_code)
          }
        };
        if(urls && urls[id]){
          gate_data.url = urls[id] + "|" + search.params.search.marker;
          gate_data.type = "direct";
        }else{
          gate_data.url = self._default_url(url_id);
          gate_data.type = "redirect";
        }
        ticket_gates.push(gate_data);
      });

      ticket_gates = ticket_gates.sort(function(gate_a, gate_b){
        return gate_a.price.default_currency_value < gate_b.price.default_currency_value ? (-1) : (1);
      });

      var param_cache = {};

      this.gates = function(param){
        if(param){
          if(!param_cache[param]){
            param_cache[param] =  _.map(ticket_gates, function(gate){
              return gate[param];
            });
          }
          return param_cache[param];
        }
        return ticket_gates;
      };
      return this.gates();
    },
    minimal_price: function(){
      var gates = this.gates();
      var minimal_price = _.min(gates, function(gate){
        return gate.price.default_currency_value;
      })[0];
      this.minimal_price = function(){
        return minimal_price;
      };
      return this.minimal_price();
    }
  };
  search.each("tickets", Ticket);

  var Tickets = function(tickets, params){
    this.list = tickets;
    if(params){
      _.extend(this, params);
    }
    var self = this;
    _.each(this.list, function(ticket){
      ticket.tickets = self;
    });
    _.each(this.init_stack, function(method){
      method.call(self);
    });
    this._tickets_index = 0;
  };
  Tickets.prototype = {
    next_tickets_count: 10,
    _constructor: Tickets,
    fn: Tickets.prototype,
    init_stack: [],
    _self: function(tickets, params){
      return new this._constructor(tickets, params);
    },
    get: function(){
      return this.list;
    },
    next: function(count){
      var start = this._tickets_index;
      var end = start + (count || this.next_tickets_count);
      this._tickets_index = end;
      return this.list.slice(start, end);
    }
  };
  search.collection("tickets", Tickets);
});<commit_msg>gettting ticket by id<commit_after>NANO.plugin(function (NANO) {
  var search = NANO.search = {

    _each: {},
    _collection: {},
    _ready_callbacks: [],

    _init_sequence: [
      "airlines",
      "airports",
      "currency_rates",
      "closest_cities",
      "gates_info",
      "metadata",
      "minimal_prices",
      "params_attributes",
      "tickets"
    ],

    new: function(search_data){
      var self = this;
      var ajax_params = {
        url: NANO.routes.search_path,
        type: NANO.routes.search_method,
        dataType: 'json',
        data: search_data.params,
        success: function(data){
          self.create(data, search_data.success);
        },
        error: search_data.error
      };
      NANO.ajax(ajax_params);
    },

    create: function (json, complete) {
      var search_results = _.extend({}, json);
      var self = this;
      var worker = null;

      _.each(this._init_sequence, function(field_name){
        var Collection = self._collection[field_name];
        var params = {
          data: search_results[field_name] || [],
          after: function(results){
            if(Collection){
              return new Collection(results);
            }
            return results;
          },
          complete: function(results){
            search_results[field_name] = results;
          }
        };
        if(self._each[field_name]){
          var Item = self._each[field_name];
          params.iterator = function(data){
            return new Item(data, search_results);
          };
        }
        if(!worker){
          worker = NANO.asyncWorker(params);
        }else{
          worker = worker.then(params);
        }
      });

      worker.then({
        data: search_results,
        complete: function(search_results){
          _.each(self._ready_callbacks, function(callback){
            callback(search_results);
          });
          complete(search_results);
        }
      });

      return worker;
    },

    each: function(field, constructor){
      this._each[field] = constructor;
    },

    collection: function (field, constructor){
      this._collection[field] = constructor;
    },

    customize: function (process_type, name, handler) {
      var constructor = this["_" + process_type][name];
      handler.call(constructor.prototype, constructor.prototype);
    },
    ready: function(callback){
      this._ready_callbacks.push(callback);
    }
  };

  var Data = function(json){
    this._data = json;
  };
  Data.prototype = {
    get: function(id){
      return id ? this._data[id] : this._data;
    }
  };

  search.collection("airlines", Data);
  search.collection("airports", Data);
  search.collection("closest_cities", Data);
  search.collection("minimal_prices", Data);
  search.collection("params_attributes", Data);

  search.collection("currency_rates", function(rates){
    NANO.currency.update_rates(rates);
    return rates;
  });

  var Gates = function(json_data){
    this._data = {};
    var self = this;
    _.each(json_data, function(gate_data){
      self._data[gate_data.id] = gate_data;
    });
  };
  Gates.prototype = {
    get: function(id){
      return id ? this._data[id] : this._data;
    }
  };
  search.collection("gates_info", Gates);

  var memoize = function(this_object){
    _.each(this_object.memoize, function(method_name){
      this_object[method_name] = _.memoize(this_object[method_name]);
    });
  };

  var Flights = function(flights_array){
    this.flights = _.map(flights_array, function(flight_json){
      return new Flight(flight_json);
    });
    memoize(this);
  };
  Flights.prototype = {
    memoize: ['airlines', 'aircompany', 'duration', 'stops_count', 'stops_durations', 'stops_data'],
    get_flights_values: function(field_name, is_function, params){
      return _.map(this.flights, function(flight){
        return is_function ? flight[field_name].apply(flight, params) : flight.data[field_name];
      });
    },
    list: function(){
      return this.flights;
    },
    origin: function(){
      return this.flights[0].origin();
    },
    destination: function(){
      return _.last(this.flights).destination();
    },
    duration: function(){
      return _.reduce(this.flights, function(duration, flight){
        return duration + flight.data.duration + flight.data.delay;
      }, 0);
    },
    numbers: function(){
      return this.get_flights_values("number");
    },
    airlines: function(){
      return _.uniq(this.get_flights_values("airline"));
    },

    main_airline: function(){
      return _(this.list()).chain().
        reduce(function(hash, flight){
          var airc = flight.airline;
          hash[airc] = hash.hasOwnProperty(airc) ? hash[airc] + flight.duration : flight.duration;
          return hash;
        }, {}).
        map(function(flight_duration, aircompany_iata){
          return {
            duration: flight_duration,
            iata: aircompany_iata
          };
        }).
        max(function(info){
          return info.duration;
        }).
        value();
    },
    depart_timestamp: function(){
      return this.flights[0].depart_timestamp();
    },
    arrival_timestamp: function(){
      return this.flights[0].arrival_timestamp();
    },
    stops_iatas: function(){
      return _.map(this.stops(), function(stop_data){
        return stop_data.iata;
      });
    },
    stops: function(){
      return _.compact(this.get_flights_values("stop_data", true));
    },
    stops_count: function(){
      return Math.max(this.flights.length - 1, 0);
    },
    stops_durations: function(){
      return _.compact(_.uniq(this.get_flights_values("delay")));
    }
  };

  var Flight = function(flight_json){
    this.data = flight_json;
  };
  Flight.prototype = {
    duration: function(){
      return this.data.duration;
    },
    number: function(){
      return this.data.number;
    },
    airline: function(){
      return this.data.airline;
    },
    origin: function(){
      return this.data.origin;
    },
    destination: function(){
      return this.data.destination;
    },
    depart_timestamp: function(){
      return this.data.departure * 1000;
    },
    arrival_timestamp: function(){
      return this.data.arrival * 1000;
    },
    stop_data: function(){
      if(this.data.delay && 0 < this.data.delay){
        return {
          iata: this.data.origin,
          delay: this.data.delay
        };
      }else{
        return null;
      }
    }
  };
  //добавляем сами билеты
  var Ticket = function(ticket_json, search){
    this.search = search;
    this.id = _.uniqueId("ticket_");
    this._data = ticket_json;
    this.direct_flights = new Flights(ticket_json.direct_flights);
    this.return_flights = ticket_json.return_flights ? new Flights(ticket_json.return_flights) : false;
    var self = this;
    _.each(this.init_stack, function(method_name){
      self[method_name]();
    });
    memoize(this);
  };
  Ticket.prototype = {
    fn: Ticket.prototype,
    init_stack: [],
    memoize: [],
    _default_url: function(url_id){
      return '/searches/' + this.search.search_id + '/order_urls/' + url_id;
    },
    send_gate_data: function(gate_id){
      if(!urls[id]){
        return;
      }
      var gate_data = _.detect(gates, function(gate){
        return gate.id === gate_id;
      });
      NANO.ajax({
        url: this._default_url(gate_data.url_id) + '.js',
        type: "get",
        data: {
          click: {
            gate_id: gate_data.id,
            proposal_price: gate_data.price.default_currency_value
          }
        }
      });
    },
    gates: function(){
      var ticket_gates = [];
      var self = this;
      var prices = this._data.native_prices;
      var gates = this.search.gates_info;
      var search = this.search;
      var urls = this.direct_order_urls;

      _.each(this._data.order_urls, function(url_id, id){
        var gate = gates.get(id);
        var gate_data = {
          ticket_id: self.id,
          id: id,
          name: gate.label,
          url_id: url_id,
          price: {
            currency: gate.currency_code,
            value: prices[id],
            default_currency_value: NANO.currency.to_default(prices[id], gate.currency_code)
          }
        };
        if(urls && urls[id]){
          gate_data.url = urls[id] + "|" + search.params.search.marker;
          gate_data.type = "direct";
        }else{
          gate_data.url = self._default_url(url_id);
          gate_data.type = "redirect";
        }
        ticket_gates.push(gate_data);
      });

      ticket_gates = ticket_gates.sort(function(gate_a, gate_b){
        return gate_a.price.default_currency_value < gate_b.price.default_currency_value ? (-1) : (1);
      });

      var param_cache = {};

      this.gates = function(param){
        if(param){
          if(!param_cache[param]){
            param_cache[param] =  _.map(ticket_gates, function(gate){
              return gate[param];
            });
          }
          return param_cache[param];
        }
        return ticket_gates;
      };
      return this.gates();
    },
    minimal_price: function(){
      var gates = this.gates();
      var minimal_price = _.min(gates, function(gate){
        return gate.price.default_currency_value;
      })[0];
      this.minimal_price = function(){
        return minimal_price;
      };
      return this.minimal_price();
    }
  };
  search.each("tickets", Ticket);

  var Tickets = function(tickets, params){
    this.list = tickets;
    if(params){
      _.extend(this, params);
    }
    var self = this;
    _.each(this.list, function(ticket){
      ticket.tickets = self;
    });
    _.each(this.init_stack, function(method){
      method.call(self);
    });
    this._tickets_index = 0;
  };
  Tickets.prototype = {
    next_tickets_count: 10,
    _constructor: Tickets,
    fn: Tickets.prototype,
    init_stack: [],
    _self: function(tickets, params){
      return new this._constructor(tickets, params);
    },
    get: function(id){
      if(_.isString(id)){
        return _.detect(this.list, function(ticket){
          return ticket.id === id;
        });
      }
      return this.list;
    },
    next: function(count){
      var start = this._tickets_index;
      var end = start + (count || this.next_tickets_count);
      this._tickets_index = end;
      return this.list.slice(start, end);
    }
  };
  search.collection("tickets", Tickets);
});<|endoftext|>


<commit_before>var CursorMaintainer = (function () {
  'use strict';

  var format,
      adHoc,
      mockCursor,
      meta,
      retro;

  //--- Plain formatting operations: no alteration of the cursor position.
  format = {};

  /* commatize takes a string of digits and commas. It adjusts commas so
     that they separate the digits into groups of three. */
  format.commatize = function (s, cursor) {
    var start,
        groups,
        i;
    s = s.replace(/,/g, '');
    start = s.length % 3 || 3;
    groups = [ s.substring(0, start) ];
    for (i = start; i < s.length; i += 3) {
      groups.push(s.substring(i, i + 3));
    }
    s = groups.join(',');
    return { text: s, cursor: cursor };
  };

  /* trimify removes all whitespace from the beginning of the string and
     reduces other whitespace sequences to a single space each. */
  format.trimify = function (s, cursor) {
    s = s.replace(/^\s+/, '');
    s = s.replace(/\s+/g, ' ');
    return { text: s, cursor: cursor };
  };

  //--- Ad hoc: idiosyncratic cursor calculations.
  adHoc = {};

  /* count returns the number of occurrences (possibly overlapping) of the
     string sub in the string s. */
  function count(s, sub) {
    var count = 0,
        searchedTo = -1;
    while ((searchedTo = s.indexOf(sub, searchedTo + 1)) != -1) {
      ++count;
    }
    return count;
  }

  adHoc.commatize = function (s, cursor) {
    var pos, ch,
        leftDigitCount = cursor - count(s.substring(0, cursor), ',');
    s = format.commatize(s).text;
    if (leftDigitCount == 0) {
      return { text: s, cursor: 0 };
    }
    for (pos = 0; pos < s.length; ++pos) {
      ch = s.charAt(pos);
      if (ch != ',') {
        if (--leftDigitCount == 0) {
          break;
        }
      }
    }
    cursor = pos + 1;
    return { text: s, cursor: cursor };
  };

  adHoc.trimify = function (s, cursor) {
    var leftTrimmed = format.trimify(s.substring(0, cursor) + '|').text;
    s = format.trimify(s).text;
    cursor = Math.min(s.length, leftTrimmed.length - 1);
    return { text: s, cursor: cursor };
  };

  //--- Mock cursor: incorporate the cursor into the text, format, fix.
  mockCursor = {};

  /* chooseCursorChar tests a set of favored characters, then all printable
     ASCII characters, until it finds one that does not occur in the text. */
  function chooseCursorChar(s) {
    var usedChars = {},
        seekChars = '|^_#',
        i, ch, code;
    for (i = 0; i < s.length; ++i) {
      usedChars[s.charAt(i)] = true;
    }
    for (i = 0; i < seekChars.length; ++i) {
      ch = seekChars.charAt(i);
      if (!(ch in usedChars)) {
        return ch;
      }
    }
    for (code = 32; code < 127; ++code) {
      ch = String.fromCharCode(code);
      if (!(ch in usedChars)) {
        return ch;
      }
    }
    return null;
  }

  mockCursor.commatize = function (s, cursor) {
    var cursorChar = chooseCursorChar(s),
        groups = [],
        groupChars = [],
        digitCount = 0,
        i, ch;
    s = s.substring(0, cursor) + cursorChar + s.substring(cursor);
    for (i = s.length - 1; i >= 0; --i) {
      ch = s.charAt(i);
      if (ch != ',') {
        groupChars.push(ch);
        if (ch != cursorChar) {
          if (++digitCount == 3) {
            groups.push(groupChars.reverse().join(''));
            groupChars = [];
            digitCount = 0;
          }
        }
      }
    }
    if (groupChars.length > 0) {
      groups.push(groupChars.reverse().join(''));
    }
    s = groups.reverse().join(',');
    cursor = s.indexOf(cursorChar);
    s = s.replace(cursorChar, '');
    return { text: s, cursor: cursor };
  };

  mockCursor.trimify = function (s, cursor) {
    var cursorChar = chooseCursorChar(s);
    s = s.substring(0, cursor) + cursorChar + s.substring(cursor);
    s = format.trimify(s).text;
    s = s.replace(' ' + cursorChar + ' ', ' ' + cursorChar);
    if (s.charAt(0) == cursorChar) {
      s = s.replace(cursorChar + ' ', cursorChar);
    }
    cursor = s.indexOf(cursorChar);
    s = s.replace(cursorChar, '');
    return { text: s, cursor: cursor };
  };

  //--- Meta: local operations on a text-with-cursor object.
  meta = {};

  function TextWithCursor(text, cursor) {
    this.text = text || '';
    this.cursor = cursor || 0;
  }

  TextWithCursor.prototype.read = function (begin, length) {
    if (length === undefined) {
      length = 1;
    }
    return this.text.substring(begin, begin + length);
  };

  TextWithCursor.prototype.insert = function (begin, subtext) {
    this.text = this.text.substring(0, begin) + subtext +
        this.text.substring(begin);
    if (this.cursor > begin) {
      this.cursor += subtext.length;
    }
  };

  TextWithCursor.prototype.delete = function (begin, length) {
    if (length === undefined) {
      length = 1;
    }
    this.text = this.text.substring(0, begin) +
        this.text.substring(begin + length);
    if (this.cursor > begin) {
      this.cursor -= Math.min(this.cursor - begin, length);
    }
  };

  TextWithCursor.prototype.length = function () {
    return this.text.length;
  };

  meta.commatize = function (s, cursor) {
    var t = new TextWithCursor(s, cursor),
        digitCount = 0,
        pos;
    for (pos = t.length() - 1; pos >= 0; --pos) {
      if (t.read(pos) == ',') {
        t.delete(pos);
      } else if (digitCount < 2) {
        ++digitCount;
      } else if (pos > 0) {
        t.insert(pos, ',');
        digitCount = 0;
      }
    }
    return t;
  };

  meta.trimify = function (s, cursor) {
    var t = new TextWithCursor(s, cursor),
        spaceCount = 0,
        pos;
    for (pos = t.length() - 1; pos >= 0; --pos) {
      if (t.read(pos) != ' ') {
        spaceCount = 0;
      } else if (spaceCount == 0) {
        spaceCount = 1;
      } else {
        t.delete(pos + 1);
      }
    }
    if (t.read(0) == ' ') {
      t.delete(pos);
    }
    return t;
  };

  //--- Retrospective: compare the old text and cursor to the new text.
  retro = {};

  function levenshtein(s, t) {
    var n = s.length,
        m = t.length,
        current,
        previous,
        temp,
        i, j;
    if (Math.min(n, m) == 0) {
      return Math.max(n, m);
    }
    current = new Array(m + 1);
    for (i = 0; i <= m; ++i) {
      current[i] = i;
    }
    previous = new Array(m + 1);
    for (i = 1; i <= n; ++i) {
      temp = current;
      current = previous;
      previous = temp;
      current[0] = previous[0] + 1;
      for (j = 1; j <= m; ++j) {
        if (t.charAt(j - 1) == s.charAt(i - 1)) {
          current[j] = previous[j - 1];
        } else {
          current[j] = Math.min(previous[j - 1] + 1,
                                previous[j] + 1,
                                current[j - 1] + 1);
        }
      }
    }
    return current[m];
  }

  function cursorLevenshtein(s, sCursor, t, tCursor) {
    var cursorChar = chooseCursorChar(s + t);
    s = s.substring(0, sCursor) + cursorChar + s.substring(sCursor);
    t = t.substring(0, tCursor) + cursorChar + t.substring(tCursor);
    return levenshtein(s, t);
  }

  function splitLevenshtein(s, sCursor, t, tCursor) {
    var cursorChar = chooseCursorChar(s + t),
        left = levenshtein(s.substring(0, sCursor), t.substring(0, tCursor)),
        right = levenshtein(s.substring(sCursor), t.substring(tCursor));
    return left + right;
  }

  function getCounts(s, chars) {
    var counts = {},
        i, ch;
    for (i = 0; i < chars.length; ++i) {
      counts[chars[i]] = 0;
    }
    for (i = 0; i < s.length; ++i) {
      ch = s.charAt(i);
      if (ch in counts) {
        counts[ch] += 1;
      }
    }
    return counts;
  }

  function leftRightCounts(s, cursor, chars) {
    var countLeft = getCounts(s.substring(0, cursor), chars),
        countRight = getCounts(s.substring(cursor), chars);
    return { left: countLeft, right: countRight };
  }

  function getCommonChars(s, t) {
    var sCharSet = {},
        tCharSet = {},
        chars = [],
        i;
    for (i = 0; i < s.length; ++i) {
      sCharSet[s.charAt(i)] = true;
    }
    for (i = 0; i < t.length; ++i) {
      tCharSet[t.charAt(i)] = true;
    }
    Object.keys(sCharSet).forEach(function (ch) {
      if (ch in tCharSet) {
        chars.push(ch);
      }
    });
    return chars;
  }

  function balancedFrequencies(s, sCursor, t, tCursor) {
    var chars = getCommonChars(s, t),
        sCounts = leftRightCounts(s, sCursor, chars),
        tCounts = leftRightCounts(t, tCursor, chars),
        cost = 0,
        i, ch, a, b;
    for (i = 0; i < chars.length; ++i) {
      ch = chars[i];
      a = sCounts.left[ch] / (sCounts.left[ch] + sCounts.right[ch]);
      b = tCounts.left[ch] / (tCounts.left[ch] + tCounts.right[ch]);
      cost += Math.pow(Math.abs(a - b), 2);
    }
    return cost;
  }

  function makeRetrospective(costFunction, plainFormatter) {
    return function (original, cursor) {
      var formatted = plainFormatter(original).text,
          bestCost = costFunction(original, cursor, formatted, 0),
          bestPos = 0,
          cost,
          pos,
          scores = [ bestCost ];
      for (pos = 1; pos <= formatted.length; ++pos) {
        cost = costFunction(original, cursor, formatted, pos);
        if (cost < bestCost) {
          bestCost = cost;
          bestPos = pos;
        }
        scores.push(cost);
      }
      return { text: formatted, cursor: bestPos, scores: scores };
    };
  }

  retro.cursorLevenshtein = {
    commatize: makeRetrospective(cursorLevenshtein, format.commatize),
    trimify: makeRetrospective(cursorLevenshtein, format.trimify)
  };

  retro.splitLevenshtein = {
    commatize: makeRetrospective(splitLevenshtein, format.commatize),
    trimify: makeRetrospective(splitLevenshtein, format.trimify)
  };

  retro.balancedFrequencies = {
    commatize: makeRetrospective(balancedFrequencies, format.commatize),
    trimify: makeRetrospective(balancedFrequencies, format.trimify)
  };

  return {
    format: format,
    adHoc: adHoc,
    mockCursor: mockCursor,
    meta: meta,
    retro: retro
  };
})();
<commit_msg>Remove cursorLevenshtein cost function<commit_after>var CursorMaintainer = (function () {
  'use strict';

  var format,
      adHoc,
      mockCursor,
      meta,
      retro;

  //--- Plain formatting operations: no alteration of the cursor position.
  format = {};

  /* commatize takes a string of digits and commas. It adjusts commas so
     that they separate the digits into groups of three. */
  format.commatize = function (s, cursor) {
    var start,
        groups,
        i;
    s = s.replace(/,/g, '');
    start = s.length % 3 || 3;
    groups = [ s.substring(0, start) ];
    for (i = start; i < s.length; i += 3) {
      groups.push(s.substring(i, i + 3));
    }
    s = groups.join(',');
    return { text: s, cursor: cursor };
  };

  /* trimify removes all whitespace from the beginning of the string and
     reduces other whitespace sequences to a single space each. */
  format.trimify = function (s, cursor) {
    s = s.replace(/^\s+/, '');
    s = s.replace(/\s+/g, ' ');
    return { text: s, cursor: cursor };
  };

  //--- Ad hoc: idiosyncratic cursor calculations.
  adHoc = {};

  /* count returns the number of occurrences (possibly overlapping) of the
     string sub in the string s. */
  function count(s, sub) {
    var count = 0,
        searchedTo = -1;
    while ((searchedTo = s.indexOf(sub, searchedTo + 1)) != -1) {
      ++count;
    }
    return count;
  }

  adHoc.commatize = function (s, cursor) {
    var pos, ch,
        leftDigitCount = cursor - count(s.substring(0, cursor), ',');
    s = format.commatize(s).text;
    if (leftDigitCount == 0) {
      return { text: s, cursor: 0 };
    }
    for (pos = 0; pos < s.length; ++pos) {
      ch = s.charAt(pos);
      if (ch != ',') {
        if (--leftDigitCount == 0) {
          break;
        }
      }
    }
    cursor = pos + 1;
    return { text: s, cursor: cursor };
  };

  adHoc.trimify = function (s, cursor) {
    var leftTrimmed = format.trimify(s.substring(0, cursor) + '|').text;
    s = format.trimify(s).text;
    cursor = Math.min(s.length, leftTrimmed.length - 1);
    return { text: s, cursor: cursor };
  };

  //--- Mock cursor: incorporate the cursor into the text, format, fix.
  mockCursor = {};

  /* chooseCursorChar tests a set of favored characters, then all printable
     ASCII characters, until it finds one that does not occur in the text. */
  function chooseCursorChar(s) {
    var usedChars = {},
        seekChars = '|^_#',
        i, ch, code;
    for (i = 0; i < s.length; ++i) {
      usedChars[s.charAt(i)] = true;
    }
    for (i = 0; i < seekChars.length; ++i) {
      ch = seekChars.charAt(i);
      if (!(ch in usedChars)) {
        return ch;
      }
    }
    for (code = 32; code < 127; ++code) {
      ch = String.fromCharCode(code);
      if (!(ch in usedChars)) {
        return ch;
      }
    }
    return null;
  }

  mockCursor.commatize = function (s, cursor) {
    var cursorChar = chooseCursorChar(s),
        groups = [],
        groupChars = [],
        digitCount = 0,
        i, ch;
    s = s.substring(0, cursor) + cursorChar + s.substring(cursor);
    for (i = s.length - 1; i >= 0; --i) {
      ch = s.charAt(i);
      if (ch != ',') {
        groupChars.push(ch);
        if (ch != cursorChar) {
          if (++digitCount == 3) {
            groups.push(groupChars.reverse().join(''));
            groupChars = [];
            digitCount = 0;
          }
        }
      }
    }
    if (groupChars.length > 0) {
      groups.push(groupChars.reverse().join(''));
    }
    s = groups.reverse().join(',');
    cursor = s.indexOf(cursorChar);
    s = s.replace(cursorChar, '');
    return { text: s, cursor: cursor };
  };

  mockCursor.trimify = function (s, cursor) {
    var cursorChar = chooseCursorChar(s);
    s = s.substring(0, cursor) + cursorChar + s.substring(cursor);
    s = format.trimify(s).text;
    s = s.replace(' ' + cursorChar + ' ', ' ' + cursorChar);
    if (s.charAt(0) == cursorChar) {
      s = s.replace(cursorChar + ' ', cursorChar);
    }
    cursor = s.indexOf(cursorChar);
    s = s.replace(cursorChar, '');
    return { text: s, cursor: cursor };
  };

  //--- Meta: local operations on a text-with-cursor object.
  meta = {};

  function TextWithCursor(text, cursor) {
    this.text = text || '';
    this.cursor = cursor || 0;
  }

  TextWithCursor.prototype.read = function (begin, length) {
    if (length === undefined) {
      length = 1;
    }
    return this.text.substring(begin, begin + length);
  };

  TextWithCursor.prototype.insert = function (begin, subtext) {
    this.text = this.text.substring(0, begin) + subtext +
        this.text.substring(begin);
    if (this.cursor > begin) {
      this.cursor += subtext.length;
    }
  };

  TextWithCursor.prototype.delete = function (begin, length) {
    if (length === undefined) {
      length = 1;
    }
    this.text = this.text.substring(0, begin) +
        this.text.substring(begin + length);
    if (this.cursor > begin) {
      this.cursor -= Math.min(this.cursor - begin, length);
    }
  };

  TextWithCursor.prototype.length = function () {
    return this.text.length;
  };

  meta.commatize = function (s, cursor) {
    var t = new TextWithCursor(s, cursor),
        digitCount = 0,
        pos;
    for (pos = t.length() - 1; pos >= 0; --pos) {
      if (t.read(pos) == ',') {
        t.delete(pos);
      } else if (digitCount < 2) {
        ++digitCount;
      } else if (pos > 0) {
        t.insert(pos, ',');
        digitCount = 0;
      }
    }
    return t;
  };

  meta.trimify = function (s, cursor) {
    var t = new TextWithCursor(s, cursor),
        spaceCount = 0,
        pos;
    for (pos = t.length() - 1; pos >= 0; --pos) {
      if (t.read(pos) != ' ') {
        spaceCount = 0;
      } else if (spaceCount == 0) {
        spaceCount = 1;
      } else {
        t.delete(pos + 1);
      }
    }
    if (t.read(0) == ' ') {
      t.delete(pos);
    }
    return t;
  };

  //--- Retrospective: compare the old text and cursor to the new text.
  retro = {};

  function levenshtein(s, t) {
    var n = s.length,
        m = t.length,
        current,
        previous,
        temp,
        i, j;
    if (Math.min(n, m) == 0) {
      return Math.max(n, m);
    }
    current = new Array(m + 1);
    for (i = 0; i <= m; ++i) {
      current[i] = i;
    }
    previous = new Array(m + 1);
    for (i = 1; i <= n; ++i) {
      temp = current;
      current = previous;
      previous = temp;
      current[0] = previous[0] + 1;
      for (j = 1; j <= m; ++j) {
        if (t.charAt(j - 1) == s.charAt(i - 1)) {
          current[j] = previous[j - 1];
        } else {
          current[j] = Math.min(previous[j - 1] + 1,
                                previous[j] + 1,
                                current[j - 1] + 1);
        }
      }
    }
    return current[m];
  }

  function splitLevenshtein(s, sCursor, t, tCursor) {
    var cursorChar = chooseCursorChar(s + t),
        left = levenshtein(s.substring(0, sCursor), t.substring(0, tCursor)),
        right = levenshtein(s.substring(sCursor), t.substring(tCursor));
    return left + right;
  }

  function getCounts(s, chars) {
    var counts = {},
        i, ch;
    for (i = 0; i < chars.length; ++i) {
      counts[chars[i]] = 0;
    }
    for (i = 0; i < s.length; ++i) {
      ch = s.charAt(i);
      if (ch in counts) {
        counts[ch] += 1;
      }
    }
    return counts;
  }

  function leftRightCounts(s, cursor, chars) {
    var countLeft = getCounts(s.substring(0, cursor), chars),
        countRight = getCounts(s.substring(cursor), chars);
    return { left: countLeft, right: countRight };
  }

  function getCommonChars(s, t) {
    var sCharSet = {},
        tCharSet = {},
        chars = [],
        i;
    for (i = 0; i < s.length; ++i) {
      sCharSet[s.charAt(i)] = true;
    }
    for (i = 0; i < t.length; ++i) {
      tCharSet[t.charAt(i)] = true;
    }
    Object.keys(sCharSet).forEach(function (ch) {
      if (ch in tCharSet) {
        chars.push(ch);
      }
    });
    return chars;
  }

  function balancedFrequencies(s, sCursor, t, tCursor) {
    var chars = getCommonChars(s, t),
        sCounts = leftRightCounts(s, sCursor, chars),
        tCounts = leftRightCounts(t, tCursor, chars),
        cost = 0,
        i, ch, a, b;
    for (i = 0; i < chars.length; ++i) {
      ch = chars[i];
      a = sCounts.left[ch] / (sCounts.left[ch] + sCounts.right[ch]);
      b = tCounts.left[ch] / (tCounts.left[ch] + tCounts.right[ch]);
      cost += Math.pow(Math.abs(a - b), 2);
    }
    return cost;
  }

  function makeRetrospective(costFunction, plainFormatter) {
    return function (original, cursor) {
      var formatted = plainFormatter(original).text,
          bestCost = costFunction(original, cursor, formatted, 0),
          bestPos = 0,
          cost,
          pos,
          scores = [ bestCost ];
      for (pos = 1; pos <= formatted.length; ++pos) {
        cost = costFunction(original, cursor, formatted, pos);
        if (cost < bestCost) {
          bestCost = cost;
          bestPos = pos;
        }
        scores.push(cost);
      }
      return { text: formatted, cursor: bestPos, scores: scores };
    };
  }

  retro.splitLevenshtein = {
    commatize: makeRetrospective(splitLevenshtein, format.commatize),
    trimify: makeRetrospective(splitLevenshtein, format.trimify)
  };

  retro.balancedFrequencies = {
    commatize: makeRetrospective(balancedFrequencies, format.commatize),
    trimify: makeRetrospective(balancedFrequencies, format.trimify)
  };

  return {
    format: format,
    adHoc: adHoc,
    mockCursor: mockCursor,
    meta: meta,
    retro: retro
  };
})();
<|endoftext|>


<commit_before>YUI.add('moodle-form-dateselector', function(Y) {

    /**
     * Add some custom methods to the node class to make our lives a little
     * easier within this module.
     */
    Y.mix(Y.Node.prototype, {
        /**
         * Gets the value of the first option in the select box
         */
        firstOptionValue : function() {
            if (this.get('nodeName').toLowerCase() != 'select') {
                return false;
            }
            return this.one('option').get('value');
        },
        /**
         * Gets the value of the last option in the select box
         */
        lastOptionValue : function() {
            if (this.get('nodeName').toLowerCase() != 'select') {
                return false;
            }
            return this.all('option').item(this.optionSize()-1).get('value');
        },
        /**
         * Gets the number of options in the select box
         */
        optionSize : function() {
            if (this.get('nodeName').toLowerCase() != 'select') {
                return false;
            }
            return parseInt(this.all('option').size());
        },
        /**
         * Gets the value of the selected option in the select box
         */
        selectedOptionValue : function() {
            if (this.get('nodeName').toLowerCase() != 'select') {
                return false;
            }
            return this.all('option').item(this.get('selectedIndex')).get('value');
        }
    });

    /**
     * Calendar class
     *
     * This is our main class
     */
    var CALENDAR = function(config) {
        CALENDAR.superclass.constructor.apply(this, arguments);
    };
    CALENDAR.prototype = {
        panel : null,
        yearselect : null,
        yearselectchange : null,
        monthselect : null,
        monthselectchange : null,
        dayselect : null,
        dayselectchange : null,
        enablecheckbox : null,
        initializer : function(config) {
            var controls = this.get('node').all('select');
            controls.each(function(node){
                if (node.get('name').match(/\[year]/)) {
                    this.yearselect = node;
                } else if (node.get('name').match(/\[month\]/)) {
                    this.monthselect = node;
                } else if (node.get('name').match(/\[day]/)) {
                    this.dayselect = node;
                } else {
                    node.on('focus', M.form.dateselector.cancel_any_timeout, M.form.dateselector);
                    node.on('blur', this.blur_event, this);
                    return;
                }
                node.on('focus', this.focus_event, this);
                node.on('click', this.focus_event, this);
                node.after('change', this.set_date_from_selects, this);
            }, this);

            if (this.yearselect && this.monthselect && this.dayselect) {
                this.enablecheckbox = this.get('node').one('input');
            }
        },
        focus_event : function(e) {
            M.form.dateselector.cancel_any_timeout();
            if (this.enablecheckbox == null || this.enablecheckbox.get('checked')) {
                this.claim_calendar();
            } else {
                if (M.form.dateselector.currentowner) {
                    M.form.dateselector.currentowner.release_calendar();
                }
            }
        },
        blur_event : function(e) {
            M.form.dateselector.hidetimeout = setTimeout(M.form.dateselector.release_current, 300);
        },
        handle_select_change : function(e) {
            this.set_date_from_selects();
        },
        claim_calendar : function() {
            M.form.dateselector.cancel_any_timeout();
            if (M.form.dateselector.currentowner == this) {
                return;
            }
            if (M.form.dateselector.currentowner) {
                M.form.dateselector.currentowner.release_calendar();
            }

            if (M.form.dateselector.currentowner != this) {
                this.connect_handlers();
                this.set_date_from_selects();
            }
            M.form.dateselector.currentowner = this;
            M.form.dateselector.calendar.cfg.setProperty('mindate', new Date(this.yearselect.firstOptionValue(), 0, 1));
            M.form.dateselector.calendar.cfg.setProperty('maxdate', new Date(this.yearselect.lastOptionValue(), 11, 31));
            M.form.dateselector.panel.set('constrain', this.get('node').ancestor('form'));
            M.form.dateselector.panel.show();
            M.form.dateselector.fix_position();
            setTimeout(function(){M.form.dateselector.cancel_any_timeout()}, 100);
        },
        set_date_from_selects : function() {
            var year = parseInt(this.yearselect.get('value'));
            var month = parseInt(this.monthselect.get('value')) - 1;
            var day = parseInt(this.dayselect.get('value'));
            M.form.dateselector.calendar.select(new Date(year, month, day));
            M.form.dateselector.calendar.setMonth(month);
            M.form.dateselector.calendar.setYear(year);
            M.form.dateselector.calendar.render();
        },
        set_selects_from_date : function(eventtype, args) {
            var date = args[0][0];
            var newyear = date[0];
            var newindex = newyear - this.yearselect.firstOptionValue();
            this.yearselect.set('selectedIndex', newindex);
            this.monthselect.set('selectedIndex', date[1] - this.monthselect.firstOptionValue());
            this.dayselect.set('selectedIndex', date[2] - this.dayselect.firstOptionValue());
        },
        connect_handlers : function() {
            M.form.dateselector.calendar.selectEvent.subscribe(this.set_selects_from_date, this, true);
        },
        release_calendar : function() {
            M.form.dateselector.panel.hide();
            M.form.dateselector.currentowner = null;
            M.form.dateselector.calendar.selectEvent.unsubscribe(this.set_selects_from_date, this);
        }
    };
    Y.extend(CALENDAR, Y.Base, CALENDAR.prototype, {
        NAME : 'Date Selector',
        ATTRS : {
            firstdayofweek  : {
                validator : Y.Lang.isString
            },
            node : {
                setter : function(node) {
                    return Y.one(node);
                }
            }
        }
    });

    M.form = M.form || {};
    M.form.dateselector = {
        panel : null,
        calendar : null,
        currentowner : null,
        hidetimeout : null,
        repositiontimeout : null,
        init_date_selectors : function(config) {
            if (this.panel === null) {
                this.initPanel(config);
            }
            Y.all('fieldset.fdate_time_selector').each(function(){
                config.node = this;
                new CALENDAR(config);
            });
            Y.all('fieldset.fdate_selector').each(function(){
                config.node = this;
                new CALENDAR(config);
            });
        },
        initPanel : function(config) {
            this.panel = new Y.Overlay({
                visible : false,
                constrain : true,
                bodyContent : Y.Node.create('

'),
                id : 'dateselector-calendar-panel'
            });
            this.panel.render(document.body);
            this.panel.on('heightChange', this.fix_position, this);

            Y.one('#dateselector-calendar-panel').on('click', function(e){e.halt();});
            Y.one(document.body).on('click', this.document_click, this);

            this.calendar = new YAHOO.widget.Calendar(document.getElementById('dateselector-calendar-content'), {
                iframe: false,
                hide_blank_weeks: true,
                start_weekday: config.firstdayofweek
            });
            this.calendar.changePageEvent.subscribe(function(){
                this.fix_position();
            }, this);
        },
        cancel_any_timeout : function() {
            if (this.hidetimeout) {
                clearTimeout(this.hidetimeout);
                this.hidetimeout = null;
            }
            if (this.repositiontimeout) {
                clearTimeout(this.repositiontimeout);
                this.repositiontimeout = null;
            }
        },
        delayed_reposition : function() {
            if (this.repositiontimeout) {
                clearTimeout(this.repositiontimeout);
                this.repositiontimeout = null;
            }
            this.repositiontimeout = setTimeout(this.fix_position, 500);
        },
        fix_position : function() {
            if (this.currentowner) {
                this.panel.set('constrain', this.currentowner.get('node').ancestor('form'));
                this.panel.set('align', {
                    node:this.currentowner.get('node').one('select'),
                    points:[Y.WidgetPositionAlign.BL, Y.WidgetPositionAlign.TL]
                });
            }
        },
        release_current : function() {
            if (this.currentowner) {
                this.currentowner.release_calendar();
            }
        },
        document_click : function(e) {
            if (this.currentowner) {
                if (this.currentowner.get('node').ancestor('div').contains(e.target)) {
                    setTimeout(function() {M.form.dateselector.cancel_any_timeout()}, 100);
                } else {
                    this.currentowner.release_calendar();
                }
            }
        }
    }

}, '@VERSION@', {requires:['base','node','overlay', 'yui2-calendar', 'moodle-form-dateselector-skin']});<commit_msg>MDL-30305 Automatically hide the dateselector after choosing a date<commit_after>YUI.add('moodle-form-dateselector', function(Y) {

    /**
     * Add some custom methods to the node class to make our lives a little
     * easier within this module.
     */
    Y.mix(Y.Node.prototype, {
        /**
         * Gets the value of the first option in the select box
         */
        firstOptionValue : function() {
            if (this.get('nodeName').toLowerCase() != 'select') {
                return false;
            }
            return this.one('option').get('value');
        },
        /**
         * Gets the value of the last option in the select box
         */
        lastOptionValue : function() {
            if (this.get('nodeName').toLowerCase() != 'select') {
                return false;
            }
            return this.all('option').item(this.optionSize()-1).get('value');
        },
        /**
         * Gets the number of options in the select box
         */
        optionSize : function() {
            if (this.get('nodeName').toLowerCase() != 'select') {
                return false;
            }
            return parseInt(this.all('option').size());
        },
        /**
         * Gets the value of the selected option in the select box
         */
        selectedOptionValue : function() {
            if (this.get('nodeName').toLowerCase() != 'select') {
                return false;
            }
            return this.all('option').item(this.get('selectedIndex')).get('value');
        }
    });

    /**
     * Calendar class
     *
     * This is our main class
     */
    var CALENDAR = function(config) {
        CALENDAR.superclass.constructor.apply(this, arguments);
    };
    CALENDAR.prototype = {
        panel : null,
        yearselect : null,
        yearselectchange : null,
        monthselect : null,
        monthselectchange : null,
        dayselect : null,
        dayselectchange : null,
        enablecheckbox : null,
        initializer : function(config) {
            var controls = this.get('node').all('select');
            controls.each(function(node){
                if (node.get('name').match(/\[year]/)) {
                    this.yearselect = node;
                } else if (node.get('name').match(/\[month\]/)) {
                    this.monthselect = node;
                } else if (node.get('name').match(/\[day]/)) {
                    this.dayselect = node;
                } else {
                    node.on('focus', M.form.dateselector.cancel_any_timeout, M.form.dateselector);
                    node.on('blur', this.blur_event, this);
                    return;
                }
                node.on('focus', this.focus_event, this);
                node.on('click', this.focus_event, this);
                node.after('change', this.set_date_from_selects, this);
            }, this);

            if (this.yearselect && this.monthselect && this.dayselect) {
                this.enablecheckbox = this.get('node').one('input');
            }
        },
        focus_event : function(e) {
            M.form.dateselector.cancel_any_timeout();
            if (this.enablecheckbox == null || this.enablecheckbox.get('checked')) {
                this.claim_calendar();
            } else {
                if (M.form.dateselector.currentowner) {
                    M.form.dateselector.currentowner.release_calendar();
                }
            }
        },
        blur_event : function(e) {
            M.form.dateselector.hidetimeout = setTimeout(M.form.dateselector.release_current, 300);
        },
        handle_select_change : function(e) {
            this.set_date_from_selects();
        },
        claim_calendar : function() {
            M.form.dateselector.cancel_any_timeout();
            if (M.form.dateselector.currentowner == this) {
                return;
            }
            if (M.form.dateselector.currentowner) {
                M.form.dateselector.currentowner.release_calendar();
            }

            if (M.form.dateselector.currentowner != this) {
                this.connect_handlers();
                this.set_date_from_selects();
            }
            M.form.dateselector.currentowner = this;
            M.form.dateselector.calendar.cfg.setProperty('mindate', new Date(this.yearselect.firstOptionValue(), 0, 1));
            M.form.dateselector.calendar.cfg.setProperty('maxdate', new Date(this.yearselect.lastOptionValue(), 11, 31));
            M.form.dateselector.panel.set('constrain', this.get('node').ancestor('form'));
            M.form.dateselector.panel.show();
            M.form.dateselector.fix_position();
            setTimeout(function(){M.form.dateselector.cancel_any_timeout()}, 100);
        },
        set_date_from_selects : function() {
            var year = parseInt(this.yearselect.get('value'));
            var month = parseInt(this.monthselect.get('value')) - 1;
            var day = parseInt(this.dayselect.get('value'));
            M.form.dateselector.calendar.select(new Date(year, month, day));
            M.form.dateselector.calendar.setMonth(month);
            M.form.dateselector.calendar.setYear(year);
            M.form.dateselector.calendar.render();
        },
        set_selects_from_date : function(eventtype, args) {
            var date = args[0][0];
            var newyear = date[0];
            var newindex = newyear - this.yearselect.firstOptionValue();
            this.yearselect.set('selectedIndex', newindex);
            this.monthselect.set('selectedIndex', date[1] - this.monthselect.firstOptionValue());
            this.dayselect.set('selectedIndex', date[2] - this.dayselect.firstOptionValue());
            M.form.dateselector.release_current();
        },
        connect_handlers : function() {
            M.form.dateselector.calendar.selectEvent.subscribe(this.set_selects_from_date, this, true);
        },
        release_calendar : function() {
            M.form.dateselector.panel.hide();
            M.form.dateselector.currentowner = null;
            M.form.dateselector.calendar.selectEvent.unsubscribe(this.set_selects_from_date, this);
        }
    };
    Y.extend(CALENDAR, Y.Base, CALENDAR.prototype, {
        NAME : 'Date Selector',
        ATTRS : {
            firstdayofweek  : {
                validator : Y.Lang.isString
            },
            node : {
                setter : function(node) {
                    return Y.one(node);
                }
            }
        }
    });

    M.form = M.form || {};
    M.form.dateselector = {
        panel : null,
        calendar : null,
        currentowner : null,
        hidetimeout : null,
        repositiontimeout : null,
        init_date_selectors : function(config) {
            if (this.panel === null) {
                this.initPanel(config);
            }
            Y.all('fieldset.fdate_time_selector').each(function(){
                config.node = this;
                new CALENDAR(config);
            });
            Y.all('fieldset.fdate_selector').each(function(){
                config.node = this;
                new CALENDAR(config);
            });
        },
        initPanel : function(config) {
            this.panel = new Y.Overlay({
                visible : false,
                constrain : true,
                bodyContent : Y.Node.create('

'),
                id : 'dateselector-calendar-panel'
            });
            this.panel.render(document.body);
            this.panel.on('heightChange', this.fix_position, this);

            Y.one('#dateselector-calendar-panel').on('click', function(e){e.halt();});
            Y.one(document.body).on('click', this.document_click, this);

            this.calendar = new YAHOO.widget.Calendar(document.getElementById('dateselector-calendar-content'), {
                iframe: false,
                hide_blank_weeks: true,
                start_weekday: config.firstdayofweek
            });
            this.calendar.changePageEvent.subscribe(function(){
                this.fix_position();
            }, this);
        },
        cancel_any_timeout : function() {
            if (this.hidetimeout) {
                clearTimeout(this.hidetimeout);
                this.hidetimeout = null;
            }
            if (this.repositiontimeout) {
                clearTimeout(this.repositiontimeout);
                this.repositiontimeout = null;
            }
        },
        delayed_reposition : function() {
            if (this.repositiontimeout) {
                clearTimeout(this.repositiontimeout);
                this.repositiontimeout = null;
            }
            this.repositiontimeout = setTimeout(this.fix_position, 500);
        },
        fix_position : function() {
            if (this.currentowner) {
                this.panel.set('constrain', this.currentowner.get('node').ancestor('form'));
                this.panel.set('align', {
                    node:this.currentowner.get('node').one('select'),
                    points:[Y.WidgetPositionAlign.BL, Y.WidgetPositionAlign.TL]
                });
            }
        },
        release_current : function() {
            if (this.currentowner) {
                this.currentowner.release_calendar();
            }
        },
        document_click : function(e) {
            if (this.currentowner) {
                if (this.currentowner.get('node').ancestor('div').contains(e.target)) {
                    setTimeout(function() {M.form.dateselector.cancel_any_timeout()}, 100);
                } else {
                    this.currentowner.release_calendar();
                }
            }
        }
    }

}, '@VERSION@', {requires:['base','node','overlay', 'yui2-calendar', 'moodle-form-dateselector-skin']});<|endoftext|>