<commit_before>var total_tils = 0;

document.querySelectorAll('h3').forEach(element => {
    count_tils(element);
});

function count_tils(element) {
    var sibling;

    if (sibling = element.nextElementSibling) {
        var list = sibling.querySelectorAll('li');
        if (list.length > 0) {
            var text_content = element.textContent + ' (' + list.length + ' tils)';
            element.textContent = text_content;

            total_tils += list.length;
        }
    }
}

const count = document.createElement('p');
count.textContent = 'A collection of ' + total_tils + ' in total.';

var sidebar = document.getElementById('sidebar');
sidebar.appendChild(count);
<commit_msg>Minor rephrasing<commit_after>var total_tils = 0;

document.querySelectorAll('h3').forEach(element => {
    count_tils(element);
});

function count_tils(element) {
    var sibling;

    if (sibling = element.nextElementSibling) {
        var list = sibling.querySelectorAll('li');
        if (list.length > 0) {
            var text_content = element.textContent + ' (' + list.length + ' tils)';
            element.textContent = text_content;

            total_tils += list.length;
        }
    }
}

const count = document.createElement('p');
count.textContent = 'A collection of ' + total_tils + ' tils in total.';

var sidebar = document.getElementById('sidebar');
sidebar.appendChild(count);
<|endoftext|>


<commit_before>(function($,undefined) {
	var resultMap = {};
	var ref = new Firebase("https://luminous-torch-8660.firebaseio.com/");//One's Firebase URL
	var imageRef = ref.child("images");

//required data for query
var bingQuery = {
	queryUrl: "https://api.datamarket.azure.com/Bing/Search/Image",
		encodedKey: btoa(":NgrxKg56U9UaZq0w0z4ZEKRxgMt7LDGjKaHrPgpoY7M="),//One's Azure account key
		inputText: "",
		setHeader: function(xhr) {
			xhr.setRequestHeader('Authorization','Basic '+bingQuery.encodedKey);
		},
		format: "json",
		page: 0,
		item: 10
	}

//query
function requestQuery(config, callback) {
	$.ajax({
		beforeSend: config.setHeader,
		type: "GET",
		url: config.queryUrl,
		data: {
			$format: config.format,
			Query: encodeURIComponent("'"+config.inputText+"'"),
			$top: config.item,
			$skip: config.page++*(config.item)
		},
		dataType: "json",
		success: function(responseData) {
			var results = responseData["d"]["results"];
			callback(results.filter(function(result) {
				if (resultMap[result["MediaUrl"]]) {
					console.error("Duplicated:", result["MediaUrl"]);
					return false;
				}
				resultMap[result["MediaUrl"]] = result;
				return true;
			}));
		}
	});
}

//buffer
function buffer(callback) {
	var storage = [];
	var minSize = 1;

	function callBufferedWrappers() {
		var wrapper;
		while (storage.length > minSize && storage[0].args) {
			wrapper = storage.shift();
			wrapper.apply(this, wrapper.args);
		}
	}

	return {
		wrapper: function(optionalCallback) {
			var wrapper = function() {
				(optionalCallback || callback).apply(this, arguments);
			};
			storage.push(wrapper);
			callBufferedWrappers();

			return function() {
				wrapper.args = arguments;
				callBufferedWrappers();
			};
		},
		clear: function() {
			storage = [];
		}
	};
}

//draw
function appendImage(result) {
	var parent = $("#result");
	var structure = $("#script_template").html();
	var template = Handlebars.compile(structure);
	for(var i = 0; i < result.length; i++) {
		var html = template({imageSrc: result[i]["MediaUrl"]});
		$(html).find(".image").data("result", result[i]).appendTo(parent);
	}
}

var BufferedCrawler = function(callback) {
	var bufferInstance = buffer(callback);
	return {
		getFirstPage: function(config, optionalCallback) {
			bufferInstance.clear();
			requestQuery(config, optionalCallback || callback);
			requestQuery(config, bufferInstance.wrapper(optionalCallback));
		},
		getNewPage: function(config, optionalCallback) {
			requestQuery(config, bufferInstance.wrapper(optionalCallback));
		}
	};
};

var UnbufferedCrawler = function(callback) {
	return {
		getFirstPage: function(config, optionalCallback) {
			requestQuery(config, optionalCallback || callback);
		},
		getNewPage: function(config, optionalCallback) {
			requestQuery(config, optionalCallback || callback);
		}
	};
};

//upload to Firebase DB
function uploadImg() {
	if($(this).find(".selection").is(':checked')) {
		var jsonData = $(this).find(".image").data("result");//resultMap[$(this).find(".image").attr("src")];
		var location = new URL(jsonData["MediaUrl"]);
		var host = btoa(location.origin);
		var path = btoa(location.href);
		var iref = imageRef.child(host).child(path);
		iref.once('value',function(snapshot) {
			if(!snapshot.exists()) {
				iref.set(jsonData);
			}
		});
	}
}

//event handling
var crawler = BufferedCrawler(appendImage);

$("#keyword").keyup(function(event) {
	if(event.keyCode == 13) {
		$("#btn_search").click();
	}
});

$('#btn_save').click(function() {
	$('#result').find(".imageView").map(uploadImg);
});

$('#btn_search').click(function() {
	resultMap = {};
	var targetApi = bingQuery;
	targetApi.inputText = $('#keyword').val();
	$('#result').empty();
	$('#label').text(targetApi.inputText);

	targetApi.page = 0;
	crawler.getFirstPage(targetApi);
});

$(document).endlessScroll({
	callback:function(n) {
		var targetApi = bingQuery;
		crawler.getNewPage(targetApi);
	}
});
})(jQuery);
<commit_msg>bugfix - data append location miss resulted in upload fail<commit_after>(function($,undefined) {
	var resultMap = {};
	var ref = new Firebase("https://luminous-torch-8660.firebaseio.com/");//One's Firebase URL
	var imageRef = ref.child("images");

//required data for query
var bingQuery = {
	queryUrl: "https://api.datamarket.azure.com/Bing/Search/Image",
		encodedKey: btoa(":NgrxKg56U9UaZq0w0z4ZEKRxgMt7LDGjKaHrPgpoY7M="),//One's Azure account key
		inputText: "",
		setHeader: function(xhr) {
			xhr.setRequestHeader('Authorization','Basic '+bingQuery.encodedKey);
		},
		format: "json",
		page: 0,
		item: 10
	}

//query
function requestQuery(config, callback) {
	$.ajax({
		beforeSend: config.setHeader,
		type: "GET",
		url: config.queryUrl,
		data: {
			$format: config.format,
			Query: encodeURIComponent("'"+config.inputText+"'"),
			$top: config.item,
			$skip: config.page++*(config.item)
		},
		dataType: "json",
		success: function(responseData) {
			var results = responseData["d"]["results"];
			callback(results.filter(function(result) {
				if (resultMap[result["MediaUrl"]]) {
					console.error("Duplicated:", result["MediaUrl"]);
					return false;
				}
				resultMap[result["MediaUrl"]] = result;
				return true;
			}));
		}
	});
}

//buffer
function buffer(callback) {
	var storage = [];
	var minSize = 1;

	function callBufferedWrappers() {
		var wrapper;
		while (storage.length > minSize && storage[0].args) {
			wrapper = storage.shift();
			wrapper.apply(this, wrapper.args);
		}
	}

	return {
		wrapper: function(optionalCallback) {
			var wrapper = function() {
				(optionalCallback || callback).apply(this, arguments);
			};
			storage.push(wrapper);
			callBufferedWrappers();

			return function() {
				wrapper.args = arguments;
				callBufferedWrappers();
			};
		},
		clear: function() {
			storage = [];
		}
	};
}

//draw
function appendImage(result) {
	var parent = $("#result");
	var structure = $("#script_template").html();
	var template = Handlebars.compile(structure);
	for(var i = 0; i < result.length; i++) {
		var html = template({imageSrc: result[i]["MediaUrl"]});
		$(html).data("result", result[i]).appendTo(parent);
	}
}

var BufferedCrawler = function(callback) {
	var bufferInstance = buffer(callback);
	return {
		getFirstPage: function(config, optionalCallback) {
			bufferInstance.clear();
			requestQuery(config, optionalCallback || callback);
			requestQuery(config, bufferInstance.wrapper(optionalCallback));
		},
		getNewPage: function(config, optionalCallback) {
			requestQuery(config, bufferInstance.wrapper(optionalCallback));
		}
	};
};

var UnbufferedCrawler = function(callback) {
	return {
		getFirstPage: function(config, optionalCallback) {
			requestQuery(config, optionalCallback || callback);
		},
		getNewPage: function(config, optionalCallback) {
			requestQuery(config, optionalCallback || callback);
		}
	};
};

//upload to Firebase DB
function uploadImg() {
	if($(this).find(".selection").is(':checked')) {
		var jsonData = $(this).data("result");//resultMap[$(this).find(".image").attr("src")];
		var location = new URL(jsonData["MediaUrl"]);
		var host = btoa(location.origin);
		var path = btoa(location.href);
		var iref = imageRef.child(host).child(path);
		iref.once('value',function(snapshot) {
			if(!snapshot.exists()) {
				iref.set(jsonData);
			}
		});
	}
}

//event handling
var crawler = BufferedCrawler(appendImage);

$("#keyword").keyup(function(event) {
	if(event.keyCode == 13) {
		$("#btn_search").click();
	}
});

$('#btn_save').click(function() {
	$('#result').find(".imageView").map(uploadImg);
});

$('#btn_search').click(function() {
	resultMap = {};
	var targetApi = bingQuery;
	targetApi.inputText = $('#keyword').val();
	$('#result').empty();
	$('#label').text(targetApi.inputText);

	targetApi.page = 0;
	crawler.getFirstPage(targetApi);
});

$(document).endlessScroll({
	callback:function(n) {
		var targetApi = bingQuery;
		crawler.getNewPage(targetApi);
	}
});
})(jQuery);
<|endoftext|>


<commit_before>var
    emitter = require('events').EventEmitter,
    util = require("util");

function spawn (config) {

    function Crawler (config) {

        //busy flag.
        this.crawling = false;

        //build the queue.
        this.queue = new Array();

        //raw config data.
        this.config = config;

        //set the url
        this.url;

        //set the search terms.
        this.terms;

        //set the stop words.
        this.stopWords;

        this.addUrl = function(url) {

            if(typeof url != 'string' || url == '')  {

                this.emit('error', Error('Invalid URL supplied: ' + url));

                return false;

            } else {

                this.queue.push(url);

                return true;
            }
        };

        this.setTerms = function(args) {

            this.terms = args;
        };

        this.setStopWords = function(args) {

            this.stopWords = args;
        };

        this.setLimit = function(args) {

            this.limit = args;
        };

        this.crawl = function() {
            var self = this;

            var http = require('http');

            if (this.queue.length > 0) {
                var url = this.queue.shift();
            } else {
                this.emit('error', Error('Crawl() - Invalid URL: ' + url));

                return false;
            }

            console.log('Crawling: ' + url);

            var req = http.get(url, function(res) {

                res.setEncoding('utf8');

                res.on('data', function (chunk) {
                    this.buffer += chunk;
                });

                res.on('end', function() {

                    //call the crawlers transform method to parse the data.
                    self.transform({status: res.statusCode,
                                                headers: JSON.stringify(res.headers),
                                                body: this.buffer});
                })

            });

            req.on('error', function(e) {
              console.log('problem with request: ' + e.message);
            });
        };

        this.transform = function(data) {

            self = this;

            //check that the data object has valid data.
            if (data && data.status == 200 && data.body != '') {
                //var matches = data.body.match(/<a[^>]*href="([^"]*)"[^>]*>.*<\/a>/g);

                // url strings contained in the document.
                var urls = data.body.match(/href="([^"]*)"/g);

                //check if relevant. If so save the page locally.
                if (this.isRelevant(data.body)) {
                    //store the document.
                    console.log('save document');
                }

                /*
                 *process the urls and push them into the queue
                 */
                urls.forEach(function(val, index, arr) {

                    var url = val.substring(6, val.indexOf('"', 6));

                    /*check if the protocol is defined
                     *if so, the url is valid, so save it and return.
                     *ex: http or https
                     */
                    if (url.substring(0, 4).toLowerCase() == 'http') {
                        self.addUrl(url);

                        return;
                    }

                    /*check if the url is a current page anchor
                     *#local_anchor
                     *return
                     */
                    if (url.charAt(0) == '#' ) { return; }

                    /*
                     *check if the string is a relative link or an external link
                     *relative link would begin with /
                     *external links would begin with //
                     */
                    var slashes = url.substring(0,2);

                    switch (slashes) {
                        case '/':
                            console.log(url);
                            break;

                        case '//':
                            /*
                             *add the protocol to the url string and push it into the links array.
                             *ex: http:
                             */

                            self.addUrl('http' + url);
                            break;
                    }

                });

                //console.log(this.queue);
            }
        };

        /*
         *parse the string into words and keep track of word occurances.
         *compare to the supplies terms.
         */
        this.isRelevant = function(str) {

            //strip out the tags and split the data on empty space.
            var parts = Crawler.prototype.stripTags(str).split(' ');

            //document index.
            var collection = Object.create(null);

            /*
             *loop thought the array and build an index.
             */
            parts.forEach(function(val, i, arr) {
                //clean the value
                var word = val.trim().toLowerCase();

                //check if the value is valid
                if (Crawler.prototype.isValid(word)) {

                    //console.log('-' +word.replace(/\W/g, ' ').split(' '));

                    var result = word.replace(/\W/g, ' ').split(/\s+/);

                    if(result instanceof Array) {

                        result.forEach(function(val){

                            //check if val is not empty
                            if (val !== "" && val.length > 2) {

                                /*
                                 *if the value exists in the collection, increment its value by one
                                 *else add the value.
                                 */
                                if (collection[val]) {

                                    collection[val] = collection[val] + 1;

                                } else {

                                    collection[val] = 1;
                                }
                            }
                        });
                    }
                }
            });

            console.log(collection);
            //console.log(index);

            //compare the index to the search terms.
        }

        this.addUrl(config.url);
        this.setTerms(config.terms);
    }

    util.inherits(Crawler, emitter);

    Crawler.prototype.isValid = function(str){
            return !/[~`!#$%\^&*+=\-\[\]\\';,/{}|\\":<>\?]/g.test(str);
    };

    Crawler.prototype.stripTags = function (str){
            var rex = /(<([^>]+)>)/ig;
            return str.replace(rex , "");
    }

    return new Crawler(config);
}

module.exports = spawn;<commit_msg>Match indexed term to relevant terms<commit_after>var
    emitter = require('events').EventEmitter,
    util = require("util");

function spawn (config) {

    function Crawler (config) {

        //busy flag.
        this.crawling = false;

        //build the queue.
        this.queue = new Array();

        //raw config data.
        this.config = config;

        //set the url
        this.url;

        //set the search terms.
        this.terms;

        //set the stop words.
        this.stopWords;

        this.addUrl = function(url) {

            if(typeof url != 'string' || url == '')  {

                this.emit('error', Error('Invalid URL supplied: ' + url));

                return false;

            } else {

                this.queue.push(url);

                return true;
            }
        };

        this.setTerms = function(args) {

            this.terms = args;
        };

        this.setStopWords = function(args) {

            this.stopWords = args;
        };

        this.setLimit = function(args) {

            this.limit = args;
        };

        this.crawl = function() {
            var self = this;

            var http = require('http');

            if (this.queue.length > 0) {
                var url = this.queue.shift();
            } else {
                this.emit('error', Error('Crawl() - Invalid URL: ' + url));

                return false;
            }

            console.log('Crawling: ' + url);

            var req = http.get(url, function(res) {

                res.setEncoding('utf8');

                res.on('data', function (chunk) {
                    this.buffer += chunk;
                });

                res.on('end', function() {

                    //call the crawlers transform method to parse the data.
                    self.transform({status: res.statusCode,
                                                headers: JSON.stringify(res.headers),
                                                body: this.buffer});
                })

            });

            req.on('error', function(e) {
              console.log('problem with request: ' + e.message);
            });
        };

        this.transform = function(data) {

            self = this;

            //check that the data object has valid data.
            if (data && data.status == 200 && data.body != '') {
                //var matches = data.body.match(/<a[^>]*href="([^"]*)"[^>]*>.*<\/a>/g);

                // url strings contained in the document.
                var urls = data.body.match(/href="([^"]*)"/g);

                //check if relevant. If so save the page locally.
                if (this.isRelevant(data.body)) {
                    //store the document.
                    console.log('save document');
                }

                /*
                 *process the urls and push them into the queue
                 */
                urls.forEach(function(val, index, arr) {

                    var url = val.substring(6, val.indexOf('"', 6));

                    /*check if the protocol is defined
                     *if so, the url is valid, so save it and return.
                     *ex: http or https
                     */
                    if (url.substring(0, 4).toLowerCase() == 'http') {
                        self.addUrl(url);

                        return;
                    }

                    /*check if the url is a current page anchor
                     *#local_anchor
                     *return
                     */
                    if (url.charAt(0) == '#' ) { return; }

                    /*
                     *check if the string is a relative link or an external link
                     *relative link would begin with /
                     *external links would begin with //
                     */
                    var slashes = url.substring(0,2);

                    switch (slashes) {
                        case '/':
                            console.log(url);
                            break;

                        case '//':
                            /*
                             *add the protocol to the url string and push it into the links array.
                             *ex: http:
                             */

                            self.addUrl('http' + url);
                            break;
                    }

                });

                //console.log(this.queue);
            }
        };

        /*
         *parse the string into words and keep track of word occurances.
         *compare to the supplies terms.
         */
        this.isRelevant = function(str) {

            //strip out the tags and split the data on empty space.
            var parts = Crawler.prototype.stripTags(str).split(' ');

            //document index.
            var collection = Object.create(null);

            /*
             *loop thought the array and build an index.
             */
            parts.forEach(function(val, i, arr) {
                //clean the value
                var word = val.trim();

                //check if the value is valid
                if (Crawler.prototype.isValid(word)) {

                    //console.log('-' +word.replace(/\W/g, ' ').split(' '));

                    var result = word.replace(/\W/g, ' ').split(/\s+/);

                    if(result instanceof Array) {

                        result.forEach(function(val){

                            //check if val is not empty
                            if (val !== "" && val.length > 2) {

                                /*
                                 *if the value exists in the collection, increment its value by one
                                 *else add the value.
                                 */
                                if (collection[val]) {

                                    collection[val] = collection[val] + 1;

                                } else {

                                    collection[val] = 1;
                                }
                            }
                        });
                    }
                }
            });

            /*
             *loop through the provided terms and compare to the
             *terms inside the collection.
             *Store matching values
             */
            var matches = Object.create(null);

            this.terms.forEach(function(val, i, arr) {
                if(val in collection) {
                    console.log(val);
                }
            });

            //console.log(collection);
            //console.log(index);

            //compare the index to the search terms.
        }

        this.addUrl(config.url);
        this.setTerms(config.terms);
    }

    util.inherits(Crawler, emitter);

    Crawler.prototype.isValid = function(str){
            return !/[~`!#$%\^&*+=\-\[\]\\';,/{}|\\":<>\?]/g.test(str);
    };

    Crawler.prototype.stripTags = function (str){
            var rex = /(<([^>]+)>)/ig;
            return str.replace(rex , "");
    }

    return new Crawler(config);
}

module.exports = spawn;<|endoftext|>


<commit_before>
	//variable qui permet de charger une instance Sig différente à chaque éxecution
	var SigLoader = new Array();

		SigLoader.getSig = function (){

			this.Sig = new Array();

			this.Sig.markersLayer = "";

			this.Sig.geoJsonCollection = "";

			this.Sig.initParameters = "";

			this.Sig.cssModuleName = "";

			this.Sig.currentFilter = "none";

			this.Sig.icoMarkersTypes = {}; //definition dans map_initializar.js
			this.Sig.icoMarkersTags = {}; //definition dans map_initializar.js

			//mémorise les identifiants des éléments de chaque carte
			this.Sig.listId = new Array();
			this.Sig.listPanel = new Array();
			this.Sig.panelFilter = "all";
			this.Sig.dataMap = {};

			//mémorise les éléments
			this.Sig.elementsMap = new Array();

			//##
			//créer un marker sur la carte, en fonction de sa position géographique
			this.Sig.markerSingleList = new Array();
			this.Sig.popupOpen = false;

			//##
			//créé une donnée GeoJson (pour les cluster)
			this.Sig.getGeoJsonMarker = function (properties/*json*/, coordinates/*array[lat, lng]*/)
			{
				properties.visible = false;
				//alert(JSON.stringify(properties));
				return { "type": 'Feature',
						 "properties": properties,
						 "geometry": { type: 'Point',
									 coordinates: coordinates } };
			};

			//##
			//créé un objet L.Marker (sans cluster)
			this.Sig.getMarkerSingle = function(thisMap, options, coordinates)
			{
				var contentString = options.content;
				if(options.content == null) contentString = "info window";

				var markerOptions = { icon : options.icon };

				var marker = L.marker(coordinates, markerOptions).addTo(thisMap)
																 .bindPopup(contentString);

				this.markerSingleList.push(marker);

				marker.on('click', function(e) {
						marker.openPopup();
				});

				return marker;
			};

			//##
			//récupère le nom de l'icon en fonction du type de marker souhaité
			this.Sig.getIcoMarker = function(thisData)
			{
				var type = thisData["type"];
				var ico = this.getIcoNameByType(type);
				var color = this.getIcoColorByType(type);

				return L.AwesomeMarkers.icon({icon: ico + " fa-" + color, iconColor:color, prefix: 'fa' });
			};

			//##
			//supprime tous les marker de la carte
			this.Sig.clearMap = function(thisMap)
			{
				if(this.markersLayer != "")
					this.markersLayer.clearLayers();

				$.each(this.markerSingleList, function(){
					thisMap.removeLayer(this);
				});

				this.listId = new Array();
				this.listPanel = new Array();
				$( this.cssModuleName + " #liste_map_element").html("");

			};

			//## TODO : UTILISER cette fonction pour gérer le fullScreenMap
			//gère les dimensions des différentes parties de la carte (carte, panel, etc)
			this.Sig.resizeMap = function()
			{
				return;
				//full screen map
				var mapHeight = $("body").height() - $(".topbar").height() - $(".toolbar").height() - $("footer").height() - 1;
				$("#mapCanvas").css({"height":mapHeight});
				$("#mapCanvas").css({"margin-bottom":mapHeight*(-1)});
				$("#right_tool_map").css({"height":mapHeight});
				$("#liste_map_element").css({"height":mapHeight - $("#map_pseudo_filters").height() - 8*2 /*padding*/ - $("#chk-scope").height() - 33 });
				$("#liste_map_element").css({"max-height":mapHeight - $("#map_pseudo_filters").height() - 8*2 /*padding*/ });
				$("#right_tool_map").css({"left":$("#carto").width() - $("#right_tool_map").width()});
				$(".btn-group-map").css({"margin-top":$(".panel_map").height()*(-1) - 20});

			};

			this.Sig.verifyPanelFilter = function (thisData){

				if(this.usePanel == false) return true;

				//si thisData n'a pas de tags
				if("undefined" == typeof thisData["tags"]) { return true; }

				var thisSig = this;
				var inArray = false;
				$.each(thisData["tags"], function(index, value){
					if(value == thisSig.panelFilter) inArray = true;
				});

				if(		inArray //$.inArray(this.panelFilter, thisData["tags"]) > -1 //ne fonctionne pas
					||  this.panelFilter == "all") {
						return true;
				}
				else{ return false; }
			};

			this.Sig.showOneElementOnMap = function(thisData, thisMap){

				var objectId = thisData._id ? thisData._id.$id.toString() : null;
				if(objectId != null)
				{
					if("undefined" != typeof thisData['geo'] || "undefined" != typeof thisData['geoPosition']){
						if(this.verifyPanelFilter(thisData)){

							//préparation du contenu de la bulle
							var content = this.getPopupCitoyen(thisData);

							//création de l'icon sur la carte
							var theIcon = this.getIcoMarker(thisData);
							var properties = { 	id : objectId,
												icon : theIcon,
												content: content };
							var coordinates;
							if( thisData['geo'].longitude != null ){ coordinates = new Array (thisData['geo'].longitude, thisData['geo'].latitude); }
							else { 							  	  	 coordinates = thisData.geoPosition.coordinates; }

							var marker;
							//si le tag de l'élément est dans la liste des éléments à ne pas mettre dans les clusters
							//on créé un marker simple

							if($.inArray(tag, this.notClusteredTag) > -1){

								marker = this.getMarkerSingle(thisMap, properties, coordinates);

								//si l'élément n'est pas déjà dans la liste, on recrée le marker et on l'enregistre
								if($.inArray(objectId, this.listId) == -1){
									this.elementsMap.push(thisData);
									this.listId.push(objectId);
									this.populatePanel(tag, objectId);

									//affiche l'éléments dans la liste de droite
									$("#liste_map_element").append(this.createItemRigthListMap(thisData, marker));
									//ajoute l'événement click sur l'élément de la liste, pour ouvrir la bulle du marker correspondant
									$("#item_map_list_" + objectId).click(function(){
										thisMap.panTo(marker.getLatLng(), {"animate" : true });
										this.checkListElementMap(thisMap);
										marker.openPopup();
									});
								}
							}
							//sinon on crée un nouveau marker pour cluster
							else{

							marker = this.getGeoJsonMarker(properties, coordinates);
							this.geoJsonCollection['features'].push(marker);

							}

							//si l'élément n'est pas déjà dans la liste, on l'enregistre
							if($.inArray(objectId, this.listId) == -1){

								this.elementsMap.push(thisData);
								this.listId.push(objectId);
								this.populatePanel(thisData["tags"], objectId);
								//affiche l'éléments dans la liste de droite
								$(this.cssModuleName + " #liste_map_element").append(this.createItemRigthListMap(thisData, marker));
							}

						}

					}

					//affiche les LINKS et les MEMBERS
					var thisSig = this;
					if(thisData.links != null)
						if(thisData.links.members != null){
							$.each(thisData.links.members, function(i, thisMember)  {
								thisMember._id = { $id : i };
								thisSig.showOneElementOnMap(thisMember);
							});
						}

				}

			};

			this.Sig.showFilterOnMap = function(data, thisFilter, thisMap){

				var thisSig = this;
				var dataFilter = data[thisFilter];
				if(dataFilter.length > 1){
					$.each(dataFilter, function(i, thisData)  {

						thisSig.showOneElementOnMap(thisData, thisMap);
					});
				}
				else{
					thisSig.showOneElementOnMap(dataFilter, thisMap);
				}

			};

			this.Sig.showMapElements = function(thisMap, data)
			{
				var filterPanelValue = "citoyens";
				//enregistre les dernières données dans une variable locale
				this.dataMap = data;
				//alert("datas : " + JSON.stringify(this.dataMap));
				//efface les elements de la carte si elle n'est pas vide
				if(this.markersLayer != "") this.clearMap(thisMap);

				//conteneur de marker cluster
				this.markersLayer = new L.MarkerClusterGroup({"maxClusterRadius" : 40});
				thisMap.addLayer(this.markersLayer);

				//collection de marker geojson
				this.geoJsonCollection = { type: 'FeatureCollection', features: new Array() };

				this.showIcoLoading(true);

				//on affiche les data filtre par filtre, en suivant la Desc des datas
				var thisSig = this;

				$.each(data, function (key, value){
					thisSig.showFilterOnMap(data, key, thisMap);
				});

				var points = L.geoJson(this.geoJsonCollection, {				//Pour les clusters seulement :
						onEachFeature: function (feature, layer) {				//sur chaque marker
							layer.bindPopup(feature["properties"]["content"]); 	//ajoute la bulle d'info avec les données
							layer.setIcon(feature["properties"]["icon"]);	   	//affiche l'icon demandé
							layer.on('mouseclick', function(e) {	layer.openPopup(); });

							//au click sur un element de la liste de droite, on zoom pour déclusturiser, et on ouvre la bulle
							$(thisSig.cssModuleName + " #item_map_list_" + feature.properties.id).click(function(){
								thisMap.setView([feature.geometry.coordinates[1],
											  feature.geometry.coordinates[0]],
											  13, {"animate" : true });

								thisSig.checkListElementMap(thisMap); //alert("check");
								layer.openPopup();
							});
						}
					});

					this.markersLayer.addLayer(points); 		// add it to the cluster group
					thisMap.addLayer(this.markersLayer);		// add it to the map

					$('#ico_reload').removeClass("fa-spin");
					$('#ico_reload').css({"display":"none"});

					if(this.initParmerters.usePanel)
						this.updatePanel(thisMap);

					this.checkListElementMap(thisMap);
					this.showIcoLoading(false);

		};

		//##
		//##	PANEL FILTER	##
		//##

		this.Sig.changeFilter = function (val, thisMap)
		{
			if(this.panelFilter != "")
				$(this.cssModuleName + '#item_panel_map_' + this.panelFilter).removeClass("selected");

			this.panelFilter = val;
			$(this.cssModuleName + '#item_panel_map_' + this.panelFilter).addClass("selected");
			this.showMapElements(thisMap, this.dataMap);
		};

		this.Sig.populatePanel = function(tags, objectId){
			var thisSig = this;
			if("undefined" == typeof tags) tags = new Array("all");
			//alert("tags : " + tags);
			$.each(tags, function(){
				thisSig.listPanel.push(this); //new Array(objectId);
			});

		};

		this.Sig.updatePanel = function(thisMap){ //alert("updatePanel : " + JSON.stringify(this.listPanel));
			var thisSig = this;
			$.each(this.listPanel, function(key, value){
				//si l'item n'existe pas encore
				//alert("value : " + this);
				var valueId = value.replace(/\s/g,"");

				var ico = thisSig.getIcoNameByTag(value);
				var color = thisSig.getIcoColorByTag(value);

				//var itemName = this;
				if(!$(thisSig.cssModuleName + ' #item_panel_map_' + valueId).length){ //on le rajoute...
					var newItem = "<button class='item_panel_map' id='item_panel_map_" + valueId + "'>" +
								     " " + value +
								   "</button>";
					$('.panel_map').append(newItem);
					$(thisSig.cssModuleName + ' #item_panel_map_' + valueId).click(function(){
						thisSig.changeFilter(value, thisMap);
					});
					//alert('len : ' + $('#item_panel_map_' + item).length);
					//alert(newItem);
				}

			});
		};

		//##
		//##	LOAD MAP	##
		//##

		//##
		//chargement de la carte
	 	this.Sig.loadMap = function(canvasId)
	 	{
			$("#"+canvasId).html("");
			$("#"+canvasId).css({"background-color": "#456074"});

			//initialisation des variables de départ de la carte
			var map = L.map(canvasId, { "zoomControl" : false,
										"scrollWheelZoom":true,
										"center" : [51.505, -0.09],
										"zoom" : 4,
										"worldCopyJump" : false });

			var tileLayer = L.tileLayer('http://{s}.tile.stamen.com/toner/{z}/{x}/{y}.png', {
				//attribution: 'Map tiles by Stamen Design, CC BY 3.0 — Map data © OpenStreetMap contributors, CC-BY-SA',
				attribution: 'Map tiles by Stamen Design',
				subdomains: 'abcd',
				minZoom: 0,
				maxZoom: 20
			}).setOpacity(0.4).addTo(map);

			return map;
		};

		//alert((this.Sig));
		this.Sig = this.getSigInitializer(this.Sig);
		this.Sig = this.getSigRightList(this.Sig);
		this.Sig = this.getSigPopupContent(this.Sig);

		return this.Sig;
	};
	<commit_msg>fix bug notClusteredTag<commit_after>
	//variable qui permet de charger une instance Sig différente à chaque éxecution
	var SigLoader = new Array();

		SigLoader.getSig = function (){

			this.Sig = new Array();

			this.Sig.markersLayer = "";

			this.Sig.geoJsonCollection = "";

			this.Sig.initParameters = "";

			this.Sig.cssModuleName = "";

			this.Sig.currentFilter = "none";

			this.Sig.icoMarkersTypes = {}; //definition dans map_initializar.js
			this.Sig.icoMarkersTags = {}; //definition dans map_initializar.js

			//mémorise les identifiants des éléments de chaque carte
			this.Sig.listId = new Array();
			this.Sig.listPanel = new Array();
			this.Sig.panelFilter = "all";
			this.Sig.dataMap = {};

			//mémorise les éléments
			this.Sig.elementsMap = new Array();

			//##
			//créer un marker sur la carte, en fonction de sa position géographique
			this.Sig.markerSingleList = new Array();
			this.Sig.popupOpen = false;

			//##
			//créé une donnée GeoJson (pour les cluster)
			this.Sig.getGeoJsonMarker = function (properties/*json*/, coordinates/*array[lat, lng]*/)
			{
				properties.visible = false;
				//alert(JSON.stringify(properties));
				return { "type": 'Feature',
						 "properties": properties,
						 "geometry": { type: 'Point',
									 coordinates: coordinates } };
			};

			//##
			//créé un objet L.Marker (sans cluster)
			this.Sig.getMarkerSingle = function(thisMap, options, coordinates)
			{
				var contentString = options.content;
				if(options.content == null) contentString = "info window";

				var markerOptions = { icon : options.icon };

				var marker = L.marker(coordinates, markerOptions).addTo(thisMap)
																 .bindPopup(contentString);

				this.markerSingleList.push(marker);

				marker.on('click', function(e) {
						marker.openPopup();
				});

				return marker;
			};

			//##
			//récupère le nom de l'icon en fonction du type de marker souhaité
			this.Sig.getIcoMarker = function(thisData)
			{
				var type = thisData["type"];
				var ico = this.getIcoNameByType(type);
				var color = this.getIcoColorByType(type);

				return L.AwesomeMarkers.icon({icon: ico + " fa-" + color, iconColor:color, prefix: 'fa' });
			};

			//##
			//supprime tous les marker de la carte
			this.Sig.clearMap = function(thisMap)
			{
				if(this.markersLayer != "")
					this.markersLayer.clearLayers();

				$.each(this.markerSingleList, function(){
					thisMap.removeLayer(this);
				});

				this.listId = new Array();
				this.listPanel = new Array();
				$( this.cssModuleName + " #liste_map_element").html("");

			};

			//## TODO : UTILISER cette fonction pour gérer le fullScreenMap
			//gère les dimensions des différentes parties de la carte (carte, panel, etc)
			this.Sig.resizeMap = function()
			{
				return;
				//full screen map
				var mapHeight = $("body").height() - $(".topbar").height() - $(".toolbar").height() - $("footer").height() - 1;
				$("#mapCanvas").css({"height":mapHeight});
				$("#mapCanvas").css({"margin-bottom":mapHeight*(-1)});
				$("#right_tool_map").css({"height":mapHeight});
				$("#liste_map_element").css({"height":mapHeight - $("#map_pseudo_filters").height() - 8*2 /*padding*/ - $("#chk-scope").height() - 33 });
				$("#liste_map_element").css({"max-height":mapHeight - $("#map_pseudo_filters").height() - 8*2 /*padding*/ });
				$("#right_tool_map").css({"left":$("#carto").width() - $("#right_tool_map").width()});
				$(".btn-group-map").css({"margin-top":$(".panel_map").height()*(-1) - 20});

			};

			this.Sig.verifyPanelFilter = function (thisData){

				if(this.usePanel == false) return true;

				//si thisData n'a pas de tags
				if("undefined" == typeof thisData["tags"]) { return true; }

				var thisSig = this;
				var inArray = false;
				$.each(thisData["tags"], function(index, value){
					if(value == thisSig.panelFilter) inArray = true;
				});

				if(		inArray //$.inArray(this.panelFilter, thisData["tags"]) > -1 //ne fonctionne pas
					||  this.panelFilter == "all") {
						return true;
				}
				else{ return false; }
			};

			this.Sig.showOneElementOnMap = function(thisData, thisMap){

				var objectId = thisData._id ? thisData._id.$id.toString() : null;
				if(objectId != null)
				{
					if("undefined" != typeof thisData['geo'] || "undefined" != typeof thisData['geoPosition']){
						if(this.verifyPanelFilter(thisData)){

							//préparation du contenu de la bulle
							var content = this.getPopupCitoyen(thisData);

							//création de l'icon sur la carte
							var theIcon = this.getIcoMarker(thisData);
							var properties = { 	id : objectId,
												icon : theIcon,
												content: content };
							var coordinates;
							if( thisData['geo'].longitude != null ){ coordinates = new Array (thisData['geo'].longitude, thisData['geo'].latitude); }
							else { 							  	  	 coordinates = thisData.geoPosition.coordinates; }

							var marker;
							//si le tag de l'élément est dans la liste des éléments à ne pas mettre dans les clusters
							//on créé un marker simple
							//TODO : refactor notClusteredTag > notClusteredType
							if($.inArray(thisData['type'], this.notClusteredTag) > -1){

								marker = this.getMarkerSingle(thisMap, properties, coordinates);

								//si l'élément n'est pas déjà dans la liste, on recrée le marker et on l'enregistre
								if($.inArray(objectId, this.listId) == -1){
									this.elementsMap.push(thisData);
									this.listId.push(objectId);
									this.populatePanel(tag, objectId);

									//affiche l'éléments dans la liste de droite
									$("#liste_map_element").append(this.createItemRigthListMap(thisData, marker));
									//ajoute l'événement click sur l'élément de la liste, pour ouvrir la bulle du marker correspondant
									$("#item_map_list_" + objectId).click(function(){
										thisMap.panTo(marker.getLatLng(), {"animate" : true });
										this.checkListElementMap(thisMap);
										marker.openPopup();
									});
								}
							}
							//sinon on crée un nouveau marker pour cluster
							else{
								marker = this.getGeoJsonMarker(properties, coordinates);
								this.geoJsonCollection['features'].push(marker);
							}

							//si l'élément n'est pas déjà dans la liste, on l'enregistre
							if($.inArray(objectId, this.listId) == -1){

								this.elementsMap.push(thisData);
								this.listId.push(objectId);
								this.populatePanel(thisData["tags"], objectId);
								//affiche l'éléments dans la liste de droite
								$(this.cssModuleName + " #liste_map_element").append(this.createItemRigthListMap(thisData, marker));
							}

						}

					}

					//affiche les LINKS et les MEMBERS
					var thisSig = this;
					if(thisData.links != null)
						if(thisData.links.members != null){
							$.each(thisData.links.members, function(i, thisMember)  {
								thisMember._id = { $id : i };
								thisSig.showOneElementOnMap(thisMember);
							});
						}

				}

			};

			this.Sig.showFilterOnMap = function(data, thisFilter, thisMap){

				var thisSig = this;
				var dataFilter = data[thisFilter];
				if(dataFilter.length > 1){
					$.each(dataFilter, function(i, thisData)  {

						thisSig.showOneElementOnMap(thisData, thisMap);
					});
				}
				else{
					thisSig.showOneElementOnMap(dataFilter, thisMap);
				}

			};

			this.Sig.showMapElements = function(thisMap, data)
			{
				var filterPanelValue = "citoyens";
				//enregistre les dernières données dans une variable locale
				this.dataMap = data;
				//alert("datas : " + JSON.stringify(this.dataMap));
				//efface les elements de la carte si elle n'est pas vide
				if(this.markersLayer != "") this.clearMap(thisMap);

				//conteneur de marker cluster
				this.markersLayer = new L.MarkerClusterGroup({"maxClusterRadius" : 40});
				thisMap.addLayer(this.markersLayer);

				//collection de marker geojson
				this.geoJsonCollection = { type: 'FeatureCollection', features: new Array() };

				this.showIcoLoading(true);

				//on affiche les data filtre par filtre, en suivant la Desc des datas
				var thisSig = this;

				$.each(data, function (key, value){
					thisSig.showFilterOnMap(data, key, thisMap);
				});

				var points = L.geoJson(this.geoJsonCollection, {				//Pour les clusters seulement :
						onEachFeature: function (feature, layer) {				//sur chaque marker
							layer.bindPopup(feature["properties"]["content"]); 	//ajoute la bulle d'info avec les données
							layer.setIcon(feature["properties"]["icon"]);	   	//affiche l'icon demandé
							layer.on('mouseclick', function(e) {	layer.openPopup(); });

							//au click sur un element de la liste de droite, on zoom pour déclusturiser, et on ouvre la bulle
							$(thisSig.cssModuleName + " #item_map_list_" + feature.properties.id).click(function(){
								thisMap.setView([feature.geometry.coordinates[1],
											  feature.geometry.coordinates[0]],
											  13, {"animate" : true });

								thisSig.checkListElementMap(thisMap); //alert("check");
								layer.openPopup();
							});
						}
					});

					this.markersLayer.addLayer(points); 		// add it to the cluster group
					thisMap.addLayer(this.markersLayer);		// add it to the map

					$('#ico_reload').removeClass("fa-spin");
					$('#ico_reload').css({"display":"none"});

					if(this.initParmerters.usePanel)
						this.updatePanel(thisMap);

					this.checkListElementMap(thisMap);
					this.showIcoLoading(false);

		};

		//##
		//##	PANEL FILTER	##
		//##

		this.Sig.changeFilter = function (val, thisMap)
		{
			if(this.panelFilter != "")
				$(this.cssModuleName + '#item_panel_map_' + this.panelFilter).removeClass("selected");

			this.panelFilter = val;
			$(this.cssModuleName + '#item_panel_map_' + this.panelFilter).addClass("selected");
			this.showMapElements(thisMap, this.dataMap);
		};

		this.Sig.populatePanel = function(tags, objectId){
			var thisSig = this;
			if("undefined" == typeof tags) tags = new Array("all");
			//alert("tags : " + tags);
			$.each(tags, function(){
				thisSig.listPanel.push(this); //new Array(objectId);
			});

		};

		this.Sig.updatePanel = function(thisMap){ //alert("updatePanel : " + JSON.stringify(this.listPanel));
			var thisSig = this;
			$.each(this.listPanel, function(key, value){
				//si l'item n'existe pas encore
				//alert("value : " + this);
				var valueId = value.replace(/\s/g,"");

				var ico = thisSig.getIcoNameByTag(value);
				var color = thisSig.getIcoColorByTag(value);

				//var itemName = this;
				if(!$(thisSig.cssModuleName + ' #item_panel_map_' + valueId).length){ //on le rajoute...
					var newItem = "<button class='item_panel_map' id='item_panel_map_" + valueId + "'>" +
								     " " + value +
								   "</button>";
					$('.panel_map').append(newItem);
					$(thisSig.cssModuleName + ' #item_panel_map_' + valueId).click(function(){
						thisSig.changeFilter(value, thisMap);
					});
					//alert('len : ' + $('#item_panel_map_' + item).length);
					//alert(newItem);
				}

			});
		};

		//##
		//##	LOAD MAP	##
		//##

		//##
		//chargement de la carte
	 	this.Sig.loadMap = function(canvasId)
	 	{
			$("#"+canvasId).html("");
			$("#"+canvasId).css({"background-color": "#456074"});

			//initialisation des variables de départ de la carte
			var map = L.map(canvasId, { "zoomControl" : false,
										"scrollWheelZoom":true,
										"center" : [51.505, -0.09],
										"zoom" : 4,
										"worldCopyJump" : false });

			var tileLayer = L.tileLayer('http://{s}.tile.stamen.com/toner/{z}/{x}/{y}.png', {
				//attribution: 'Map tiles by Stamen Design, CC BY 3.0 — Map data © OpenStreetMap contributors, CC-BY-SA',
				attribution: 'Map tiles by Stamen Design',
				subdomains: 'abcd',
				minZoom: 0,
				maxZoom: 20
			}).setOpacity(0.4).addTo(map);

			return map;
		};

		//alert((this.Sig));
		this.Sig = this.getSigInitializer(this.Sig);
		this.Sig = this.getSigRightList(this.Sig);
		this.Sig = this.getSigPopupContent(this.Sig);

		return this.Sig;
	};
	<|endoftext|>


<commit_before>/* globals $ */

;(function () {
  'use strict'

  function getTemplateContent(id) {
    return document.getElementById(id).content.firstElementChild.cloneNode(true)
  }

  function getChapterHeading(chapter) {
    return chapter.querySelector('h2[id]')
  }

  function getChapterName(chapter) {
    return chapter.id.split('chapter-')[1]
  }

  function getCurrentLocale() {
    return document.documentElement.getAttribute('lang')
  }

  function getChapterEditUrl(chapter) {
    var BASE_URL =
      'https://github.com/KittyGiraudel/sass-guidelines/tree/main/pages'
    var locale = getCurrentLocale()
    var fileName = '_' + getChapterName(chapter) + '.md'

    return [BASE_URL, locale, fileName].join('/')
  }

  function createChapterLinks(chapter, heading) {
    var editLink = getTemplateContent('svg-pencil-tpl')
    var chapterLink = getTemplateContent('svg-link-tpl')
    var wrapper = document.createElement('div')
    var title = heading.innerText || heading.textContent

    editLink.href = getChapterEditUrl(chapter)
    chapterLink.href = '#' + heading.id
    editLink.querySelector('.sr-only').innerHTML += ' ' + title
    chapterLink.querySelector('.sr-only').innerHTML += ' ' + title

    wrapper.classList.add('button-wrapper', 'chapter__buttons')
    wrapper.appendChild(chapterLink)
    wrapper.appendChild(editLink)

    return wrapper
  }

  function redirectUrl(event) {
    window.location.href = this.value
  }

  function initialiseLanguagePicker() {
    var languagePicker = document.querySelector('#language-picker')

    languagePicker.on('change', redirectUrl, false)
  }

  function initialiseSyntaxToggle() {
    var syntaxToggle = $('input[name="syntax"]')

    syntaxToggle.on('click', function (event) {
      var fn = this.value === 'sass' ? 'add' : 'remove'
      document.body.classList[fn]('sass')
    })
  }

  function replaceSidebarOpener(link) {
    var button = document.createElement('button')

    button.setAttribute('type', 'button')
    button.setAttribute('data-a11y-dialog-show', 'options-panel')
    button.setAttribute('class', link.getAttribute('class'))
    button.innerHTML = link.innerHTML

    link.parentNode.replaceChild(button, link)
  }

  function initialiseOptionsPanel() {
    $('a[href="#options-panel"]').forEach(replaceSidebarOpener)

    var dialog = new A11yDialog(document.querySelector('#options-panel'))

    dialog
      .on('show', function (el) {
        el.classList.add('is-open')
      })
      .on('hide', function (el) {
        el.classList.remove('is-open')
      })
  }

  function initialiseChapters() {
    var chapters = $('.chapter:not(.toc)')

    chapters.forEach(function (chapter) {
      var heading = getChapterHeading(chapter)

      heading.parentNode.insertBefore(
        createChapterLinks(chapter, heading),
        heading.nextSibling
      )
    })
  }

  document.addEventListener('DOMContentLoaded', function (event) {
    initialiseLanguagePicker()
    initialiseSyntaxToggle()
    initialiseOptionsPanel()
    initialiseChapters()
  })
})()
<commit_msg>Apply a minor syntactic change to avoid a PhantomJS crash<commit_after>/* globals $ */

;(function () {
  'use strict'

  function getTemplateContent(id) {
    const template = document.getElementById(id)
    return template.content.firstElementChild.cloneNode(true)
  }

  function getChapterHeading(chapter) {
    return chapter.querySelector('h2[id]')
  }

  function getChapterName(chapter) {
    return chapter.id.split('chapter-')[1]
  }

  function getCurrentLocale() {
    return document.documentElement.getAttribute('lang')
  }

  function getChapterEditUrl(chapter) {
    var BASE_URL =
      'https://github.com/KittyGiraudel/sass-guidelines/tree/main/pages'
    var locale = getCurrentLocale()
    var fileName = '_' + getChapterName(chapter) + '.md'

    return [BASE_URL, locale, fileName].join('/')
  }

  function createChapterLinks(chapter, heading) {
    var editLink = getTemplateContent('svg-pencil-tpl')
    var chapterLink = getTemplateContent('svg-link-tpl')
    var wrapper = document.createElement('div')
    var title = heading.innerText || heading.textContent

    editLink.href = getChapterEditUrl(chapter)
    chapterLink.href = '#' + heading.id
    editLink.querySelector('.sr-only').innerHTML += ' ' + title
    chapterLink.querySelector('.sr-only').innerHTML += ' ' + title

    wrapper.classList.add('button-wrapper', 'chapter__buttons')
    wrapper.appendChild(chapterLink)
    wrapper.appendChild(editLink)

    return wrapper
  }

  function redirectUrl(event) {
    window.location.href = this.value
  }

  function initialiseLanguagePicker() {
    var languagePicker = document.querySelector('#language-picker')

    languagePicker.on('change', redirectUrl, false)
  }

  function initialiseSyntaxToggle() {
    var syntaxToggle = $('input[name="syntax"]')

    syntaxToggle.on('click', function (event) {
      var fn = this.value === 'sass' ? 'add' : 'remove'
      document.body.classList[fn]('sass')
    })
  }

  function replaceSidebarOpener(link) {
    var button = document.createElement('button')

    button.setAttribute('type', 'button')
    button.setAttribute('data-a11y-dialog-show', 'options-panel')
    button.setAttribute('class', link.getAttribute('class'))
    button.innerHTML = link.innerHTML

    link.parentNode.replaceChild(button, link)
  }

  function initialiseOptionsPanel() {
    $('a[href="#options-panel"]').forEach(replaceSidebarOpener)

    var dialog = new A11yDialog(document.querySelector('#options-panel'))

    dialog
      .on('show', function (el) {
        el.classList.add('is-open')
      })
      .on('hide', function (el) {
        el.classList.remove('is-open')
      })
  }

  function initialiseChapters() {
    var chapters = $('.chapter:not(.toc)')

    chapters.forEach(function (chapter) {
      var heading = getChapterHeading(chapter)

      heading.parentNode.insertBefore(
        createChapterLinks(chapter, heading),
        heading.nextSibling
      )
    })
  }

  document.addEventListener('DOMContentLoaded', function (event) {
    initialiseLanguagePicker()
    initialiseSyntaxToggle()
    initialiseOptionsPanel()
    initialiseChapters()
  })
})()
<|endoftext|>


<commit_before>/**
 * @file Allows uploading, cropping (with automatic resizing) and exporting
 * of images.
 * @author Billy Brown
 * @license MIT
 * @version 1.0.0
 */

/**
 *
Creates an Uploader instance with parameters passed as an object.

 *
Available parameters are:

 *

 *
exceptions {function}: the exceptions handler to use, function that takes a string.

 *
input {string} (required): the classname (prefixed with ".") for the input element. Instantiation fails if not provided.

 *
types {array}: the file types accepted by the uploader.

 *

 *
 * @example
 * var uploader = new Uploader({
 *  input: document.querySelector('.js-fileinput'),
 *  types: [ 'gif', 'jpg', 'jpeg', 'png' ]
 * });
 *
 * @class
 * @param {object} options - the parameters to be passed for instantiation
 */
class Uploader {
  constructor(options) {
    if (!options.input) {
      throw '[Uploader] Missing input file element.';
    }
    this.fileInput = options.input;
    this.types = options.types || [ 'gif', 'jpg', 'jpeg', 'png' ];
    this.reader = new FileReader();
  }

  listen() {
    return new Promise((resolve, reject) => {
      this.fileInput.onchange = (e) => {
        // Do not submit the form
        e.preventDefault();
        // Make sure one file was selected
        if (!this.fileInput.files || this.fileInput.files.length !== 1) {
          reject('[Uploader:listen] Select only one file.');
        } else {
          this.fileReaderSetup(this.fileInput.files[0], resolve, reject);
        }
      };
    });
  }

  fileReaderSetup(file, resolve) {
    if (this.validFileType(file.type)) {
      // Read the image as base64 data
      this.reader.readAsDataURL(file);
      // Resolve the promise with the image data
      this.reader.onload = (e) => resolve(e.target.result);
    } else {
      reject('[Uploader:fileReaderSetup] Invalid file type.');
    }
  }

  validFileType(filename) {
    // Get the second part of the MIME type
    let extension = filename.split('/').pop().toLowerCase();
    // See if it is in the array of allowed types
    return this.types.includes(extension);
  }
}

/**
 *
Creates a Cropper instance with parameters passed as an object.

 *
Available parameters are:

 *

 *
exceptions {function}: the exceptions handler to use, function that takes a string.

 *
size {object} (required): the dimensions of the cropped, resized image. Must have 'width' and 'height' fields.

 *
canvas {string} (required): the classname (prefixed with ".") for the cropping canvas element. Instantiation fails if not provided.

 *
preview {string} (required): the classname (prefixed with ".") for the preview canvas element. Instantiation fails if not provided.

 *

 *
 * @example
 * var editor = new Cropper({
 *  size: { width: 128, height: 128 },
 *  limit: 600,
 *  canvas: document.querySelector('.js-editorcanvas'),
 *  preview: document.querySelector('.js-previewcanvas')
 * });
 *
 * @class
 * @param {object} options - the parameters to be passed for instantiation
 */
class Cropper {
  constructor(options) {
    // Check the inputs
    if (!options.size) {
      throw 'Size field in options is required';
    }
    if (!options.canvas) {
      throw 'Could not find image canvas element.';
    }
    if (!options.preview) {
      throw 'Could not find preview canvas element.';
    }
    // Hold on to the values
    this.imageCanvas = options.canvas;
    this.previewCanvas = options.preview;
    this.c = this.imageCanvas.getContext("2d");
    // Create the cropping square
    this.crop = {
      size: options.size,
      tl: { x: 0, y: 0 },
      br: { x: options.size.width, y: options.size.height }
    };
    this.lastEvent = null;
    // Images larger than options.limit are resized
    this.limit = options.limit || 600; // default to 600px
  }

  setImageSource(source) {
    this.image = new Image();
    this.image.src = source;
    this.image.onload = this.readyEditor.bind(this);
    // Perform an initial render
    this.render();
  }

  export(img) {
    img.setAttribute('src', this.previewCanvas.toDataURL());
  }

  /** @private */
  readyEditor(e) {
    this.imageCanvas.classList.remove('hidden');
    this.render();
    // Listen for clicks in the canvas
    this.imageCanvas.onmousedown = this.clickStart.bind(this);
    this.boundDrag = this.drag.bind(this);
    this.boundClickStop = this.clickStop.bind(this);
  }

  /** @private */
  render() {
    this.c.clearRect(0, 0, this.imageCanvas.width, this.imageCanvas.height);
    this.displayImage();
    this.preview();
    this.drawCropWindow();
  }

  /** @private */
  clickStart(e) {
  // Get the crop handle to use
  var position = { x: e.offsetX, y: e.offsetY };
  this.lastEvent = {
    position: position,
    resizing: this.isResizing(position),
    moving: this.isMoving(position)
  };
  // Listen for mouse movement and mouse release
  this.imageCanvas.addEventListener('mousemove', this.boundDrag);
  this.imageCanvas.addEventListener('mouseup', this.boundClickStop);
  }

  /** @private */
  clickStop(e) {
    this.imageCanvas.removeEventListener("mousemove", this.boundDrag);
    this.imageCanvas.removeEventListener("mouseup", this.boundClickStop);
  }

  /** @private */
  drag(e) {
    var position = { x: e.offsetX, y: e.offsetY };
    var dx = position.x - this.lastEvent.position.x;
    var dy = position.y - this.lastEvent.position.y;
    // Determine whether we are resizing, moving, or nothing
    if (this.lastEvent.resizing) {
      this.resize(dx, dy);
    } else if (this.lastEvent.moving) {
      this.move(dx, dy);
    }
    // Update the last position
    this.lastEvent.position = position;
    this.render();
  }

  /** @private */
  resize(dx, dy) {
    var handle = this.crop.br;
    // Maintain the aspect ratio
    var amount = Math.abs(dx) > Math.abs(dy) ? dx : dy;
    if (this.inBounds(handle.x + amount, handle.y + amount)) {
      handle.x += amount;
      handle.y += amount;
    }
  }

  /** @private */
  move(dx, dy) {
    var tl = this.crop.tl;
    var br = this.crop.br;
    // Make sure all four corners are in bounds
    if (this.inBounds(tl.x + dx, tl.y + dy) &&
        this.inBounds(br.x + dx, tl.y + dy) &&
        this.inBounds(br.x + dx, br.y + dy) &&
        this.inBounds(tl.x + dx, br.y + dy)) {
      tl.x += dx;
      tl.y += dy;
      br.x += dx;
      br.y += dy;
    }
  }

  /** @private */
  displayImage() {
    // Resize the original to the maximum allowed size
    if (this.image.width > this.limit) {
      this.image.height *= this.limit / this.image.width;
      this.image.width = this.limit;
    } else if (this.image.height > this.limit) {
      this.image.width *= this.limit / this.image.height;
      this.image.height = this.limit;
    }
    // Fit the image to the canvas (fixed width canvas, dynamic height)
    this.imageCanvas.width = this.image.width;
    this.imageCanvas.height = this.image.height;
    this.c.drawImage(this.image, 0, 0, this.image.width, this.image.height);
  }

  /** @private */
  drawCropWindow() {
    var tl = this.crop.tl;
    var br = this.crop.br;
    this.c.strokeStyle = 'red';
    this.c.fillStyle = 'red';
    // Draw the crop window outline
    this.c.strokeRect(tl.x, tl.y, br.x - tl.x, br.y - tl.y);
    // Draw the draggable handle
    var path = new Path2D();
    path.arc(br.x, br.y, 3, 0, Math.PI * 2, true);
    this.c.fill(path);
  }

  /** @private */
  preview() {
    var tl = this.crop.tl;
    var br = this.crop.br;
    var imageData = this.c.getImageData(tl.x, tl.y, br.x - tl.x, br.y - tl.y);
    if (imageData === null) {
      return false;
    }
    var ctx = this.previewCanvas.getContext('2d');
    this.previewCanvas.width = this.crop.size.width;
    this.previewCanvas.height = this.crop.size.height;
    ctx.clearRect(0, 0, this.previewCanvas.width, this.previewCanvas.height);
    // Draw the image to the preview canvas, resizing it to fit
    ctx.drawImage(this.imageCanvas,
        // Top left corner coordinates of image
        tl.x, tl.y,
        // Width and height of image
        br.x - tl.x, br.y - tl.y,
        // Top left corner coordinates of result in canvas
        0, 0,
        // Width and height of result in canvas
        this.previewCanvas.width, this.previewCanvas.height);
  }

  /** @private */
  isResizing(pos) {
    var errorOffset = 10;
    var handle = this.crop.br;
    return !(pos.x < handle.x - errorOffset || pos.x > handle.x + errorOffset ||
        pos.y < handle.y - errorOffset || pos.y > handle.y + errorOffset);
  }

  /** @private */
  isMoving(pos) {
    var tl = this.crop.tl;
    var br = this.crop.br;
    return !(pos.x < tl.x || pos.x > br.x || pos.y < tl.y || pos.y > br.y);
  }

  /** @private */
  inBounds(x, y) {
    return x >= 0 && x <= this.imageCanvas.width &&
           y >= 0 && y <= this.imageCanvas.height;
  }
}
<commit_msg>Fixed documentation for ES6 classes<commit_after>/**
 * @file Allows uploading, cropping (with automatic resizing) and exporting
 * of images.
 * @author Billy Brown
 * @license MIT
 * @version 1.0.0
 */

/** Class used for uploading images. */
class Uploader {
/**
 *
Creates an Uploader instance with parameters passed as an object.

 *
Available parameters are:

 *

 *
exceptions {function}: the exceptions handler to use, function that takes a string.

 *
input {HTMLElement} (required): the file input element. Instantiation fails if not provided.

 *
types {array}: the file types accepted by the uploader.

 *

 *
 * @example
 * var uploader = new Uploader({
 *  input: document.querySelector('.js-fileinput'),
 *  types: [ 'gif', 'jpg', 'jpeg', 'png' ]
 * });
 * *
 * @param {object} options - the parameters to be passed for instantiation
 */
  constructor(options) {
    if (!options.input) {
      throw '[Uploader] Missing input file element.';
    }
    this.fileInput = options.input;
    this.types = options.types || [ 'gif', 'jpg', 'jpeg', 'png' ];
    this.reader = new FileReader();
  }

  listen() {
    return new Promise((resolve, reject) => {
      this.fileInput.onchange = (e) => {
        // Do not submit the form
        e.preventDefault();
        // Make sure one file was selected
        if (!this.fileInput.files || this.fileInput.files.length !== 1) {
          reject('[Uploader:listen] Select only one file.');
        } else {
          this.fileReaderSetup(this.fileInput.files[0], resolve, reject);
        }
      };
    });
  }

  fileReaderSetup(file, resolve) {
    if (this.validFileType(file.type)) {
      // Read the image as base64 data
      this.reader.readAsDataURL(file);
      // Resolve the promise with the image data
      this.reader.onload = (e) => resolve(e.target.result);
    } else {
      reject('[Uploader:fileReaderSetup] Invalid file type.');
    }
  }

  validFileType(filename) {
    // Get the second part of the MIME type
    let extension = filename.split('/').pop().toLowerCase();
    // See if it is in the array of allowed types
    return this.types.includes(extension);
  }
}

/** Class for cropping an image. */
class Cropper {
  /**
   *
Creates a Cropper instance with parameters passed as an object.

   *
Available parameters are:

   *

   *
size {object} (required): the dimensions of the cropped, resized image. Must have 'width' and 'height' fields.

   *
limit {integer}: the longest side that the cropping area will be limited to, resizing any larger images.

   *
canvas {HTMLElement} (required): the cropping canvas element. Instantiation fails if not provided.

   *
preview {HTMLElement} (required): the preview canvas element. Instantiation fails if not provided.

   *

   *
   * @example
   * var editor = new Cropper({
   *  size: { width: 128, height: 128 },
   *  limit: 600,
   *  canvas: document.querySelector('.js-editorcanvas'),
   *  preview: document.querySelector('.js-previewcanvas')
   * });
   *
   * @param {object} options - the parameters to be passed for instantiation
   */
  constructor(options) {
    // Check the inputs
    if (!options.size) {
      throw 'Size field in options is required';
    }
    if (!options.canvas) {
      throw 'Could not find image canvas element.';
    }
    if (!options.preview) {
      throw 'Could not find preview canvas element.';
    }
    // Hold on to the values
    this.imageCanvas = options.canvas;
    this.previewCanvas = options.preview;
    this.c = this.imageCanvas.getContext("2d");
    // Create the cropping square
    this.crop = {
      size: options.size,
      tl: { x: 0, y: 0 },
      br: { x: options.size.width, y: options.size.height }
    };
    this.lastEvent = null;
    // Images larger than options.limit are resized
    this.limit = options.limit || 600; // default to 600px
  }

  setImageSource(source) {
    this.image = new Image();
    this.image.src = source;
    this.image.onload = this.readyEditor.bind(this);
    // Perform an initial render
    this.render();
  }

  export(img) {
    img.setAttribute('src', this.previewCanvas.toDataURL());
  }

  /** @private */
  readyEditor(e) {
    this.imageCanvas.classList.remove('hidden');
    this.render();
    // Listen for clicks in the canvas
    this.imageCanvas.onmousedown = this.clickStart.bind(this);
    this.boundDrag = this.drag.bind(this);
    this.boundClickStop = this.clickStop.bind(this);
  }

  /** @private */
  render() {
    this.c.clearRect(0, 0, this.imageCanvas.width, this.imageCanvas.height);
    this.displayImage();
    this.preview();
    this.drawCropWindow();
  }

  /** @private */
  clickStart(e) {
  // Get the crop handle to use
  var position = { x: e.offsetX, y: e.offsetY };
  this.lastEvent = {
    position: position,
    resizing: this.isResizing(position),
    moving: this.isMoving(position)
  };
  // Listen for mouse movement and mouse release
  this.imageCanvas.addEventListener('mousemove', this.boundDrag);
  this.imageCanvas.addEventListener('mouseup', this.boundClickStop);
  }

  /** @private */
  clickStop(e) {
    this.imageCanvas.removeEventListener("mousemove", this.boundDrag);
    this.imageCanvas.removeEventListener("mouseup", this.boundClickStop);
  }

  /** @private */
  drag(e) {
    var position = { x: e.offsetX, y: e.offsetY };
    var dx = position.x - this.lastEvent.position.x;
    var dy = position.y - this.lastEvent.position.y;
    // Determine whether we are resizing, moving, or nothing
    if (this.lastEvent.resizing) {
      this.resize(dx, dy);
    } else if (this.lastEvent.moving) {
      this.move(dx, dy);
    }
    // Update the last position
    this.lastEvent.position = position;
    this.render();
  }

  /** @private */
  resize(dx, dy) {
    var handle = this.crop.br;
    // Maintain the aspect ratio
    var amount = Math.abs(dx) > Math.abs(dy) ? dx : dy;
    if (this.inBounds(handle.x + amount, handle.y + amount)) {
      handle.x += amount;
      handle.y += amount;
    }
  }

  /** @private */
  move(dx, dy) {
    var tl = this.crop.tl;
    var br = this.crop.br;
    // Make sure all four corners are in bounds
    if (this.inBounds(tl.x + dx, tl.y + dy) &&
        this.inBounds(br.x + dx, tl.y + dy) &&
        this.inBounds(br.x + dx, br.y + dy) &&
        this.inBounds(tl.x + dx, br.y + dy)) {
      tl.x += dx;
      tl.y += dy;
      br.x += dx;
      br.y += dy;
    }
  }

  /** @private */
  displayImage() {
    // Resize the original to the maximum allowed size
    if (this.image.width > this.limit) {
      this.image.height *= this.limit / this.image.width;
      this.image.width = this.limit;
    } else if (this.image.height > this.limit) {
      this.image.width *= this.limit / this.image.height;
      this.image.height = this.limit;
    }
    // Fit the image to the canvas (fixed width canvas, dynamic height)
    this.imageCanvas.width = this.image.width;
    this.imageCanvas.height = this.image.height;
    this.c.drawImage(this.image, 0, 0, this.image.width, this.image.height);
  }

  /** @private */
  drawCropWindow() {
    var tl = this.crop.tl;
    var br = this.crop.br;
    this.c.strokeStyle = 'red';
    this.c.fillStyle = 'red';
    // Draw the crop window outline
    this.c.strokeRect(tl.x, tl.y, br.x - tl.x, br.y - tl.y);
    // Draw the draggable handle
    var path = new Path2D();
    path.arc(br.x, br.y, 3, 0, Math.PI * 2, true);
    this.c.fill(path);
  }

  /** @private */
  preview() {
    var tl = this.crop.tl;
    var br = this.crop.br;
    var imageData = this.c.getImageData(tl.x, tl.y, br.x - tl.x, br.y - tl.y);
    if (imageData === null) {
      return false;
    }
    var ctx = this.previewCanvas.getContext('2d');
    this.previewCanvas.width = this.crop.size.width;
    this.previewCanvas.height = this.crop.size.height;
    ctx.clearRect(0, 0, this.previewCanvas.width, this.previewCanvas.height);
    // Draw the image to the preview canvas, resizing it to fit
    ctx.drawImage(this.imageCanvas,
        // Top left corner coordinates of image
        tl.x, tl.y,
        // Width and height of image
        br.x - tl.x, br.y - tl.y,
        // Top left corner coordinates of result in canvas
        0, 0,
        // Width and height of result in canvas
        this.previewCanvas.width, this.previewCanvas.height);
  }

  /** @private */
  isResizing(pos) {
    var errorOffset = 10;
    var handle = this.crop.br;
    return !(pos.x < handle.x - errorOffset || pos.x > handle.x + errorOffset ||
        pos.y < handle.y - errorOffset || pos.y > handle.y + errorOffset);
  }

  /** @private */
  isMoving(pos) {
    var tl = this.crop.tl;
    var br = this.crop.br;
    return !(pos.x < tl.x || pos.x > br.x || pos.y < tl.y || pos.y > br.y);
  }

  /** @private */
  inBounds(x, y) {
    return x >= 0 && x <= this.imageCanvas.width &&
           y >= 0 && y <= this.imageCanvas.height;
  }
}
<|endoftext|>


<commit_before>function d3graph (div, width, height, drawNode, drawEdge) {
  let svg = div.append('svg')
  .attr('width', width).attr('height', height);

  let nodes = {};
  let edges = {};
  let history = [];

  function addNode(id, data) {
    if (nodes[id] !== undefined) throw 'Node ' + id + ' already exists';
    nodes[id] = data;
  }
  function getNode(id) {
    let node = nodes[id];
    if (node == undefined) throw 'Node ' + id + ' does not exist';
    return node;
  }
  function delNode(nodeId) {
    nodes[nodeId] = undefined;
  }
  function addEdge(edgeId, srcId, dstId, data) {
    edges[edgeId] = {id: edgeId, srcId: srcId, dstId: dstId, data: data};
  }
  function delEdge(edgeId) {
    edges[edgeId] = undefined;
  }

  function buildGraph() {
    let graph = {};
    for (let id in nodes) {
      if (nodes[id] !== undefined) {
        graph[id] = {src:[], dst: []};
      }
    }
    for (let eid in edges) {
      let edge = edges[eid];
      if (graph[edge.srcId] !== undefined && graph[edge.dstId] != undefined) {
        graph[edge.srcId].dst.push(edge);
        graph[edge.dstId].src.push(edge);
      }
    }
    return graph;
  }

  function buildLayers(graph) {
    let queue = [];
    // first layer has no src
    for (let id in graph) {
      if (graph[id].src.length === 0) {
        queue.push(id);
      }
    }
    queue.push(undefined);

    let level = 0;
    while (queue.length > 0) {
      let id = queue.shift();
      if (id === undefined) {
        level++;
        if (queue.length === 0) break;
        queue.push(undefined);
        continue;
      }

      graph[id].level = graph[id].level || level;
      graph[id].dst.forEach(function (edge) {
        queue.push(edge.dstId);
      });
    }
    let layers = [];
    for (let id in graph) {
      let node = graph[id];
      if (layers[node.level] === undefined) {
        layers[node.level] = [];
      }
      layers[node.level].push(id);
    }
    return layers;
  }

  function sortLayers(graph, layers) {
    let round = layers.length;
    while (true) {
      layers.forEach(function (layer) {
        layer.forEach(function (id, index) {
          graph[id].y = (index + 1) / layer.length + 1;
        });
      });
      if (round-- < 0) {
        break;
      }

      layers.forEach(function (layer) {
        layer.forEach(function (id) {
          let node = graph[id];
          let sum = 0.0;
          node.src.forEach(function (edge) {
            sum += graph[edge.dstId].y;
          });
          node.dst.forEach(function (edge) {
            sum += graph[edge.srcId].y;
          });
          graph[id].y = sum / (node.src.length + node.dst.length);
        });
        layer = layer.sort(function (id1, id2) {
          return graph[id1].y < graph[id2].y ? -1 : 1;
        });
      });
    }
  }

  function layoutNodes(graph, layers) {
    layers.forEach(function (layer, i) {
      let x = width * (i + 1) / (layers.length + 1);
      layer.forEach(function (id, j) {
        let y = height * (j + 1) / (layer.length + 1);
        graph[id].x = x;
        graph[id].y = y;
      });
    });
  }

  function layoutEdges(graph, layers) {
    let edgeAnchors = {};
    for (let srcId in graph) {
      let srcNode = graph[srcId];
      let edgeGroups = {};
      srcNode.dst.forEach(function (edge) {
        if (edgeGroups[edge.dstId] === undefined) {
          edgeGroups[edge.dstId] = [];
        }
        edgeGroups[edge.dstId].push(edge);
      });

      let srcMargin = 0.5 * height / (layers[srcNode.level].length + 1);

      for (let dstId in edgeGroups) {
        let edges = edgeGroups[dstId];
        edges = edges.sort(function (edge1, edge2) {
          return edge1.id < edge2.id ? -1 : 1;
        })
        let dstNode = graph[dstId];
        if (dstNode.level !== srcNode.level) {
          let dstMargin = 0.5 * height / (layers[dstNode.level].length + 1);
          let top = (srcNode.y - srcMargin + dstNode.y - dstMargin) / 2;
          let bottom = (srcNode.y + srcMargin + dstNode.y + dstMargin) / 2;
          let step = (bottom - top) / (edges.length + 1);
          let x = (srcNode.x + dstNode.x) / 2;
          edges.forEach(function (edge, i) {
            let y = (i+1) * step + top;
            edgeAnchors[edge.id] = {x: x, y: y};
          });
        } else {
          // same level, scatter anchors horizontally
          let y = (srcNode.y + dstNode.y) / 2;
          let margin = (0.5 * width / layers.length + 1) * Math.abs(srcNode.y - dstNode.y) / height;
          let left = srcNode.x - margin;
          let right = srcNode.x + margin;
          if (edges.length % 2 === 0) {
            var step = (right - left) / (edges.length - 1);
          } else {
            var step = (right - left) / edges.length;
          }
          edges.forEach(function (edge, i) {
            let x = i * step + left;
            edgeAnchors[edge.id] = {x: x, y: y};
          });
        }
      }
    }
    return edgeAnchors;
  }

  function redraw(duration, ease) {
    svg.selectAll('*').remove();

    let graph = buildGraph();
    let layers = buildLayers(graph);
    sortLayers(graph, layers);
    layoutNodes(graph, layers);
    let anchors = layoutEdges(graph, layers);

    if (duration !== undefined && history.length > 0) {
      ease = ease || 'cos';
      let prevGraph = history.shift();
      let prevAnchors = history.shift();
      renderAnimation(prevGraph, graph, prevAnchors, anchors, duration, ease);
    } else {
      render(graph, anchors);
    }

    let saveGraph = {};
    for (let id in graph) {
      if (graph[id].dummy === undefined) {
        saveGraph[id] = graph[id];
      }
    }
    history.push(saveGraph);
    history.push(anchors);
  }

  function buildDummyNodes(graph1, graph2) {
    for (let id in graph2) {
      if (graph1[id] !== undefined) continue;
      let node = graph2[id];
      // fill graph1[id] with nearest parent
      let queue = [id];
      while (queue.length > 0) {
        let pid = queue.shift();
        let parent = graph1[pid];
        if (parent !== undefined) {
          graph1[id] = {x: parent.x, y: parent.y, dummy: true};
          break;
        }
        graph2[pid].src.forEach(function (edge) {
          queue.push(edge.srcId);
        });
      }
    }
  }

  function render(graph, anchors) {
    for (let id in edges) {
      let edge = edges[id];
      let srcNode = graph[edge.srcId];
      let dstNode = graph[edge.dstId];
      let anchor = anchors[id];
      if (anchor !== undefined) {
        renderEdge([srcNode, anchor, dstNode], edge.data);
      }
    }

    for (let id in graph) {
      renderNode(graph[id], nodes[id]);
    }
  }

  function renderAnimation(prevGraph, graph, prevAnchors, anchors, duration, ease) {
    buildDummyNodes(prevGraph, graph);
    buildDummyNodes(graph, prevGraph);

    for (let id in anchors) {
      let edge = edges[id];
      let path = [graph[edge.srcId], anchors[id], graph[edge.dstId]];

      let prevSrcNode = prevGraph[edge.srcId];
      let prevDstNode = prevGraph[edge.dstId];
      if (prevSrcNode === undefined || prevDstNode === undefined) {
        renderEdge(path, edge.data);
      } else {
        let prevAnchor = prevAnchors[id];
        if (prevAnchor === undefined) {
          prevAnchor = {x: (prevSrcNode.x + prevDstNode.x) / 2, y: (prevSrcNode.y + prevDstNode.y) / 2};
        }
        let prevPath = [prevSrcNode, prevAnchor, prevDstNode];
        renderEdgeAnimation(prevPath, path, edge.data, duration, ease);
      }
    }

    // for vanishing edges
    for (let id in prevAnchors) {
      if (anchors[id] !== undefined) continue;
      let edge = edges[id];
      let srcNode = graph[edge.srcId];
      let dstNode = graph[edge.dstId];
      if (srcNode === undefined || dstNode === undefined) continue;
      let anchor = {x: (srcNode.x + dstNode.x) / 2, y: (srcNode.y + dstNode.y) / 2};
      let path = [srcNode, anchor, dstNode];
      let prevPath = [prevGraph[edge.srcId], prevAnchors[id], prevGraph[edge.dstId]];
      renderEdgeAnimation(prevPath, path, edge.data, duration, ease);
    }

    for (let id in graph) {
      let node = graph[id];
      let prevNode = prevGraph[id];

      if (prevNode !== undefined) {
        renderNodeAnimation(prevNode, node, nodes[id], duration, ease);
      } else {
        renderNode(node, nodes[id]);
      }
    }
  }

  function renderNode(node, data) {
    let group = svg.append('g')
    .attr('transform', 'translate(' + node.x + ',' + node.y + ')');
    drawNode(group, data);
  }

  function renderNodeAnimation(node1, node2, data, duration, ease) {
    let group = svg.append('g');
    group.attr('transform', 'translate(' + node1.x + ',' + node1.y + ')')
    drawNode(group, data);
    let animation = group.transition().duration(duration).ease(ease)
    .attr('transform', 'translate(' + node2.x + ',' + node2.y + ')');

    if (node2.dummy) {
      animation.each('end', function () {
        group.remove();
      });
    }
  }

  let lineFunc = d3.svg.line()
  .x(function (d) { return d.x; })
  .y(function (d) { return d.y; })
  .interpolate('basis');

  function renderEdge(anchors, data) {
    let mid = anchors[Math.floor(anchors.length / 2)];
    let path = svg.append('path')
    .attr('d', lineFunc(anchors)).attr('fill', 'none');
    let group = svg.append('g')
    .attr('transform', 'translate(' + mid.x + ',' + mid.y + ')');
    drawEdge(path, group, data);
  }

  function renderEdgeAnimation(anchors1, anchors2, data, duration, ease) {
    let path = svg.append('path').attr('fill', 'none');
    path.attr('d', lineFunc(anchors1));
    path.transition().duration(duration).ease(ease)
    .attr('d', lineFunc(anchors2));

    let mid1 = anchors1[Math.floor(anchors1.length / 2)];
    let mid2 = anchors2[Math.floor(anchors2.length / 2)];

    let group = svg.append('g');
    group.attr('transform', 'translate(' + mid1.x + ',' + mid1.y + ')');
    group.transition().duration(duration).ease(ease)
    .attr('transform', 'translate(' + mid2.x + ',' + mid2.y + ')');

    drawEdge(path, group, data);
  }

  return {
    addNode: addNode,
    delNode: delNode,
    addEdge: addEdge,
    delEdge: delEdge,
    redraw: redraw,
    nodes: nodes,
    edges: edges
  };
}
<commit_msg>fix sortLayers<commit_after>function d3graph (div, width, height, drawNode, drawEdge) {
  let svg = div.append('svg')
  .attr('width', width).attr('height', height);

  let nodes = {};
  let edges = {};
  let history = [];

  function addNode(id, data) {
    if (nodes[id] !== undefined) throw 'Node ' + id + ' already exists';
    nodes[id] = data || id;
  }
  function getNode(id) {
    let node = nodes[id];
    if (node == undefined) throw 'Node ' + id + ' does not exist';
    return node;
  }
  function delNode(nodeId) {
    nodes[nodeId] = undefined;
  }
  function addEdge(edgeId, srcId, dstId, data) {
    edges[edgeId] = {id: edgeId, srcId: srcId, dstId: dstId, data: data};
  }
  function delEdge(edgeId) {
    edges[edgeId] = undefined;
  }

  function buildGraph() {
    let graph = {};
    for (let id in nodes) {
      if (nodes[id] !== undefined) {
        graph[id] = {src:[], dst: []};
      }
    }
    for (let eid in edges) {
      let edge = edges[eid];
      if (graph[edge.srcId] !== undefined && graph[edge.dstId] != undefined) {
        graph[edge.srcId].dst.push(edge);
        graph[edge.dstId].src.push(edge);
      }
    }
    return graph;
  }

  function buildLayers(graph) {
    let queue = [];
    // first layer has no src
    for (let id in graph) {
      if (graph[id].src.length === 0) {
        queue.push(id);
      }
    }
    queue.push(undefined);

    let level = 0;
    while (queue.length > 0) {
      let id = queue.shift();
      if (id === undefined) {
        level++;
        if (queue.length === 0) break;
        queue.push(undefined);
        continue;
      }

      graph[id].level = graph[id].level || level;
      graph[id].dst.forEach(function (edge) {
        queue.push(edge.dstId);
      });
    }
    let layers = [];
    for (let id in graph) {
      let node = graph[id];
      if (layers[node.level] === undefined) {
        layers[node.level] = [];
      }
      layers[node.level].push(id);
    }
    return layers;
  }

  function sortLayers(graph, layers) {
    let round = layers.length;
    while (true) {
      layers.forEach(function (layer) {
        layer.forEach(function (id, index) {
          graph[id].y = (index + 1) / (layer.length + 1);
        });
      });
      if (round-- < 0) {
        break;
      }

      layers.forEach(function (layer) {
        layer.forEach(function (id) {
          let node = graph[id];
          let sum = node.y;
          node.src.forEach(function (edge) {
            sum += graph[edge.srcId].y;
          });
          node.dst.forEach(function (edge) {
            sum += graph[edge.dstId].y;
          });
          graph[id].y = sum / (1 + node.src.length + node.dst.length);
        });
        layer = layer.sort(function (id1, id2) {
          return graph[id1].y < graph[id2].y ? -1 : 1;
        });
      });
    }
  }

  function layoutNodes(graph, layers) {
    layers.forEach(function (layer, i) {
      let x = width * (i + 1) / (layers.length + 1);
      layer.forEach(function (id, j) {
        let y = height * (j + 1) / (layer.length + 1);
        graph[id].x = x;
        graph[id].y = y;
      });
    });
  }

  function layoutEdges(graph, layers) {
    let edgeAnchors = {};
    for (let srcId in graph) {
      let srcNode = graph[srcId];
      let edgeGroups = {};
      srcNode.dst.forEach(function (edge) {
        if (edgeGroups[edge.dstId] === undefined) {
          edgeGroups[edge.dstId] = [];
        }
        edgeGroups[edge.dstId].push(edge);
      });

      let srcMargin = 0.5 * height / (layers[srcNode.level].length + 1);

      for (let dstId in edgeGroups) {
        let edges = edgeGroups[dstId];
        edges = edges.sort(function (edge1, edge2) {
          return edge1.id < edge2.id ? -1 : 1;
        })
        let dstNode = graph[dstId];
        if (dstNode.level !== srcNode.level) {
          let dstMargin = 0.5 * height / (layers[dstNode.level].length + 1);
          let top = (srcNode.y - srcMargin + dstNode.y - dstMargin) / 2;
          let bottom = (srcNode.y + srcMargin + dstNode.y + dstMargin) / 2;
          let step = (bottom - top) / (edges.length + 1);
          let x = (srcNode.x + dstNode.x) / 2;
          edges.forEach(function (edge, i) {
            let y = (i+1) * step + top;
            edgeAnchors[edge.id] = {x: x, y: y};
          });
        } else {
          // same level, scatter anchors horizontally
          let y = (srcNode.y + dstNode.y) / 2;
          let margin = (0.5 * width / layers.length + 1) * Math.abs(srcNode.y - dstNode.y) / height;
          let left = srcNode.x - margin;
          let right = srcNode.x + margin;
          if (edges.length % 2 === 0) {
            var step = (right - left) / (edges.length - 1);
          } else {
            var step = (right - left) / edges.length;
          }
          edges.forEach(function (edge, i) {
            let x = i * step + left;
            edgeAnchors[edge.id] = {x: x, y: y};
          });
        }
      }
    }
    return edgeAnchors;
  }

  function redraw(duration, ease) {
    svg.selectAll('*').remove();

    let graph = buildGraph();
    let layers = buildLayers(graph);
    sortLayers(graph, layers);
    layoutNodes(graph, layers);
    let anchors = layoutEdges(graph, layers);

    if (duration !== undefined && history.length > 0) {
      ease = ease || 'cos';
      let prevGraph = history.shift();
      let prevAnchors = history.shift();
      renderAnimation(prevGraph, graph, prevAnchors, anchors, duration, ease);
    } else {
      render(graph, anchors);
    }

    let saveGraph = {};
    for (let id in graph) {
      if (graph[id].dummy === undefined) {
        saveGraph[id] = graph[id];
      }
    }
    history.push(saveGraph);
    history.push(anchors);
  }

  function buildDummyNodes(graph1, graph2) {
    for (let id in graph2) {
      if (graph1[id] !== undefined) continue;
      let node = graph2[id];
      // fill graph1[id] with nearest parent
      let queue = [id];
      while (queue.length > 0) {
        let pid = queue.shift();
        let parent = graph1[pid];
        if (parent !== undefined) {
          graph1[id] = {x: parent.x, y: parent.y, dummy: true};
          break;
        }
        graph2[pid].src.forEach(function (edge) {
          queue.push(edge.srcId);
        });
      }
    }
  }

  function render(graph, anchors) {
    for (let id in edges) {
      let edge = edges[id];
      let srcNode = graph[edge.srcId];
      let dstNode = graph[edge.dstId];
      let anchor = anchors[id];
      if (anchor !== undefined) {
        renderEdge([srcNode, anchor, dstNode], edge.data);
      }
    }

    for (let id in graph) {
      renderNode(graph[id], nodes[id]);
    }
  }

  function renderAnimation(prevGraph, graph, prevAnchors, anchors, duration, ease) {
    buildDummyNodes(prevGraph, graph);
    buildDummyNodes(graph, prevGraph);

    for (let id in anchors) {
      let edge = edges[id];
      let path = [graph[edge.srcId], anchors[id], graph[edge.dstId]];

      let prevSrcNode = prevGraph[edge.srcId];
      let prevDstNode = prevGraph[edge.dstId];
      if (prevSrcNode === undefined || prevDstNode === undefined) {
        renderEdge(path, edge.data);
      } else {
        let prevAnchor = prevAnchors[id];
        if (prevAnchor === undefined) {
          prevAnchor = {x: (prevSrcNode.x + prevDstNode.x) / 2, y: (prevSrcNode.y + prevDstNode.y) / 2};
        }
        let prevPath = [prevSrcNode, prevAnchor, prevDstNode];
        renderEdgeAnimation(prevPath, path, edge.data, duration, ease);
      }
    }

    // for vanishing edges
    for (let id in prevAnchors) {
      if (anchors[id] !== undefined) continue;
      let edge = edges[id];
      let srcNode = graph[edge.srcId];
      let dstNode = graph[edge.dstId];
      if (srcNode === undefined || dstNode === undefined) continue;
      let anchor = {x: (srcNode.x + dstNode.x) / 2, y: (srcNode.y + dstNode.y) / 2};
      let path = [srcNode, anchor, dstNode];
      let prevPath = [prevGraph[edge.srcId], prevAnchors[id], prevGraph[edge.dstId]];
      renderEdgeAnimation(prevPath, path, edge.data, duration, ease);
    }

    for (let id in graph) {
      let node = graph[id];
      let prevNode = prevGraph[id];

      if (prevNode !== undefined) {
        renderNodeAnimation(prevNode, node, nodes[id], duration, ease);
      } else {
        renderNode(node, nodes[id]);
      }
    }
  }

  function renderNode(node, data) {
    let group = svg.append('g')
    .attr('transform', 'translate(' + node.x + ',' + node.y + ')');
    drawNode(group, data);
  }

  function renderNodeAnimation(node1, node2, data, duration, ease) {
    let group = svg.append('g');
    group.attr('transform', 'translate(' + node1.x + ',' + node1.y + ')')
    drawNode(group, data);
    let animation = group.transition().duration(duration).ease(ease)
    .attr('transform', 'translate(' + node2.x + ',' + node2.y + ')');

    if (node2.dummy) {
      animation.each('end', function () {
        group.remove();
      });
    }
  }

  let lineFunc = d3.svg.line()
  .x(function (d) { return d.x; })
  .y(function (d) { return d.y; })
  .interpolate('basis');

  function renderEdge(anchors, data) {
    let mid = anchors[Math.floor(anchors.length / 2)];
    let path = svg.append('path')
    .attr('d', lineFunc(anchors)).attr('fill', 'none');
    let group = svg.append('g')
    .attr('transform', 'translate(' + mid.x + ',' + mid.y + ')');
    drawEdge(path, group, data);
  }

  function renderEdgeAnimation(anchors1, anchors2, data, duration, ease) {
    let path = svg.append('path').attr('fill', 'none');
    path.attr('d', lineFunc(anchors1));
    path.transition().duration(duration).ease(ease)
    .attr('d', lineFunc(anchors2));

    let mid1 = anchors1[Math.floor(anchors1.length / 2)];
    let mid2 = anchors2[Math.floor(anchors2.length / 2)];

    let group = svg.append('g');
    group.attr('transform', 'translate(' + mid1.x + ',' + mid1.y + ')');
    group.transition().duration(duration).ease(ease)
    .attr('transform', 'translate(' + mid2.x + ',' + mid2.y + ')');

    drawEdge(path, group, data);
  }

  return {
    addNode: addNode,
    delNode: delNode,
    addEdge: addEdge,
    delEdge: delEdge,
    redraw: redraw,
    nodes: nodes,
    edges: edges
  };
}
<|endoftext|>


<commit_before>export default class MoUrlParams {
  constructor(){
    this.params = {};
    this._load();
    this.updateTitleTag();
  }
  _load(){
    if (location.search.length > 0){
      const q = decodeURIComponent(location.search.substring(1)).split('&');
      q.forEach((pair)=>{
        let kv = pair.split('=');
        this.params[kv[0]] = kv[1];
      });
    }
    this.title = this.getTitle();
  }
  _save(){
    let pathnames = window.location.pathname.split('/');
    if (pathnames[pathnames.length-1].indexOf('map')>=0){
      pathnames.push(this.title);
    }else{
      pathnames[pathnames.length-1] = this.title;
    }

    let queryString = '?';
    for (let key in this.params) {
      if (/[^0-9,.]/.test(this.params[key])){
        queryString = queryString + `&${key}=${encodeURIComponent(this.params[key])}`;
      }else{
        queryString = queryString + `&${key}=${this.params[key]}`;
      }
    }
    history.replaceState( '', null, pathnames.join('/').replace(/\/$/,'')+queryString.replace('?&','?') );
  }
  set(key,value){
    this.params[key] = value;
    this._save();
    return this;
  }
  get(key){
    if (!this.params[key]){
      let match = location.search.match(new RegExp(key+'=(.*?)(&|$)'));
      if(match) {
        this.params[key] = decodeURIComponent(match[1]);
      }
    }
    return this.params[key];
  }
  getInt(key){
    return parseInt(this.get(key));
  }
  getBool(key){
    return (this.get(key)==='true');
  }
  setLatLng(lat,lng){
    const ll= `${lat},${lng}`;
    return this.set('ll',ll);
  }
  getLatLng(){
    const ll = this.get('ll');
    if (ll){
      const latlng = ll.split(',');
      return { lat: parseFloat(latlng[0]), lng: parseFloat(latlng[1]) };
    }else{
      return null;
    }
  }
  getLat(){
    const latlng = this.getLatLng();
    if (latlng){
      return this.getLatLng().lat;
    }else{
      return null;
    }
  }
  getLng(){
    const latlng = this.getLatLng();
    if (latlng){
      return this.getLatLng().lng;
    }else{
      return null;
    }
  }
  setTitle(title){
    this.title = title;
    this._save();
    this.updateTitleTag();
    return this;
  }
  getTitle(){
    if (!this.title){
      const title = window.location.pathname.split('/').pop();
      if (title.indexOf('map')<0){
        this.title = title;
      }else{
        this.title = null;
      }
    }
    return this.title;
  }
  updateTitleTag(){
    let title = this.getTitle();
    if (title){
      document.title = `${decodeURIComponent(title)}付近の電源マップ - モバイラーズオアシス`;
    }else{
      document.title = '電源マップ - モバイラーズオアシス';
    }
  }
}
<commit_msg>タイトルが「〜駅付近付近のマップ」になっているページが見つかったのでパッチ<commit_after>export default class MoUrlParams {
  constructor(){
    this.params = {};
    this._load();
    this.updateTitleTag();
  }
  _load(){
    if (location.search.length > 0){
      const q = decodeURIComponent(location.search.substring(1)).split('&');
      q.forEach((pair)=>{
        let kv = pair.split('=');
        this.params[kv[0]] = kv[1];
      });
    }
    this.title = this.getTitle();
  }
  _save(){
    let pathnames = window.location.pathname.split('/');
    if (pathnames[pathnames.length-1].indexOf('map')>=0){
      pathnames.push(this.title);
    }else{
      pathnames[pathnames.length-1] = this.title;
    }

    let queryString = '?';
    for (let key in this.params) {
      if (/[^0-9,.]/.test(this.params[key])){
        queryString = queryString + `&${key}=${encodeURIComponent(this.params[key])}`;
      }else{
        queryString = queryString + `&${key}=${this.params[key]}`;
      }
    }
    history.replaceState( '', null, pathnames.join('/').replace(/\/$/,'')+queryString.replace('?&','?') );
  }
  set(key,value){
    if (value===null){
      delete this.params[key];
    }else{
      this.params[key] = value;
    }
    this._save();
    return this;
  }
  get(key){
    if (!this.params[key]){
      let match = location.search.match(new RegExp(key+'=(.*?)(&|$)'));
      if(match) {
        this.params[key] = decodeURIComponent(match[1]);
      }
    }
    return this.params[key];
  }
  getInt(key){
    return parseInt(this.get(key));
  }
  getBool(key){
    return (this.get(key)==='true');
  }
  setLatLng(lat,lng){
    const ll= `${lat},${lng}`;
    return this.set('ll',ll);
  }
  getLatLng(){
    const ll = this.get('ll');
    if (ll){
      const latlng = ll.split(',');
      return { lat: parseFloat(latlng[0]), lng: parseFloat(latlng[1]) };
    }else{
      return null;
    }
  }
  getLat(){
    const latlng = this.getLatLng();
    if (latlng){
      return this.getLatLng().lat;
    }else{
      return null;
    }
  }
  getLng(){
    const latlng = this.getLatLng();
    if (latlng){
      return this.getLatLng().lng;
    }else{
      return null;
    }
  }
  setTitle(title){
    this.title = title;
    this._save();
    this.updateTitleTag();
    return this;
  }
  getTitle(){
    if (!this.title){
      let title = window.location.pathname.split('/').pop();
      if (title.indexOf('map')>=0){
        title = this.get('title');
        this.set('title',null);
      }
      if (title){
        this.title = decodeURIComponent(title).replace(/付近$/,'');
      }else{
        this.title = null;
      }
    }
    return this.title;
  }
  updateTitleTag(){
    let title = this.getTitle();
    if (title){
      document.title = `${title}付近の電源マップ - モバイラーズオアシス`;
    }else{
      document.title = '電源マップ - モバイラーズオアシス';
    }
  }
}
<|endoftext|>