Browse Source

added "Auto-settings rules" in the options page

Gildas 7 năm trước cách đây
mục cha
commit
e7222b3d96

+ 53 - 1
_locales/en/messages.json

@@ -219,6 +219,54 @@
         "message": "Auto-save",
         "description": "Options sub-title: 'Auto-save'"
     },
+    "optionsAutoSettingsSubTitle": {
+        "message": "Auto-settings rules",
+        "description": "Options sub-title: 'Auto-settings rules'"
+    },
+    "optionsAutoSettingsUrl": {
+        "message": "URL",
+        "description": "Options label in the Auto-settings rules: 'URL'"
+    },
+    "optionsAutoSettingsUrlPlaceholder": {
+        "message": "Type a complete or partial URL (e.g. example.com)",
+        "description": "Placeholder in the Auto-settings rules: 'Type a complete or partial URL (e.g. example.com)'"
+    },    
+    "optionsAutoSettingsProfile": {
+        "message": "Profile",
+        "description": "Options label in the Auto-settings rules: 'Profile'"
+    },
+    "optionsAutoSettingsAutoSaveProfile": {
+        "message": "Auto-save profile",
+        "description": "Options label in the Auto-settings rules: 'Auto-save profile'"
+    },
+    "optionsAutoSettingsShowAllProfiles": {
+        "message": "display all profiles",
+        "description": "Options label in the Auto-settings rules: 'display all profiles'"
+    },
+    "optionsAutoSettingsShowAutoSaveProfile": {
+        "message": "display 'Auto-save profile' column",
+        "description": "Options label in the Auto-settings rules: 'display 'Auto-save profile' column'"
+    },
+    "optionsUpdateRuleTooltip": {
+        "message": "Update the rule",
+        "description": "Popup text 'Update the rule' in the options page"
+    },
+    "optionsDeleteRuleTooltip": {
+        "message": "Delete the rule",
+        "description": "Popup text 'Delete the rule' in the options page"
+    },
+    "optionsAddRuleTooltip": {
+        "message": "Add the rule",
+        "description": "Popup text 'Delete the rule' in the options page"
+    },
+    "optionsValidateChangesTooltip": {
+        "message": "Validate changes",
+        "description": "Popup text 'Validate changes' in the options page"
+    },
+    "optionsDeleteRuleConfirm": {
+        "message": "Confirm deletion of the selected rule",
+        "description": "Popup text 'Confirm deletion of the selected rule' in the options page"
+    },
     "optionAutoSaveLoadOrUnload": {
         "message": "auto-save after page load or on page unload",
         "description": "auto-save after page load or on page unload"
@@ -291,10 +339,14 @@
         "message": "120",
         "description": "Width of the log panel in pixels, it should be adjusted for the longest label beginning with 'log' (e.g. 'logPanelDeferredImages')"
     },
-    "profileDefaultSettingsLabel": {
+    "profileDefaultSettings": {
         "message": "Default settings",
         "description": "Label 'Default settings' of the default settings in the options page"
     },
+    "profileDisabled": {
+        "message": "Disabled",
+        "description": "Label 'Disabled' of the disabled profile name in the options page"
+    },
     "profileAddButtonTooltip": {
         "message": "Add a new profile",
         "description": "Tooltip 'Add a new profile' in the options page"

+ 53 - 1
_locales/fr/messages.json

@@ -219,6 +219,54 @@
         "message": "Auto-sauvegarde",
         "description": "Options sub-title: 'Auto-save'"
     },
+    "optionsAutoSettingsSubTitle": {
+        "message": "Règles d'auto-configuration",
+        "description": "Options sub-title: 'Auto-settings rules'"
+    },
+    "optionsAutoSettingsUrl": {
+        "message": "URL",
+        "description": "Options label in the auto-settings rules: 'URL'"
+    },
+    "optionsAutoSettingsUrlPlaceholder": {
+        "message": "Entrer une URL partielle ou complète (e.g. example.com)",
+        "description": "Placeholder in the Auto-settings rules: 'Type a complete or partial URL (e.g. example.com)'"
+    },
+    "optionsAutoSettingsProfile": {
+        "message": "Profil",
+        "description": "Options label in the auto-settings rules: 'Profile'"
+    },
+    "optionsAutoSettingsAutoSaveProfile": {
+        "message": "Profil auto-sauvegarde",
+        "description": "Options label in the auto-settings rules: 'Auto-save profile'"
+    },
+    "optionsAutoSettingsShowAllProfiles": {
+        "message": "afficher tous les profils",
+        "description": "Options label in the Auto-settings rules: 'display all profiles'"
+    },
+    "optionsAutoSettingsShowAutoSaveProfile": {
+        "message": "afficher la colonne 'Profil auto-sauvegarde'",
+        "description": "Options label in the Auto-settings rules: 'display 'Auto-save profile' column'"
+    },
+    "optionsUpdateRuleTooltip": {
+        "message": "Mettre à jour la règle",
+        "description": "Popup text 'Update the rule' in the options page"
+    },
+    "optionsDeleteRuleTooltip": {
+        "message": "Supprimer la règle",
+        "description": "Popup text 'Delete the rule' in the options page"
+    },
+    "optionsAddRuleTooltip": {
+        "message": "Ajouter la règle",
+        "description": "Popup text 'Delete the rule' in the options page"
+    },
+    "optionsValidateChangesTooltip": {
+        "message": "Valider les changements",
+        "description": "Popup text 'Valider les changements' in the options page"
+    },
+    "optionsDeleteRuleConfirm": {
+        "message": "Confimer la suppression de la règle selectionnée",
+        "description": "Popup text 'Confirm deletion of the selected rule' in the options page"
+    },
     "optionAutoSaveLoadOrUnload": {
         "message": "auto-sauvegarder après le chargement de la page ou au déchargement",
         "description": "auto-save after page load or on page unload"
@@ -291,10 +339,14 @@
         "message": "130",
         "description": "Width of the log panel in pixels, it should be adjusted for the longest label beginning with 'log' (e.g. 'logPanelDeferredImages')"
     },
-    "profileDefaultSettingsLabel": {
+    "profileDefaultSettings": {
         "message": "Configuration par défaut",
         "description": "Label 'Default settings' of the default settings in the options page"
     },
+    "profileDisabled": {
+        "message": "Désactivé",
+        "description": "Label 'Disabled' of the disabled profile name in the options page"
+    },
     "profileAddButtonTooltip": {
         "message": "Ajouter un nouveau profil",
         "description": "Tooltip 'Add a new profile' in the options page"

+ 53 - 1
_locales/ja/messages.json

@@ -219,6 +219,54 @@
         "message": "自動保存",
         "description": "オプションのサブタイトル: '自動保存'"
     },
+    "optionsAutoSettingsSubTitle": {
+        "message": "Auto-settings rules",
+        "description": "Options sub-title: 'Auto-settings rules'"
+    },
+    "optionsAutoSettingsUrl": {
+        "message": "URL",
+        "description": "Options label in the auto-settings rules: 'URL'"
+    },
+    "optionsAutoSettingsUrlPlaceholder": {
+        "message": "Entrer une URL partielle ou complète (e.g. example.com)",
+        "description": "Placeholder in the Auto-settings rules: 'Type a complete or partial URL (e.g. example.com)'"
+    },
+    "optionsAutoSettingsProfile": {
+        "message": "Profile",
+        "description": "Options label in the Auto-settings rules: 'Profile'"
+    },
+    "optionsAutoSettingsAutoSaveProfile": {
+        "message": "Auto-save profile",
+        "description": "Options label in the Auto-settings rules: 'Auto-save profile'"
+    },
+    "optionsAutoSettingsShowAllProfiles": {
+        "message": "display all profiles",
+        "description": "Options label in the Auto-settings rules: 'display all profiles'"
+    },
+    "optionsAutoSettingsShowAutoSaveProfile": {
+        "message": "display 'Auto-save profile' column",
+        "description": "Options label in the Auto-settings rules: 'display 'Auto-save profile' column'"
+    },
+    "optionsUpdateRuleTooltip": {
+        "message": "Update the rule",
+        "description": "Popup text 'Update the rule' in the options page"
+    },
+    "optionsDeleteRuleTooltip": {
+        "message": "Delete the rule",
+        "description": "Popup text 'Delete the rule' in the options page"
+    },
+    "optionsAddRuleTooltip": {
+        "message": "Add the rule",
+        "description": "Popup text 'Delete the rule' in the options page"
+    },
+    "optionsValidateChangesTooltip": {
+        "message": "Validate changes",
+        "description": "Popup text 'Validate changes' in the options page"
+    },
+    "optionsDeleteRuleConfirm": {
+        "message": "Confirm deletion of the selected rule",
+        "description": "Popup text 'Confirm deletion of the selected rule' in the options page"
+    },
     "optionAutoSaveLoadOrUnload": {
         "message": "ページの読み込み後またはページの非読み込み時に自動保存",
         "description": "ページの読み込み後またはページの非読み込み時に自動保存"
@@ -291,10 +339,14 @@
         "message": "110",
         "description": "Width of the log panel in pixels, it should be adjusted for the longest label beginning with 'log' (e.g. 'logPanelDeferredImages')"
     },
-    "profileDefaultSettingsLabel": {
+    "profileDefaultSettings": {
         "message": "デフォルトの設定",
         "description": "Label 'Default settings' of the default settings in the options page"
     },
+    "profileDisabled": {
+        "message": "Disabled",
+        "description": "Label 'Disabled' of the disabled profile name in the options page"
+    },
     "profileAddButtonTooltip": {
         "message": "新しいプロファイルを追加します",
         "description": "Tooltip 'Add a new profile' in the options page"

+ 53 - 1
_locales/pl/messages.json

@@ -219,6 +219,54 @@
         "message": "Automatyczny zapis",
         "description": "Options sub-title: 'Auto-save'"
     },
+    "optionsAutoSettingsSubTitle": {
+        "message": "Auto-settings",
+        "description": "Options sub-title: 'Auto-settings rules'"
+    },
+    "optionsAutoSettingsUrl": {
+        "message": "URL",
+        "description": "Options label in the auto-settings rules: 'URL'"
+    },
+    "optionsAutoSettingsUrlPlaceholder": {
+        "message": "Entrer une URL partielle ou complète (e.g. example.com)",
+        "description": "Placeholder in the Auto-settings rules: 'Type a complete or partial URL (e.g. example.com)'"
+    },
+    "optionsAutoSettingsProfile": {
+        "message": "Profile",
+        "description": "Options label in the Auto-settings rules: 'Profile'"
+    },
+    "optionsAutoSettingsAutoSaveProfile": {
+        "message": "Auto-save profile",
+        "description": "Options label in the Auto-settings rules: 'Auto-save profile'"
+    },
+    "optionsAutoSettingsShowAllProfiles": {
+        "message": "display all profiles",
+        "description": "Options label in the Auto-settings rules: 'display all profiles'"
+    },
+    "optionsAutoSettingsShowAutoSaveProfile": {
+        "message": "display 'Auto-save profile' column",
+        "description": "Options label in the Auto-settings rules: 'display 'Auto-save profile' column'"
+    },
+    "optionsUpdateRuleTooltip": {
+        "message": "Update the rule",
+        "description": "Popup text 'Update the rule' in the options page"
+    },
+    "optionsDeleteRuleTooltip": {
+        "message": "Delete the rule",
+        "description": "Popup text 'Delete the rule' in the options page"
+    },
+    "optionsAddRuleTooltip": {
+        "message": "Add the rule",
+        "description": "Popup text 'Delete the rule' in the options page"
+    },
+    "optionsValidateChangesTooltip": {
+        "message": "Validate changes",
+        "description": "Popup text 'Validate changes' in the options page"
+    },
+    "optionsDeleteRuleConfirm": {
+        "message": "Confirm deletion of the selected rule",
+        "description": "Popup text 'Confirm deletion of the selected rule' in the options page"
+    },
     "optionAutoSaveLoadOrUnload": {
         "message": "automatycznie zapisuj po załadowaniu lub przy wyładowaniu strony",
         "description": "auto-save after page load or on page unload"
@@ -291,10 +339,14 @@
         "message": "130",
         "description": "Width of the log panel in pixels, it should be adjusted for the longest label beginning with 'log' (e.g. 'logPanelDeferredImages')"
     },
-    "profileDefaultSettingsLabel": {
+    "profileDefaultSettings": {
         "message": "Ustawienia domyślne",
         "description": "Label 'Default settings' of the default settings in the options page"
     },
+    "profileDisabled": {
+        "message": "Disabled",
+        "description": "Label 'Disabled' of the disabled profile name in the options page"
+    },
     "profileAddButtonTooltip": {
         "message": "Dodaj nowy profil",
         "description": "Tooltip 'Add a new profile' in the options page"

+ 53 - 1
_locales/ru/messages.json

@@ -219,6 +219,54 @@
         "message": "Автосохранение",
         "description": "Options sub-title: 'Auto-save'"
     },
+    "optionsAutoSettingsSubTitle": {
+        "message": "Auto-settings",
+        "description": "Options sub-title: 'Auto-settings rules'"
+    },
+    "optionsAutoSettingsUrl": {
+        "message": "URL",
+        "description": "Options label in the auto-settings rules: 'URL'"
+    },
+    "optionsAutoSettingsUrlPlaceholder": {
+        "message": "Entrer une URL partielle ou complète (e.g. example.com)",
+        "description": "Placeholder in the Auto-settings rules: 'Type a complete or partial URL (e.g. example.com)'"
+    },
+    "optionsAutoSettingsProfile": {
+        "message": "Profile",
+        "description": "Options label in the Auto-settings rules: 'Profile'"
+    },
+    "optionsAutoSettingsAutoSaveProfile": {
+        "message": "Auto-save profile",
+        "description": "Options label in the Auto-settings rules: 'Auto-save profile'"
+    },
+    "optionsAutoSettingsShowAllProfiles": {
+        "message": "display all profiles",
+        "description": "Options label in the Auto-settings rules: 'display all profiles'"
+    },
+    "optionsAutoSettingsShowAutoSaveProfile": {
+        "message": "display 'Auto-save profile' column",
+        "description": "Options label in the Auto-settings rules: 'display 'Auto-save profile' column'"
+    },
+    "optionsUpdateRuleTooltip": {
+        "message": "Update the rule",
+        "description": "Popup text 'Update the rule' in the options page"
+    },
+    "optionsDeleteRuleTooltip": {
+        "message": "Delete the rule",
+        "description": "Popup text 'Delete the rule' in the options page"
+    },
+    "optionsAddRuleTooltip": {
+        "message": "Add the rule",
+        "description": "Popup text 'Delete the rule' in the options page"
+    },
+    "optionsValidateChangesTooltip": {
+        "message": "Validate changes",
+        "description": "Popup text 'Validate changes' in the options page"
+    },
+    "optionsDeleteRuleConfirm": {
+        "message": "Confirm deletion of the selected rule",
+        "description": "Popup text 'Confirm deletion of the selected rule' in the options page"
+    },
     "optionAutoSaveLoadOrUnload": {
         "message": "автосохранение после загрузки или выгрузки страницы",
         "description": "auto-save after page load or on page unload"
@@ -291,10 +339,14 @@
         "message": "120",
         "description": "Width of the log panel in pixels, it should be adjusted for the longest label beginning with 'log' (e.g. 'logPanelDeferredImages')"
     },
-    "profileDefaultSettingsLabel": {
+    "profileDefaultSettings": {
         "message": "Настройки по умолчанию",
         "description": "Label 'Default settings' of the default settings in the options page"
     },
+    "profileDisabled": {
+        "message": "Disabled",
+        "description": "Label 'Disabled' of the disabled profile name in the options page"
+    },
     "profileAddButtonTooltip": {
         "message": "Добавить профиль",
         "description": "Tooltip 'Add a new profile' in the options page"

+ 53 - 1
_locales/zh_CN/messages.json

@@ -219,6 +219,54 @@
         "message": "自动保存",
         "description": "选项页副标题: '自动保存'"
     },
+    "optionsAutoSettingsSubTitle": {
+        "message": "Auto-settings",
+        "description": "Options sub-title: 'Auto-settings rules'"
+    },
+    "optionsAutoSettingsUrl": {
+        "message": "URL",
+        "description": "Options label in the auto-settings rules: 'URL'"
+    },
+    "optionsAutoSettingsUrlPlaceholder": {
+        "message": "Entrer une URL partielle ou complète (e.g. example.com)",
+        "description": "Placeholder in the Auto-settings rules: 'Type a complete or partial URL (e.g. example.com)'"
+    },
+    "optionsAutoSettingsProfile": {
+        "message": "Profile",
+        "description": "Options label in the Auto-settings rules: 'Profile'"
+    },
+    "optionsAutoSettingsAutoSaveProfile": {
+        "message": "Auto-save profile",
+        "description": "Options label in the Auto-settings rules: 'Auto-save profile'"
+    },
+    "optionsAutoSettingsShowAllProfiles": {
+        "message": "display all profiles",
+        "description": "Options label in the Auto-settings rules: 'display all profiles'"
+    },
+    "optionsAutoSettingsShowAutoSaveProfile": {
+        "message": "display 'Auto-save profile' column",
+        "description": "Options label in the Auto-settings rules: 'display 'Auto-save profile' column'"
+    },
+    "optionsUpdateRuleTooltip": {
+        "message": "Update the rule",
+        "description": "Popup text 'Update the rule' in the options page"
+    },
+    "optionsDeleteRuleTooltip": {
+        "message": "Delete the rule",
+        "description": "Popup text 'Delete the rule' in the options page"
+    },
+    "optionsAddRuleTooltip": {
+        "message": "Add the rule",
+        "description": "Popup text 'Delete the rule' in the options page"
+    },
+    "optionsValidateChangesTooltip": {
+        "message": "Validate changes",
+        "description": "Popup text 'Validate changes' in the options page"
+    },
+    "optionsDeleteRuleConfirm": {
+        "message": "Confirm deletion of the selected rule",
+        "description": "Popup text 'Confirm deletion of the selected rule' in the options page"
+    },
     "optionAutoSaveLoadOrUnload": {
         "message": "页面加载或页面卸载后自动保存",
         "description": "页面加载或页面卸载后自动保存"
@@ -291,10 +339,14 @@
         "message": "115",
         "description": "Width of the log panel in pixels, it should be adjusted for the longest label beginning with 'log' (e.g. 'logPanelDeferredImages')"
     },
-    "profileDefaultSettingsLabel": {
+    "profileDefaultSettings": {
         "message": "默认设置",
         "description": "Label 'Default settings' of the default settings in the options page"
     },
+    "profileDisabled": {
+        "message": "Disabled",
+        "description": "Label 'Disabled' of the disabled profile name in the options page"
+    },
     "profileAddButtonTooltip": {
         "message": "添加一个新配置文件",
         "description": "Tooltip 'Add a new profile' in the options page"

+ 16 - 15
extension/core/bg/autosave.js

@@ -23,9 +23,9 @@
 singlefile.autosave = (() => {
 
 	browser.tabs.onUpdated.addListener(async (tabId, changeInfo, tab) => {
-		const [config, tabsData] = await Promise.all([singlefile.config.get(), singlefile.tabsData.get()]);
-		const options = config.profiles[tabsData.profileName || singlefile.config.DEFAULT_PROFILE_NAME];
-		if ((options.autoSaveLoad || options.autoSaveLoadOrUnload) && (tabsData.autoSaveAll || (tabsData.autoSaveUnpinned && !tab.pinned) || (tabsData[tab.id] && tabsData[tab.id].autoSave))) {
+		const tabsData = await singlefile.tabsData.get();
+		const options = await singlefile.config.getOptions(tabsData.profileName, tab.url, true);
+		if (options && ((options.autoSaveLoad || options.autoSaveLoadOrUnload) && (tabsData.autoSaveAll || (tabsData.autoSaveUnpinned && !tab.pinned) || (tabsData[tab.id] && tabsData[tab.id].autoSave)))) {
 			if (changeInfo.status == "complete") {
 				singlefile.ui.saveTab(tab, { autoSave: true });
 			}
@@ -33,10 +33,10 @@ singlefile.autosave = (() => {
 	});
 	browser.runtime.onMessage.addListener((message, sender) => {
 		if (message.isAutoSaveEnabled) {
-			return isAutoSaveEnabled(sender.tab.id);
+			return isAutoSaveEnabled(sender.tab);
 		}
 		if (message.autoSaveContent) {
-			saveContent(message, sender.tab.id, sender.tab.incognito);
+			saveContent(message, sender.tab);
 		}
 	});
 
@@ -63,9 +63,10 @@ singlefile.autosave = (() => {
 		refresh
 	};
 
-	async function saveContent(message, tabId, incognito) {
-		const [config, tabsData] = await Promise.all([singlefile.config.get(), singlefile.tabsData.get()]);
-		const options = config.profiles[tabsData.profileName || singlefile.config.DEFAULT_PROFILE_NAME];
+	async function saveContent(message, tab) {
+		const tabsData = await singlefile.tabsData.get();
+		const options = await singlefile.config.getOptions(tabsData.profileName, tab.url, true);
+		const tabId = tab.id;
 		options.content = message.content;
 		options.url = message.url;
 		options.framesData = message.framesData;
@@ -80,7 +81,7 @@ singlefile.autosave = (() => {
 		options.insertFaviconLink = true;
 		options.backgroundTab = true;
 		options.autoSave = true;
-		options.incognito = incognito;
+		options.incognito = tab.incognito;
 		options.tabId = tabId;
 		options.sessionId = 0;
 		let index = 0, maxIndex = 0;
@@ -115,13 +116,13 @@ singlefile.autosave = (() => {
 			}
 			tabsData[tabId].autoSave = enabled;
 			await singlefile.tabsData.set(tabsData);
-			singlefile.ui.refresh(tabId);
+			singlefile.ui.refresh(tab);
 		}
 	}
 
-	async function isAutoSaveEnabled(tabId) {
-		const [config, tabsData, autoSaveEnabled] = await Promise.all([singlefile.config.get(), singlefile.tabsData.get(), enabled(tabId)]);
-		const options = config.profiles[tabsData.profileName || singlefile.config.DEFAULT_PROFILE_NAME];
+	async function isAutoSaveEnabled(tab) {
+		const tabsData = await singlefile.tabsData.get();
+		const [options, autoSaveEnabled] = await Promise.all([singlefile.config.getOptions(tabsData.profileName, tab.url, true), enabled(tab.id)]);
 		return { autoSaveEnabled, options };
 	}
 
@@ -134,8 +135,8 @@ singlefile.autosave = (() => {
 		const tabs = await browser.tabs.query({});
 		return Promise.all(tabs.map(async tab => {
 			try {
-				const [config, tabsData, autoSaveEnabled] = await Promise.all([singlefile.config.get(), singlefile.tabsData.get(), enabled(tab.id)]);
-				const options = config.profiles[tabsData.profileName || singlefile.config.DEFAULT_PROFILE_NAME];
+				const tabsData = await singlefile.tabsData.get();
+				const [options, autoSaveEnabled] = await Promise.all([singlefile.config.getOptions(tabsData.profileName, tab.url, true), enabled(tab.id)]);
 				await browser.tabs.sendMessage(tab.id, { autoSaveUnloadEnabled: true, autoSaveEnabled, options });
 			} catch (error) {
 				/* ignored */

+ 88 - 16
extension/core/bg/config.js

@@ -23,6 +23,7 @@
 singlefile.config = (() => {
 
 	const DEFAULT_PROFILE_NAME = "__Default_Settings__";
+	const DISABLED_PROFILE_NAME = "__Disabled_Settings__";
 
 	const DEFAULT_CONFIG = {
 		removeHiddenElements: true,
@@ -73,13 +74,17 @@ singlefile.config = (() => {
 		if (!config.profiles) {
 			delete defaultConfig.tabsData;
 			applyUpgrade(defaultConfig);
-			const config = { profiles: {}, defaultProfile: DEFAULT_PROFILE_NAME };
-			config.profiles[config.defaultProfile] = defaultConfig;
+			const config = { profiles: {}, rules: [] };
+			config.profiles[DEFAULT_PROFILE_NAME] = defaultConfig;
 			browser.storage.local.remove(Object.keys(DEFAULT_CONFIG));
 			return browser.storage.local.set(config);
 		} else {
+			if (!config.rules) {
+				config.rules = [];
+			}
 			Object.keys(config.profiles).forEach(profileName => applyUpgrade(config.profiles[profileName]));
-			return browser.storage.local.set({ profiles: config.profiles });
+			await browser.storage.local.remove(["profiles", "defaultProfile", "rules"]);
+			return browser.storage.local.set({ profiles: config.profiles, rules: config.rules });
 		}
 	}
 
@@ -184,31 +189,38 @@ singlefile.config = (() => {
 
 	async function getConfig() {
 		await pendingUpgradePromise;
-		return browser.storage.local.get(["profiles", "defaultProfile"]);
+		return browser.storage.local.get(["profiles", "rules"]);
 	}
 
 	return {
+		DISABLED_PROFILE_NAME,
 		DEFAULT_PROFILE_NAME,
-		async create(profileName) {
+		async createProfile(profileName) {
 			const config = await getConfig();
 			if (Object.keys(config.profiles).includes(profileName)) {
 				throw new Error("Duplicate profile name");
 			}
 			config.profiles[profileName] = DEFAULT_CONFIG;
-			await browser.storage.local.set({ profiles: config.profiles, defaultProfile: DEFAULT_PROFILE_NAME });
+			await browser.storage.local.set({ profiles: config.profiles });
+		},
+		async getProfiles() {
+			const config = await getConfig();
+			return config.profiles;
 		},
-		async get() {
-			return getConfig();
+		async getOptions(profileName, url, autoSave) {
+			const config = await getConfig();
+			const urlRule = config.rules.find(rule => url && url.includes(rule.url));
+			return urlRule ? config.profiles[urlRule[autoSave ? "autoSaveProfile" : "profile"]] : config.profiles[profileName || singlefile.config.DEFAULT_PROFILE_NAME];
 		},
-		async update(profileName, data) {
+		async updateProfile(profileName, profile) {
 			const config = await getConfig();
 			if (!Object.keys(config.profiles).includes(profileName)) {
 				throw new Error("Profile not found");
 			}
-			config.profiles[profileName] = data;
+			config.profiles[profileName] = profile;
 			await browser.storage.local.set({ profiles: config.profiles });
 		},
-		async rename(oldProfileName, profileName) {
+		async renameProfile(oldProfileName, profileName) {
 			const [config, tabsData] = await Promise.all([getConfig(), singlefile.tabsData.get()]);
 			if (!Object.keys(config.profiles).includes(oldProfileName)) {
 				throw new Error("Profile not found");
@@ -224,10 +236,18 @@ singlefile.config = (() => {
 				await singlefile.tabsData.set(tabsData);
 			}
 			config.profiles[profileName] = config.profiles[oldProfileName];
+			config.rules.forEach(rule => {
+				if (rule.profile == oldProfileName) {
+					rule.profile = profileName;
+				}
+				if (rule.autoSaveProfile == oldProfileName) {
+					rule.autoSaveProfile = profileName;
+				}
+			});
 			delete config.profiles[oldProfileName];
-			await browser.storage.local.set({ profiles: config.profiles });
+			await browser.storage.local.set({ profiles: config.profiles, rules: config.rules });
 		},
-		async delete(profileName) {
+		async deleteProfile(profileName) {
 			const [config, tabsData] = await Promise.all([getConfig(), singlefile.tabsData.get()]);
 			if (!Object.keys(config.profiles).includes(profileName)) {
 				throw new Error("Profile not found");
@@ -239,16 +259,68 @@ singlefile.config = (() => {
 				delete tabsData.profileName;
 				await singlefile.tabsData.set(tabsData);
 			}
+			config.rules.forEach(rule => {
+				if (rule.profile == profileName) {
+					rule.profile = DEFAULT_PROFILE_NAME;
+				}
+				if (rule.autoSaveProfile == profileName) {
+					rule.autoSaveProfile = DEFAULT_PROFILE_NAME;
+				}
+			});
 			delete config.profiles[profileName];
-			await browser.storage.local.set({ profiles: config.profiles });
+			await browser.storage.local.set({ profiles: config.profiles, rules: config.rules });
+		},
+		async getRules() {
+			const config = await getConfig();
+			return config.rules;
+		},
+		async addRule(url, profile, autoSaveProfile) {
+			if (!url) {
+				throw new Error("URL is empty");
+			}
+			const config = await getConfig();
+			if (config.rules.find(rule => rule.url == url)) {
+				throw new Error("URL already exists");
+			}
+			config.rules.push({
+				url,
+				profile,
+				autoSaveProfile
+			});
+			await browser.storage.local.set({ rules: config.rules });
+		},
+		async deleteRule(url) {
+			if (!url) {
+				throw new Error("URL is empty");
+			}
+			const config = await getConfig();
+			config.rules = config.rules.filter(rule => rule.url != url);
+			await browser.storage.local.set({ rules: config.rules });
+		},
+		async updateRule(url, newURL, profile, autoSaveProfile) {
+			if (!url || !newURL) {
+				throw new Error("URL is empty");
+			}
+			const config = await getConfig();
+			const urlConfig = config.rules.find(rule => rule.url == url);
+			if (!urlConfig) {
+				throw new Error("URL not found");
+			}
+			if (config.rules.find(rule => rule.url == newURL && rule.url != url)) {
+				throw new Error("New URL already exists");
+			}
+			urlConfig.url = newURL;
+			urlConfig.profile = profile;
+			urlConfig.autoSaveProfile = autoSaveProfile;
+			await browser.storage.local.set({ rules: config.rules });
 		},
 		async reset() {
 			await pendingUpgradePromise;
 			const tabsData = await singlefile.tabsData.get();
 			delete tabsData.profileName;
 			await singlefile.tabsData.set(tabsData);
-			await browser.storage.local.remove(["profiles", "defaultProfile"]);
-			await browser.storage.local.set({ profiles: { [DEFAULT_PROFILE_NAME]: DEFAULT_CONFIG }, defaultProfile: DEFAULT_PROFILE_NAME });
+			await browser.storage.local.remove(["profiles", "rules"]);
+			await browser.storage.local.set({ profiles: { [DEFAULT_PROFILE_NAME]: DEFAULT_CONFIG }, rules: [] });
 		}
 	};
 

+ 4 - 4
extension/core/bg/core.js

@@ -27,15 +27,15 @@ singlefile.core = (() => {
 	return { saveTab, autoSaveTab, isAllowedURL };
 
 	async function saveTab(tab, options) {
-		const [config, tabsData] = await Promise.all([singlefile.config.get(), singlefile.tabsData.get()]);
-		const mergedOptions = config.profiles[tabsData.profileName || singlefile.config.DEFAULT_PROFILE_NAME];
+		const tabsData = await singlefile.tabsData.get();
+		const mergedOptions = await singlefile.config.getOptions(tabsData.profileName, tab.url);
 		Object.keys(options).forEach(key => mergedOptions[key] = options[key]);
 		return singlefile.runner.saveTab(tab, mergedOptions);
 	}
 
 	async function autoSaveTab(tab) {
-		const [config, tabsData] = await Promise.all([singlefile.config.get(), singlefile.tabsData.get()]);
-		const options = config.profiles[tabsData.profileName || singlefile.config.DEFAULT_PROFILE_NAME];
+		const tabsData = await singlefile.tabsData.get();
+		const options = await singlefile.config.getOptions(tabsData.profileName, tab.url, true);
 		if (singlefile.autosave.enabled(tab.id)) {
 			await browser.tabs.sendMessage(tab.id, { autoSavePage: true, options });
 		}

+ 1 - 1
extension/core/content/content-bootstrap.js

@@ -80,7 +80,7 @@ this.singlefile.autosave = this.singlefile.autosave || (async () => {
 	}
 
 	async function refresh() {
-		if (autoSaveEnabled && (options.autoSaveUnload || options.autoSaveLoadOrUnload)) {
+		if (autoSaveEnabled && options && (options.autoSaveUnload || options.autoSaveLoadOrUnload)) {
 			if (!listenerAdded) {
 				addEventListener("unload", onUnload);
 				addEventListener("single-file-push-state", onUnload);

+ 2 - 2
extension/ui/bg/bg-ui.js

@@ -42,8 +42,8 @@ singlefile.ui = (() => {
 		isAllowedURL(url) {
 			return singlefile.core.isAllowedURL(url);
 		},
-		refresh(tabId) {
-			return Promise.all([singlefile.ui.menu.refresh(), singlefile.ui.button.refresh(tabId)]);
+		refresh(tab) {
+			return Promise.all([singlefile.ui.menu.refresh(tab), singlefile.ui.button.refresh(tab.id)]);
 		},
 		onProgress(tabId, index, maxIndex, options) {
 			singlefile.ui.button.onProgress(tabId, index, maxIndex, options);

+ 8 - 8
extension/ui/bg/ui-menu.js

@@ -47,9 +47,9 @@ singlefile.ui.menu = (() => {
 		refresh
 	};
 
-	async function refresh() {
-		const [config, tabsData] = await Promise.all([singlefile.config.get(), singlefile.tabsData.get()]);
-		const options = config.profiles[tabsData.profileName || singlefile.config.DEFAULT_PROFILE_NAME];
+	async function refresh(tab) {
+		const [profiles, tabsData] = await Promise.all([singlefile.config.getProfiles(), singlefile.tabsData.get()]);
+		const options = await singlefile.config.getOptions(tabsData.profileName, tab && tab.url, true);
 		if (BROWSER_MENUS_API_SUPPORTED) {
 			const pageContextsEnabled = ["page", "frame", "image", "link", "video", "audio"];
 			const defaultContextsDisabled = ["browser_action"];
@@ -104,7 +104,7 @@ singlefile.ui.menu = (() => {
 					type: "separator"
 				});
 			}
-			if (Object.keys(config.profiles).length > 1) {
+			if (Object.keys(profiles).length > 1) {
 				browser.menus.create({
 					id: MENU_ID_SELECT_PROFILE,
 					title: browser.i18n.getMessage("menuSelectProfile"),
@@ -114,11 +114,11 @@ singlefile.ui.menu = (() => {
 					id: MENU_ID_SELECT_PROFILE_PREFIX + "default",
 					type: "radio",
 					contexts: defaultContexts,
-					title: browser.i18n.getMessage("profileDefaultSettingsLabel"),
+					title: browser.i18n.getMessage("profileDefaultSettings"),
 					checked: !tabsData.profileName || tabsData.profileName == singlefile.config.DEFAULT_PROFILE_NAME,
 					parentId: MENU_ID_SELECT_PROFILE
 				});
-				Object.keys(config.profiles).forEach((profileName, profileIndex) => {
+				Object.keys(profiles).forEach((profileName, profileIndex) => {
 					if (profileName != singlefile.config.DEFAULT_PROFILE_NAME) {
 						browser.menus.create({
 							id: MENU_ID_SELECT_PROFILE_PREFIX + profileIndex,
@@ -225,13 +225,13 @@ singlefile.ui.menu = (() => {
 					refreshExternalComponents(tab.id, { autoSave: true });
 				}
 				if (event.menuItemId.startsWith(MENU_ID_SELECT_PROFILE_PREFIX)) {
-					const [config, tabsData] = await Promise.all([singlefile.config.get(), singlefile.tabsData.get()]);
+					const [profiles, tabsData] = await Promise.all([singlefile.config.getProfiles(), singlefile.tabsData.get()]);
 					const profileId = event.menuItemId.split(MENU_ID_SELECT_PROFILE_PREFIX)[1];
 					if (profileId == "default") {
 						tabsData.profileName = singlefile.config.DEFAULT_PROFILE_NAME;
 					} else {
 						const profileIndex = Number(profileId);
-						tabsData.profileName = Object.keys(config.profiles)[profileIndex];
+						tabsData.profileName = Object.keys(profiles)[profileIndex];
 					}
 					await singlefile.tabsData.set(tabsData);
 					refresh();

+ 141 - 16
extension/ui/bg/ui-options.js

@@ -68,6 +68,12 @@
 	const stylesheetsLabel = document.getElementById("stylesheetsLabel");
 	const otherResourcesLabel = document.getElementById("otherResourcesLabel");
 	const autoSaveLabel = document.getElementById("autoSaveLabel");
+	const autoSettingsLabel = document.getElementById("autoSettingsLabel");
+	const autoSettingsUrlLabel = document.getElementById("autoSettingsUrlLabel");
+	const autoSettingsProfileLabel = document.getElementById("autoSettingsProfileLabel");
+	const autoSettingsAutoSaveProfileLabel = document.getElementById("autoSettingsAutoSaveProfileLabel");
+	const showAllProfilesLabel = document.getElementById("showAllProfilesLabel");
+	const showAutoSaveProfileLabel = document.getElementById("showAutoSaveProfileLabel");
 	const groupDuplicateImagesLabel = document.getElementById("groupDuplicateImagesLabel");
 	const confirmInfobarLabel = document.getElementById("confirmInfobarLabel");
 	const infobarTemplateLabel = document.getElementById("infobarTemplateLabel");
@@ -111,13 +117,49 @@
 	const infobarTemplateInput = document.getElementById("infobarTemplateInput");
 	const confirmInfobarInput = document.getElementById("confirmInfobarInput");
 	const expandAllButton = document.getElementById("expandAllButton");
+	const ruleUrlInput = document.getElementById("ruleUrlInput");
+	const ruleProfileInput = document.getElementById("ruleProfileInput");
+	const ruleAutoSaveProfileInput = document.getElementById("ruleAutoSaveProfileInput");
+	const ruleEditProfileInput = document.getElementById("ruleEditProfileInput");
+	const ruleEditAutoSaveProfileInput = document.getElementById("ruleEditAutoSaveProfileInput");
+	const ruleAddButton = document.getElementById("ruleAddButton");
+	const rulesElement = document.querySelector(".rules-table");
+	const rulesContainerElement = document.querySelector(".rules-table-container");
+	const ruleEditUrlInput = document.getElementById("ruleEditUrlInput");
+	const ruleEditButton = document.getElementById("ruleEditButton");
+	const showAllProfilesInput = document.getElementById("showAllProfilesInput");
+	const showAutoSaveProfileInput = document.getElementById("showAutoSaveProfileInput");
 	let pendingSave = Promise.resolve();
+	ruleAddButton.addEventListener("click", async () => {
+		try {
+			await singlefile.config.addRule(ruleUrlInput.value, ruleProfileInput.value, ruleAutoSaveProfileInput.value);
+			ruleUrlInput.value = "";
+			ruleProfileInput.value = ruleAutoSaveProfileInput.value = singlefile.config.DEFAULT_PROFILE_NAME;
+			await refresh();
+		} catch (error) {
+			// ignored
+		}
+	}, false);
+	ruleUrlInput.onclick = ruleUrlInput.onkeyup = ruleUrlInput.onchange = async () => {
+		ruleAddButton.disabled = !ruleUrlInput.value;
+		const rules = await singlefile.config.getRules();
+		if (rules.find(rule => rule.url == ruleUrlInput.value)) {
+			ruleAddButton.disabled = true;
+		}
+	};
+	showAutoSaveProfileInput.addEventListener("click", () => {
+		if (showAutoSaveProfileInput.checked) {
+			rulesContainerElement.classList.remove("compact");
+		} else {
+			rulesContainerElement.classList.add("compact");
+		}
+	}, false);
 	addProfileButton.addEventListener("click", async () => {
 		const profileName = prompt(browser.i18n.getMessage("profileAddPrompt"));
 		if (profileName) {
 			try {
-				await singlefile.config.create(profileName);
-				await await Promise.all([refresh(profileName), singlefile.ui.menu.refresh()]);
+				await singlefile.config.createProfile(profileName);
+				await Promise.all([refresh(profileName), singlefile.ui.menu.refresh()]);
 			} catch (error) {
 				// ignored
 			}
@@ -126,7 +168,7 @@
 	deleteProfileButton.addEventListener("click", async () => {
 		if (confirm(browser.i18n.getMessage("profileDeleteConfirm"))) {
 			try {
-				await singlefile.config.delete(profileNamesInput.value);
+				await singlefile.config.deleteProfile(profileNamesInput.value);
 				profileNamesInput.value = null;
 				await Promise.all([refresh(), singlefile.ui.menu.refresh()]);
 			} catch (error) {
@@ -138,7 +180,7 @@
 		const profileName = prompt(browser.i18n.getMessage("profileRenamePrompt"), profileNamesInput.value);
 		if (profileName) {
 			try {
-				await singlefile.config.rename(profileNamesInput.value, profileName);
+				await singlefile.config.renameProfile(profileNamesInput.value, profileName);
 				await Promise.all([refresh(profileName), singlefile.ui.menu.refresh()]);
 			} catch (error) {
 				// ignored
@@ -179,10 +221,17 @@
 		document.querySelectorAll("details").forEach(detailElement => detailElement.open = Boolean(expandAllButton.className));
 	}, false);
 	document.body.onchange = async event => {
-		if (event.target != profileNamesInput) {
-			await update();
+		let target = event.target;
+		if (target != ruleUrlInput && target != ruleProfileInput && target != ruleAutoSaveProfileInput && target != ruleEditUrlInput && target != ruleEditProfileInput && target != ruleEditAutoSaveProfileInput && target != showAutoSaveProfileInput) {
+			if (target != profileNamesInput && target != showAllProfilesInput) {
+				await update();
+			}
+			if (target == profileNamesInput) {
+				await refresh(profileNamesInput.value);
+			} else {
+				await refresh();
+			}
 		}
-		await refresh();
 	};
 	addProfileButton.title = browser.i18n.getMessage("profileAddButtonTooltip");
 	deleteProfileButton.title = browser.i18n.getMessage("profileDeleteButtonTooltip");
@@ -234,29 +283,101 @@
 	confirmInfobarLabel.textContent = browser.i18n.getMessage("optionConfirmInfobar");
 	resetButton.textContent = browser.i18n.getMessage("optionsResetButton");
 	resetButton.title = browser.i18n.getMessage("optionsResetTooltip");
+	autoSettingsLabel.textContent = browser.i18n.getMessage("optionsAutoSettingsSubTitle");
+	autoSettingsUrlLabel.textContent = browser.i18n.getMessage("optionsAutoSettingsUrl");
+	autoSettingsProfileLabel.textContent = browser.i18n.getMessage("optionsAutoSettingsProfile");
+	autoSettingsAutoSaveProfileLabel.textContent = browser.i18n.getMessage("optionsAutoSettingsAutoSaveProfile");
+	ruleAddButton.title = browser.i18n.getMessage("optionsAddRuleTooltip");
+	ruleEditButton.title = browser.i18n.getMessage("optionsValidateChangesTooltip");
+	showAllProfilesLabel.textContent = browser.i18n.getMessage("optionsAutoSettingsShowAllProfiles");
+	showAutoSaveProfileLabel.textContent = browser.i18n.getMessage("optionsAutoSettingsShowAutoSaveProfile");
+	ruleUrlInput.placeholder = ruleEditUrlInput.placeholder = browser.i18n.getMessage("optionsAutoSettingsUrlPlaceholder");
 
 	refresh();
 
 	async function refresh(profileName) {
-		const options = await bgPage.singlefile.config.get();
-		const selectedProfileName = profileName || profileNamesInput.value || options.defaultProfile;
-		profileNamesInput.childNodes.forEach(node => node.remove());
-		const profileNames = Object.keys(options.profiles);
+		const [profiles, rules] = await Promise.all([singlefile.config.getProfiles(), singlefile.config.getRules()]);
+		const selectedProfileName = profileName || profileNamesInput.value || singlefile.config.DEFAULT_PROFILE_NAME;
+		Array.from(profileNamesInput.childNodes).forEach(node => node.remove());
+		const profileNames = Object.keys(profiles);
 		profileNamesInput.options.length = 0;
-		const optionElement = document.createElement("option");
+		ruleProfileInput.options.length = 0;
+		ruleAutoSaveProfileInput.options.length = 0;
+		ruleEditProfileInput.options.length = 0;
+		ruleEditAutoSaveProfileInput.options.length = 0;
+		let optionElement = document.createElement("option");
 		optionElement.value = singlefile.config.DEFAULT_PROFILE_NAME;
-		optionElement.textContent = browser.i18n.getMessage("profileDefaultSettingsLabel");
+		optionElement.textContent = browser.i18n.getMessage("profileDefaultSettings");
 		profileNamesInput.appendChild(optionElement);
+		ruleProfileInput.appendChild(optionElement.cloneNode(true));
+		ruleAutoSaveProfileInput.appendChild(optionElement.cloneNode(true));
+		ruleEditProfileInput.appendChild(optionElement.cloneNode(true));
+		ruleEditAutoSaveProfileInput.appendChild(optionElement.cloneNode(true));
 		profileNames.forEach(profileName => {
 			if (profileName != singlefile.config.DEFAULT_PROFILE_NAME) {
 				const optionElement = document.createElement("option");
 				optionElement.value = optionElement.textContent = profileName;
 				profileNamesInput.appendChild(optionElement);
+				ruleProfileInput.appendChild(optionElement.cloneNode(true));
+				ruleAutoSaveProfileInput.appendChild(optionElement.cloneNode(true));
+				ruleEditProfileInput.appendChild(optionElement.cloneNode(true));
+				ruleEditAutoSaveProfileInput.appendChild(optionElement.cloneNode(true));
 			}
 		});
+		optionElement = document.createElement("option");
+		optionElement.value = singlefile.config.DISABLED_PROFILE_NAME;
+		optionElement.textContent = browser.i18n.getMessage("profileDisabled");
+		ruleAutoSaveProfileInput.appendChild(optionElement);
+		ruleEditAutoSaveProfileInput.appendChild(optionElement.cloneNode(true));
+		const rulesDataElement = rulesElement.querySelector(".rules-data");
+		Array.from(rulesDataElement.childNodes).forEach(node => node.remove());
+		const editURLElement = rulesElement.querySelector(".rule-edit");
+		const createURLElement = rulesElement.querySelector(".rule-create");
+		createURLElement.hidden = false;
+		editURLElement.hidden = true;
+		rules.forEach(rule => {
+			if (showAllProfilesInput.checked || selectedProfileName == rule.profile || selectedProfileName == rule.autoSaveProfile) {
+				const ruleElement = rulesElement.querySelector(".rule-view").cloneNode(true);
+				const ruleUrlElement = ruleElement.querySelector(".rule-url");
+				const ruleProfileElement = ruleElement.querySelector(".rule-profile");
+				const ruleAutoSaveProfileElement = ruleElement.querySelector(".rule-autosave-profile");
+				ruleUrlElement.textContent = ruleUrlElement.title = rule.url;
+				ruleProfileElement.textContent = ruleProfileElement.title = getProfileText(rule.profile);
+				ruleAutoSaveProfileElement.textContent = ruleAutoSaveProfileElement.title = getProfileText(rule.autoSaveProfile);
+				ruleElement.hidden = false;
+				ruleElement.className = "tr data";
+				rulesDataElement.appendChild(ruleElement);
+				const ruleDeleteButton = ruleElement.querySelector(".rule-delete-button");
+				const ruleUpdateButton = ruleElement.querySelector(".rule-update-button");
+				ruleDeleteButton.title = browser.i18n.getMessage("optionsDeleteRuleTooltip");
+				ruleDeleteButton.addEventListener("click", async () => {
+					if (confirm(browser.i18n.getMessage("optionsDeleteRuleConfirm"))) {
+						await singlefile.config.deleteRule(rule.url);
+						await refresh();
+					}
+				}, false);
+				ruleUpdateButton.title = browser.i18n.getMessage("optionsUpdateRuleTooltip");
+				ruleUpdateButton.addEventListener("click", async () => {
+					if (editURLElement.hidden) {
+						createURLElement.hidden = true;
+						editURLElement.hidden = false;
+						rulesDataElement.replaceChild(editURLElement, ruleElement);
+						ruleEditUrlInput.value = rule.url;
+						ruleEditProfileInput.value = rule.profile;
+						ruleEditAutoSaveProfileInput.value = rule.autoSaveProfile;
+						ruleEditButton.onclick = async () => {
+							rulesElement.appendChild(editURLElement);
+							await singlefile.config.updateRule(rule.url, ruleEditUrlInput.value, ruleEditProfileInput.value, ruleEditAutoSaveProfileInput.value);
+							await refresh();
+						};
+					}
+				}, false);
+			}
+		});
+		rulesElement.appendChild(createURLElement);
 		profileNamesInput.value = selectedProfileName;
 		renameProfileButton.disabled = deleteProfileButton.disabled = profileNamesInput.value == singlefile.config.DEFAULT_PROFILE_NAME;
-		const profileOptions = options.profiles[selectedProfileName];
+		const profileOptions = profiles[selectedProfileName];
 		removeHiddenElementsInput.checked = profileOptions.removeHiddenElements;
 		removeUnusedStylesInput.checked = profileOptions.removeUnusedStyles;
 		removeFramesInput.checked = profileOptions.removeFrames;
@@ -296,9 +417,13 @@
 		confirmInfobarInput.checked = profileOptions.confirmInfobar;
 	}
 
+	function getProfileText(profileName) {
+		return profileName == singlefile.config.DEFAULT_PROFILE_NAME ? browser.i18n.getMessage("profileDefaultSettings") : profileName == singlefile.config.DISABLED_PROFILE_NAME ? browser.i18n.getMessage("profileDisabled") : profileName;
+	}
+
 	async function update() {
 		await pendingSave;
-		pendingSave = bgPage.singlefile.config.update(profileNamesInput.value, {
+		pendingSave = singlefile.config.updateProfile(profileNamesInput.value, {
 			removeHiddenElements: removeHiddenElementsInput.checked,
 			removeUnusedStyles: removeUnusedStylesInput.checked,
 			removeFrames: removeFramesInput.checked,
@@ -333,7 +458,7 @@
 			confirmInfobar: confirmInfobarInput.checked
 		});
 		await pendingSave;
-		await bgPage.singlefile.ui.menu.refresh();
+		await singlefile.ui.menu.refresh();
 	}
 
 })();

+ 102 - 6
extension/ui/pages/options.css

@@ -13,7 +13,8 @@ button {
 }
 
 button,
-select {
+select,
+input[type=text] {
     background-color: transparent;
     border-color: rgb(191, 191, 191);
     border-style: solid;
@@ -57,9 +58,76 @@ h3 {
     margin-right: 12px;
     position: relative;
     top: -3px;
+    max-width: calc(100% - 100px);
 }
 
-.profiles button {
+.rules-table-container {
+    width: 100%;
+    border-spacing: 0;
+    border-color: rgb(191, 191, 191);
+    border-style: solid;
+    border-width: 1px;
+    margin-top: 24px;
+}
+
+.rules-table-container .tr {
+    height: 26px;
+    display: grid;
+    grid-template-columns: 1fr 1fr 1fr 56px;
+}
+
+.rules-table-container.compact .tr {
+    grid-template-columns: 1fr 1fr 56px;
+}
+
+.rules-table-container.compact .tr .rule-autosave-profile {
+    display: none;
+}
+
+.rules-table-container .tr[hidden] {
+    display: none;
+}
+
+.rules-table-container .th {
+    text-align: left;
+    border-color: rgb(225, 225, 225);
+    border-style: solid;
+    border-width: 0;
+    border-bottom-width: 1px;
+}
+
+.rules-table-container .th,
+.rules-table-container .td {
+    padding: 4px;
+    text-overflow: ellipsis;
+    overflow: hidden;
+    white-space: nowrap;
+    align-self: center;
+}
+
+.rules-table .td:first-of-type {
+    padding-right: 8px;
+}
+
+.rules-table .td:last-of-type {
+    text-align: right;
+}
+
+.rules-data {
+    max-height: 552px;
+    overflow-y: auto;
+    padding-top: 2px;
+    padding-bottom: 2px;
+    scrollbar-width: thin;
+}
+
+.rules-data::-webkit-scrollbar {
+    width: 6px;
+    background-color: #cdcdcd;
+}
+
+.profiles button,
+.rules-table button {
     padding: 0;
     margin: 0;
     width: 22px;
@@ -67,26 +135,54 @@ h3 {
 }
 
 .profiles button,
-.profiles select {
+.profiles select,
+.rules-table button,
+.rules-table select,
+.rules-table input {
     height: 22px;
     vertical-align: top;
 }
 
+.rules-table input {
+    width: 100%;
+    height: 20px;
+    margin: 0;
+    padding: 0;
+    padding-left: 2px;
+    padding-right: 2px;
+}
+
+.rules-table .rule-create {
+    margin-top: 8px;
+}
+
 .profiles select {
-    max-width: 180px;
+    max-width: calc(100% - 78px);
 }
 
-.profiles button:disabled {
+.rules-table select {
+    max-width: 100%;
+}
+
+.profiles button:disabled,
+.rules-table-container button:disabled {
     opacity: .25;
 }
 
-.profiles button>img {
+.profiles button>img,
+.rules-table button>img {
     width: 14px;
     height: auto;
     vertical-align: middle;
     padding: 2px;
 }
 
+.rules-table-container+.option label,
+.rules-table-container+.option+.option label {
+    text-align: right;
+    width: 100%;
+}
+
 #profilesLabel {
     font-weight: normal;
     padding-top: 5px;

+ 45 - 0
extension/ui/pages/options.html

@@ -5,6 +5,7 @@
 	<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
 	<title>SingleFile options</title>
 	<link rel="stylesheet" href="options.css">
+	<meta name="viewport" content="width=device-width,initial-scale=1">
 </head>
 
 <body>
@@ -173,6 +174,50 @@
 			<input type="checkbox" id="saveRawPageInput">
 		</div>
 	</details>
+	<details>
+		<summary id="autoSettingsLabel"></summary>
+		<div class="rules-table-container compact">
+			<div class="thead">
+				<div class="tr">
+					<span class="th" id="autoSettingsUrlLabel"></span>
+					<span class="th" id="autoSettingsProfileLabel"></span>
+					<span class="th rule-autosave-profile" id="autoSettingsAutoSaveProfileLabel"></span>
+					<span class="th">&nbsp;</span>
+				</div>
+			</div>
+			<div class="rules-table">
+				<div class="rules-data">
+				</div>
+				<div hidden class="tr rule-view">
+					<span class="td rule-url"></span>
+					<span class="td rule-profile"></span>
+					<span class="td rule-autosave-profile"></span>
+					<span class="td"><button class="rule-update-button"><img src="../resources/button_edit.png"></button> <button
+						 class="rule-delete-button"><img src="../resources/button_delete.png"></button></span>
+				</div>
+				<div class="tr rule-create">
+					<span class="td rule-url"><input type="text" id="ruleUrlInput" placeholder="Type a complete or partial URL (e.g. example.com)"></span>
+					<span class="td rule-profile"><select id="ruleProfileInput"></select></span>
+					<span class="td rule-autosave-profile"><select id="ruleAutoSaveProfileInput"></select></span>
+					<span class="td"><button id="ruleAddButton" disabled><img src="../resources/button_new.png"></button></span>
+				</div>
+				<div hidden class="tr rule-edit">
+					<span class="td rule-url"><input type="text" id="ruleEditUrlInput"></span>
+					<span class="td rule-profile"><select id="ruleEditProfileInput"></select></span>
+					<span class="td rule-autosave-profile"><select id="ruleEditAutoSaveProfileInput"></select></span>
+					<span class="td"><button id="ruleEditButton"><img src="../resources/button_ok.png"></button></span>
+				</div>
+			</div>
+		</div>
+		<div class="option">
+			<label for="showAllProfilesInput" id="showAllProfilesLabel"></label>
+			<input type="checkbox" id="showAllProfilesInput">
+		</div>
+		<div class="option">
+				<label for="showAutoSaveProfileInput" id="showAutoSaveProfileLabel"></label>
+				<input type="checkbox" id="showAutoSaveProfileInput">
+			</div>
+	</details>
 	<div class="option bottom">
 		<a href="help.html" target="SingleFileHelpPage" id="helpLabel"></a>
 		<button id="resetButton"></button>

BIN
extension/ui/resources/button_ok.png