Procházet zdrojové kódy

add "Batch save URLs" feature (fix #904, see #905)

Gildas před 3 roky
rodič
revize
ce1ada8bb7

+ 32 - 0
_locales/de/messages.json

@@ -71,6 +71,10 @@
 		"message": "Speichern aller Tabs",
 		"description": "Menu entry: 'Save all tabs'"
 	},
+	"menuBatchSaveUrls": {
+		"message": "Stapelweise URLs speichern...",
+		"description": "Menu entry: 'Batch save URLs...'"
+	},
 	"menuAutoSave": {
 		"message": "Automatische Speicherung",
 		"description": "Menu entry: 'Auto-save'"
@@ -786,5 +790,33 @@
 	"pendingsAddUrlsCancelButton": {
 		"message": "Abbrechen",
 		"description": "Add URLs popup cancel button: 'Cancel'"
+	},
+	"batchSaveUrlsTitle": {
+		"message": "Stapelweise URLs speichern",
+		"description": "Title of the 'batch save URLs' page"
+	},
+	"batchSaveUrlsNoURLs": {
+		"message": "Keine URLs",
+		"description": "Label displayed when they are no URLs"
+	},
+	"batchSaveUrlsAddUrlButton": {
+		"message": "URL hinzufügen",
+		"description": "label of the button 'Add URL'"
+	},
+	"batchSaveUrlsAddUrlsButton": {
+		"message": "URLs hinzufügen...",
+		"description": "label of the button 'Add URLs...'"
+	},
+	"batchSaveUrlsRemoveAllButton": {
+		"message": "Alle entfernen",
+		"description": "label of the button 'Remove all'"
+	},
+	"batchSaveUrlsSavePagesButton": {
+		"message": "Seiten speichern",
+		"description": "label of the button 'Save pages'"
+	},
+	"batchSaveUrlsURLTitle": {
+		"message": "URL",
+		"description": "Title of the column in the table of the URLs"
 	}
 }

+ 32 - 0
_locales/en/messages.json

@@ -71,6 +71,10 @@
 		"message": "Save all tabs",
 		"description": "Menu entry: 'Save all tabs'"
 	},
+	"menuBatchSaveUrls": {
+		"message": "Batch save URLs...",
+		"description": "Menu entry: 'Batch save URLs...'"
+	},
 	"menuAutoSave": {
 		"message": "Auto-save",
 		"description": "Menu entry: 'Auto-save'"
@@ -786,5 +790,33 @@
 	"pendingsAddUrlsCancelButton": {
 		"message": "Cancel",
 		"description": "Add URLs popup cancel button: 'Cancel'"
+	},
+	"batchSaveUrlsTitle": {
+		"message": "Batch save URLs",
+		"description": "Title of the 'batch save URLs' page"
+	},
+	"batchSaveUrlsNoURLs": {
+		"message": "No URLs",
+		"description": "Label displayed when they are no URLs"
+	},
+	"batchSaveUrlsAddUrlButton": {
+		"message": "Add URL",
+		"description": "label of the button 'Add URL'"
+	},
+	"batchSaveUrlsAddUrlsButton": {
+		"message": "Add URLs...",
+		"description": "label of the button 'Add URLs...'"
+	},
+	"batchSaveUrlsRemoveAllButton": {
+		"message": "Remove all",
+		"description": "label of the button 'Remove all'"
+	},
+	"batchSaveUrlsSavePagesButton": {
+		"message": "Save pages",
+		"description": "label of the button 'Save pages'"
+	},
+	"batchSaveUrlsURLTitle": {
+		"message": "URL",
+		"description": "Title of the column in the table of the URLs"
 	}
 }

+ 32 - 0
_locales/es/messages.json

@@ -71,6 +71,10 @@
 		"message": "Guardar todas las pestañas",
 		"description": "Menu entry: 'Save all tabs'"
 	},
+	"menuBatchSaveUrls": {
+		"message": "Guardar por lotes URLs...",
+		"description": "Menu entry: 'Batch save URLs...'"
+	},
 	"menuAutoSave": {
 		"message": "Auto-guardar",
 		"description": "Menu entry: 'Auto-save'"
@@ -786,5 +790,33 @@
 	"pendingsAddUrlsCancelButton": {
 		"message": "Cancel",
 		"description": "Add URLs popup cancel button: 'Cancel'"
+	},
+	"batchSaveUrlsTitle": {
+		"message": "Batch save URLs",
+		"description": "Title of the 'batch save URLs' page"
+	},
+	"batchSaveUrlsNoURLs": {
+		"message": "No URLs",
+		"description": "Label displayed when they are no URLs"
+	},
+	"batchSaveUrlsAddUrlButton": {
+		"message": "Add URL",
+		"description": "label of the button 'Add URL'"
+	},
+	"batchSaveUrlsAddUrlsButton": {
+		"message": "Add URLs...",
+		"description": "label of the button 'Add URLs...'"
+	},
+	"batchSaveUrlsRemoveAllButton": {
+		"message": "Remove all",
+		"description": "label of the button 'Remove all'"
+	},
+	"batchSaveUrlsSavePagesButton": {
+		"message": "Save pages",
+		"description": "label of the button 'Save pages'"
+	},
+	"batchSaveUrlsURLTitle": {
+		"message": "URL",
+		"description": "Title of the column in the table of the URLs"
 	}
 }

+ 32 - 0
_locales/fr/messages.json

@@ -71,6 +71,10 @@
 		"message": "Sauver tous les onglets",
 		"description": "Menu entry: 'Save all tabs'"
 	},
+	"menuBatchSaveUrls": {
+		"message": "Sauver des URLs par lot...",
+		"description": "Menu entry: 'Batch save URLs...'"
+	},
 	"menuAutoSave": {
 		"message": "Auto-sauvegarde",
 		"description": "Menu entry: 'Auto-save'"
@@ -786,5 +790,33 @@
 	"pendingsAddUrlsCancelButton": {
 		"message": "Annuler",
 		"description": "Add URLs popup cancel button: 'Cancel'"
+	},
+	"batchSaveUrlsTitle": {
+		"message": "Sauver des URLs par lot",
+		"description": "Title of the 'batch save URLs' page"
+	},
+	"batchSaveUrlsNoURLs": {
+		"message": "Pas d'URLs",
+		"description": "Label displayed when they are no URLs"
+	},
+	"batchSaveUrlsAddUrlButton": {
+		"message": "Ajouter une URL",
+		"description": "label of the button 'Add URL'"
+	},
+	"batchSaveUrlsAddUrlsButton": {
+		"message": "Ajouter des URLs...",
+		"description": "label of the button 'Add URLs...'"
+	},
+	"batchSaveUrlsRemoveAllButton": {
+		"message": "Supprimer tout",
+		"description": "label of the button 'Remove all'"
+	},
+	"batchSaveUrlsSavePagesButton": {
+		"message": "Sauver les pages",
+		"description": "label of the button 'Save pages'"
+	},
+	"batchSaveUrlsURLTitle": {
+		"message": "URL",
+		"description": "Title of the column in the table of the URLs"
 	}
 }

+ 32 - 0
_locales/it/messages.json

@@ -71,6 +71,10 @@
 		"message": "Salva tutte le schede",
 		"description": "Menu entry: 'Save all tabs'"
 	},
+	"menuBatchSaveUrls": {
+		"message": "Batch save URLs...",
+		"description": "Menu entry: 'Batch save URLs...'"
+	},
 	"menuAutoSave": {
 		"message": "Auto-salvataggio",
 		"description": "Menu entry: 'Auto-save'"
@@ -786,5 +790,33 @@
 	"pendingsAddUrlsCancelButton": {
 		"message": "Annulla",
 		"description": "Add URLs popup cancel button: 'Cancel'"
+	},
+	"batchSaveUrlsTitle": {
+		"message": "Batch save URLs",
+		"description": "Title of the 'batch save URLs' page"
+	},
+	"batchSaveUrlsNoURLs": {
+		"message": "No URLs",
+		"description": "Label displayed when they are no URLs"
+	},
+	"batchSaveUrlsAddUrlButton": {
+		"message": "Add URL",
+		"description": "label of the button 'Add URL'"
+	},
+	"batchSaveUrlsAddUrlsButton": {
+		"message": "Add URLs...",
+		"description": "label of the button 'Add URLs...'"
+	},
+	"batchSaveUrlsRemoveAllButton": {
+		"message": "Remove all",
+		"description": "label of the button 'Remove all'"
+	},
+	"batchSaveUrlsSavePagesButton": {
+		"message": "Save pages",
+		"description": "label of the button 'Save pages'"
+	},
+	"batchSaveUrlsURLTitle": {
+		"message": "URL",
+		"description": "Title of the column in the table of the URLs"
 	}
 }

+ 32 - 0
_locales/ja/messages.json

@@ -71,6 +71,10 @@
 		"message": "すべてのタブを保存",
 		"description": "Menu entry: 'Save all tabs'"
 	},
+	"menuBatchSaveUrls": {
+		"message": "Batch save URLs...",
+		"description": "Menu entry: 'Batch save URLs...'"
+	},
 	"menuAutoSave": {
 		"message": "自動保存",
 		"description": "Menu entry: 'Auto-save'"
@@ -786,5 +790,33 @@
 	"pendingsAddUrlsCancelButton": {
 		"message": "Cancel",
 		"description": "Add URLs popup cancel button: 'Cancel'"
+	},
+	"batchSaveUrlsTitle": {
+		"message": "Batch save URLs",
+		"description": "Title of the 'batch save URLs' page"
+	},
+	"batchSaveUrlsNoURLs": {
+		"message": "No URLs",
+		"description": "Label displayed when they are no URLs"
+	},
+	"batchSaveUrlsAddUrlButton": {
+		"message": "Add URL",
+		"description": "label of the button 'Add URL'"
+	},
+	"batchSaveUrlsAddUrlsButton": {
+		"message": "Add URLs...",
+		"description": "label of the button 'Add URLs...'"
+	},
+	"batchSaveUrlsRemoveAllButton": {
+		"message": "Remove all",
+		"description": "label of the button 'Remove all'"
+	},
+	"batchSaveUrlsSavePagesButton": {
+		"message": "Save pages",
+		"description": "label of the button 'Save pages'"
+	},
+	"batchSaveUrlsURLTitle": {
+		"message": "URL",
+		"description": "Title of the column in the table of the URLs"
 	}
 }

+ 32 - 0
_locales/pl/messages.json

@@ -71,6 +71,10 @@
 		"message": "Zapisz wszystkie karty",
 		"description": "Menu entry: 'Save all tabs'"
 	},
+	"menuBatchSaveUrls": {
+		"message": "Batch save URLs...",
+		"description": "Menu entry: 'Batch save URLs...'"
+	},
 	"menuAutoSave": {
 		"message": "Automatyczny zapis",
 		"description": "Menu entry: 'Auto-save'"
@@ -786,5 +790,33 @@
 	"pendingsAddUrlsCancelButton": {
 		"message": "Anuluj",
 		"description": "Add URLs popup cancel button: 'Cancel'"
+	},
+	"batchSaveUrlsTitle": {
+		"message": "Batch save URLs",
+		"description": "Title of the 'batch save URLs' page"
+	},
+	"batchSaveUrlsNoURLs": {
+		"message": "No URLs",
+		"description": "Label displayed when they are no URLs"
+	},
+	"batchSaveUrlsAddUrlButton": {
+		"message": "Add URL",
+		"description": "label of the button 'Add URL'"
+	},
+	"batchSaveUrlsAddUrlsButton": {
+		"message": "Add URLs...",
+		"description": "label of the button 'Add URLs...'"
+	},
+	"batchSaveUrlsRemoveAllButton": {
+		"message": "Remove all",
+		"description": "label of the button 'Remove all'"
+	},
+	"batchSaveUrlsSavePagesButton": {
+		"message": "Save pages",
+		"description": "label of the button 'Save pages'"
+	},
+	"batchSaveUrlsURLTitle": {
+		"message": "URL",
+		"description": "Title of the column in the table of the URLs"
 	}
 }

+ 32 - 0
_locales/pt_br/messages.json

@@ -71,6 +71,10 @@
 		"message": "Salvar todas abas",
 		"description": "Menu entry: 'Save all tabs'"
 	},
+	"menuBatchSaveUrls": {
+		"message": "Batch save URLs...",
+		"description": "Menu entry: 'Batch save URLs...'"
+	},
 	"menuAutoSave": {
 		"message": "Salvar autom.",
 		"description": "Menu entry: 'Auto-save'"
@@ -786,5 +790,33 @@
 	"pendingsAddUrlsCancelButton": {
 		"message": "Cancelar",
 		"description": "Add URLs popup cancel button: 'Cancel'"
+	},
+	"batchSaveUrlsTitle": {
+		"message": "Batch save URLs",
+		"description": "Title of the 'batch save URLs' page"
+	},
+	"batchSaveUrlsNoURLs": {
+		"message": "No URLs",
+		"description": "Label displayed when they are no URLs"
+	},
+	"batchSaveUrlsAddUrlButton": {
+		"message": "Add URL",
+		"description": "label of the button 'Add URL'"
+	},
+	"batchSaveUrlsAddUrlsButton": {
+		"message": "Add URLs...",
+		"description": "label of the button 'Add URLs...'"
+	},
+	"batchSaveUrlsRemoveAllButton": {
+		"message": "Remove all",
+		"description": "label of the button 'Remove all'"
+	},
+	"batchSaveUrlsSavePagesButton": {
+		"message": "Save pages",
+		"description": "label of the button 'Save pages'"
+	},
+	"batchSaveUrlsURLTitle": {
+		"message": "URL",
+		"description": "Title of the column in the table of the URLs"
 	}
 }

+ 32 - 0
_locales/ru/messages.json

@@ -71,6 +71,10 @@
 		"message": "Сохранить все вкладки",
 		"description": "Menu entry: 'Save all tabs'"
 	},
+	"menuBatchSaveUrls": {
+		"message": "Batch save URLs...",
+		"description": "Menu entry: 'Batch save URLs...'"
+	},
 	"menuAutoSave": {
 		"message": "Автосохранение",
 		"description": "Menu entry: 'Auto-save'"
@@ -786,5 +790,33 @@
 	"pendingsAddUrlsCancelButton": {
 		"message": "Отмена",
 		"description": "Add URLs popup cancel button: 'Cancel'"
+	},
+	"batchSaveUrlsTitle": {
+		"message": "Batch save URLs",
+		"description": "Title of the 'batch save URLs' page"
+	},
+	"batchSaveUrlsNoURLs": {
+		"message": "No URLs",
+		"description": "Label displayed when they are no URLs"
+	},
+	"batchSaveUrlsAddUrlButton": {
+		"message": "Add URL",
+		"description": "label of the button 'Add URL'"
+	},
+	"batchSaveUrlsAddUrlsButton": {
+		"message": "Add URLs...",
+		"description": "label of the button 'Add URLs...'"
+	},
+	"batchSaveUrlsRemoveAllButton": {
+		"message": "Remove all",
+		"description": "label of the button 'Remove all'"
+	},
+	"batchSaveUrlsSavePagesButton": {
+		"message": "Save pages",
+		"description": "label of the button 'Save pages'"
+	},
+	"batchSaveUrlsURLTitle": {
+		"message": "URL",
+		"description": "Title of the column in the table of the URLs"
 	}
 }

+ 32 - 0
_locales/uk/messages.json

@@ -71,6 +71,10 @@
 		"message": "Зберегти всі вкладки",
 		"description": "Menu entry: 'Save all tabs'"
 	},
+	"menuBatchSaveUrls": {
+		"message": "Batch save URLs...",
+		"description": "Menu entry: 'Batch save URLs...'"
+	},
 	"menuAutoSave": {
 		"message": "Автозбереження",
 		"description": "Menu entry: 'Auto-save'"
@@ -786,5 +790,33 @@
 	"pendingsAddUrlsCancelButton": {
 		"message": "Cancel",
 		"description": "Add URLs popup cancel button: 'Cancel'"
+	},
+	"batchSaveUrlsTitle": {
+		"message": "Batch save URLs",
+		"description": "Title of the 'batch save URLs' page"
+	},
+	"batchSaveUrlsNoURLs": {
+		"message": "No URLs",
+		"description": "Label displayed when they are no URLs"
+	},
+	"batchSaveUrlsAddUrlButton": {
+		"message": "Add URL",
+		"description": "label of the button 'Add URL'"
+	},
+	"batchSaveUrlsAddUrlsButton": {
+		"message": "Add URLs...",
+		"description": "label of the button 'Add URLs...'"
+	},
+	"batchSaveUrlsRemoveAllButton": {
+		"message": "Remove all",
+		"description": "label of the button 'Remove all'"
+	},
+	"batchSaveUrlsSavePagesButton": {
+		"message": "Save pages",
+		"description": "label of the button 'Save pages'"
+	},
+	"batchSaveUrlsURLTitle": {
+		"message": "URL",
+		"description": "Title of the column in the table of the URLs"
 	}
 }

+ 32 - 0
_locales/zh_CN/messages.json

@@ -71,6 +71,10 @@
 		"message": "保存所有标签页",
 		"description": "Menu entry: 'Save all tabs'"
 	},
+	"menuBatchSaveUrls": {
+		"message": "Batch save URLs...",
+		"description": "Menu entry: 'Batch save URLs...'"
+	},
 	"menuAutoSave": {
 		"message": "自动保存",
 		"description": "Menu entry: 'Auto-save'"
@@ -786,5 +790,33 @@
 	"pendingsAddUrlsCancelButton": {
 		"message": "取消",
 		"description": "Add URLs popup cancel button: 'Cancel'"
+	},
+	"batchSaveUrlsTitle": {
+		"message": "Batch save URLs",
+		"description": "Title of the 'batch save URLs' page"
+	},
+	"batchSaveUrlsNoURLs": {
+		"message": "No URLs",
+		"description": "Label displayed when they are no URLs"
+	},
+	"batchSaveUrlsAddUrlButton": {
+		"message": "Add URL",
+		"description": "label of the button 'Add URL'"
+	},
+	"batchSaveUrlsAddUrlsButton": {
+		"message": "Add URLs...",
+		"description": "label of the button 'Add URLs...'"
+	},
+	"batchSaveUrlsRemoveAllButton": {
+		"message": "Remove all",
+		"description": "label of the button 'Remove all'"
+	},
+	"batchSaveUrlsSavePagesButton": {
+		"message": "Save pages",
+		"description": "label of the button 'Save pages'"
+	},
+	"batchSaveUrlsURLTitle": {
+		"message": "URL",
+		"description": "Title of the column in the table of the URLs"
 	}
 }

+ 32 - 0
_locales/zh_TW/messages.json

@@ -71,6 +71,10 @@
 		"message": "保存所有標籤頁",
 		"description": "Menu entry: 'Save all tabs'"
 	},
+	"menuBatchSaveUrls": {
+		"message": "Batch save URLs...",
+		"description": "Menu entry: 'Batch save URLs...'"
+	},
 	"menuAutoSave": {
 		"message": "自動保存",
 		"description": "Menu entry: 'Auto-save'"
@@ -786,5 +790,33 @@
 	"pendingsAddUrlsCancelButton": {
 		"message": "取消",
 		"description": "Add URLs popup cancel button: 'Cancel'"
+	},
+	"batchSaveUrlsTitle": {
+		"message": "Batch save URLs",
+		"description": "Title of the 'batch save URLs' page"
+	},
+	"batchSaveUrlsNoURLs": {
+		"message": "No URLs",
+		"description": "Label displayed when they are no URLs"
+	},
+	"batchSaveUrlsAddUrlButton": {
+		"message": "Add URL",
+		"description": "label of the button 'Add URL'"
+	},
+	"batchSaveUrlsAddUrlsButton": {
+		"message": "Add URLs...",
+		"description": "label of the button 'Add URLs...'"
+	},
+	"batchSaveUrlsRemoveAllButton": {
+		"message": "Remove all",
+		"description": "label of the button 'Remove all'"
+	},
+	"batchSaveUrlsSavePagesButton": {
+		"message": "Save pages",
+		"description": "label of the button 'Save pages'"
+	},
+	"batchSaveUrlsURLTitle": {
+		"message": "URL",
+		"description": "Title of the column in the table of the URLs"
 	}
 }

+ 13 - 2
src/extension/core/bg/business.js

@@ -46,7 +46,7 @@ const extensionScriptFiles = [
 
 const tasks = [];
 let currentTaskId = 0, maxParallelWorkers;
-ui.init({ isSavingTab, saveTabs, saveUrls, cancelTab, openEditor, saveSelectedLinks });
+ui.init({ isSavingTab, saveTabs, saveUrls, cancelTab, openEditor, saveSelectedLinks, batchSaveUrls });
 
 export {
 	saveTabs,
@@ -67,13 +67,24 @@ async function saveSelectedLinks(tab) {
 	if (scriptsInjected) {
 		const response = await browser.tabs.sendMessage(tab.id, { method: "content.getSelectedLinks" });
 		if (response.urls && response.urls.length) {
-			await saveUrls(response.urls);
+			const tab = await batchSaveUrls();
+			const onTabUpdated = (tabId, changeInfo) => {
+				if (changeInfo.status == "complete" && tabId == tab.id) {
+					browser.tabs.onUpdated.removeListener(onTabUpdated);
+					browser.tabs.sendMessage(tab.id, { method: "newUrls.addURLs", urls: response.urls });
+				}
+			};
+			browser.tabs.onUpdated.addListener(onTabUpdated);
 		}
 	} else {
 		ui.onForbiddenDomain(tab);
 	}
 }
 
+async function batchSaveUrls() {
+	return browser.tabs.create({ active: true, url: "/src/extension/ui/pages/batch-save-urls.html" });
+}
+
 async function saveUrls(urls, options = {}) {
 	await initMaxParallelWorkers();
 	await Promise.all(urls.map(async url => {

+ 155 - 0
src/extension/ui/bg/ui-batch-save-urls.js

@@ -0,0 +1,155 @@
+/*
+ * Copyright 2010-2020 Gildas Lormeau
+ * contact : gildas.lormeau <at> gmail.com
+ * 
+ * This file is part of SingleFile.
+ *
+ *   The code in this file is free software: you can redistribute it and/or 
+ *   modify it under the terms of the GNU Affero General Public License 
+ *   (GNU AGPL) as published by the Free Software Foundation, either version 3
+ *   of the License, or (at your option) any later version.
+ * 
+ *   The code in this file is distributed in the hope that it will be useful, 
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of 
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero 
+ *   General Public License for more details.
+ *
+ *   As additional permission under GNU AGPL version 3 section 7, you may 
+ *   distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU 
+ *   AGPL normally required by section 4, provided you include this license 
+ *   notice and a URL through which recipients can access the Corresponding 
+ *   Source.
+ */
+
+/* global browser, window, document, location */
+
+const URLLabel = document.getElementById("URLLabel");
+const addUrlsLabel = document.getElementById("addUrlsLabel");
+const urlsTable = document.getElementById("urlsTable");
+const removeAllButton = document.getElementById("removeAllButton");
+const addUrlForm = document.getElementById("addUrlForm");
+const addUrlInput = document.getElementById("addUrlInput");
+const addUrlButton = document.getElementById("addUrlButton");
+const addUrlsButton = document.getElementById("addUrlsButton");
+const addUrlsInput = document.getElementById("addUrlsInput");
+const addUrlsCancelButton = document.getElementById("addUrlsCancelButton");
+const addUrlsOKButton = document.getElementById("addUrlsOKButton");
+const saveUrlsButton = document.getElementById("saveUrlsButton");
+document.title = browser.i18n.getMessage("batchSaveUrlsTitle");
+const noPendingsText = browser.i18n.getMessage("batchSaveUrlsNoURLs");
+addUrlButton.textContent = browser.i18n.getMessage("batchSaveUrlsAddUrlButton");
+addUrlsButton.textContent = browser.i18n.getMessage("batchSaveUrlsAddUrlsButton");
+removeAllButton.textContent = browser.i18n.getMessage("batchSaveUrlsRemoveAllButton");
+saveUrlsButton.textContent = browser.i18n.getMessage("batchSaveUrlsSavePagesButton");
+addUrlsCancelButton.textContent = browser.i18n.getMessage("pendingsAddUrlsCancelButton");
+addUrlsOKButton.textContent = browser.i18n.getMessage("pendingsAddUrlsOKButton");
+addUrlsLabel.textContent = browser.i18n.getMessage("pendingsAddUrls");
+URLLabel.textContent = browser.i18n.getMessage("batchSaveUrlsURLTitle");
+addUrlForm.onsubmit = () => {
+	const value = addUrlInput.value.trim();
+	if (value.length && !urls.includes(value) && (value.startsWith("http://") || value.startsWith("https://") || value.startsWith("file://"))) {
+		urls.push(value);
+		addUrlInput.value = "";
+		refresh();
+	}
+	return false;
+};
+removeAllButton.onclick = async () => {
+	urls = [];
+	await refresh();
+};
+addUrlsButton.onclick = displayAddUrlsPopup;
+if (location.href.endsWith("#side-panel")) {
+	document.documentElement.classList.add("side-panel");
+}
+saveUrlsButton.onclick = async () => {
+	if (urls.length) {
+		await browser.runtime.sendMessage({ method: "downloads.saveUrls", urls });
+		urls.length = 0;
+		refresh();
+	}
+};
+
+let previousState;
+let urls = [];
+browser.runtime.onMessage.addListener(message => {
+	if (message.method == "newUrls.addURLs") {
+		urls = message.urls;
+		refresh();
+	}
+});
+refresh();
+
+function resetTable() {
+	urlsTable.innerHTML = "";
+}
+
+function updateTable(urls) {
+	if (urls.length) {
+		urls.forEach((url, indexUrl) => {
+			const row = document.createElement("div");
+			const cellURL = document.createElement("span");
+			const cellCancel = document.createElement("span");
+			const buttonCancel = document.createElement("button");
+			row.className = "urls-row";
+			cellURL.textContent = url;
+			cellURL.className = "result-url-title";
+			buttonCancel.textContent = "×";
+			buttonCancel.onclick = () => cancel(indexUrl);
+			cellCancel.appendChild(buttonCancel);
+			cellCancel.className = "result-cancel";
+			row.appendChild(cellURL);
+			row.appendChild(cellCancel);
+			urlsTable.appendChild(row);
+		});
+	}
+}
+
+async function cancel(index) {
+	urls.splice(index, 1);
+	await refresh();
+}
+
+async function displayAddUrlsPopup() {
+	document.getElementById("formAddUrls").style.setProperty("display", "flex");
+	document.querySelector("#formAddUrls .popup-content").style.setProperty("align-self", "center");
+	addUrlsInput.value = "";
+	addUrlsInput.focus();
+	document.body.style.setProperty("overflow-y", "hidden");
+	const newUrls = await new Promise(resolve => {
+		addUrlsOKButton.onclick = event => hideAndResolve(event, addUrlsInput.value);
+		addUrlsCancelButton.onclick = event => hideAndResolve(event);
+		window.onkeyup = event => {
+			if (event.key == "Escape") {
+				hideAndResolve(event);
+			}
+		};
+
+		function hideAndResolve(event, value = "") {
+			event.preventDefault();
+			document.getElementById("formAddUrls").style.setProperty("display", "none");
+			document.body.style.setProperty("overflow-y", "");
+			resolve(value.split("\n").map(url => url.trim()).filter(url => url));
+		}
+	});
+	urls = Array.from(new Set(urls.concat(newUrls)));
+	refresh();
+}
+
+async function refresh(force) {
+	const currentState = JSON.stringify(urls);
+	if (previousState != currentState || force) {
+		previousState = currentState;
+		resetTable();
+		updateTable(urls);
+		if (!urls.length) {
+			const row = document.createElement("div");
+			row.className = "urls-row";
+			const cell = document.createElement("span");
+			cell.className = "no-result";
+			cell.textContent = noPendingsText;
+			row.appendChild(cell);
+			urlsTable.appendChild(row);
+		}
+	}
+}

+ 10 - 0
src/extension/ui/bg/ui-menus.js

@@ -48,6 +48,7 @@ const MENU_ID_SAVE_TABS = "save-tabs";
 const MENU_ID_SAVE_SELECTED_TABS = "save-selected-tabs";
 const MENU_ID_SAVE_UNPINNED_TABS = "save-unpinned-tabs";
 const MENU_ID_SAVE_ALL_TABS = "save-all-tabs";
+const MENU_ID_BATCH_SAVE_URLS = "batch-save-urls";
 const MENU_ID_BUTTON_SAVE_SELECTED_TABS = "button-" + MENU_ID_SAVE_SELECTED_TABS;
 const MENU_ID_BUTTON_SAVE_UNPINNED_TABS = "button-" + MENU_ID_SAVE_UNPINNED_TABS;
 const MENU_ID_BUTTON_SAVE_ALL_TABS = "button-" + MENU_ID_SAVE_ALL_TABS;
@@ -70,6 +71,7 @@ const MENU_SAVE_TABS_MESSAGE = browser.i18n.getMessage("menuSaveTabs");
 const MENU_SAVE_SELECTED_TABS_MESSAGE = browser.i18n.getMessage("menuSaveSelectedTabs");
 const MENU_SAVE_UNPINNED_TABS_MESSAGE = browser.i18n.getMessage("menuSaveUnpinnedTabs");
 const MENU_SAVE_ALL_TABS_MESSAGE = browser.i18n.getMessage("menuSaveAllTabs");
+const MENU_BATCH_SAVE_URLS_MESSAGE = browser.i18n.getMessage("menuBatchSaveUrls");
 const MENU_SELECT_PROFILE_MESSAGE = browser.i18n.getMessage("menuSelectProfile");
 const PROFILE_DEFAULT_SETTINGS_MESSAGE = browser.i18n.getMessage("profileDefaultSettings");
 const MENU_AUTOSAVE_MESSAGE = browser.i18n.getMessage("menuAutoSave");
@@ -368,6 +370,11 @@ async function createMenus(tab) {
 			contexts: defaultContexts,
 			type: "separator"
 		});
+		menus.create({
+			id: MENU_ID_BATCH_SAVE_URLS,
+			contexts: defaultContexts,
+			title: MENU_BATCH_SAVE_URLS_MESSAGE
+		});
 		menus.create({
 			id: MENU_ID_VIEW_PENDINGS,
 			contexts: defaultContexts,
@@ -428,6 +435,9 @@ async function initialize() {
 				const tabs = await queryTabs({ currentWindow: true });
 				business.saveTabs(tabs);
 			}
+			if (event.menuItemId == MENU_ID_BATCH_SAVE_URLS) {
+				business.batchSaveUrls();
+			}
 			if (event.menuItemId == MENU_ID_AUTO_SAVE_TAB) {
 				const allTabsData = await tabsData.get(tab.id);
 				allTabsData[tab.id].autoSave = true;

+ 12 - 2
src/extension/ui/bg/ui-panel.js

@@ -25,18 +25,28 @@
 
 const optionsTab = document.getElementById("tab-options");
 const pendingsTab = document.getElementById("tab-pendings");
+const batchSaveUrlsTab = document.getElementById("tab-batch-save-urls");
 const viewPanel = document.getElementById("view-panel");
 
-optionsTab.textContent = browser.i18n.getMessage("optionsTitle");
-pendingsTab.textContent = browser.i18n.getMessage("pendingsTitle");
+optionsTab.textContent = optionsTab.title = browser.i18n.getMessage("optionsTitle");
+pendingsTab.textContent = pendingsTab.title = browser.i18n.getMessage("pendingsTitle");
+batchSaveUrlsTab.textContent = batchSaveUrlsTab.title = browser.i18n.getMessage("batchSaveUrlsTitle");
 
 optionsTab.onclick = () => {
 	optionsTab.classList.add("tab-selected");
 	pendingsTab.classList.remove("tab-selected");
+	batchSaveUrlsTab.classList.remove("tab-selected");
 	viewPanel.src = "options.html#side-panel";
 };
 pendingsTab.onclick = () => {
 	optionsTab.classList.remove("tab-selected");
 	pendingsTab.classList.add("tab-selected");
+	batchSaveUrlsTab.classList.remove("tab-selected");
 	viewPanel.src = "pendings.html#side-panel";
+};
+batchSaveUrlsTab.onclick = () => {
+	optionsTab.classList.remove("tab-selected");
+	pendingsTab.classList.remove("tab-selected");
+	batchSaveUrlsTab.classList.add("tab-selected");
+	viewPanel.src = "batch-save-urls.html#side-panel";
 };

+ 1 - 33
src/extension/ui/bg/ui-pendings.js

@@ -28,14 +28,9 @@ const titleLabel = document.getElementById("titleLabel");
 const resultsTable = document.getElementById("resultsTable");
 const cancelAllButton = document.getElementById("cancelAllButton");
 const addUrlsButton = document.getElementById("addUrlsButton");
-const addUrlsInput = document.getElementById("addUrlsInput");
-const addUrlsCancelButton = document.getElementById("addUrlsCancelButton");
-const addUrlsOKButton = document.getElementById("addUrlsOKButton");
 document.title = browser.i18n.getMessage("pendingsTitle");
 cancelAllButton.textContent = browser.i18n.getMessage("pendingsCancelAllButton");
 addUrlsButton.textContent = browser.i18n.getMessage("pendingsAddUrlsButton");
-addUrlsCancelButton.textContent = browser.i18n.getMessage("pendingsAddUrlsCancelButton");
-addUrlsOKButton.textContent = browser.i18n.getMessage("pendingsAddUrlsOKButton");
 document.getElementById("addUrlsLabel").textContent = browser.i18n.getMessage("pendingsAddUrls");
 URLLabel.textContent = browser.i18n.getMessage("pendingsURLTitle");
 titleLabel.textContent = browser.i18n.getMessage("pendingsTitleTitle");
@@ -50,7 +45,7 @@ cancelAllButton.onclick = async () => {
 	await browser.runtime.sendMessage({ method: "downloads.cancelAll" });
 	await refresh();
 };
-addUrlsButton.onclick = displayAddUrlsPopup;
+addUrlsButton.onclick = () => window.open("batch-save-urls.html", "sf-add-urls");
 if (location.href.endsWith("#side-panel")) {
 	document.documentElement.classList.add("side-panel");
 }
@@ -112,33 +107,6 @@ async function selectTab(tabId) {
 	await refresh();
 }
 
-async function displayAddUrlsPopup() {
-	document.getElementById("formAddUrls").style.setProperty("display", "flex");
-	document.querySelector("#formAddUrls .popup-content").style.setProperty("align-self", "center");
-	addUrlsInput.value = "";
-	addUrlsInput.focus();
-	document.body.style.setProperty("overflow-y", "hidden");
-	const urls = await new Promise(resolve => {
-		addUrlsOKButton.onclick = event => hideAndResolve(event, addUrlsInput.value);
-		addUrlsCancelButton.onclick = event => hideAndResolve(event);
-		window.onkeyup = event => {
-			if (event.key == "Escape") {
-				hideAndResolve(event);
-			}
-		};
-
-		function hideAndResolve(event, value = "") {
-			event.preventDefault();
-			document.getElementById("formAddUrls").style.setProperty("display", "none");
-			document.body.style.setProperty("overflow-y", "");
-			resolve(value.split("\n").map(url => url.trim()).filter(url => url));
-		}
-	});
-	if (urls.length) {
-		await browser.runtime.sendMessage({ method: "downloads.saveUrls", urls });
-	}
-}
-
 async function refresh(force) {
 	const results = await browser.runtime.sendMessage({ method: "downloads.getInfo" });
 	const currentState = JSON.stringify(results);

+ 381 - 0
src/extension/ui/pages/batch-save-urls.css

@@ -0,0 +1,381 @@
+html {
+    background-color: #f0f0f0;
+    color: black;
+}
+
+body {
+    margin: 0;
+    margin-left: 5px;
+    margin-right: 5px;
+}
+
+main,
+header {
+    font-family: sans-serif;
+}
+
+body>main {
+    background-color: #fff;
+    border: solid 1px rgb(191, 191, 191);
+}
+
+body>main,
+body>header,
+body>footer {
+    font-size: 12px;
+    font-family: sans-serif;
+    margin: 0;
+    margin-left: auto;
+    margin-right: auto;
+    max-width: 1024px;
+}
+
+body>header, body>footer {
+    display: flex;
+    flex-direction: column;
+    align-items: flex-end;
+}
+
+.header-buttons {
+    display: flex;
+    width: 100%;
+}
+
+.header-buttons-group {
+    display: flex;
+    align-self: end;
+}
+
+.header-buttons>form {
+    flex: 1;
+    display: flex;
+    align-items: center;
+}
+
+.header-buttons>form>input {
+    background-color: white;
+    color: black;
+    min-height: 18px;
+    width: 100%;
+    flex: 1;
+    margin-top: 5px;
+    margin-bottom: 5px;
+    margin-left: 2px;
+    padding: 2px;  
+    border: solid 1px rgb(191, 191, 191);
+}
+
+button {
+    background-color: #fbfbfb;
+    border-color: rgb(191, 191, 191);
+    border-style: solid;
+    border-radius: 2px;
+    border-width: 1px;
+    color: black;
+    white-space: nowrap;
+}
+
+button:not([disabled]):hover {
+    background-color: #ededed;
+}
+
+body>header button, body>footer button {
+    margin-top: 5px;
+    margin-bottom: 5px;
+    align-self: flex-end;
+    padding: 5px;
+    padding-left: 10px;
+    padding-right: 10px;
+    margin-left: 8px;
+}
+
+.urls-row {
+    display: flex;
+    flex-direction: row;
+    padding-top: 5px;
+    padding-bottom: 5px;
+    min-height: 40px;
+    padding-left: 0px;
+}
+
+.urls-row:not(:first-child) {
+    border-top: #bfbfbf 1px dashed;
+}
+
+.urls-head {
+    background-color: #ececec;
+    padding-left: 5px;
+}
+
+.urls-row>span {
+    padding: 10px;
+    align-self: center;
+}
+
+.urls-head .unselected {
+    opacity: .5;
+}
+
+.result-url-title {
+    flex: 1;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    display: inline-block;
+    white-space: nowrap;
+    user-select: none;
+}
+
+.result-cancel {
+    text-align: right;
+    width: 19px;
+}
+
+.result-cancel button {
+    background-color: #fbfbfb;
+    width: 19px;
+    padding: 0;
+}
+
+.no-result {
+    color: #888;
+}
+
+html.side-panel body {
+    margin: 0;
+}
+
+html.side-panel,
+.side-panel .urls-head,
+.side-panel body>main {
+    background-color: #fbfbfb;
+}
+
+.side-panel .header-buttons {
+    display: flex;
+}
+
+.side-panel body>header,
+.side-panel body>main,
+.side-panel body>footer {
+    border: 0;
+    margin-left: 8px;
+    margin-right: 12px;
+}
+
+.side-panel .urls-head {
+    min-height: 0;
+    padding-top: 3px;
+}
+
+.side-panel #URLLabel {
+    display: none;
+}
+
+.side-panel .urls-row>span,
+.side-panel .urls-row>span>span {
+    padding: 0px;
+}
+
+.side-panel .urls-head {
+    border-bottom: dashed 1px rgb(191, 191, 191);
+}
+
+.popup {
+    width: 100%;
+    height: 100%;
+    position: fixed;
+    top: 0;
+    left: 0;
+    background-color: rgba(191, 191, 191, .75);
+    display: none;
+}
+
+.popup-content {
+    align-self: center;
+    min-width: min(calc(100% - 16px), 480px);
+    max-width: calc(100% - 16px);
+    margin-top: 100px;
+    margin-bottom: 100px;
+    margin-left: auto;
+    margin-right: auto;
+    background-color: white;
+    box-shadow: 5px 5px #ababab;
+}
+
+.popup-content header {
+    padding-top: 10px;
+    padding-left: 10px;
+    padding-right: 10px;
+    padding-bottom: 20px;
+    font-size: 14px;
+    line-height: 20px;
+}
+
+.popup-content main {
+    padding-top: 10px;
+    padding-left: 10px;
+    padding-bottom: 10px;
+}
+
+.popup-content main textarea {
+    background-color: white;
+    color: black;
+    margin-bottom: 10px;
+    padding: 2px;
+    font-size: 13px;
+    margin-left: 0px;
+    width: calc(100% - 16px);
+    min-width: calc(100% - 16px);
+    max-width: calc(100% - 16px);
+    height: 60px;
+    min-height: 30px;
+}
+
+.popup-content footer {
+    text-align: right;
+    padding-left: 10px;
+    padding-right: 10px;
+}
+
+.popup-content footer button {
+    margin-bottom: 10px;
+}
+
+@media (max-width:450px) {
+
+    .header-buttons {
+        flex-direction: column;
+    }
+}
+
+@media (max-width:400px) {
+
+    body>main,
+    body>header,
+    .header-buttons button,
+    .footer-buttons button {
+        font-size: 11px;
+    }
+
+    .header-buttons>form>input {
+        padding: 1px;
+    }
+
+    .urls-row {
+        padding-top: 3px;
+        padding-bottom: 3px;
+        min-height: 30px;
+    }
+
+    .side-panel .header-buttons {
+        top: 0px;
+    }
+}
+
+@media (max-width:300px) {
+    .side-panel :not(.result-cancel)>button {
+        display: flex;
+    }
+
+    .side-panel .header-buttons {
+        flex-direction: column;
+    }
+}
+
+@media (max-width:250px) {
+    .header-buttons > form {
+        flex-direction: column;
+    }
+}
+
+@media (max-width:200px) {
+    .header-buttons-group {
+        flex-direction: column;
+    }
+}
+
+@media (prefers-color-scheme: dark) {
+    html {
+        background-color: #373737;
+        color: #fdfdfd;
+    }
+
+    body>main {
+        border-color: rgb(81, 81, 81);
+    }
+
+    body>main,
+    button,
+    .popup-content {
+        background-color: #202023;
+        color: #fdfdfd;
+    }
+
+    .urls-row {
+        color: #dedede;
+    }
+
+    .urls-head {
+        background-color: #191919;
+        color: #fdfdfd;
+    }
+
+    .header-buttons>form>input {
+        background-color: white;
+        color: black;
+    }
+
+    .no-result {
+        color: #888;
+    }
+
+    .result-cancel button {
+        color: #202023;
+    }
+
+    .result-cancel button:hover {
+        background-color: #ccc;
+    }
+
+    button:hover {
+        color: #2A2A2E;
+    }
+
+    textarea {
+        background-color: #fff;
+    }
+
+    button:focus {
+        color: gray;
+        border-color: gray;
+        background-color: #d2d2d2;
+    }
+
+    .popup {
+        background-color: rgba(59, 59, 59, 0.95);
+    }
+
+    .popup-content {
+        box-shadow: 5px 5px #000000;
+    }
+
+    html.side-panel,
+    .side-panel .urls-head,
+    .side-panel body>main,
+    .side-panel button,
+    .side-panel .popup-content {
+        background-color: #38383d;
+    }
+
+    .side-panel .urls-head {
+        border-bottom: dashed 1px rgb(191, 191, 191);
+    }
+
+    .side-panel .result-cancel button {
+        color: #fdfdfd;
+    }
+
+    .popup-content main textarea {
+        color: black;
+    }
+}

+ 53 - 0
src/extension/ui/pages/batch-save-urls.html

@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+	<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+	<title>&nbsp;</title>
+	<link rel="stylesheet" href="batch-save-urls.css">
+	<meta name="viewport" content="width=device-width,initial-scale=1">
+	<meta name="color-scheme" content="light dark">
+</head>
+
+<body>
+	<header>
+		<div class="header-buttons">
+			<form id="addUrlForm">
+				<input id="addUrlInput" type="text">
+				<button id="addUrlButton"></button>
+			</form>
+			<div class="header-buttons-group">
+				<button id="addUrlsButton"></button>
+				<button id="removeAllButton"></button>
+			</div>
+		</div>
+	</header>
+	<main>
+		<div class="urls-row urls-head">
+			<span id="URLLabel"></span>
+		</div>
+		<div id="urlsTable">
+		</div>
+	</main>
+	<footer>
+		<div class="footer-buttons">
+			<button id="saveUrlsButton"></button>
+		</div>
+	</footer>
+	<div id="formAddUrls" class="popup">
+		<form class="popup-content">
+			<header>
+				<span id="addUrlsLabel"></span>
+			</header>
+			<main><textarea id="addUrlsInput" type="text"></textarea></main>
+			<footer>
+				<button type="submit" id="addUrlsOKButton"></button>
+				<button id="addUrlsCancelButton"></button>
+			</footer>
+		</form>
+	</div>
+	<script src="/lib/chrome-browser-polyfill.js"></script>
+	<script type="module" src="../bg/ui-batch-save-urls.js"></script>
+</body>
+
+</html>

+ 2 - 2
src/extension/ui/pages/options.css

@@ -93,10 +93,10 @@ input.large-input {
 h3 {
     display: flex;
     padding-left: 8px;
-    padding-top: 10px;
-    margin-top: 4px;
+    padding-top: 5px;
     margin-right: 10px;
     min-height: 28px;
+    margin-top: 0px;
 }
 
 h3 a {

+ 1 - 0
src/extension/ui/pages/panel.html

@@ -12,6 +12,7 @@
 <body>
 	<header>
 		<span id="tab-options" class="tab tab-selected"></span>
+		<span id="tab-batch-save-urls" class="tab"></span>
 		<span id="tab-pendings" class="tab"></span>
 	</header>
 	<main>

+ 22 - 80
src/extension/ui/pages/pendings.css

@@ -1,9 +1,12 @@
 html {
     background-color: #f0f0f0;
+    color: black;
 }
 
 body {
     margin: 0;
+    margin-left: 5px;
+    margin-right: 5px;
 }
 
 main,
@@ -84,6 +87,11 @@ body>header button {
     opacity: .5;
 }
 
+.result-row.result-head .result-head-separator {
+    padding-left: 2px;
+    padding-right: 2px;
+}
+
 .result-url-title {
     flex: 1;
     overflow: hidden;
@@ -117,6 +125,10 @@ body>header button {
     color: #888;
 }
 
+html.side-panel body {
+    margin: 0;
+}
+
 html.side-panel,
 .side-panel .result-head,
 .side-panel body>main {
@@ -125,11 +137,14 @@ html.side-panel,
 
 .side-panel .header-buttons {
     position: absolute;
-    top: 4px;
     right: 12px;
     display: flex;
 }
 
+html.side-panel #addUrlsButton {
+    display: none;
+}
+
 .side-panel body>main {
     border: 0;
     margin-left: 8px;
@@ -145,63 +160,8 @@ html.side-panel,
     padding: 0px;
 }
 
-.popup {
-    width: 100%;
-    height: 100%;
-    position: fixed;
-    top: 0;
-    left: 0;
-    background-color: rgba(191, 191, 191, .75);
-    display: none;
-}
-
-.popup-content {
-    align-self: center;
-    min-width: min(calc(100% - 16px), 480px);
-    max-width: calc(100% - 16px);
-    margin-top: 100px;
-    margin-bottom: 100px;
-    margin-left: auto;
-    margin-right: auto;
-    background-color: white;
-    box-shadow: 5px 5px #ababab;
-}
-
-.popup-content header {
-    padding-top: 10px;
-    padding-left: 10px;
-    padding-right: 10px;
-    padding-bottom: 20px;
-    font-size: 14px;
-    line-height: 20px;
-}
-
-.popup-content main {
-    padding-top: 10px;
-    padding-left: 10px;
-    padding-bottom: 10px;
-}
-
-.popup-content main textarea {
-    margin-bottom: 10px;
-    padding: 2px;
-    font-size: 13px;
-    margin-left: 0px;
-    width: calc(100% - 16px);
-    min-width: calc(100% - 16px);
-    max-width: calc(100% - 16px);
-    height: 60px;
-    min-height: 30px;
-}
-
-.popup-content footer {
-    text-align: right;
-    padding-left: 10px;
-    padding-right: 10px;
-}
-
-.popup-content footer button {
-    margin-bottom: 10px;
+.side-panel .result-head {
+    border-bottom: dashed 1px rgb(191, 191, 191);
 }
 
 @media (max-width:400px) {
@@ -248,8 +208,7 @@ html.side-panel,
     }
 
     body>main,
-    button,
-    .popup-content {
+    button {
         background-color: #202023;
         color: #fdfdfd;
     }
@@ -284,36 +243,19 @@ html.side-panel,
     }
 
     button:focus {
-        color: black;
-        border-color: black;
+        color: gray;
+        border-color: gray;
         background-color: #d2d2d2;
     }
 
-    .popup {
-        background-color: rgba(59, 59, 59, 0.95);
-    }
-
-    .popup-content {
-        box-shadow: 5px 5px #000000;
-    }
-
     html.side-panel,
     .side-panel .result-head,
     .side-panel body>main,
-    .side-panel button,
-    .side-panel .popup-content {
+    .side-panel button {
         background-color: #38383d;
     }
 
-    .side-panel .result-head {
-        border-bottom: dashed 1px rgb(191, 191, 191);
-    }
-
     .side-panel .result-cancel button {
         color: #fdfdfd;
     }
-
-    .popup-content main textarea {
-        color: black;
-    }
 }

+ 3 - 13
src/extension/ui/pages/pendings.html

@@ -19,7 +19,9 @@
 	<main>
 		<div class="result-row result-head">
 			<span class="result-url-title" id="URLTitleLabel">
-				<span id="URLLabel"></span> / <span id="titleLabel"></span>
+				<span id="URLLabel"></span>
+				<span class="result-head-separator">/</span>
+				<span id="titleLabel"></span>
 			</span>
 			<span class="result-status" id="statusLabel"></span>
 			<span class="result-cancel">&nbsp;</span>
@@ -27,18 +29,6 @@
 		<div id="resultsTable">
 		</div>
 	</main>
-	<div id="formAddUrls" class="popup">
-		<form class="popup-content">
-			<header>
-				<span id="addUrlsLabel"></span>
-			</header>
-			<main><textarea id="addUrlsInput" type="text"></textarea></main>
-			<footer>
-				<button type="submit" id="addUrlsOKButton"></button>
-				<button id="addUrlsCancelButton"></button>
-			</footer>
-		</form>
-	</div>
 	<script src="/lib/chrome-browser-polyfill.js"></script>
 	<script type="module" src="../bg/ui-pendings.js"></script>
 </body>