Преглед изворни кода

implemented export/import (fixes issue #116)

Gildas пре 7 година
родитељ
комит
112d7909c3

+ 9 - 1
_locales/en/messages.json

@@ -230,7 +230,7 @@
     "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'"
@@ -323,6 +323,14 @@
         "message": "Confirm the reset of all options",
         "description": "Popup text 'Confirm the reset of all options' in the options page"
     },
+    "optionsExportButton": {
+        "message": "Export",
+        "description": "Options button: 'Export'"
+    },
+    "optionsImportButton": {
+        "message": "Import",
+        "description": "Options button: 'Import'"
+    },
     "logPanelDeferredImages": {
         "message": "Deferred images",
         "description": "Label 'Deferred images' in the log panel"

+ 8 - 0
_locales/fr/messages.json

@@ -323,6 +323,14 @@
         "message": "Confirmez la remise à zéro de toutes les options",
         "description": "Popup text 'Confirm the reset of all options' in the options page"
     },
+    "optionsExportButton": {
+        "message": "Exporter",
+        "description": "Options button: 'Export'"
+    },
+    "optionsImportButton": {
+        "message": "Importer",
+        "description": "Options button: 'Import'"
+    },
     "logPanelDeferredImages": {
         "message": "Images différées",
         "description": "Label 'Deferred images' in the log panel"

+ 8 - 0
_locales/ja/messages.json

@@ -323,6 +323,14 @@
         "message": "すべてのオプションのリセットを確認する",
         "description": "Popup text 'Confirm the reset of all options' in the options page"
     },
+    "optionsExportButton": {
+        "message": "エクスポート",
+        "description": "Options button: 'Export'"
+    },
+    "optionsImportButton": {
+        "message": "インポート",
+        "description": "Options button: 'Import'"
+    },
     "logPanelDeferredImages": {
         "message": "遅延画像",
         "description": "Label 'Deferred images' in the log panel"

+ 8 - 0
_locales/pl/messages.json

@@ -323,6 +323,14 @@
         "message": "Potwierdź reset wszystkich opcji",
         "description": "Popup text 'Confirm the reset of all options' in the options page"
     },
+    "optionsExportButton": {
+        "message": "Eksport",
+        "description": "Options button: 'Export'"
+    },
+    "optionsImportButton": {
+        "message": "Import",
+        "description": "Options button: 'Import'"
+    },
     "logPanelDeferredImages": {
         "message": "Odroczone obrazy",
         "description": "Label 'Deferred images' in the log panel"

+ 8 - 0
_locales/ru/messages.json

@@ -323,6 +323,14 @@
         "message": "Подтвердите сброс всех параметров",
         "description": "Popup text 'Confirm the reset of all options' in the options page"
     },
+    "optionsExportButton": {
+        "message": "Экспорт",
+        "description": "Options button: 'Export'"
+    },
+    "optionsImportButton": {
+        "message": "Импорт",
+        "description": "Options button: 'Import'"
+    },
     "logPanelDeferredImages": {
         "message": "Отложенные изображения",
         "description": "Label 'Deferred images' in the log panel"

+ 9 - 1
_locales/zh_CN/messages.json

@@ -323,6 +323,14 @@
         "message": "确认重置所有选项",
         "description": "Popup text 'Confirm the reset of all options' in the options page"
     },
+    "optionsExportButton": {
+        "message": "导出",
+        "description": "Options button: 'Export'"
+    },
+    "optionsImportButton": {
+        "message": "导入",
+        "description": "Options button: 'Import'"
+    },
     "logPanelDeferredImages": {
         "message": "延迟加载的图像",
         "description": "Label 'Deferred images' in the log panel"
@@ -374,4 +382,4 @@
     "__WET_LOCALE__": {
         "message": "zh-cn"
     }
-}
+}

+ 41 - 2
extension/core/bg/config.js

@@ -18,7 +18,7 @@
  *   along with SingleFile.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-/* global browser, singlefile */
+/* global browser, singlefile, URL, Blob, FileReader */
 
 singlefile.config = (() => {
 
@@ -178,7 +178,7 @@ singlefile.config = (() => {
 		if (config.filenameConflictAction === undefined) {
 			config.filenameConflictAction = config.conflictAction || DEFAULT_CONFIG.filenameConflictAction;
 			delete config.conflictAction;
-	}
+		}
 		if (config.loadDeferredImages === undefined) {
 			config.loadDeferredImages = config.lazyLoadImages || true;
 			delete config.lazyLoadImages;
@@ -328,6 +328,45 @@ singlefile.config = (() => {
 			await singlefile.tabsData.set(tabsData);
 			await browser.storage.local.remove(["profiles", "rules"]);
 			await browser.storage.local.set({ profiles: { [DEFAULT_PROFILE_NAME]: DEFAULT_CONFIG }, rules: [] });
+		},
+		async export() {
+			const config = await getConfig();
+			const url = URL.createObjectURL(new Blob([JSON.stringify({ profiles: config.profiles, rules: config.rules }, null, 2)], { type: "text/json" }));
+			const downloadInfo = {
+				url,
+				filename: "singlefile-settings.json",
+				saveAs: true
+			};
+			const downloadId = await browser.downloads.download(downloadInfo);
+			return new Promise((resolve, reject) => {
+				browser.downloads.onChanged.addListener(onChanged);
+
+				function onChanged(event) {
+					if (event.id == downloadId && event.state) {
+						if (event.state.current == "complete") {
+							URL.revokeObjectURL(url);
+							resolve({});
+							browser.downloads.onChanged.removeListener(onChanged);
+						}
+						if (event.state.current == "interrupted" && (!event.error || event.error.current != "USER_CANCELED")) {
+							URL.revokeObjectURL(url);
+							reject(new Error(event.state.current));
+							browser.downloads.onChanged.removeListener(onChanged);
+						}
+					}
+				}
+			});
+		},
+		async import(file) {
+			const reader = new FileReader();
+			reader.readAsText(file);
+			const serializedConfig = await new Promise((resolve, reject) => {
+				reader.addEventListener("load", () => resolve(reader.result), false);
+				reader.addEventListener("error", reject, false);
+			});
+			const config = JSON.parse(serializedConfig);
+			await browser.storage.local.remove(["profiles", "rules"]);
+			await browser.storage.local.set({ profiles: config.profiles, rules: config.rules });
 		}
 	};
 

+ 17 - 0
extension/ui/bg/ui-options.js

@@ -83,6 +83,9 @@
 	const deleteProfileButton = document.getElementById("deleteProfileButton");
 	const renameProfileButton = document.getElementById("renameProfileButton");
 	const resetButton = document.getElementById("resetButton");
+	const exportButton = document.getElementById("exportButton");
+	const importButton = document.getElementById("importButton");
+	const fileInput = document.getElementById("fileInput");
 	const profileNamesInput = document.getElementById("profileNamesInput");
 	const removeHiddenElementsInput = document.getElementById("removeHiddenElementsInput");
 	const removeUnusedStylesInput = document.getElementById("removeUnusedStylesInput");
@@ -202,6 +205,18 @@
 			await update();
 		}
 	}, false);
+	exportButton.addEventListener("click", async () => {
+		await singlefile.config.export();
+	}, false);
+	importButton.addEventListener("click", () => {
+		fileInput.onchange = async () => {
+			if (fileInput.files.length) {
+				await singlefile.config.import(fileInput.files[0]);
+				await refresh(singlefile.config.DEFAULT_PROFILE_NAME);
+			}
+		};
+		fileInput.click();
+	}, false);
 	autoSaveUnloadInput.addEventListener("click", async () => {
 		if (!autoSaveLoadInput.checked && !autoSaveUnloadInput.checked) {
 			autoSaveLoadOrUnloadInput.checked = true;
@@ -290,6 +305,8 @@
 	infobarTemplateLabel.textContent = browser.i18n.getMessage("optionInfobarTemplate");
 	confirmInfobarLabel.textContent = browser.i18n.getMessage("optionConfirmInfobar");
 	resetButton.textContent = browser.i18n.getMessage("optionsResetButton");
+	exportButton.textContent = browser.i18n.getMessage("optionsExportButton");
+	importButton.textContent = browser.i18n.getMessage("optionsImportButton");
 	resetButton.title = browser.i18n.getMessage("optionsResetTooltip");
 	autoSettingsLabel.textContent = browser.i18n.getMessage("optionsAutoSettingsSubTitle");
 	autoSettingsUrlLabel.textContent = browser.i18n.getMessage("optionsAutoSettingsUrl");

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

@@ -7,8 +7,8 @@ body {
 button {
     padding-top: 5px;
     padding-bottom: 5px;
-    padding-left: 15px;
-    padding-right: 15px;
+    padding-left: 8px;
+    padding-right: 8px;
     border-width: 1px;
 }
 

+ 9 - 4
extension/ui/pages/options.html

@@ -214,13 +214,18 @@
 			<input type="checkbox" id="showAllProfilesInput">
 		</div>
 		<div class="option">
-				<label for="showAutoSaveProfileInput" id="showAutoSaveProfileLabel"></label>
-				<input type="checkbox" id="showAutoSaveProfileInput">
-			</div>
+			<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>
+		<div>
+			<button id="importButton"></button>
+			<button id="exportButton"></button>
+			<button id="resetButton"></button>
+			<input type="file" id="fileInput" hidden>
+		</div>
 	</div>
 	<script type="text/javascript" src="/lib/browser-polyfill/chrome-browser-polyfill.js"></script>
 	<script type="text/javascript" src="../bg/ui-options.js"></script>