| 0 | 1 // mercurial.js - JavaScript utility functions | 
|  | 2 // | 
|  | 3 // Rendering of branch DAGs on the client side | 
|  | 4 // Display of elapsed time | 
|  | 5 // Show or hide diffstat | 
|  | 6 // | 
|  | 7 // Copyright 2008 Dirkjan Ochtman <dirkjan AT ochtman DOT nl> | 
|  | 8 // Copyright 2006 Alexander Schremmer <alex AT alexanderweb DOT de> | 
|  | 9 // | 
|  | 10 // derived from code written by Scott James Remnant <scott@ubuntu.com> | 
|  | 11 // Copyright 2005 Canonical Ltd. | 
|  | 12 // | 
|  | 13 // This software may be used and distributed according to the terms | 
|  | 14 // of the GNU General Public License, incorporated herein by reference. | 
|  | 15 | 
|  | 16 var colors = [ | 
|  | 17 	[ 1.0, 0.0, 0.0 ], | 
|  | 18 	[ 1.0, 1.0, 0.0 ], | 
|  | 19 	[ 0.0, 1.0, 0.0 ], | 
|  | 20 	[ 0.0, 1.0, 1.0 ], | 
|  | 21 	[ 0.0, 0.0, 1.0 ], | 
|  | 22 	[ 1.0, 0.0, 1.0 ] | 
|  | 23 ]; | 
|  | 24 | 
|  | 25 function Graph() { | 
|  | 26 | 
|  | 27 	this.canvas = document.getElementById('graph'); | 
|  | 28 	this.ctx = this.canvas.getContext('2d'); | 
|  | 29 	this.ctx.strokeStyle = 'rgb(0, 0, 0)'; | 
|  | 30 	this.ctx.fillStyle = 'rgb(0, 0, 0)'; | 
|  | 31 	this.bg = [0, 4]; | 
|  | 32 	this.cell = [2, 0]; | 
|  | 33 	this.columns = 0; | 
|  | 34 | 
|  | 35 } | 
|  | 36 | 
|  | 37 Graph.prototype = { | 
|  | 38 	reset: function() { | 
|  | 39 		this.bg = [0, 4]; | 
|  | 40 		this.cell = [2, 0]; | 
|  | 41 		this.columns = 0; | 
|  | 42 	}, | 
|  | 43 | 
|  | 44 	scale: function(height) { | 
|  | 45 		this.bg_height = height; | 
|  | 46 		this.box_size = Math.floor(this.bg_height / 1.2); | 
|  | 47 		this.cell_height = this.box_size; | 
|  | 48 	}, | 
|  | 49 | 
|  | 50 	setColor: function(color, bg, fg) { | 
|  | 51 | 
|  | 52 		// Set the colour. | 
|  | 53 		// | 
|  | 54 		// If color is a string, expect an hexadecimal RGB | 
|  | 55 		// value and apply it unchanged. If color is a number, | 
|  | 56 		// pick a distinct colour based on an internal wheel; | 
|  | 57 		// the bg parameter provides the value that should be | 
|  | 58 		// assigned to the 'zero' colours and the fg parameter | 
|  | 59 		// provides the multiplier that should be applied to | 
|  | 60 		// the foreground colours. | 
|  | 61 		var s; | 
|  | 62 		if(typeof color === "string") { | 
|  | 63 			s = "#" + color; | 
|  | 64 		} else { //typeof color === "number" | 
|  | 65 			color %= colors.length; | 
|  | 66 			var red = (colors[color][0] * fg) || bg; | 
|  | 67 			var green = (colors[color][1] * fg) || bg; | 
|  | 68 			var blue = (colors[color][2] * fg) || bg; | 
|  | 69 			red = Math.round(red * 255); | 
|  | 70 			green = Math.round(green * 255); | 
|  | 71 			blue = Math.round(blue * 255); | 
|  | 72 			s = 'rgb(' + red + ', ' + green + ', ' + blue + ')'; | 
|  | 73 		} | 
|  | 74 		this.ctx.strokeStyle = s; | 
|  | 75 		this.ctx.fillStyle = s; | 
|  | 76 		return s; | 
|  | 77 | 
|  | 78 	}, | 
|  | 79 | 
|  | 80 	edge: function(x0, y0, x1, y1, color, width) { | 
|  | 81 | 
|  | 82 		this.setColor(color, 0.0, 0.65); | 
|  | 83 		if(width >= 0) | 
|  | 84 			 this.ctx.lineWidth = width; | 
|  | 85 		this.ctx.beginPath(); | 
|  | 86 		this.ctx.moveTo(x0, y0); | 
|  | 87 		this.ctx.lineTo(x1, y1); | 
|  | 88 		this.ctx.stroke(); | 
|  | 89 | 
|  | 90 	}, | 
|  | 91 | 
|  | 92 	graphNodeCurrent: function(x, y, radius) { | 
|  | 93 		this.ctx.lineWidth = 2; | 
|  | 94 		this.ctx.beginPath(); | 
|  | 95 		this.ctx.arc(x, y, radius * 1.75, 0, Math.PI * 2, true); | 
|  | 96 		this.ctx.stroke(); | 
|  | 97 	}, | 
|  | 98 | 
|  | 99 	graphNodeClosing: function(x, y, radius) { | 
|  | 100 		this.ctx.fillRect(x - radius, y - 1.5, radius * 2, 3); | 
|  | 101 	}, | 
|  | 102 | 
|  | 103 	graphNodeUnstable: function(x, y, radius) { | 
|  | 104 		var x30 = radius * Math.cos(Math.PI / 6); | 
|  | 105 		var y30 = radius * Math.sin(Math.PI / 6); | 
|  | 106 		this.ctx.lineWidth = 2; | 
|  | 107 		this.ctx.beginPath(); | 
|  | 108 		this.ctx.moveTo(x, y - radius); | 
|  | 109 		this.ctx.lineTo(x, y + radius); | 
|  | 110 		this.ctx.moveTo(x - x30, y - y30); | 
|  | 111 		this.ctx.lineTo(x + x30, y + y30); | 
|  | 112 		this.ctx.moveTo(x - x30, y + y30); | 
|  | 113 		this.ctx.lineTo(x + x30, y - y30); | 
|  | 114 		this.ctx.stroke(); | 
|  | 115 	}, | 
|  | 116 | 
|  | 117 	graphNodeObsolete: function(x, y, radius) { | 
|  | 118 		var p45 = radius * Math.cos(Math.PI / 4); | 
|  | 119 		this.ctx.lineWidth = 3; | 
|  | 120 		this.ctx.beginPath(); | 
|  | 121 		this.ctx.moveTo(x - p45, y - p45); | 
|  | 122 		this.ctx.lineTo(x + p45, y + p45); | 
|  | 123 		this.ctx.moveTo(x - p45, y + p45); | 
|  | 124 		this.ctx.lineTo(x + p45, y - p45); | 
|  | 125 		this.ctx.stroke(); | 
|  | 126 	}, | 
|  | 127 | 
|  | 128 	graphNodeNormal: function(x, y, radius) { | 
|  | 129 		this.ctx.beginPath(); | 
|  | 130 		this.ctx.arc(x, y, radius, 0, Math.PI * 2, true); | 
|  | 131 		this.ctx.fill(); | 
|  | 132 	}, | 
|  | 133 | 
|  | 134 	vertex: function(x, y, radius, color, parity, cur) { | 
|  | 135 		this.ctx.save(); | 
|  | 136 		this.setColor(color, 0.25, 0.75); | 
|  | 137 		if (cur.graphnode[0] === '@') { | 
|  | 138 			this.graphNodeCurrent(x, y, radius); | 
|  | 139 		} | 
|  | 140 		switch (cur.graphnode.substr(-1)) { | 
|  | 141 			case '_': | 
|  | 142 				this.graphNodeClosing(x, y, radius); | 
|  | 143 				break; | 
|  | 144 			case '*': | 
|  | 145 				this.graphNodeUnstable(x, y, radius); | 
|  | 146 				break; | 
|  | 147 			case 'x': | 
|  | 148 				this.graphNodeObsolete(x, y, radius); | 
|  | 149 				break; | 
|  | 150 			default: | 
|  | 151 				this.graphNodeNormal(x, y, radius); | 
|  | 152 		} | 
|  | 153 		this.ctx.restore(); | 
|  | 154 | 
|  | 155 		var left = (this.bg_height - this.box_size) + (this.columns + 1) * this.box_size; | 
|  | 156 		var item = document.querySelector('[data-node="' + cur.node + '"]'); | 
|  | 157 		if (item) { | 
|  | 158 			item.style.paddingLeft = left + 'px'; | 
|  | 159 		} | 
|  | 160 	}, | 
|  | 161 | 
|  | 162 	render: function(data) { | 
|  | 163 | 
|  | 164 		var i, j, cur, line, start, end, color, x, y, x0, y0, x1, y1, column, radius; | 
|  | 165 | 
|  | 166 		var cols = 0; | 
|  | 167 		for (i = 0; i < data.length; i++) { | 
|  | 168 			cur = data[i]; | 
|  | 169 			for (j = 0; j < cur.edges.length; j++) { | 
|  | 170 				line = cur.edges[j]; | 
|  | 171 				cols = Math.max(cols, line[0], line[1]); | 
|  | 172 			} | 
|  | 173 		} | 
|  | 174 		this.canvas.width = (cols + 1) * this.bg_height; | 
|  | 175 		this.canvas.height = (data.length + 1) * this.bg_height - 27; | 
|  | 176 | 
|  | 177 		for (i = 0; i < data.length; i++) { | 
|  | 178 | 
|  | 179 			var parity = i % 2; | 
|  | 180 			this.cell[1] += this.bg_height; | 
|  | 181 			this.bg[1] += this.bg_height; | 
|  | 182 | 
|  | 183 			cur = data[i]; | 
|  | 184 			var fold = false; | 
|  | 185 | 
|  | 186 			var prevWidth = this.ctx.lineWidth; | 
|  | 187 			for (j = 0; j < cur.edges.length; j++) { | 
|  | 188 | 
|  | 189 				line = cur.edges[j]; | 
|  | 190 				start = line[0]; | 
|  | 191 				end = line[1]; | 
|  | 192 				color = line[2]; | 
|  | 193 				var width = line[3]; | 
|  | 194 				if(width < 0) | 
|  | 195 					 width = prevWidth; | 
|  | 196 				var branchcolor = line[4]; | 
|  | 197 				if(branchcolor) | 
|  | 198 					color = branchcolor; | 
|  | 199 | 
|  | 200 				if (end > this.columns || start > this.columns) { | 
|  | 201 					this.columns += 1; | 
|  | 202 				} | 
|  | 203 | 
|  | 204 				if (start === this.columns && start > end) { | 
|  | 205 					fold = true; | 
|  | 206 				} | 
|  | 207 | 
|  | 208 				x0 = this.cell[0] + this.box_size * start + this.box_size / 2; | 
|  | 209 				y0 = this.bg[1] - this.bg_height / 2; | 
|  | 210 				x1 = this.cell[0] + this.box_size * end + this.box_size / 2; | 
|  | 211 				y1 = this.bg[1] + this.bg_height / 2; | 
|  | 212 | 
|  | 213 				this.edge(x0, y0, x1, y1, color, width); | 
|  | 214 | 
|  | 215 			} | 
|  | 216 			this.ctx.lineWidth = prevWidth; | 
|  | 217 | 
|  | 218 			// Draw the revision node in the right column | 
|  | 219 | 
|  | 220 			column = cur.vertex[0]; | 
|  | 221 			color = cur.vertex[1]; | 
|  | 222 | 
|  | 223 			radius = this.box_size / 8; | 
|  | 224 			x = this.cell[0] + this.box_size * column + this.box_size / 2; | 
|  | 225 			y = this.bg[1] - this.bg_height / 2; | 
|  | 226 			this.vertex(x, y, radius, color, parity, cur); | 
|  | 227 | 
|  | 228 			if (fold) this.columns -= 1; | 
|  | 229 | 
|  | 230 		} | 
|  | 231 | 
|  | 232 	} | 
|  | 233 | 
|  | 234 }; | 
|  | 235 | 
|  | 236 | 
|  | 237 function process_dates(parentSelector){ | 
|  | 238 | 
|  | 239 	// derived from code from mercurial/templatefilter.py | 
|  | 240 | 
|  | 241 	var scales = { | 
|  | 242 		'year':  365 * 24 * 60 * 60, | 
|  | 243 		'month':  30 * 24 * 60 * 60, | 
|  | 244 		'week':    7 * 24 * 60 * 60, | 
|  | 245 		'day':    24 * 60 * 60, | 
|  | 246 		'hour':   60 * 60, | 
|  | 247 		'minute': 60, | 
|  | 248 		'second': 1 | 
|  | 249 	}; | 
|  | 250 | 
|  | 251 	function format(count, string){ | 
|  | 252 		var ret = count + ' ' + string; | 
|  | 253 		if (count > 1){ | 
|  | 254 			ret = ret + 's'; | 
|  | 255 		} | 
|  | 256  		return ret; | 
|  | 257  	} | 
|  | 258 | 
|  | 259 	function shortdate(date){ | 
|  | 260 		var ret = date.getFullYear() + '-'; | 
|  | 261 		// getMonth() gives a 0-11 result | 
|  | 262 		var month = date.getMonth() + 1; | 
|  | 263 		if (month <= 9){ | 
|  | 264 			ret += '0' + month; | 
|  | 265 		} else { | 
|  | 266 			ret += month; | 
|  | 267 		} | 
|  | 268 		ret += '-'; | 
|  | 269 		var day = date.getDate(); | 
|  | 270 		if (day <= 9){ | 
|  | 271 			ret += '0' + day; | 
|  | 272 		} else { | 
|  | 273 			ret += day; | 
|  | 274 		} | 
|  | 275 		return ret; | 
|  | 276 	} | 
|  | 277 | 
|  | 278  	function age(datestr){ | 
|  | 279  		var now = new Date(); | 
|  | 280  		var once = new Date(datestr); | 
|  | 281 		if (isNaN(once.getTime())){ | 
|  | 282 			// parsing error | 
|  | 283 			return datestr; | 
|  | 284 		} | 
|  | 285 | 
|  | 286 		var delta = Math.floor((now.getTime() - once.getTime()) / 1000); | 
|  | 287 | 
|  | 288 		var future = false; | 
|  | 289 		if (delta < 0){ | 
|  | 290 			future = true; | 
|  | 291 			delta = -delta; | 
|  | 292 			if (delta > (30 * scales.year)){ | 
|  | 293 				return "in the distant future"; | 
|  | 294 			} | 
|  | 295 		} | 
|  | 296 | 
|  | 297 		if (delta > (2 * scales.year)){ | 
|  | 298 			return shortdate(once); | 
|  | 299 		} | 
|  | 300 | 
|  | 301 		for (var unit in scales){ | 
|  | 302 			if (!scales.hasOwnProperty(unit)) { continue; } | 
|  | 303 			var s = scales[unit]; | 
|  | 304 			var n = Math.floor(delta / s); | 
|  | 305 			if ((n >= 2) || (s === 1)){ | 
|  | 306 				if (future){ | 
|  | 307 					return format(n, unit) + ' from now'; | 
|  | 308 				} else { | 
|  | 309 					return format(n, unit) + ' ago'; | 
|  | 310 				} | 
|  | 311 			} | 
|  | 312 		} | 
|  | 313 	} | 
|  | 314 | 
|  | 315 	var nodes = document.querySelectorAll((parentSelector || '') + ' .age'); | 
|  | 316 	var dateclass = new RegExp('\\bdate\\b'); | 
|  | 317 	for (var i=0; i<nodes.length; ++i){ | 
|  | 318 		var node = nodes[i]; | 
|  | 319 		var classes = node.className; | 
|  | 320 		var agevalue = age(node.textContent); | 
|  | 321 		if (dateclass.test(classes)){ | 
|  | 322 			// We want both: date + (age) | 
|  | 323 			node.textContent += ' ('+agevalue+')'; | 
|  | 324 		} else { | 
|  | 325 			node.title = node.textContent; | 
|  | 326 			node.textContent = agevalue; | 
|  | 327 		} | 
|  | 328 	} | 
|  | 329 } | 
|  | 330 | 
|  | 331 function toggleDiffstat(event) { | 
|  | 332     var curdetails = document.getElementById('diffstatdetails').style.display; | 
|  | 333     var curexpand = curdetails === 'none' ? 'inline' : 'none'; | 
|  | 334     document.getElementById('diffstatdetails').style.display = curexpand; | 
|  | 335     document.getElementById('diffstatexpand').style.display = curdetails; | 
|  | 336     event.preventDefault(); | 
|  | 337 } | 
|  | 338 | 
|  | 339 function toggleLinewrap(event) { | 
|  | 340     function getLinewrap() { | 
|  | 341         var nodes = document.getElementsByClassName('sourcelines'); | 
|  | 342         // if there are no such nodes, error is thrown here | 
|  | 343         return nodes[0].classList.contains('wrap'); | 
|  | 344     } | 
|  | 345 | 
|  | 346     function setLinewrap(enable) { | 
|  | 347         var nodes = document.getElementsByClassName('sourcelines'); | 
|  | 348         var i; | 
|  | 349         for (i = 0; i < nodes.length; i++) { | 
|  | 350             if (enable) { | 
|  | 351                 nodes[i].classList.add('wrap'); | 
|  | 352             } else { | 
|  | 353                 nodes[i].classList.remove('wrap'); | 
|  | 354             } | 
|  | 355         } | 
|  | 356 | 
|  | 357         var links = document.getElementsByClassName('linewraplink'); | 
|  | 358         for (i = 0; i < links.length; i++) { | 
|  | 359             links[i].innerHTML = enable ? 'on' : 'off'; | 
|  | 360         } | 
|  | 361     } | 
|  | 362 | 
|  | 363     setLinewrap(!getLinewrap()); | 
|  | 364     event.preventDefault(); | 
|  | 365 } | 
|  | 366 | 
|  | 367 function format(str, replacements) { | 
|  | 368     return str.replace(/%(\w+)%/g, function(match, p1) { | 
|  | 369         return String(replacements[p1]); | 
|  | 370     }); | 
|  | 371 } | 
|  | 372 | 
|  | 373 function makeRequest(url, method, onstart, onsuccess, onerror, oncomplete) { | 
|  | 374     var xhr = new XMLHttpRequest(); | 
|  | 375     xhr.onreadystatechange = function() { | 
|  | 376         if (xhr.readyState === 4) { | 
|  | 377             try { | 
|  | 378                 if (xhr.status === 200) { | 
|  | 379                     onsuccess(xhr.responseText); | 
|  | 380                 } else { | 
|  | 381                     throw 'server error'; | 
|  | 382                 } | 
|  | 383             } catch (e) { | 
|  | 384                 onerror(e); | 
|  | 385             } finally { | 
|  | 386                 oncomplete(); | 
|  | 387             } | 
|  | 388         } | 
|  | 389     }; | 
|  | 390 | 
|  | 391     xhr.open(method, url); | 
|  | 392     xhr.overrideMimeType("text/xhtml; charset=" + document.characterSet.toLowerCase()); | 
|  | 393     xhr.send(); | 
|  | 394     onstart(); | 
|  | 395     return xhr; | 
|  | 396 } | 
|  | 397 | 
|  | 398 function removeByClassName(className) { | 
|  | 399     var nodes = document.getElementsByClassName(className); | 
|  | 400     while (nodes.length) { | 
|  | 401         nodes[0].parentNode.removeChild(nodes[0]); | 
|  | 402     } | 
|  | 403 } | 
|  | 404 | 
|  | 405 function docFromHTML(html) { | 
|  | 406     var doc = document.implementation.createHTMLDocument(''); | 
|  | 407     doc.documentElement.innerHTML = html; | 
|  | 408     return doc; | 
|  | 409 } | 
|  | 410 | 
|  | 411 function appendFormatHTML(element, formatStr, replacements) { | 
|  | 412     element.insertAdjacentHTML('beforeend', format(formatStr, replacements)); | 
|  | 413 } | 
|  | 414 | 
|  | 415 function adoptChildren(from, to) { | 
|  | 416     var nodes = from.children; | 
|  | 417     var curClass = 'c' + Date.now(); | 
|  | 418     while (nodes.length) { | 
|  | 419         var node = nodes[0]; | 
|  | 420         node = document.adoptNode(node); | 
|  | 421         node.classList.add(curClass); | 
|  | 422         to.appendChild(node); | 
|  | 423     } | 
|  | 424     process_dates('.' + curClass); | 
|  | 425 } | 
|  | 426 | 
|  | 427 function ajaxScrollInit(urlFormat, | 
|  | 428                         nextPageVar, | 
|  | 429                         nextPageVarGet, | 
|  | 430                         containerSelector, | 
|  | 431                         messageFormat, | 
|  | 432                         mode) { | 
|  | 433     var updateInitiated = false; | 
|  | 434     var container = document.querySelector(containerSelector); | 
|  | 435 | 
|  | 436     function scrollHandler() { | 
|  | 437         if (updateInitiated) { | 
|  | 438             return; | 
|  | 439         } | 
|  | 440 | 
|  | 441         var scrollHeight = document.documentElement.scrollHeight; | 
|  | 442         var clientHeight = document.documentElement.clientHeight; | 
|  | 443         var scrollTop = document.body.scrollTop || document.documentElement.scrollTop; | 
|  | 444 | 
|  | 445         if (scrollHeight - (scrollTop + clientHeight) < 50) { | 
|  | 446             updateInitiated = true; | 
|  | 447             removeByClassName('scroll-loading-error'); | 
|  | 448             container.lastElementChild.classList.add('scroll-separator'); | 
|  | 449 | 
|  | 450             if (!nextPageVar) { | 
|  | 451                 var message = { | 
|  | 452                     'class': 'scroll-loading-info', | 
|  | 453                     text: 'No more entries' | 
|  | 454                 }; | 
|  | 455                 appendFormatHTML(container, messageFormat, message); | 
|  | 456                 return; | 
|  | 457             } | 
|  | 458 | 
|  | 459             makeRequest( | 
|  | 460                 format(urlFormat, {next: nextPageVar}), | 
|  | 461                 'GET', | 
|  | 462                 function onstart() { | 
|  | 463                     var message = { | 
|  | 464                         'class': 'scroll-loading', | 
|  | 465                         text: 'Loading...' | 
|  | 466                     }; | 
|  | 467                     appendFormatHTML(container, messageFormat, message); | 
|  | 468                 }, | 
|  | 469                 function onsuccess(htmlText) { | 
|  | 470                     var doc = docFromHTML(htmlText); | 
|  | 471 | 
|  | 472                     if (mode === 'graph') { | 
|  | 473                         var graph = window.graph; | 
|  | 474                         var dataStr = htmlText.match(/^\s*var data = (.*);$/m)[1]; | 
|  | 475                         var data = JSON.parse(dataStr); | 
|  | 476                         graph.reset(); | 
|  | 477                         adoptChildren(doc.querySelector('#graphnodes'), container.querySelector('#graphnodes')); | 
|  | 478                         graph.render(data); | 
|  | 479                     } else { | 
|  | 480                         adoptChildren(doc.querySelector(containerSelector), container); | 
|  | 481                     } | 
|  | 482 | 
|  | 483                     nextPageVar = nextPageVarGet(htmlText); | 
|  | 484                 }, | 
|  | 485                 function onerror(errorText) { | 
|  | 486                     var message = { | 
|  | 487                         'class': 'scroll-loading-error', | 
|  | 488                         text: 'Error: ' + errorText | 
|  | 489                     }; | 
|  | 490                     appendFormatHTML(container, messageFormat, message); | 
|  | 491                 }, | 
|  | 492                 function oncomplete() { | 
|  | 493                     removeByClassName('scroll-loading'); | 
|  | 494                     updateInitiated = false; | 
|  | 495                     scrollHandler(); | 
|  | 496                 } | 
|  | 497             ); | 
|  | 498         } | 
|  | 499     } | 
|  | 500 | 
|  | 501     window.addEventListener('scroll', scrollHandler); | 
|  | 502     window.addEventListener('resize', scrollHandler); | 
|  | 503     scrollHandler(); | 
|  | 504 } | 
|  | 505 | 
|  | 506 function renderDiffOptsForm() { | 
|  | 507     // We use URLSearchParams for query string manipulation. Old browsers don't | 
|  | 508     // support this API. | 
|  | 509     if (!("URLSearchParams" in window)) { | 
|  | 510         return; | 
|  | 511     } | 
|  | 512 | 
|  | 513     var form = document.getElementById("diffopts-form"); | 
|  | 514 | 
|  | 515     var KEYS = [ | 
|  | 516         "ignorews", | 
|  | 517         "ignorewsamount", | 
|  | 518         "ignorewseol", | 
|  | 519         "ignoreblanklines", | 
|  | 520     ]; | 
|  | 521 | 
|  | 522     var urlParams = new window.URLSearchParams(window.location.search); | 
|  | 523 | 
|  | 524     function updateAndRefresh(e) { | 
|  | 525         var checkbox = e.target; | 
|  | 526         var name = checkbox.id.substr(0, checkbox.id.indexOf("-")); | 
|  | 527         urlParams.set(name, checkbox.checked ? "1" : "0"); | 
|  | 528         window.location.search = urlParams.toString(); | 
|  | 529     } | 
|  | 530 | 
|  | 531     var allChecked = form.getAttribute("data-ignorews") === "1"; | 
|  | 532 | 
|  | 533     for (var i = 0; i < KEYS.length; i++) { | 
|  | 534         var key = KEYS[i]; | 
|  | 535 | 
|  | 536         var checkbox = document.getElementById(key + "-checkbox"); | 
|  | 537         if (!checkbox) { | 
|  | 538             continue; | 
|  | 539         } | 
|  | 540 | 
|  | 541         var currentValue = form.getAttribute("data-" + key); | 
|  | 542         checkbox.checked = currentValue !== "0"; | 
|  | 543 | 
|  | 544         // ignorews implies ignorewsamount and ignorewseol. | 
|  | 545         if (allChecked && (key === "ignorewsamount" || key === "ignorewseol")) { | 
|  | 546             checkbox.checked = true; | 
|  | 547             checkbox.disabled = true; | 
|  | 548         } | 
|  | 549 | 
|  | 550         checkbox.addEventListener("change", updateAndRefresh, false); | 
|  | 551     } | 
|  | 552 | 
|  | 553     form.style.display = 'block'; | 
|  | 554 } | 
|  | 555 | 
|  | 556 function addDiffStatToggle() { | 
|  | 557     var els = document.getElementsByClassName("diffstattoggle"); | 
|  | 558 | 
|  | 559     for (var i = 0; i < els.length; i++) { | 
|  | 560         els[i].addEventListener("click", toggleDiffstat, false); | 
|  | 561     } | 
|  | 562 } | 
|  | 563 | 
|  | 564 function addLineWrapToggle() { | 
|  | 565     var els = document.getElementsByClassName("linewraptoggle"); | 
|  | 566 | 
|  | 567     for (var i = 0; i < els.length; i++) { | 
|  | 568         var nodes = els[i].getElementsByClassName("linewraplink"); | 
|  | 569 | 
|  | 570         for (var j = 0; j < nodes.length; j++) { | 
|  | 571             nodes[j].addEventListener("click", toggleLinewrap, false); | 
|  | 572         } | 
|  | 573     } | 
|  | 574 } | 
|  | 575 | 
|  | 576 document.addEventListener('DOMContentLoaded', function() { | 
|  | 577    process_dates(); | 
|  | 578    addDiffStatToggle(); | 
|  | 579    addLineWrapToggle(); | 
|  | 580 }, false); |