Kaynağa Gözat

add option "auto-save on tab removal" (fix #703)

Gildas 4 yıl önce
ebeveyn
işleme
734c48a404

+ 4 - 0
_locales/de/messages.json

@@ -379,6 +379,10 @@
 		"message": "auto-save on tab discard",
 		"description": "Options page label: 'auto-save on tab discard'"
 	},
+	"optionAutoSaveRemove": {
+		"message": "auto-save on tab removal",
+		"description": "Options page label: 'auto-save on tab removal'"
+	},
 	"optionAutoSaveDelay": {
 		"message": "Automatische Speicherung nach Seitenaufbau verzögern (s)",
 		"description": "Options page label: 'auto-save waiting delay after page load (s)'"

+ 4 - 0
_locales/en/messages.json

@@ -379,6 +379,10 @@
 		"message": "auto-save on tab discard",
 		"description": "Options page label: 'auto-save on tab discard'"
 	},
+	"optionAutoSaveRemove": {
+		"message": "auto-save on tab removal",
+		"description": "Options page label: 'auto-save on tab removal'"
+	},
 	"optionAutoSaveDelay": {
 		"message": "auto-save waiting delay after page load (s)",
 		"description": "Options page label: 'auto-save waiting delay after page load (s)'"

+ 4 - 0
_locales/es/messages.json

@@ -379,6 +379,10 @@
 		"message": "auto-save on tab discard",
 		"description": "Options page label: 'auto-save on tab discard'"
 	},
+	"optionAutoSaveRemove": {
+		"message": "auto-save on tab removal",
+		"description": "Options page label: 'auto-save on tab removal'"
+	},
 	"optionAutoSaveDelay": {
 		"message": "retardo de auto-guardado tras cargar la página (s)",
 		"description": "Options page label: 'auto-save waiting delay after page load (s)'"

+ 5 - 1
_locales/fr/messages.json

@@ -376,9 +376,13 @@
 		"description": "Options page label: 'auto-save on page unload'"
 	},
 	"optionAutoSaveDiscard": {
-		"message": "auto-sauvegarder à la fermeture de l'onglet",
+		"message": "auto-sauvegarder à la mise à l'écart de l'onglet",
 		"description": "Options page label: 'auto-save on tab discard'"
 	},
+	"optionAutoSaveRemove": {
+		"message": "auto-save à la supression de l'onglet",
+		"description": "Options page label: 'auto-save on tab removal'"
+	},
 	"optionAutoSaveDelay": {
 		"message": "délai d'attente après le chargement de la page (s)",
 		"description": "Options page label: 'auto-save waiting delay after page load (s)'"

+ 4 - 0
_locales/ja/messages.json

@@ -379,6 +379,10 @@
 		"message": "auto-save on tab discard",
 		"description": "Options page label: 'auto-save on tab discard'"
 	},
+	"optionAutoSaveRemove": {
+		"message": "auto-save on tab removal",
+		"description": "Options page label: 'auto-save on tab removal'"
+	},
 	"optionAutoSaveDelay": {
 		"message": "ページ読み込み後の自動保存待ち時間(秒)",
 		"description": "Options page label: 'auto-save waiting delay after page load (s)'"

+ 4 - 0
_locales/pl/messages.json

@@ -379,6 +379,10 @@
 		"message": "automatycznie zapisuj przy odrzucaniu karty",
 		"description": "Options page label: 'auto-save on tab discard'"
 	},
+	"optionAutoSaveRemove": {
+		"message": "auto-save on tab removal",
+		"description": "Options page label: 'auto-save on tab removal'"
+	},
 	"optionAutoSaveDelay": {
 		"message": "opóźnienie oczekiwania automatycznego zapisywania po załadowaniu strony (s.)",
 		"description": "Options page label: 'auto-save waiting delay after page load (s)'"

+ 4 - 0
_locales/ru/messages.json

@@ -379,6 +379,10 @@
 		"message": "автосохранение при сбросе вкладки",
 		"description": "Options page label: 'auto-save on tab discard'"
 	},
+	"optionAutoSaveRemove": {
+		"message": "auto-save on tab removal",
+		"description": "Options page label: 'auto-save on tab removal'"
+	},
 	"optionAutoSaveDelay": {
 		"message": "задержка автосохранения после загрузки страницы (сек.)",
 		"description": "Options page label: 'auto-save waiting delay after page load (s)'"

+ 4 - 0
_locales/uk/messages.json

@@ -379,6 +379,10 @@
 		"message": "auto-save on tab discard",
 		"description": "Options page label: 'auto-save on tab discard'"
 	},
+	"optionAutoSaveRemove": {
+		"message": "auto-save on tab removal",
+		"description": "Options page label: 'auto-save on tab removal'"
+	},
 	"optionAutoSaveDelay": {
 		"message": "затримка автозбереження після завантаження сторінки (с)",
 		"description": "Options page label: 'auto-save waiting delay after page load (s)'"

+ 4 - 0
_locales/zh_CN/messages.json

@@ -379,6 +379,10 @@
 		"message": "在页面丢弃时进行自动保存",
 		"description": "Options page label: 'auto-save on tab discard'"
 	},
+	"optionAutoSaveRemove": {
+		"message": "auto-save on tab removal",
+		"description": "Options page label: 'auto-save on tab removal'"
+	},
 	"optionAutoSaveDelay": {
 		"message": "页面加载完成后延迟该时间再进行自动保存(秒)",
 		"description": "Options page label: 'auto-save waiting delay after page load (s)'"

+ 4 - 0
_locales/zh_TW/messages.json

@@ -379,6 +379,10 @@
 		"message": "在頁面丟棄時進行自動保存",
 		"description": "Options page label: 'auto-save on tab discard'"
 	},
+	"optionAutoSaveRemove": {
+		"message": "auto-save on tab removal",
+		"description": "Options page label: 'auto-save on tab removal'"
+	},
 	"optionAutoSaveDelay": {
 		"message": "頁面加載完成後延遲該時間再進行自動保存(秒)",
 		"description": "Options page label: 'auto-save waiting delay after page load (s)'"

+ 51 - 14
extension/core/bg/autosave.js

@@ -33,7 +33,8 @@ import * as ui from "./../../ui/bg/index.js";
 import { getPageData } from "./../../index.js";
 import * as woleet from "./../../lib/woleet/woleet.js";
 
-const pendingDiscardedTabs = {};
+const pendingMessages = {};
+const replacedTabIds = {};
 
 export {
 	onMessage,
@@ -41,35 +42,67 @@ export {
 	onInit,
 	isEnabled,
 	refreshTabs,
-	onTabRemoved
+	onTabUpdated,
+	onTabRemoved,
+	onTabDiscarded,
+	onTabReplaced
 };
 
 async function onMessage(message, sender) {
 	if (message.method.endsWith(".init")) {
 		const [options, autoSaveEnabled] = await Promise.all([config.getOptions(sender.tab.url, true), isEnabled(sender.tab)]);
-		return { options, autoSaveEnabled };
+		return { options, autoSaveEnabled, tabId: sender.tab.id, tabIndex: sender.tab.index };
 	}
 	if (message.method.endsWith(".save")) {
-		const tabId = sender.tab.id;
-		let resolvePendingDiscardedTab;
-		pendingDiscardedTabs[tabId] = new Promise(resolve => resolvePendingDiscardedTab = resolve);
-		if (message.autoSaveDiscard) {
-			message.tab = sender.tab;
-			resolvePendingDiscardedTab(message);
+		if (message.autoSaveDiscard || message.autoSaveRemove) {
+			if (sender.tab) {
+				message.tab = sender.tab;
+				pendingMessages[sender.tab.id] = message;
+			} else if (pendingMessages[message.tabId] && pendingMessages[message.tabId].removed && message.autoSaveRemove) {
+				delete pendingMessages[message.tabId];
+				await saveContent(message, { id: message.tabId, index: message.tabIndex, url: sender.url });
+			}
 			if (message.autoSaveUnload) {
+				delete pendingMessages[message.tabId];
 				await saveContent(message, sender.tab);
 			}
 		} else {
+			delete pendingMessages[message.tabId];
 			await saveContent(message, sender.tab);
 		}
 		return {};
 	}
 }
 
+function onTabUpdated(tabId) {
+	delete pendingMessages[tabId];
+}
+
 async function onTabRemoved(tabId) {
-	const pendingDiscardedTab = await pendingDiscardedTabs[tabId];
-	if (pendingDiscardedTab) {
-		await saveContent(pendingDiscardedTab, pendingDiscardedTab.tab);
+	const message = pendingMessages[tabId];
+	if (message) {
+		if (message.autoSaveRemove) {
+			delete pendingMessages[tabId];
+			await saveContent(message, message.tab);
+		}
+	} else {
+		pendingMessages[tabId] = { removed: true };
+	}
+}
+
+async function onTabDiscarded(tabId) {
+	const message = pendingMessages[tabId];
+	if (message) {
+		delete pendingMessages[tabId];
+		await saveContent(message, message.tab);
+	}
+}
+
+function onTabReplaced(addedTabId, removedTabId) {
+	if (pendingMessages[removedTabId] && !pendingMessages[addedTabId]) {
+		pendingMessages[addedTabId] = pendingMessages[removedTabId];
+		delete pendingMessages[removedTabId];
+		replacedTabIds[removedTabId] = addedTabId;
 	}
 }
 
@@ -116,7 +149,6 @@ async function refreshTabs() {
 
 async function saveContent(message, tab) {
 	const tabId = tab.id;
-	delete pendingDiscardedTabs[tabId];
 	const options = await config.getOptions(tab.url, true);
 	if (options) {
 		ui.onStart(tabId, 1, true);
@@ -171,7 +203,12 @@ async function saveContent(message, tab) {
 				}
 			}
 		} finally {
-			business.onSaveEnd(message.taskId);
+			if (message.taskId) {
+				business.onSaveEnd(message.taskId);
+			} else if (options.autoSaveDiscard && options.autoClose) {
+				tabs.remove(replacedTabIds[tabId] || tabId);
+				delete replacedTabIds[tabId];
+			}
 			if (pageData && pageData.url) {
 				URL.revokeObjectURL(pageData.url);
 			}

+ 1 - 0
extension/core/bg/config.js

@@ -76,6 +76,7 @@ const DEFAULT_CONFIG = {
 	autoSaveUnload: false,
 	autoSaveLoadOrUnload: true,
 	autoSaveDiscard: false,
+	autoSaveRemove: false,
 	autoSaveRepeat: false,
 	autoSaveRepeatDelay: 10,
 	removeAlternativeFonts: true,

+ 7 - 1
extension/core/bg/tabs.js

@@ -37,6 +37,7 @@ browser.tabs.onCreated.addListener(tab => onTabCreated(tab));
 browser.tabs.onActivated.addListener(activeInfo => onTabActivated(activeInfo));
 browser.tabs.onRemoved.addListener(tabId => onTabRemoved(tabId));
 browser.tabs.onUpdated.addListener((tabId, changeInfo) => onTabUpdated(tabId, changeInfo));
+browser.tabs.onReplaced.addListener((addedTabId, removedTabId) => onTabReplaced(addedTabId, removedTabId));
 export {
 	onMessage,
 	get,
@@ -193,6 +194,7 @@ async function onTabUpdated(tabId, changeInfo) {
 				// ignored
 			}
 		}, DELAY_MAYBE_INIT);
+		autosave.onTabUpdated(tabId);
 		const tab = await browser.tabs.get(tabId);
 		if (editor.isEditor(tab)) {
 			const allTabsData = await tabsData.get(tab.id);
@@ -202,10 +204,14 @@ async function onTabUpdated(tabId, changeInfo) {
 		}
 	}
 	if (changeInfo.discarded) {
-		autosave.onTabRemoved(tabId);
+		autosave.onTabDiscarded(tabId);
 	}
 }
 
+function onTabReplaced(addedTabId, removedTabId) {
+	autosave.onTabReplaced(addedTabId, removedTabId);
+}
+
 function onTabCreated(tab) {
 	ui.onTabCreated(tab);
 }

+ 32 - 15
extension/core/content/content-bootstrap.js

@@ -27,13 +27,15 @@ const singlefile = globalThis.singlefileBootstrap;
 
 const MAX_CONTENT_SIZE = 32 * (1024 * 1024);
 
-let unloadListenerAdded, options, autoSaveEnabled, autoSaveTimeout, autoSavingPage, pageAutoSaved, previousLocationHref;
+let unloadListenerAdded, options, tabId, tabIndex, autoSaveEnabled, autoSaveTimeout, autoSavingPage, pageAutoSaved, previousLocationHref;
 singlefile.pageInfo = {
 	updatedResources: {},
 	visitDate: new Date()
 };
 browser.runtime.sendMessage({ method: "autosave.init" }).then(message => {
 	options = message.options;
+	tabId = message.tabId;
+	tabIndex = message.tabIndex;
 	autoSaveEnabled = message.autoSaveEnabled;
 	if (options && options.autoOpenEditor && detectSavedPage(document)) {
 		if (document.readyState == "loading") {
@@ -151,40 +153,54 @@ async function autoSavePage() {
 }
 
 function refresh() {
-	if (autoSaveEnabled && options && (options.autoSaveUnload || options.autoSaveLoadOrUnload || options.autoSaveDiscard)) {
+	if (autoSaveEnabled && options && (options.autoSaveUnload || options.autoSaveLoadOrUnload || options.autoSaveDiscard || options.autoSaveRemove)) {
 		if (!unloadListenerAdded) {
 			globalThis.addEventListener("unload", onUnload);
+			document.addEventListener("visibilitychange", onVisibilityChange);
 			unloadListenerAdded = true;
 		}
 	} else {
 		globalThis.removeEventListener("unload", onUnload);
+		document.removeEventListener("visibilitychange", onVisibilityChange);
 		unloadListenerAdded = false;
 	}
 }
 
+function onVisibilityChange() {
+	if (document.visibilityState == "hidden" && options.autoSaveDiscard) {
+		autoSaveUnloadedPage({ autoSaveDiscard: options.autoSaveDiscard });
+	}
+}
+
 function onUnload() {
+	if (!pageAutoSaved && (options.autoSaveUnload || options.autoSaveLoadOrUnload || options.autoSaveRemove)) {
+		autoSaveUnloadedPage({ autoSaveUnload: options.autoSaveUnload, autoSaveRemove: options.autoSaveRemove });
+	}
+}
+
+function autoSaveUnloadedPage({ autoSaveUnload, autoSaveDiscard, autoSaveRemove }) {
 	const helper = singlefile.helper;
-	if (!pageAutoSaved || options.autoSaveUnload || options.autoSaveDiscard) {
-		const waitForUserScript = window._singleFile_waitForUserScript;
-		let frames = [];
-		if (!options.removeFrames && globalThis.frames && globalThis.frames.length) {
-			frames = singlefile.processors.frameTree.getSync(options);
-		}
-		if (options.userScriptEnabled && waitForUserScript) {
-			waitForUserScript(helper.ON_BEFORE_CAPTURE_EVENT_NAME);
-		}
-		const docData = helper.preProcessDoc(document, globalThis, options);
-		savePage(docData, frames, options.autoSaveUnload, options.autoSaveDiscard);
+	const waitForUserScript = window._singleFile_waitForUserScript;
+	let frames = [];
+	if (!options.removeFrames && globalThis.frames && globalThis.frames.length) {
+		frames = singlefile.processors.frameTree.getSync(options);
+	}
+	if (options.userScriptEnabled && waitForUserScript) {
+		waitForUserScript(helper.ON_BEFORE_CAPTURE_EVENT_NAME);
 	}
+	const docData = helper.preProcessDoc(document, globalThis, options);
+	savePage(docData, frames, { autoSaveUnload, autoSaveDiscard, autoSaveRemove });
 }
 
-function savePage(docData, frames, autoSaveUnload, autoSaveDiscard) {
+function savePage(docData, frames, { autoSaveUnload, autoSaveDiscard, autoSaveRemove } = {}) {
 	const helper = singlefile.helper;
 	const updatedResources = singlefile.pageInfo.updatedResources;
 	const visitDate = singlefile.pageInfo.visitDate.getTime();
 	Object.keys(updatedResources).forEach(url => updatedResources[url].retrieved = false);
 	browser.runtime.sendMessage({
 		method: "autosave.save",
+		tabId,
+		tabIndex,
 		taskId: options.taskId,
 		content: helper.serialize(document),
 		canvases: docData.canvases,
@@ -201,7 +217,8 @@ function savePage(docData, frames, autoSaveUnload, autoSaveDiscard) {
 		updatedResources,
 		visitDate,
 		autoSaveUnload,
-		autoSaveDiscard
+		autoSaveDiscard,
+		autoSaveRemove
 	});
 }
 

+ 4 - 0
extension/lib/single-file/browser-polyfill/chrome-browser-polyfill.js

@@ -421,6 +421,10 @@ if (typeof globalThis == "undefined") {
 					addListener: listener => nativeAPI.tabs.onRemoved.addListener(listener),
 					removeListener: listener => nativeAPI.tabs.onRemoved.removeListener(listener)
 				},
+				onReplaced: {
+					addListener: listener => nativeAPI.tabs.onReplaced.addListener(listener),
+					removeListener: listener => nativeAPI.tabs.onReplaced.removeListener(listener)
+				},
 				executeScript: (tabId, details) => new Promise((resolve, reject) => {
 					nativeAPI.tabs.executeScript(tabId, details, () => {
 						if (nativeAPI.runtime.lastError) {

+ 8 - 11
extension/ui/bg/ui-options.js

@@ -66,6 +66,7 @@ const autoSaveLoadLabel = document.getElementById("autoSaveLoadLabel");
 const autoSaveUnloadLabel = document.getElementById("autoSaveUnloadLabel");
 const autoSaveLoadOrUnloadLabel = document.getElementById("autoSaveLoadOrUnloadLabel");
 const autoSaveDiscardLabel = document.getElementById("autoSaveDiscardLabel");
+const autoSaveRemoveLabel = document.getElementById("autoSaveRemoveLabel");
 const autoSaveRepeatLabel = document.getElementById("autoSaveRepeatLabel");
 const autoSaveRepeatDelayLabel = document.getElementById("autoSaveRepeatDelayLabel");
 const autoSaveExternalSaveLabel = document.getElementById("autoSaveExternalSaveLabel");
@@ -151,6 +152,7 @@ const autoSaveDelayInput = document.getElementById("autoSaveDelayInput");
 const autoSaveLoadInput = document.getElementById("autoSaveLoadInput");
 const autoSaveUnloadInput = document.getElementById("autoSaveUnloadInput");
 const autoSaveDiscardInput = document.getElementById("autoSaveDiscardInput");
+const autoSaveRemoveInput = document.getElementById("autoSaveRemoveInput");
 const autoSaveLoadOrUnloadInput = document.getElementById("autoSaveLoadOrUnloadInput");
 const autoSaveRepeatInput = document.getElementById("autoSaveRepeatInput");
 const autoSaveRepeatDelayInput = document.getElementById("autoSaveRepeatDelayInput");
@@ -361,26 +363,20 @@ importButton.addEventListener("click", () => {
 	fileInput.click();
 }, false);
 autoSaveUnloadInput.addEventListener("click", async () => {
-	if (!autoSaveLoadInput.checked && !autoSaveUnloadInput.checked && !autoSaveDiscardInput.checked) {
+	if (!autoSaveLoadInput.checked && !autoSaveUnloadInput.checked) {
 		autoSaveLoadOrUnloadInput.checked = true;
 	}
 }, false);
 autoSaveLoadInput.addEventListener("click", async () => {
-	if (!autoSaveLoadInput.checked && !autoSaveUnloadInput.checked && !autoSaveDiscardInput.checked) {
-		autoSaveLoadOrUnloadInput.checked = true;
-	}
-}, false);
-autoSaveDiscardInput.addEventListener("click", async () => {
-	if (!autoSaveLoadInput.checked && !autoSaveUnloadInput.checked && !autoSaveDiscardInput.checked) {
+	if (!autoSaveLoadInput.checked && !autoSaveUnloadInput.checked) {
 		autoSaveLoadOrUnloadInput.checked = true;
 	}
 }, false);
 autoSaveLoadOrUnloadInput.addEventListener("click", async () => {
 	if (autoSaveLoadOrUnloadInput.checked) {
 		autoSaveUnloadInput.checked = autoSaveLoadInput.checked = false;
-	} else if (!autoSaveDiscardInput.checked) {
+	} else {
 		autoSaveUnloadInput.checked = false;
-		autoSaveLoadInput.checked = true;
 	}
 }, false);
 expandAllButton.addEventListener("click", () => {
@@ -496,6 +492,7 @@ autoSaveLoadLabel.textContent = browser.i18n.getMessage("optionAutoSaveLoad");
 autoSaveUnloadLabel.textContent = browser.i18n.getMessage("optionAutoSaveUnload");
 autoSaveLoadOrUnloadLabel.textContent = browser.i18n.getMessage("optionAutoSaveLoadOrUnload");
 autoSaveDiscardLabel.textContent = browser.i18n.getMessage("optionAutoSaveDiscard");
+autoSaveRemoveLabel.textContent = browser.i18n.getMessage("optionAutoSaveRemove");
 autoSaveRepeatLabel.textContent = browser.i18n.getMessage("optionAutoSaveRepeat");
 autoSaveRepeatDelayLabel.textContent = browser.i18n.getMessage("optionAutoSaveRepeatDelay");
 autoSaveExternalSaveLabel.textContent = browser.i18n.getMessage("optionAutoSaveExternalSave");
@@ -694,15 +691,14 @@ async function refresh(profileName) {
 	displayStatsInput.checked = profileOptions.displayStats;
 	backgroundSaveInput.checked = profileOptions.backgroundSave;
 	autoSaveDelayInput.value = profileOptions.autoSaveDelay;
-	autoSaveDelayInput.disabled = !profileOptions.autoSaveLoadOrUnload && !profileOptions.autoSaveLoad;
 	autoSaveLoadInput.checked = !profileOptions.autoSaveLoadOrUnload && profileOptions.autoSaveLoad;
 	autoSaveLoadOrUnloadInput.checked = profileOptions.autoSaveLoadOrUnload;
 	autoSaveUnloadInput.checked = !profileOptions.autoSaveLoadOrUnload && profileOptions.autoSaveUnload;
 	autoSaveLoadInput.disabled = profileOptions.autoSaveLoadOrUnload;
 	autoSaveUnloadInput.disabled = profileOptions.autoSaveLoadOrUnload;
 	autoSaveDiscardInput.checked = profileOptions.autoSaveDiscard;
+	autoSaveRemoveInput.checked = profileOptions.autoSaveRemove;
 	autoSaveRepeatInput.checked = profileOptions.autoSaveRepeat;
-	autoSaveRepeatInput.disabled = !profileOptions.autoSaveLoadOrUnload && !profileOptions.autoSaveLoad;
 	autoSaveRepeatDelayInput.value = profileOptions.autoSaveRepeatDelay;
 	autoSaveRepeatDelayInput.disabled = !profileOptions.autoSaveRepeat;
 	autoSaveExternalSaveInput.checked = profileOptions.autoSaveExternalSave;
@@ -778,6 +774,7 @@ async function update() {
 			autoSaveLoad: autoSaveLoadInput.checked,
 			autoSaveUnload: autoSaveUnloadInput.checked,
 			autoSaveDiscard: autoSaveDiscardInput.checked,
+			autoSaveRemove: autoSaveRemoveInput.checked,
 			autoSaveLoadOrUnload: autoSaveLoadOrUnloadInput.checked,
 			autoSaveRepeat: autoSaveRepeatInput.checked,
 			autoSaveRepeatDelay: Math.max(autoSaveRepeatDelayInput.value, 1),

+ 7 - 2
extension/ui/pages/help.html

@@ -434,8 +434,13 @@
 					</li>
 					<li data-options-label="autoSaveDiscardLabel"> <span class="option">Option: auto-save on tab
 							discard</span>
-						<p>Check this option to auto-save pages when discarding or closing a tab. Some frame contents
-							may be missing (if you checked "remove frames"). </p>
+						<p>Check this option to auto-save pages when discarding a tab. Some frame contents may be
+							missing (if you checked "remove frames"). </p>
+					</li>
+					<li data-options-label="autoSaveRemoveLabel"> <span class="option">Option: auto-save on tab
+							removal</span>
+						<p>Check this option to auto-save pages when closing a tab. Some frame contents may be missing
+							(if you checked "remove frames"). </p>
 					</li>
 					<li data-options-label="autoSaveDelayLabel"> <span class="option">Option: auto-save waiting delay
 							after load (s)</span>

+ 6 - 2
extension/ui/pages/options.html

@@ -244,11 +244,11 @@
 				<label for="autoSaveLoadOrUnloadInput" id="autoSaveLoadOrUnloadLabel"></label>
 				<input type="checkbox" id="autoSaveLoadOrUnloadInput">
 			</div>
-			<div class="option">
+			<div class="option second-level">
 				<label for="autoSaveLoadInput" id="autoSaveLoadLabel"></label>
 				<input type="checkbox" id="autoSaveLoadInput">
 			</div>
-			<div class="option">
+			<div class="option second-level">
 				<label for="autoSaveUnloadInput" id="autoSaveUnloadLabel"></label>
 				<input type="checkbox" id="autoSaveUnloadInput">
 			</div>
@@ -256,6 +256,10 @@
 				<label for="autoSaveDiscardInput" id="autoSaveDiscardLabel"></label>
 				<input type="checkbox" id="autoSaveDiscardInput">
 			</div>
+			<div class="option">
+				<label for="autoSaveRemoveInput" id="autoSaveRemoveLabel"></label>
+				<input type="checkbox" id="autoSaveRemoveInput">
+			</div>
 			<div class="option">
 				<label for="autoSaveDelayInput" id="autoSaveDelayLabel"></label>
 				<input type="number" id="autoSaveDelayInput" min="0">