git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@3648 ed5197a5-7fde-0310-b194-c3ffbd925b24
		
			
				
	
	
		
			662 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			662 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
"use strict";
 | 
						|
 | 
						|
var gSocket;
 | 
						|
var gCredentials;
 | 
						|
 | 
						|
var gCurrentFile;
 | 
						|
var gFiles = {};
 | 
						|
var gApp = {files: {}};
 | 
						|
var gEditor;
 | 
						|
 | 
						|
var kErrorColor = "#dc322f";
 | 
						|
var kStatusColor = "#fff";
 | 
						|
 | 
						|
window.addEventListener("keydown", function(event) {
 | 
						|
	if (event.keyCode == 69 && event.altKey) {
 | 
						|
		if (!editing()) {
 | 
						|
			edit();
 | 
						|
			event.preventDefault();
 | 
						|
		}
 | 
						|
	} else if (event.keyCode == 83 && (event.altKey || event.ctrlKey)) {
 | 
						|
		if (editing()) {
 | 
						|
			save();
 | 
						|
			event.preventDefault();
 | 
						|
		}
 | 
						|
	} else if (event.keyCode == 66 && event.altKey) {
 | 
						|
		if (editing()) {
 | 
						|
			closeEditor();
 | 
						|
			event.preventDefault();
 | 
						|
		}
 | 
						|
	}
 | 
						|
});
 | 
						|
 | 
						|
function ensureLoaded(nodes, callback) {
 | 
						|
	if (!nodes.length) {
 | 
						|
		callback();
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	var search = nodes.shift();
 | 
						|
	var head = document.head;
 | 
						|
	var found = false;
 | 
						|
	for (var i = 0; i < head.childNodes.length; i++) {
 | 
						|
		if (head.childNodes[i].tagName == search.tagName) {
 | 
						|
			var match = true;
 | 
						|
			for (var attribute in search.attributes) {
 | 
						|
				if (head.childNodes[i].attributes[attribute].value != search.attributes[attribute]) {
 | 
						|
					match = false;
 | 
						|
				}
 | 
						|
			}
 | 
						|
			if (match) {
 | 
						|
				found = true;
 | 
						|
				break;
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if (found) {
 | 
						|
		ensureLoaded(nodes, callback);
 | 
						|
	} else {
 | 
						|
		var node = document.createElement(search.tagName);
 | 
						|
		node.onreadystatechange = node.onload = function() {
 | 
						|
			ensureLoaded(nodes, callback);
 | 
						|
		};
 | 
						|
		for (var attribute in search.attributes) {
 | 
						|
			node.setAttribute(attribute, search.attributes[attribute]);
 | 
						|
		}
 | 
						|
		head.insertBefore(node, head.firstChild);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
function editing() {
 | 
						|
	return document.getElementById("editPane").style.display != 'none';
 | 
						|
}
 | 
						|
 | 
						|
function edit() {
 | 
						|
	if (editing()) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	ensureLoaded([
 | 
						|
		{tagName: "script", attributes: {src: "https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.57.0/codemirror.min.js"}},
 | 
						|
		{tagName: "link", attributes: {rel: "stylesheet", href: "https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.57.0/theme/base16-dark.min.css"}},
 | 
						|
		{tagName: "link", attributes: {rel: "stylesheet", href: "https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.57.0/addon/search/matchesonscrollbar.min.css"}},
 | 
						|
		{tagName: "link", attributes: {rel: "stylesheet", href: "https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.57.0/addon/dialog/dialog.min.css"}},
 | 
						|
		{tagName: "link", attributes: {rel: "stylesheet", href: "https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.57.0/codemirror.min.css"}},
 | 
						|
		{tagName: "script", attributes: {src: "https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.57.0/addon/edit/trailingspace.min.js"}},
 | 
						|
		{tagName: "script", attributes: {src: "https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.57.0/addon/dialog/dialog.min.js"}},
 | 
						|
		{tagName: "script", attributes: {src: "https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.57.0/addon/search/search.min.js"}},
 | 
						|
		{tagName: "script", attributes: {src: "https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.57.0/addon/search/searchcursor.min.js"}},
 | 
						|
		{tagName: "script", attributes: {src: "https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.57.0/addon/search/jump-to-line.min.js"}},
 | 
						|
		{tagName: "script", attributes: {src: "https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.57.0/addon/search/matchesonscrollbar.min.js"}},
 | 
						|
		{tagName: "script", attributes: {src: "https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.57.0/addon/scroll/annotatescrollbar.min.js"}},
 | 
						|
		{tagName: "script", attributes: {src: "https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.57.0/mode/javascript/javascript.min.js"}},
 | 
						|
		{tagName: "script", attributes: {src: "https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.57.0/mode/css/css.min.js"}},
 | 
						|
		{tagName: "script", attributes: {src: "https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.57.0/mode/xml/xml.min.js"}},
 | 
						|
		{tagName: "script", attributes: {src: "https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.57.0/mode/htmlmixed/htmlmixed.min.js"}},
 | 
						|
	], function() {
 | 
						|
		load();
 | 
						|
	});
 | 
						|
}
 | 
						|
 | 
						|
function guessMode(name) {
 | 
						|
	return name.endsWith(".js") ? "javascript" :
 | 
						|
		name.endsWith(".html") ? "htmlmixed" :
 | 
						|
		null;
 | 
						|
}
 | 
						|
 | 
						|
function loadFile(name, id) {
 | 
						|
	var request = new XMLHttpRequest();
 | 
						|
	request.addEventListener("loadend", function() {
 | 
						|
		if (request.status == 200) {
 | 
						|
			gFiles[name].doc = new CodeMirror.Doc(request.responseText, guessMode(name));
 | 
						|
			if (!Object.values(gFiles).some(x => !x.doc)) {
 | 
						|
				document.getElementById("editPane").style.display = 'flex';
 | 
						|
				openFile(Object.keys(gFiles).sort()[0]);
 | 
						|
			}
 | 
						|
		}
 | 
						|
	});
 | 
						|
	request.addEventListener("error", function() {
 | 
						|
		alert("Error loading source.");
 | 
						|
		closeEditor();
 | 
						|
	});
 | 
						|
	request.addEventListener("timeout", function() {
 | 
						|
		alert("Timed out loading source.");
 | 
						|
		closeEditor();
 | 
						|
	});
 | 
						|
	request.addEventListener("abort", function() {
 | 
						|
		alert("Loading source aborted.");
 | 
						|
		closeEditor();
 | 
						|
	});
 | 
						|
	request.open("GET", "/" + id + "/view");
 | 
						|
	request.send();
 | 
						|
}
 | 
						|
 | 
						|
function load() {
 | 
						|
	var request = new XMLHttpRequest();
 | 
						|
	request.addEventListener("loadend", function() {
 | 
						|
		if (request.status == 200 || request.status == 404) {
 | 
						|
			if (!gEditor) {
 | 
						|
				gEditor = CodeMirror.fromTextArea(document.getElementById("editor"), {
 | 
						|
					'theme': 'base16-dark',
 | 
						|
					'lineNumbers': true,
 | 
						|
					'tabSize': 4,
 | 
						|
					'indentUnit': 4,
 | 
						|
					'indentWithTabs': true,
 | 
						|
					'showTrailingSpace': true,
 | 
						|
				});
 | 
						|
				gEditor.on('changes', function() {
 | 
						|
					updateFiles();
 | 
						|
				});
 | 
						|
			}
 | 
						|
			gFiles = {};
 | 
						|
			var text;
 | 
						|
			var isApp = false;
 | 
						|
			if (request.status == 200) {
 | 
						|
				text = request.responseText;
 | 
						|
				try {
 | 
						|
					var json = JSON.parse(text);
 | 
						|
					if (json && json['type'] == 'tildefriends-app') {
 | 
						|
						isApp = true;
 | 
						|
						Object.keys(json['files']).forEach(function(name) {
 | 
						|
							gFiles[name] = {};
 | 
						|
							loadFile(name, json['files'][name]);
 | 
						|
						});
 | 
						|
						if (Object.keys(json['files']).length == 0) {
 | 
						|
							document.getElementById("editPane").style.display = 'flex';
 | 
						|
						}
 | 
						|
						gApp = JSON.parse(text);
 | 
						|
					}
 | 
						|
				} catch {
 | 
						|
				}
 | 
						|
			}
 | 
						|
			if (!isApp) {
 | 
						|
				document.getElementById("editPane").style.display = 'flex';
 | 
						|
				if (!text) {
 | 
						|
					text = '// New script.\n';
 | 
						|
				}
 | 
						|
				gCurrentFile = 'app.js';
 | 
						|
				gFiles[gCurrentFile] = {
 | 
						|
					doc: new CodeMirror.Doc(text, guessMode(gCurrentFile)),
 | 
						|
				};
 | 
						|
				openFile(gCurrentFile);
 | 
						|
			}
 | 
						|
		}
 | 
						|
	});
 | 
						|
	request.addEventListener("error", function() {
 | 
						|
		alert("Error loading source.");
 | 
						|
		closeEditor();
 | 
						|
	});
 | 
						|
	request.addEventListener("timeout", function() {
 | 
						|
		alert("Timed out loading source.");
 | 
						|
		closeEditor();
 | 
						|
	});
 | 
						|
	request.addEventListener("abort", function() {
 | 
						|
		alert("Loading source aborted.");
 | 
						|
		closeEditor();
 | 
						|
	});
 | 
						|
	request.open("GET", url() + "view");
 | 
						|
	request.send();
 | 
						|
}
 | 
						|
 | 
						|
function closeEditor() {
 | 
						|
	document.getElementById("editPane").style.display = 'none';
 | 
						|
}
 | 
						|
 | 
						|
function explodePath() {
 | 
						|
	return /^\/~([^\/]+)\/([^\/]+)(.*)/.exec(window.location.pathname);
 | 
						|
}
 | 
						|
 | 
						|
function save() {
 | 
						|
	document.getElementById("save").disabled = true;
 | 
						|
	if (gCurrentFile) {
 | 
						|
		gFiles[gCurrentFile].doc = gEditor.getDoc();
 | 
						|
	}
 | 
						|
 | 
						|
	var run = document.getElementById("run").checked;
 | 
						|
 | 
						|
	var appFinished = function(success) {
 | 
						|
		document.getElementById("save").disabled = false;
 | 
						|
		Object.values(gFiles).forEach(function(file) {
 | 
						|
			file.generation = file.doc.changeGeneration();
 | 
						|
		});
 | 
						|
		updateFiles();
 | 
						|
	}
 | 
						|
 | 
						|
	var always = function() {
 | 
						|
		var anyUnfinished = Object.values(gFiles).some(x => x.request);
 | 
						|
		var anyUnsaved = Object.values(gFiles).some(x => !x.doc.isClean(x.generation) && !x.id);
 | 
						|
 | 
						|
		if (!anyUnfinished && !anyUnsaved) {
 | 
						|
			var app = {
 | 
						|
				type: "tildefriends-app",
 | 
						|
				files: Object.fromEntries(Object.keys(gFiles).map(x => [x, gFiles[x].id || gApp.files[x]])),
 | 
						|
			};
 | 
						|
			Object.values(gFiles).forEach(function(file) { delete file.id; });
 | 
						|
			gApp = JSON.parse(JSON.stringify(app));
 | 
						|
 | 
						|
			var request = new XMLHttpRequest();
 | 
						|
			request.addEventListener("error", function() {
 | 
						|
				alert("Error saving: " + request.responseText);
 | 
						|
				appFinished(false);
 | 
						|
			});
 | 
						|
			request.addEventListener("loadend", function() {
 | 
						|
				if (request.status == 200) {
 | 
						|
					var latest = document.getElementById('latest');
 | 
						|
					latest.href = request.responseText;
 | 
						|
					latest.style.visibility = request.responseText != window.location.path ? 'visible' : 'hidden';
 | 
						|
					if (run) {
 | 
						|
						if (request.responseText) {
 | 
						|
							reconnect(request.responseText);
 | 
						|
						} else {
 | 
						|
							reconnect(window.location.path);
 | 
						|
						}
 | 
						|
					}
 | 
						|
					appFinished(true);
 | 
						|
				} else {
 | 
						|
					alert("Unable to save: " + request.responseText);
 | 
						|
					appFinished(false);
 | 
						|
				}
 | 
						|
			});
 | 
						|
			request.addEventListener("timeout", function() {
 | 
						|
				alert("Timed out saving: " + request.responseText);
 | 
						|
				appFinished(false);
 | 
						|
			});
 | 
						|
			request.addEventListener("abort", function() {
 | 
						|
				alert("Save aborted: " + request.responseText);
 | 
						|
				appFinished(false);
 | 
						|
			});
 | 
						|
 | 
						|
			var saveTo = null;
 | 
						|
			var name = document.getElementById("name");
 | 
						|
			if (name && name.value) {
 | 
						|
				saveTo = name.value + "save";
 | 
						|
			} else {
 | 
						|
				saveTo = url() + "save";
 | 
						|
			}
 | 
						|
			request.open("POST", saveTo, true);
 | 
						|
			request.setRequestHeader("Content-Type", "text/json");
 | 
						|
			request.send(JSON.stringify(app));
 | 
						|
		} else if (!anyUnfinished) {
 | 
						|
			appFinished(false);
 | 
						|
		}
 | 
						|
	};
 | 
						|
 | 
						|
	var anySkipped = false;
 | 
						|
	Object.values(gFiles).forEach(function(file) {
 | 
						|
		if (file.doc.isClean(file.generation)) {
 | 
						|
			anySkipped = true;
 | 
						|
			return;
 | 
						|
		}
 | 
						|
 | 
						|
		delete file.id;
 | 
						|
		file.request = new XMLHttpRequest();
 | 
						|
		file.request.addEventListener("error", function() {
 | 
						|
			alert("Error saving: " + file.request.responseText);
 | 
						|
			file.request = null;
 | 
						|
			always();
 | 
						|
		});
 | 
						|
		file.request.addEventListener("loadend", function() {
 | 
						|
			if (file.request.status == 200) {
 | 
						|
				file.id = file.request.responseText;
 | 
						|
				if (file.id.charAt(0) == '/') {
 | 
						|
					file.id = file.id.substr(1);
 | 
						|
				}
 | 
						|
			} else {
 | 
						|
				alert("Unable to save: " + file.request.responseText);
 | 
						|
			}
 | 
						|
			file.request = null;
 | 
						|
			always();
 | 
						|
		});
 | 
						|
		file.request.addEventListener("timeout", function() {
 | 
						|
			alert("Timed out saving: " + file.request.responseText);
 | 
						|
			file.request = null;
 | 
						|
			always();
 | 
						|
		});
 | 
						|
		file.request.addEventListener("abort", function() {
 | 
						|
			alert("Save aborted: " + file.request.responseText);
 | 
						|
			file.request = null;
 | 
						|
			always();
 | 
						|
		});
 | 
						|
 | 
						|
		file.request.open("POST", "/save", true);
 | 
						|
		file.request.setRequestHeader("Content-Type", "text/plain");
 | 
						|
		file.request.send(file.doc.getValue());
 | 
						|
	});
 | 
						|
 | 
						|
	if (anySkipped) {
 | 
						|
		always();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
function url() {
 | 
						|
	var hash = window.location.href.indexOf('#');
 | 
						|
	var question = window.location.href.indexOf('?');
 | 
						|
	var end = -1;
 | 
						|
	if (hash != -1 && (hash < end || end == -1))
 | 
						|
	{
 | 
						|
		end = hash;
 | 
						|
	}
 | 
						|
	if (question != -1 && (question < end || end == -1))
 | 
						|
	{
 | 
						|
		end = question;
 | 
						|
	}
 | 
						|
	return end != -1 ? window.location.href.substring(0, end) : window.location.href;
 | 
						|
}
 | 
						|
 | 
						|
function hash() {
 | 
						|
	return window.location.hash != "#" ? window.location.hash : "";
 | 
						|
}
 | 
						|
 | 
						|
function receive(message) {
 | 
						|
	if (message && message.action == "session") {
 | 
						|
		setStatusMessage("...Executing...", kStatusColor, true);
 | 
						|
		gCredentials = message.credentials;
 | 
						|
		updateLogin();
 | 
						|
	} else if (message && message.action == "ready") {
 | 
						|
		setStatusMessage(null);
 | 
						|
		if (window.location.hash) {
 | 
						|
			send({event: "hashChange", hash: window.location.hash});
 | 
						|
		}
 | 
						|
	} else if (message && message.action == "setDocument") {
 | 
						|
		var iframe = document.getElementById("document");
 | 
						|
		iframe.srcdoc = message.content;
 | 
						|
	} else if (message && message.action == "postMessage") {
 | 
						|
		var iframe = document.getElementById("document");
 | 
						|
		iframe.contentWindow.postMessage(message.message, "*");
 | 
						|
	} else if (message && message.action == "ping") {
 | 
						|
		gSocket.send(JSON.stringify({action: "pong"}));
 | 
						|
	} else if (message && message.action == "error") {
 | 
						|
		setStatusMessage(message.error.message + '\n' + message.error.stack, '#f00', false);
 | 
						|
		console.log(message.error);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
function keyEvent(event) {
 | 
						|
	send({
 | 
						|
		event: "key",
 | 
						|
		type: event.type,
 | 
						|
		which: event.which,
 | 
						|
		keyCode: event.keyCode,
 | 
						|
		charCode: event.charCode,
 | 
						|
		character: String.fromCharCode(event.keyCode || event.which),
 | 
						|
		altKey: event.altKey,
 | 
						|
 | 
						|
	});
 | 
						|
}
 | 
						|
 | 
						|
function setStatusMessage(message, color, keep) {
 | 
						|
	var node = document.getElementById("status");
 | 
						|
	if (!keep) {
 | 
						|
		while (node.firstChild) {
 | 
						|
			node.removeChild(node.firstChild);
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if (message) {
 | 
						|
		node.appendChild(document.createTextNode(message));
 | 
						|
		node.setAttribute("style", "display: inline-block; vertical-align: top; white-space: pre; color: " + (color || kErrorColor));
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
function send(value) {
 | 
						|
	try {
 | 
						|
		gSocket.send(JSON.stringify(value));
 | 
						|
	} catch (error) {
 | 
						|
		setStatusMessage("Send failed: " + error.toString(), kErrorColor);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
function updateLogin() {
 | 
						|
	var login = document.getElementById("login");
 | 
						|
	while (login.firstChild) {
 | 
						|
		login.removeChild(login.firstChild);
 | 
						|
	}
 | 
						|
 | 
						|
	var a = document.createElement("a");
 | 
						|
	if (gCredentials && gCredentials.session) {
 | 
						|
		a.appendChild(document.createTextNode("logout " + gCredentials.session.name));
 | 
						|
		a.setAttribute("href", "/login/logout?return=" + encodeURIComponent(url() + hash()));
 | 
						|
	} else {
 | 
						|
		a.appendChild(document.createTextNode("login"));
 | 
						|
		a.setAttribute("href", "/login?return=" + encodeURIComponent(url() + hash()));
 | 
						|
	}
 | 
						|
	login.appendChild(a);
 | 
						|
}
 | 
						|
 | 
						|
var gOriginalInput;
 | 
						|
function dragHover(event) {
 | 
						|
	event.stopPropagation();
 | 
						|
	event.preventDefault();
 | 
						|
	var input = document.getElementById("input");
 | 
						|
	if (event.type == "dragover") {
 | 
						|
		if (!input.classList.contains("drop")) {
 | 
						|
			input.classList.add("drop");
 | 
						|
			gOriginalInput = input.value;
 | 
						|
			input.value = "drop file to upload";
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		input.classList.remove("drop");
 | 
						|
		input.value = gOriginalInput;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
function fixImage(sourceData, maxWidth, maxHeight, callback) {
 | 
						|
	var result = sourceData;
 | 
						|
	var image = new Image();
 | 
						|
	image.crossOrigin = "anonymous";
 | 
						|
	image.referrerPolicy = "no-referrer";
 | 
						|
	image.onload = function() {
 | 
						|
		if (image.width > maxWidth || image.height > maxHeight) {
 | 
						|
			var downScale = Math.min(maxWidth / image.width, maxHeight / image.height);
 | 
						|
			var canvas = document.createElement("canvas");
 | 
						|
			canvas.width = image.width * downScale;
 | 
						|
			canvas.height = image.height * downScale;
 | 
						|
			var context = canvas.getContext("2d");
 | 
						|
			context.clearRect(0, 0, canvas.width, canvas.height);
 | 
						|
			image.width = canvas.width;
 | 
						|
			image.height = canvas.height;
 | 
						|
			context.drawImage(image, 0, 0, image.width, image.height);
 | 
						|
			result = canvas.toDataURL();
 | 
						|
		}
 | 
						|
		callback(result);
 | 
						|
	};
 | 
						|
	image.src = sourceData;
 | 
						|
}
 | 
						|
 | 
						|
function sendImage(image) {
 | 
						|
	fixImage(image, 320, 240, function(result) {
 | 
						|
		send({image: result});
 | 
						|
	});
 | 
						|
}
 | 
						|
 | 
						|
function fileDropRead(event) {
 | 
						|
	sendImage(event.target.result);
 | 
						|
}
 | 
						|
 | 
						|
function fileDrop(event) {
 | 
						|
	dragHover(event);
 | 
						|
 | 
						|
	var done = false;
 | 
						|
	if (!done) {
 | 
						|
		var files = event.target.files || event.dataTransfer.files;
 | 
						|
		for (var i = 0; i < files.length; i++) {
 | 
						|
			var file = files[i];
 | 
						|
			if (file.type.substring(0, "image/".length) == "image/") {
 | 
						|
				var reader = new FileReader();
 | 
						|
				reader.onloadend = fileDropRead;
 | 
						|
				reader.readAsDataURL(file);
 | 
						|
				done = true;
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if (!done) {
 | 
						|
		var html = event.dataTransfer.getData("text/html");
 | 
						|
		var match = /<img.*src="([^"]+)"/.exec(html);
 | 
						|
		if (match) {
 | 
						|
			sendImage(match[1]);
 | 
						|
			done = true;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if (!done) {
 | 
						|
		var text = event.dataTransfer.getData("text/plain");
 | 
						|
		if (text) {
 | 
						|
			send(text);
 | 
						|
			done = true;
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
function enableDragDrop() {
 | 
						|
	var body = document.body;
 | 
						|
	body.addEventListener("dragover", dragHover);
 | 
						|
	body.addEventListener("dragleave", dragHover);
 | 
						|
	body.addEventListener("drop", fileDrop);
 | 
						|
}
 | 
						|
 | 
						|
function hashChange() {
 | 
						|
	send({event: 'hashChange', hash: window.location.hash});
 | 
						|
}
 | 
						|
 | 
						|
function focus() {
 | 
						|
	if (gSocket && gSocket.readyState == gSocket.CLOSED) {
 | 
						|
		connectSocket();
 | 
						|
	} else {
 | 
						|
		send({event: "focus"});
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
function blur() {
 | 
						|
	if (gSocket && gSocket.readyState == gSocket.OPEN) {
 | 
						|
		send({event: "blur"});
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
function message(event) {
 | 
						|
	if (event.data && event.data.event == "resizeMe" && event.data.width && event.data.height) {
 | 
						|
		var iframe = document.getElementById("iframe_" + event.data.name);
 | 
						|
		iframe.setAttribute("width", event.data.width);
 | 
						|
		iframe.setAttribute("height", event.data.height);
 | 
						|
	} else {
 | 
						|
		send({event: "message", message: event.data});
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
function reconnect(path) {
 | 
						|
	let oldSocket = gSocket;
 | 
						|
	gSocket = null
 | 
						|
	oldSocket.onopen = null;
 | 
						|
	oldSocket.onclose = null;
 | 
						|
	oldSocket.onmessage = null;
 | 
						|
	oldSocket.close();
 | 
						|
	connectSocket(path);
 | 
						|
}
 | 
						|
 | 
						|
function connectSocket(path) {
 | 
						|
	if (!gSocket || gSocket.readyState != gSocket.OPEN) {
 | 
						|
		if (gSocket) {
 | 
						|
			gSocket.onopen = null;
 | 
						|
			gSocket.onclose = null;
 | 
						|
			gSocket.onmessage = null;
 | 
						|
			gSocket.close();
 | 
						|
		}
 | 
						|
		setStatusMessage("Connecting...", kStatusColor, false);
 | 
						|
		gSocket = new WebSocket(
 | 
						|
			(window.location.protocol == "https:" ? "wss://" : "ws://")
 | 
						|
			+ window.location.hostname
 | 
						|
			+ (window.location.port.length ? ":" + window.location.port : "")
 | 
						|
			+ "/app/socket");
 | 
						|
		gSocket.onopen = function() {
 | 
						|
			setStatusMessage("...Authenticating...", kStatusColor, true);
 | 
						|
			gSocket.send(JSON.stringify({
 | 
						|
				action: "hello",
 | 
						|
				path: path,
 | 
						|
				api: [
 | 
						|
					['setDocument', 'content'],
 | 
						|
					['postMessage', 'message'],
 | 
						|
					['error', 'error'],
 | 
						|
				],
 | 
						|
			}));
 | 
						|
		}
 | 
						|
		gSocket.onmessage = function(event) {
 | 
						|
			receive(JSON.parse(event.data));
 | 
						|
		}
 | 
						|
		gSocket.onclose = function(event) {
 | 
						|
			setStatusMessage("Connection closed with code " + event.code, kErrorColor);
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
function openFile(name) {
 | 
						|
	var newDoc = (name && gFiles[name]) ? gFiles[name].doc : new CodeMirror.Doc("", guessMode(name));
 | 
						|
	var oldDoc = gEditor.swapDoc(newDoc);
 | 
						|
	if (gFiles[gCurrentFile]) {
 | 
						|
		gFiles[gCurrentFile].doc = oldDoc;
 | 
						|
	}
 | 
						|
	gCurrentFile = name;
 | 
						|
	updateFiles();
 | 
						|
	gEditor.focus();
 | 
						|
}
 | 
						|
 | 
						|
function onFileClicked(event) {
 | 
						|
	openFile(event.target.textContent);
 | 
						|
}
 | 
						|
 | 
						|
function updateFiles() {
 | 
						|
	var node = document.getElementById("files");
 | 
						|
	while (node.firstChild) {
 | 
						|
		node.removeChild(node.firstChild);
 | 
						|
	}
 | 
						|
 | 
						|
	for (var file of Object.keys(gFiles).sort()) {
 | 
						|
		var li = document.createElement("li");
 | 
						|
		li.onclick = onFileClicked;
 | 
						|
		li.appendChild(document.createTextNode(file));
 | 
						|
		if (file == gCurrentFile) {
 | 
						|
			li.classList.add("current");
 | 
						|
		}
 | 
						|
		if (!gFiles[file].doc.isClean(gFiles[file].generation)) {
 | 
						|
			li.classList.add("dirty");
 | 
						|
		}
 | 
						|
		node.appendChild(li);
 | 
						|
	}
 | 
						|
 | 
						|
	gEditor.focus();
 | 
						|
}
 | 
						|
 | 
						|
function makeNewFile(name) {
 | 
						|
	gFiles[name] = {
 | 
						|
		doc: new CodeMirror.Doc("", guessMode(name)),
 | 
						|
		generation: -1,
 | 
						|
	};
 | 
						|
	openFile(name);
 | 
						|
}
 | 
						|
 | 
						|
function newFile() {
 | 
						|
	var name = prompt("Name of new file:", "file.js");
 | 
						|
	if (name && !gFiles[name]) {
 | 
						|
		makeNewFile(name);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
function removeFile() {
 | 
						|
	if (confirm("Remove " + gCurrentFile + "?")) {
 | 
						|
		delete gFiles[gCurrentFile];
 | 
						|
		openFile(Object.keys(gFiles)[0]);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
window.addEventListener("load", function() {
 | 
						|
	if (window.Notification) {
 | 
						|
		Notification.requestPermission();
 | 
						|
	}
 | 
						|
	window.addEventListener("hashchange", hashChange);
 | 
						|
	window.addEventListener("focus", focus);
 | 
						|
	window.addEventListener("blur", blur);
 | 
						|
	window.addEventListener("message", message, false);
 | 
						|
	window.addEventListener("online", connectSocket);
 | 
						|
	document.getElementById("name").value = window.location.pathname;
 | 
						|
	enableDragDrop();
 | 
						|
	connectSocket(window.location.pathname);
 | 
						|
});
 |