Merge pull request #70 from hideheader/save-me-too
Manage dirty flags on all inputs
This commit is contained in:
		
						commit
						0e391f928f
					
				|  | @ -199,7 +199,7 @@ | ||||||
| 			<button id="to-mozilla"></button><img id="to-mozilla-help" src="help.png"><br><br> | 			<button id="to-mozilla"></button><img id="to-mozilla-help" src="help.png"><br><br> | ||||||
| 			<button id="save-button" title="Ctrl-S"></button> | 			<button id="save-button" title="Ctrl-S"></button> | ||||||
| 			<a href="manage.html"><button id="cancel-button"></button></a> | 			<a href="manage.html"><button id="cancel-button"></button></a> | ||||||
| 			<div id="options"> | 			<form id="options"> | ||||||
| 				<h2 id="options-heading"></h2> | 				<h2 id="options-heading"></h2> | ||||||
| 				<table cols="2"> | 				<table cols="2"> | ||||||
| 				<tr> | 				<tr> | ||||||
|  | @ -229,7 +229,7 @@ | ||||||
| 					<td><select data-option="keyMap" id="editor.keyMap"></select></td> | 					<td><select data-option="keyMap" id="editor.keyMap"></select></td> | ||||||
| 				</tr> | 				</tr> | ||||||
| 			</table> | 			</table> | ||||||
| 			</div> | 			</form> | ||||||
| 		</div> | 		</div> | ||||||
| 		<section id="sections"> | 		<section id="sections"> | ||||||
| 			<h2><span id="sections-heading"></span> <img id="sections-help" src="help.png"></h2> | 			<h2><span id="sections-heading"></span> <img id="sections-help" src="help.png"></h2> | ||||||
|  |  | ||||||
							
								
								
									
										148
									
								
								edit.js
									
									
									
									
									
								
							
							
						
						
									
										148
									
								
								edit.js
									
									
									
									
									
								
							|  | @ -1,3 +1,5 @@ | ||||||
|  | "use strict"; | ||||||
|  | 
 | ||||||
| var styleId = null; | var styleId = null; | ||||||
| var dirty = false; | var dirty = false; | ||||||
| var lockScroll; // ensure the section doesn't jump when clicking selected text
 | var lockScroll; // ensure the section doesn't jump when clicking selected text
 | ||||||
|  | @ -12,10 +14,73 @@ appliesToEverythingTemplate.innerHTML = t("appliesToEverything") + ' <button cla | ||||||
| var sectionTemplate = document.createElement("div"); | var sectionTemplate = document.createElement("div"); | ||||||
| sectionTemplate.innerHTML = '<label>' + t('sectionCode') + '</label><textarea class="code"></textarea><br><div class="applies-to"><label>' + t("appliesLabel") + ' <img class="applies-to-help" src="help.png" alt="' + t('helpAlt') + '"></label><ul class="applies-to-list"></ul></div><button class="remove-section">' + t('sectionRemove') + '</button><button class="add-section">' + t('sectionAdd') + '</button>'; | sectionTemplate.innerHTML = '<label>' + t('sectionCode') + '</label><textarea class="code"></textarea><br><div class="applies-to"><label>' + t("appliesLabel") + ' <img class="applies-to-help" src="help.png" alt="' + t('helpAlt') + '"></label><ul class="applies-to-list"></ul></div><button class="remove-section">' + t('sectionRemove') + '</button><button class="add-section">' + t('sectionAdd') + '</button>'; | ||||||
| 
 | 
 | ||||||
|  | document.addEventListener("change", function(event) { | ||||||
|  | 	var node = event.target; | ||||||
|  | 	if (node.type && !node.form) { // INPUTs that aren't in a FORM are stylesheet
 | ||||||
|  | 		switch (node.type) { | ||||||
|  | 			case "checkbox": | ||||||
|  | 				setCleanItem(node, node.checked === node.defaultChecked); | ||||||
|  | 				break; | ||||||
|  | 			case "text": | ||||||
|  | 			case "select-one": | ||||||
|  | 			case "select-multiple": | ||||||
|  | 				setCleanItem(node, node.value === node.defaultValue); | ||||||
|  | 				break; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | // Set .dirty on stylesheet contributors that have changed
 | ||||||
|  | var items = {}; | ||||||
|  | function isCleanItem(node) { | ||||||
|  | 	return items[node.id]; | ||||||
|  | } | ||||||
|  | function setCleanItem(node, clean) { | ||||||
|  | 	var id = node.id; | ||||||
|  | 	if (!id) id = node.id = Date.now().toString(32).substr(-6); | ||||||
|  | 	items[id] = clean; | ||||||
|  | 
 | ||||||
|  | 	if (clean) node.classList.remove("dirty"); | ||||||
|  | 	else node.classList.add("dirty"); | ||||||
|  | 
 | ||||||
|  | 	initTitle(); | ||||||
|  | } | ||||||
|  | function isCleanGlobal() { | ||||||
|  | 	var clean = Object.keys(items) | ||||||
|  | 				      .every(function(item) { return items[item] }); | ||||||
|  | 
 | ||||||
|  | 	if (clean) document.body.classList.remove("dirty"); | ||||||
|  | 	else document.body.classList.add("dirty"); | ||||||
|  | 
 | ||||||
|  | 	return clean; | ||||||
|  | } | ||||||
|  | function setCleanGlobal(form) { | ||||||
|  | 	if (!form) form = null; | ||||||
|  | 	Array.prototype.forEach.call(document.querySelectorAll("input, select"), function(node) { | ||||||
|  | 		if (node.form === form) { | ||||||
|  | 			if ("checkbox" === node.type) { | ||||||
|  | 				node.defaultChecked = node.checked; | ||||||
|  | 			} else { | ||||||
|  | 				node.defaultValue = node.value; | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			node.classList.remove("dirty"); | ||||||
|  | 			delete items[node.id]; | ||||||
|  | 		} | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	editors.forEach(function(cm) { | ||||||
|  | 		cm.lastChange = cm.changeGeneration(); | ||||||
|  | 		cm.getTextArea().parentNode.defaultValue = cm.lastChange; | ||||||
|  | 		indicateCodeChange(cm); | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	initTitle(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| var editors = []; // array of all CodeMirror instances
 | var editors = []; // array of all CodeMirror instances
 | ||||||
| function initCodeMirror() { | function initCodeMirror() { | ||||||
| 	var CM = CodeMirror; | 	var CM = CodeMirror; | ||||||
| 
 |  | ||||||
| 	// default option values
 | 	// default option values
 | ||||||
| 	var userOptions = prefs.getPref("editor.options"); | 	var userOptions = prefs.getPref("editor.options"); | ||||||
| 	var stylishOptions = { | 	var stylishOptions = { | ||||||
|  | @ -153,10 +218,6 @@ document.addEventListener("keydown", function(e) { | ||||||
| 	} | 	} | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| function makeDirty() { |  | ||||||
| 	dirty = true; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| window.onbeforeunload = function() { | window.onbeforeunload = function() { | ||||||
| 	prefs.setPref('windowPosition', { | 	prefs.setPref('windowPosition', { | ||||||
| 		left: screenLeft, | 		left: screenLeft, | ||||||
|  | @ -164,25 +225,13 @@ window.onbeforeunload = function() { | ||||||
| 		width: outerWidth, | 		width: outerWidth, | ||||||
| 		height: outerHeight | 		height: outerHeight | ||||||
| 	}); | 	}); | ||||||
| 	return dirty || isCodeDirty() ? t('styleChangesNotSaved') : null; | 	document.activeElement.blur(); | ||||||
| } | 	return !isCleanGlobal() ? t('styleChangesNotSaved') : null; | ||||||
| 
 |  | ||||||
| function isCodeDirty() { |  | ||||||
| 	for(var i=0; i < editors.length; i++) { |  | ||||||
| 		if (!editors[i].isClean(editors[i].lastChange)) { |  | ||||||
| 			return true; |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return false; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function indicateCodeChange(cm) { | function indicateCodeChange(cm) { | ||||||
| 	var clean = cm.isClean(cm.lastChange); | 	setCleanItem(cm.getTextArea().parentNode, cm.isClean(cm.lastChange)); | ||||||
| 	if (clean != cm.lastClean) { | } | ||||||
| 		cm.lastClean = clean; |  | ||||||
| 		cm.getTextArea().parentNode.classList[clean ? "remove" : "add"]("dirty"); |  | ||||||
| 	} |  | ||||||
| }; |  | ||||||
| 
 | 
 | ||||||
| function addAppliesTo(list, name, value) { | function addAppliesTo(list, name, value) { | ||||||
| 	var showingEverything = list.querySelector(".applies-to-everything") != null; | 	var showingEverything = list.querySelector(".applies-to-everything") != null; | ||||||
|  | @ -196,16 +245,12 @@ function addAppliesTo(list, name, value) { | ||||||
| 		e.querySelector("[name=applies-type]").value = name; | 		e.querySelector("[name=applies-type]").value = name; | ||||||
| 		e.querySelector("[name=applies-value]").value = value; | 		e.querySelector("[name=applies-value]").value = value; | ||||||
| 		e.querySelector(".remove-applies-to").addEventListener("click", removeAppliesTo, false); | 		e.querySelector(".remove-applies-to").addEventListener("click", removeAppliesTo, false); | ||||||
| 		e.querySelector(".applies-value").addEventListener("input", makeDirty, false); |  | ||||||
| 		e.querySelector(".applies-type").addEventListener("change", makeDirty, false); |  | ||||||
| 	} else if (showingEverything || list.hasChildNodes()) { | 	} else if (showingEverything || list.hasChildNodes()) { | ||||||
| 		e = appliesToTemplate.cloneNode(true); | 		e = appliesToTemplate.cloneNode(true); | ||||||
| 		if (list.hasChildNodes()) { | 		if (list.hasChildNodes()) { | ||||||
| 			e.querySelector("[name=applies-type]").value = list.querySelector("li:last-child [name='applies-type']").value; | 			e.querySelector("[name=applies-type]").value = list.querySelector("li:last-child [name='applies-type']").value; | ||||||
| 		} | 		} | ||||||
| 		e.querySelector(".remove-applies-to").addEventListener("click", removeAppliesTo, false); | 		e.querySelector(".remove-applies-to").addEventListener("click", removeAppliesTo, false); | ||||||
| 		e.querySelector(".applies-value").addEventListener("input", makeDirty, false); |  | ||||||
| 		e.querySelector(".applies-type").addEventListener("change", makeDirty, false); |  | ||||||
| 	} else { | 	} else { | ||||||
| 		e = appliesToEverythingTemplate.cloneNode(true); | 		e = appliesToEverythingTemplate.cloneNode(true); | ||||||
| 	} | 	} | ||||||
|  | @ -224,7 +269,8 @@ function addSection(event, section) { | ||||||
| 	if (section) { | 	if (section) { | ||||||
| 		var codeElement = div.querySelector(".code"); | 		var codeElement = div.querySelector(".code"); | ||||||
| 		codeElement.value = section.code; | 		codeElement.value = section.code; | ||||||
| 		codeElement.addEventListener("change", makeDirty, false); | 		// codeElement.addEventListener("change", makeDirty, false);
 | ||||||
|  | 		// // Why is this here? Is it possible for CM to not load?
 | ||||||
| 		if (section.urls) { | 		if (section.urls) { | ||||||
| 			section.urls.forEach(function(url) { | 			section.urls.forEach(function(url) { | ||||||
| 				addAppliesTo(appliesTo, "url", url); | 				addAppliesTo(appliesTo, "url", url); | ||||||
|  | @ -269,24 +315,32 @@ function addSection(event, section) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function removeAppliesTo(event) { | function removeAppliesTo(event) { | ||||||
| 	var appliesToList = event.target.parentNode.parentNode; | 	var appliesTo = event.target.parentNode, | ||||||
| 	appliesToList.removeChild(event.target.parentNode); | 	    appliesToList = appliesTo.parentNode; | ||||||
|  | 	appliesToList.removeChild(appliesTo); | ||||||
| 	if (!appliesToList.hasChildNodes()) { | 	if (!appliesToList.hasChildNodes()) { | ||||||
| 		var e = appliesToEverythingTemplate.cloneNode(true); | 		var e = appliesToEverythingTemplate.cloneNode(true); | ||||||
| 		e.querySelector(".add-applies-to").addEventListener("click", function() {addAppliesTo(this.parentNode.parentNode)}, false); | 		e.querySelector(".add-applies-to").addEventListener("click", function() {addAppliesTo(this.parentNode.parentNode)}, false); | ||||||
| 		appliesToList.appendChild(e); | 		appliesToList.appendChild(e); | ||||||
| 	} | 	} | ||||||
| 	makeDirty(); | 	Array.prototype.forEach.call(appliesTo.querySelectorAll("input, select"), function(node) { | ||||||
|  | 		setCleanItem(node, !node.defaultValue); | ||||||
|  | 	}); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function removeSection(event) { | function removeSection(event) { | ||||||
|     var section = event.target.parentNode; |     var section = event.target.parentNode; | ||||||
|     var cm = section.querySelector(".CodeMirror-wrap"); |     var wrapper = section.querySelector(".CodeMirror-wrap"); | ||||||
|     if (cm && editors.indexOf(cm.CodeMirror) >= 0) { | 	var idx = editors.indexOf(wrapper && wrapper.CodeMirror); | ||||||
|         editors.splice(editors.indexOf(cm.CodeMirror), 1); |     if (idx >= 0) { | ||||||
|  |         editors.splice(idx, 1); | ||||||
|  | 		setCleanItem(wrapper.parentNode, true); | ||||||
|     } |     } | ||||||
| 	section.parentNode.removeChild(section); | 	section.parentNode.removeChild(section); | ||||||
| 	makeDirty(); | 	Array.prototype.forEach.call(section.querySelectorAll("input, select"), function(node) { | ||||||
|  | 		setCleanItem(node, !node.defaultValue); | ||||||
|  | 	}); | ||||||
|  | 	setCleanItem(section, !section.defaultValue); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function setupGlobalSearch() { | function setupGlobalSearch() { | ||||||
|  | @ -406,9 +460,10 @@ function init() { | ||||||
| 		addSection(null, section); | 		addSection(null, section); | ||||||
| 		// default to enabled
 | 		// default to enabled
 | ||||||
| 		document.getElementById("enabled").checked = true | 		document.getElementById("enabled").checked = true | ||||||
| 		document.title = t("addStyleTitle"); |  | ||||||
| 		tE("heading", "addStyleTitle"); | 		tE("heading", "addStyleTitle"); | ||||||
| 		setupGlobalSearch(); | 		setupGlobalSearch(); | ||||||
|  | 		setCleanGlobal(null); | ||||||
|  | 		initTitle(); | ||||||
| 		return; | 		return; | ||||||
| 	} | 	} | ||||||
| 	// This is an edit
 | 	// This is an edit
 | ||||||
|  | @ -423,17 +478,23 @@ function initWithStyle(style) { | ||||||
| 	document.getElementById("name").value = style.name; | 	document.getElementById("name").value = style.name; | ||||||
| 	document.getElementById("enabled").checked = style.enabled == "true"; | 	document.getElementById("enabled").checked = style.enabled == "true"; | ||||||
| 	document.getElementById("heading").innerHTML = t("editStyleHeading"); | 	document.getElementById("heading").innerHTML = t("editStyleHeading"); | ||||||
| 	initTitle(style.name); |  | ||||||
| 	// if this was done in response to an update, we need to clear existing sections
 | 	// if this was done in response to an update, we need to clear existing sections
 | ||||||
| 	Array.prototype.forEach.call(document.querySelectorAll("#sections > div"), function(div) { | 	Array.prototype.forEach.call(document.querySelectorAll("#sections > div"), function(div) { | ||||||
| 		div.parentNode.removeChild(div); | 		div.parentNode.removeChild(div); | ||||||
| 	}); | 	}); | ||||||
| 	style.sections.forEach(function(section) { addSection(null, section) }); | 	style.sections.forEach(function(section) { addSection(null, section) }); | ||||||
| 	setupGlobalSearch(); | 	setupGlobalSearch(); | ||||||
|  | 	setCleanGlobal(null); | ||||||
|  | 	initTitle(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function initTitle(name) { | function initTitle() { | ||||||
| 	document.title = t('editStyleTitle', [name]); | 	const DIRTY_TITLE = "* $"; | ||||||
|  | 
 | ||||||
|  | 	var name = document.getElementById("name").defaultValue; | ||||||
|  | 	var dirty = !isCleanGlobal(); | ||||||
|  | 	var title = styleId === null ? t("addStyleTitle") : t('editStyleTitle', [name]); | ||||||
|  | 	document.title = !dirty ? title : DIRTY_TITLE.replace("$", title); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function validate() { | function validate() { | ||||||
|  | @ -534,20 +595,15 @@ function getMeta(e) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function saveComplete(style) { | function saveComplete(style) { | ||||||
| 	dirty = false; | 	styleId = style.id; | ||||||
| 
 | 	setCleanGlobal(null); | ||||||
| 	for(var i=0; i < editors.length; i++) { |  | ||||||
| 		var cm = editors[i]; |  | ||||||
| 		cm.lastChange = cm.changeGeneration(true); |  | ||||||
| 		indicateCodeChange(cm); |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	// Go from new style URL to edit style URL
 | 	// Go from new style URL to edit style URL
 | ||||||
| 	if (location.href.indexOf("id=") == -1) { | 	if (location.href.indexOf("id=") == -1) { | ||||||
| 		// give the code above a moment before we kill the page
 | 		// give the code above a moment before we kill the page
 | ||||||
| 		setTimeout(function() {location.href = "edit.html?id=" + style.id;}, 200); | 		setTimeout(function() {location.href = "edit.html?id=" + style.id;}, 200); | ||||||
| 	} else { | 	} else { | ||||||
| 		initTitle(document.getElementById("name").value); | 		initTitle(); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -630,8 +686,6 @@ tE("cancel-button", "styleCancelEditLabel"); | ||||||
| tE("sections-heading", "styleSectionsTitle"); | tE("sections-heading", "styleSectionsTitle"); | ||||||
| tE("options-heading", "optionsHeading"); | tE("options-heading", "optionsHeading"); | ||||||
| 
 | 
 | ||||||
| document.getElementById("name").addEventListener("change", makeDirty, false); |  | ||||||
| document.getElementById("enabled").addEventListener("change", makeDirty, false); |  | ||||||
| document.getElementById("to-mozilla").addEventListener("click", showMozillaFormat, false); | document.getElementById("to-mozilla").addEventListener("click", showMozillaFormat, false); | ||||||
| document.getElementById("to-mozilla-help").addEventListener("click", showToMozillaHelp, false); | document.getElementById("to-mozilla-help").addEventListener("click", showToMozillaHelp, false); | ||||||
| document.getElementById("save-button").addEventListener("click", save, false); | document.getElementById("save-button").addEventListener("click", save, false); | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user