Browse Source

add option `embed image > page screenshot`

Gildas 1 year ago
parent
commit
b6f06c63b6

+ 10 - 2
_locales/de/messages.json

@@ -300,8 +300,16 @@
 		"description": "Options page label: 'make text searchable'"
 	},
 	"optionInsertEmbeddedImage": {
-		"message": "insert embedded image",
-		"description": "Options page label: 'insert embedded image'"
+		"message": "Eingebettetes Bild",
+		"description": "Options page label: 'embed image'"
+	},
+	"optionInsertEmbeddedScreenshotImage": {
+		"message": "Bildschirmfoto",
+		"description": "Options page label: 'page screenshot'"
+	},
+	"optionInsertEmbeddedCustomImage": {
+		"message": "Benutzerdefiniertes Bild",
+		"description": "Options page label: 'custom image'"
 	},
 	"optionsInfobarSubTitle": {
 		"message": "Infoknopf",

+ 10 - 2
_locales/en/messages.json

@@ -300,8 +300,16 @@
 		"description": "Options page label: 'make text searchable'"
 	},
 	"optionInsertEmbeddedImage": {
-		"message": "insert embedded image",
-		"description": "Options page label: 'insert embedded image'"
+		"message": "embed image",
+		"description": "Options page label: 'embed image'"
+	},
+	"optionInsertEmbeddedScreenshotImage": {
+		"message": "page screenshot",
+		"description": "Options page label: 'page screenshot'"
+	},
+	"optionInsertEmbeddedCustomImage": {
+		"message": "custom image",
+		"description": "Options page label: 'custom image'"
 	},
 	"optionsInfobarSubTitle": {
 		"message": "Infobar",

+ 10 - 2
_locales/es/messages.json

@@ -300,8 +300,16 @@
 		"description": "Options page label: 'make text searchable'"
 	},
 	"optionInsertEmbeddedImage": {
-		"message": "insert embedded image",
-		"description": "Options page label: 'insert embedded image'"
+		"message": "imagen incrustada",
+		"description": "Options page label: 'embed image'"
+	},
+	"optionInsertEmbeddedScreenshotImage": {
+		"message": "captura de pantalla",
+		"description": "Options page label: 'page screenshot'"
+	},
+	"optionInsertEmbeddedCustomImage": {
+		"message": "imagen personalizada",
+		"description": "Options page label: 'custom image'"
 	},
 	"optionsInfobarSubTitle": {
 		"message": "Barra informativa",

+ 10 - 2
_locales/fr/messages.json

@@ -300,8 +300,16 @@
 		"description": "Options page label: 'make text searchable'"
 	},
 	"optionInsertEmbeddedImage": {
-		"message": "insert embedded image",
-		"description": "Options page label: 'insert embedded image'"
+		"message": "intégrer une image",
+		"description": "Options page label: 'embed image'"
+	},
+	"optionInsertEmbeddedScreenshotImage": {
+		"message": "capture d'écran",
+		"description": "Options page label: 'page screenshot'"
+	},
+	"optionInsertEmbeddedCustomImage": {
+		"message": "image personnalisée",
+		"description": "Options page label: 'custom image'"
 	},
 	"optionsInfobarSubTitle": {
 		"message": "Barre d'information",

+ 10 - 2
_locales/it/messages.json

@@ -300,8 +300,16 @@
 		"description": "Options page label: 'make text searchable'"
 	},
 	"optionInsertEmbeddedImage": {
-		"message": "insert embedded image",
-		"description": "Options page label: 'insert embedded image'"
+		"message": "immagine incorporata",
+		"description": "Options page label: 'embed image'"
+	},
+	"optionInsertEmbeddedScreenshotImage": {
+		"message": "screenshot",
+		"description": "Options page label: 'page screenshot'"
+	},
+	"optionInsertEmbeddedCustomImage": {
+		"message": "immagine personalizzata",
+		"description": "Options page label: 'custom image'"
 	},
 	"optionsInfobarSubTitle": {
 		"message": "Barra informativa",

+ 10 - 2
_locales/ja/messages.json

@@ -300,8 +300,16 @@
 		"description": "Options page label: 'make text searchable'"
 	},
 	"optionInsertEmbeddedImage": {
-		"message": "insert embedded image",
-		"description": "Options page label: 'insert embedded image'"
+		"message": "埋め込み画像",
+		"description": "Options page label: 'embed image'"
+	},
+	"optionInsertEmbeddedScreenshotImage": {
+		"message": "スクリーンショット",
+		"description": "Options page label: 'page screenshot'"
+	},
+	"optionInsertEmbeddedCustomImage": {
+		"message": "カスタム画像",
+		"description": "Options page label: 'custom image'"
 	},
 	"optionsInfobarSubTitle": {
 		"message": "インフォバー",

+ 10 - 2
_locales/pl/messages.json

@@ -300,8 +300,16 @@
 		"description": "Options page label: 'make text searchable'"
 	},
 	"optionInsertEmbeddedImage": {
-		"message": "wstaw osadzony obraz",
-		"description": "Options page label: 'insert embedded image'"
+		"message": "embed image",
+		"description": "Options page label: 'embed image'"
+	},
+	"optionInsertEmbeddedScreenshotImage": {
+		"message": "page screenshot",
+		"description": "Options page label: 'page screenshot'"
+	},
+	"optionInsertEmbeddedCustomImage": {
+		"message": "custom image",
+		"description": "Options page label: 'custom image'"
 	},
 	"optionsInfobarSubTitle": {
 		"message": "Pasek informacyjny",

+ 10 - 2
_locales/pt_PT/messages.json

@@ -300,8 +300,16 @@
 		"description": "Options page label: 'make text searchable'"
 	},
 	"optionInsertEmbeddedImage": {
-		"message": "insert embedded image",
-		"description": "Options page label: 'insert embedded image'"
+		"message": "imagem incorporada",
+		"description": "Options page label: 'embed image'"
+	},
+	"optionInsertEmbeddedScreenshotImage": {
+		"message": "captura de ecrã",
+		"description": "Options page label: 'page screenshot'"
+	},
+	"optionInsertEmbeddedCustomImage": {
+		"message": "imagem personalizada",
+		"description": "Options page label: 'custom image'"
 	},
 	"optionsInfobarSubTitle": {
 		"message": "Barra de Informações",

+ 10 - 2
_locales/pt_br/messages.json

@@ -300,8 +300,16 @@
 		"description": "Options page label: 'make text searchable'"
 	},
 	"optionInsertEmbeddedImage": {
-		"message": "insert embedded image",
-		"description": "Options page label: 'insert embedded image'"
+		"message": "imagem incorporada",
+		"description": "Options page label: 'embed image'"
+	},
+	"optionInsertEmbeddedScreenshotImage": {
+		"message": "captura de tela",
+		"description": "Options page label: 'page screenshot'"
+	},
+	"optionInsertEmbeddedCustomImage": {
+		"message": "imagem personalizada",
+		"description": "Options page label: 'custom image'"
 	},
 	"optionsInfobarSubTitle": {
 		"message": "Infobar",

+ 10 - 2
_locales/ru/messages.json

@@ -300,8 +300,16 @@
 		"description": "Options page label: 'make text searchable'"
 	},
 	"optionInsertEmbeddedImage": {
-		"message": "вставить встроенное изображение",
-		"description": "Options page label: 'insert embedded image'"
+		"message": "embed image",
+		"description": "Options page label: 'embed image'"
+	},
+	"optionInsertEmbeddedScreenshotImage": {
+		"message": "page screenshot",
+		"description": "Options page label: 'page screenshot'"
+	},
+	"optionInsertEmbeddedCustomImage": {
+		"message": "custom image",
+		"description": "Options page label: 'custom image'"
 	},
 	"optionsInfobarSubTitle": {
 		"message": "Информационная панель",

+ 10 - 2
_locales/tr/messages.json

@@ -300,8 +300,16 @@
 		"description": "Options page label: 'make text searchable'"
 	},
 	"optionInsertEmbeddedImage": {
-		"message": "insert embedded image",
-		"description": "Options page label: 'insert embedded image'"
+		"message": "gömülü görüntü",
+		"description": "Options page label: 'embed image'"
+	},
+	"optionInsertEmbeddedScreenshotImage": {
+		"message": "ekran görüntüsü",
+		"description": "Options page label: 'page screenshot'"
+	},
+	"optionInsertEmbeddedCustomImage": {
+		"message": "özel görüntü",
+		"description": "Options page label: 'custom image'"
 	},
 	"optionsInfobarSubTitle": {
 		"message": "Bilgi çubuğu",

+ 10 - 2
_locales/uk/messages.json

@@ -300,8 +300,16 @@
 		"description": "Options page label: 'make text searchable'"
 	},
 	"optionInsertEmbeddedImage": {
-		"message": "insert embedded image",
-		"description": "Options page label: 'insert embedded image'"
+		"message": "вбудоване зображення",
+		"description": "Options page label: 'embed image'"
+	},
+	"optionInsertEmbeddedScreenshotImage": {
+		"message": "знімок екрану",
+		"description": "Options page label: 'page screenshot'"
+	},
+	"optionInsertEmbeddedCustomImage": {
+		"message": "власне зображення",
+		"description": "Options page label: 'custom image'"
 	},
 	"optionsInfobarSubTitle": {
 		"message": "Інфобар",

+ 9 - 1
_locales/zh_CN/messages.json

@@ -301,7 +301,15 @@
 	},
 	"optionInsertEmbeddedImage": {
 		"message": "嵌入图像",
-		"description": "Options page label: 'insert embedded image'"
+		"description": "Options page label: 'embed image'"
+	},
+	"optionInsertEmbeddedScreenshotImage": {
+		"message": "截图",
+		"description": "Options page label: 'page screenshot'"
+	},
+	"optionInsertEmbeddedCustomImage": {
+		"message": "自定义图像",
+		"description": "Options page label: 'custom image'"
 	},
 	"optionsInfobarSubTitle": {
 		"message": "信息栏",

+ 10 - 2
_locales/zh_TW/messages.json

@@ -300,8 +300,16 @@
 		"description": "Options page label: 'make text searchable'"
 	},
 	"optionInsertEmbeddedImage": {
-		"message": "嵌入圖像",
-		"description": "Options page label: 'insert embedded image'"
+		"message": "嵌入圖片",
+		"description": "Options page label: 'embed image'"
+	},
+	"optionInsertEmbeddedScreenshotImage": {
+		"message": "截圖",
+		"description": "Options page label: 'page screenshot'"
+	},
+	"optionInsertEmbeddedCustomImage": {
+		"message": "自定義圖片",
+		"description": "Options page label: 'custom image'"
 	},
 	"optionsInfobarSubTitle": {
 		"message": "信息欄",

File diff suppressed because it is too large
+ 0 - 0
lib/chrome-browser-polyfill.js


File diff suppressed because it is too large
+ 0 - 0
lib/single-file-extension-background.js


File diff suppressed because it is too large
+ 0 - 0
lib/single-file-extension.js


File diff suppressed because it is too large
+ 0 - 0
lib/single-file.js


+ 7 - 7
package-lock.json

@@ -9,7 +9,7 @@
 			"version": "1.2.2",
 			"license": "AGPL-3.0-or-later",
 			"dependencies": {
-				"single-file-core": "1.3.29"
+				"single-file-core": "1.3.31"
 			},
 			"devDependencies": {
 				"@rollup/plugin-node-resolve": "15.0.1",
@@ -516,9 +516,9 @@
 			}
 		},
 		"node_modules/single-file-core": {
-			"version": "1.3.29",
-			"resolved": "https://registry.npmjs.org/single-file-core/-/single-file-core-1.3.29.tgz",
-			"integrity": "sha512-qUSyTauiGkmvpTKa61I7B0PSfXxL7/9Y9zOnpCUk4EsUioCasWhfQ5p2BfaC8gFGqBl2T60YqntWrEkxbeuzaA=="
+			"version": "1.3.31",
+			"resolved": "https://registry.npmjs.org/single-file-core/-/single-file-core-1.3.31.tgz",
+			"integrity": "sha512-g8DrKL3hPYZpMkjPUxH177ru5zTBv68I4Uw8U89TueyRoINO4aDK1Ig8hwQk3e0F6NauOm8ArMJHzbSoq9bfIg=="
 		},
 		"node_modules/source-map": {
 			"version": "0.6.1",
@@ -564,9 +564,9 @@
 			}
 		},
 		"node_modules/terser": {
-			"version": "5.30.3",
-			"resolved": "https://registry.npmjs.org/terser/-/terser-5.30.3.tgz",
-			"integrity": "sha512-STdUgOUx8rLbMGO9IOwHLpCqolkDITFFQSMYYwKE1N2lY6MVSaeoi10z/EhWxRc6ybqoVmKSkhKYH/XUpl7vSA==",
+			"version": "5.30.4",
+			"resolved": "https://registry.npmjs.org/terser/-/terser-5.30.4.tgz",
+			"integrity": "sha512-xRdd0v64a8mFK9bnsKVdoNP9GQIKUAaJPTaqEQDL4w/J8WaW4sWXXoMZ+6SimPkfT5bElreXf8m9HnmPc3E1BQ==",
 			"dev": true,
 			"dependencies": {
 				"@jridgewell/source-map": "^0.3.3",

+ 1 - 1
package.json

@@ -9,7 +9,7 @@
 		"build": "./build-extension.sh"
 	},
 	"dependencies": {
-		"single-file-core": "1.3.29"
+		"single-file-core": "1.3.31"
 	},
 	"devDependencies": {
 		"@rollup/plugin-node-resolve": "15.0.1",

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

@@ -135,6 +135,7 @@ const DEFAULT_CONFIG = {
 	extractDataFromPage: true,
 	preventAppendedData: false,
 	insertEmbeddedImage: false,
+	insertEmbeddedScreenshotImage: false,
 	insertTextBody: false,
 	autoSaveExternalSave: false,
 	insertMetaNoIndex: false,

+ 47 - 1
src/core/bg/tabs.js

@@ -21,7 +21,7 @@
  *   Source.
  */
 
-/* global browser, setTimeout */
+/* global browser, setTimeout, OffscreenCanvas, Image, URL */
 
 import * as config from "./config.js";
 import * as autosave from "./autosave.js";
@@ -54,6 +54,9 @@ async function onMessage(message, sender) {
 	if (message.method.endsWith(".activate")) {
 		await browser.tabs.update(message.tabId, { active: true });
 	}
+	if (message.method.endsWith(".getScreenshot")) {
+		return captureTab(sender.tab.id, message);
+	}
 }
 
 async function onInit(tab, options) {
@@ -107,4 +110,47 @@ function onTabRemoved(tabId) {
 	editor.onTabRemoved(tabId);
 	business.onTabRemoved(tabId);
 	autosave.onTabRemoved(tabId);
+}
+
+async function captureTab(tabId, options) {
+	const { width, height } = options;
+	const canvas = new OffscreenCanvas(width, height);
+	const context = canvas.getContext("2d");
+	const image = new Image();
+	let y = 0, scrollYStep, activeTabId;
+	if (browser.tabs.captureTab) {
+		scrollYStep = 4 * 1024;
+	} else {
+		scrollYStep = options.innerHeight;
+		activeTabId = (await browser.tabs.query({ active: true, currentWindow: true }))[0].id;
+		await browser.tabs.sendMessage(tabId, { method: "content.beginScrollTo" });
+	}
+	while (y < height) {
+		let imageSrc;
+		if (browser.tabs.captureTab) {
+			imageSrc = await browser.tabs.captureTab(tabId, {
+				format: "png",
+				rect: { x: 0, y, width, height: Math.min(height - y, scrollYStep) }
+			});
+		} else {
+			await browser.tabs.sendMessage(tabId, { method: "content.scrollTo", y });
+			await browser.tabs.update(tabId, { active: true });
+			imageSrc = await browser.tabs.captureVisibleTab(null, {
+				format: "png"
+			});
+		}
+		await new Promise((resolve, reject) => {
+			image.onload = resolve;
+			image.onerror = event => reject(new Error(event.detail));
+			image.src = imageSrc;
+		});
+		context.drawImage(image, 0, y, width, Math.min(height - y, scrollYStep));
+		y += scrollYStep;
+	}
+	if (!browser.tabs.captureTab) {
+		await browser.tabs.update(activeTabId, { active: true });
+		await browser.tabs.sendMessage(tabId, { method: "content.endScrollTo" });
+	}
+	const blob = await canvas.convertToBlob({ type: "image/png" });
+	return URL.createObjectURL(blob);
 }

+ 52 - 6
src/core/content/content.js

@@ -21,7 +21,7 @@
  *   Source.
  */
 
-/* global browser, document, globalThis, location, setTimeout */
+/* global browser, document, globalThis, location, setTimeout, URL */
 
 import * as download from "./../common/download.js";
 import { fetch, frameFetch } from "./../../lib/single-file/fetch/content/content-fetch.js";
@@ -38,7 +38,7 @@ const SHARE_PAGE_BUTTON_MESSAGE = browser.i18n.getMessage("topPanelSharePageButt
 const SHARE_SELECTION_BUTTON_MESSAGE = browser.i18n.getMessage("topPanelShareSelectionButton");
 const ERROR_TITLE_MESSAGE = browser.i18n.getMessage("topPanelError");
 
-let processor, processing, downloadParser, openFileInfobar;
+let processor, processing, downloadParser, openFileInfobar, scrollY, transform, overflow;
 
 setLabels({
 	EMBEDDED_IMAGE_BUTTON_MESSAGE,
@@ -50,7 +50,15 @@ setLabels({
 if (!bootstrap || !bootstrap.initializedSingleFile) {
 	singlefile.init({ fetch, frameFetch });
 	browser.runtime.onMessage.addListener(message => {
-		if (message.method == "content.save" || message.method == "content.cancelSave" || message.method == "content.download" || message.method == "content.getSelectedLinks" || message.method == "content.error" || message.method == "content.prompt") {
+		if (message.method == "content.save" ||
+			message.method == "content.cancelSave" ||
+			message.method == "content.download" ||
+			message.method == "content.getSelectedLinks" ||
+			message.method == "content.error" ||
+			message.method == "content.prompt" ||
+			message.method == "content.beginScrollTo" ||
+			message.method == "content.scrollTo" ||
+			message.method == "content.endScrollTo") {
 			return onMessage(message);
 		}
 	});
@@ -116,6 +124,26 @@ async function onMessage(message) {
 		if (message.method == "content.prompt") {
 			return ui.prompt(message.message, message.value);
 		}
+		if (message.method == "content.beginScrollTo") {
+			scrollY = globalThis.scrollY;
+			transform = document.documentElement.style.getPropertyValue("transform");
+			overflow = document.documentElement.style.getPropertyValue("overflow");
+			globalThis.scrollTo(0, 0);
+			document.documentElement.style.setProperty("transform", "translateY(0px)");
+			document.documentElement.style.setProperty("overflow", "hidden");
+			return {};
+		}
+		if (message.method == "content.scrollTo") {
+			document.documentElement.style.setProperty("transform", "translateY(-" + message.y + "px)");
+			await new Promise(resolve => setTimeout(resolve, 500));
+			return {};
+		}
+		if (message.method == "content.endScrollTo") {
+			globalThis.scrollTo(0, scrollY);
+			document.documentElement.style.setProperty("transform", transform);
+			document.documentElement.style.setProperty("overflow", overflow);
+			return {};
+		}
 	}
 }
 
@@ -172,9 +200,27 @@ async function processPage(options) {
 	processor = new singlefile.SingleFile(options);
 	const preInitializationPromises = [];
 	options.insertCanonicalLink = true;
-	let index = 0, maxIndex = 0;
-	options.onprogress = event => {
+	let index = 0, maxIndex = 0, initializing;
+	options.onprogress = async event => {
+		const { options } = event.detail;
 		if (!processor.cancelled) {
+			if (event.type == event.RESOURCES_INITIALIZING) {
+				if (!initializing && options.insertEmbeddedScreenshotImage && options.compressContent) {
+					initializing = true;
+					ui.setVisible(false);
+					const screenshotBlobURI = await browser.runtime.sendMessage({
+						method: "tabs.getScreenshot",
+						width: document.documentElement.scrollWidth,
+						height: document.documentElement.scrollHeight,
+						innerHeight: globalThis.innerHeight
+					});
+					ui.setVisible(true);
+					ui.onInsertingEmbeddedImage(options);
+					options.embeddedImage = new Uint8Array(await (await fetch(screenshotBlobURI)).arrayBuffer());
+					URL.revokeObjectURL(screenshotBlobURI);
+					ui.onInsertEmbeddedImage(options);
+				}
+			}
 			if (event.type == event.RESOURCES_INITIALIZED) {
 				maxIndex = event.detail.max;
 				if (options.loadDeferredImages) {
@@ -185,7 +231,7 @@ async function processPage(options) {
 				if (event.type == event.RESOURCE_LOADED) {
 					index++;
 				}
-				browser.runtime.sendMessage({ method: "ui.processProgress", index, maxIndex });
+				await browser.runtime.sendMessage({ method: "ui.processProgress", index, maxIndex });
 				ui.onLoadResource(index, maxIndex, options);
 			} else if (!event.detail.frame) {
 				if (event.type == event.PAGE_LOADING) {

+ 9 - 0
src/lib/single-file/browser-polyfill/chrome-browser-polyfill.js

@@ -451,6 +451,15 @@ if (typeof globalThis == "undefined") {
 					addListener: listener => nativeAPI.tabs.onReplaced.addListener(listener),
 					removeListener: listener => nativeAPI.tabs.onReplaced.removeListener(listener)
 				},
+				captureVisibleTab: (windowId, options) => new Promise((resolve, reject) => {
+					nativeAPI.tabs.captureVisibleTab(windowId, options, dataUrl => {
+						if (nativeAPI.runtime.lastError) {
+							reject(nativeAPI.runtime.lastError);
+						} else {
+							resolve(dataUrl);
+						}
+					});
+				}),
 				executeScript: (tabId, details) => new Promise((resolve, reject) => {
 					nativeAPI.tabs.executeScript(tabId, details, () => {
 						if (nativeAPI.runtime.lastError) {

+ 27 - 2
src/ui/bg/ui-options.js

@@ -95,6 +95,8 @@ const saveWithCompanionLabel = document.getElementById("saveWithCompanionLabel")
 const compressHTMLLabel = document.getElementById("compressHTMLLabel");
 const insertTextBodyLabel = document.getElementById("insertTextBodyLabel");
 const insertEmbeddedImageLabel = document.getElementById("insertEmbeddedImageLabel");
+const insertEmbeddedCustomImageLabel = document.getElementById("insertEmbeddedCustomImageLabel");
+const insertEmbeddedScreenshotImageLabel = document.getElementById("insertEmbeddedScreenshotImageLabel");
 const compressCSSLabel = document.getElementById("compressCSSLabel");
 const moveStylesInHeadLabel = document.getElementById("moveStylesInHeadLabel");
 const loadDeferredImagesLabel = document.getElementById("loadDeferredImagesLabel");
@@ -236,6 +238,8 @@ const saveToFilesystemInput = document.getElementById("saveToFilesystemInput");
 const compressHTMLInput = document.getElementById("compressHTMLInput");
 const insertTextBodyInput = document.getElementById("insertTextBodyInput");
 const insertEmbeddedImageInput = document.getElementById("insertEmbeddedImageInput");
+const insertEmbeddedCustomImageInput = document.getElementById("insertEmbeddedCustomImageInput");
+const insertEmbeddedScreenshotImageInput = document.getElementById("insertEmbeddedScreenshotImageInput");
 const compressCSSInput = document.getElementById("compressCSSInput");
 const moveStylesInHeadInput = document.getElementById("moveStylesInHeadInput");
 const loadDeferredImagesInput = document.getElementById("loadDeferredImagesInput");
@@ -559,6 +563,20 @@ synchronizeInput.addEventListener("click", async () => {
 		await refresh();
 	}
 }, false);
+insertEmbeddedImageInput.addEventListener("click", () => {
+	if (insertEmbeddedImageInput.checked) {
+		insertEmbeddedScreenshotImageInput.checked = true;
+	} else {
+		insertEmbeddedScreenshotImageInput.checked = false;
+		insertEmbeddedCustomImageInput.checked = false;
+	}
+}, false);
+fileFormatSelectInput.addEventListener("change", () => {
+	if (fileFormatSelectInput.value == "html") {
+		insertEmbeddedScreenshotImageInput.checked = false;
+		insertEmbeddedCustomImageInput.checked = false;
+	}
+}, false);
 document.body.onchange = async event => {
 	let target = event.target;
 	if (target != ruleUrlInput &&
@@ -635,6 +653,8 @@ saveWithCompanionLabel.textContent = browser.i18n.getMessage("optionSaveWithComp
 compressHTMLLabel.textContent = browser.i18n.getMessage("optionCompressHTML");
 insertTextBodyLabel.textContent = browser.i18n.getMessage("optionInsertTextBody");
 insertEmbeddedImageLabel.textContent = browser.i18n.getMessage("optionInsertEmbeddedImage");
+insertEmbeddedCustomImageLabel.textContent = browser.i18n.getMessage("optionInsertEmbeddedCustomImage");
+insertEmbeddedScreenshotImageLabel.textContent = browser.i18n.getMessage("optionInsertEmbeddedScreenshotImage");
 compressCSSLabel.textContent = browser.i18n.getMessage("optionCompressCSS");
 moveStylesInHeadLabel.textContent = browser.i18n.getMessage("optionMoveStylesInHead");
 loadDeferredImagesLabel.textContent = browser.i18n.getMessage("optionLoadDeferredImages");
@@ -998,7 +1018,11 @@ async function refresh(profileName) {
 	passwordInput.disabled = !profileOptions.compressContent;
 	insertTextBodyInput.checked = profileOptions.insertTextBody;
 	insertTextBodyInput.disabled = !profileOptions.compressContent || (!profileOptions.selfExtractingArchive && !profileOptions.extractDataFromPage);
-	insertEmbeddedImageInput.checked = profileOptions.insertEmbeddedImage;
+	insertEmbeddedCustomImageInput.checked = profileOptions.insertEmbeddedImage;
+	insertEmbeddedCustomImageInput.disabled = !profileOptions.compressContent || (!profileOptions.insertEmbeddedImage && !profileOptions.insertEmbeddedScreenshotImage);
+	insertEmbeddedScreenshotImageInput.checked = profileOptions.insertEmbeddedScreenshotImage;
+	insertEmbeddedScreenshotImageInput.disabled = !profileOptions.compressContent || (!profileOptions.insertEmbeddedImage && !profileOptions.insertEmbeddedScreenshotImage);
+	insertEmbeddedImageInput.checked = profileOptions.compressContent && (profileOptions.insertEmbeddedImage || profileOptions.insertEmbeddedScreenshotImage);
 	insertEmbeddedImageInput.disabled = !profileOptions.compressContent;
 	infobarTemplateInput.value = profileOptions.infobarTemplate;
 	blockMixedContentInput.checked = profileOptions.blockMixedContent;
@@ -1069,7 +1093,8 @@ async function update() {
 			sharePage: sharePageInput.checked,
 			compressHTML: compressHTMLInput.checked,
 			insertTextBody: insertTextBodyInput.checked,
-			insertEmbeddedImage: insertEmbeddedImageInput.checked,
+			insertEmbeddedImage: insertEmbeddedCustomImageInput.checked,
+			insertEmbeddedScreenshotImage: insertEmbeddedScreenshotImageInput.checked,
 			compressCSS: compressCSSInput.checked,
 			moveStylesInHead: moveStylesInHeadInput.checked,
 			loadDeferredImages: loadDeferredImagesInput.checked,

+ 11 - 0
src/ui/content/content-ui.js

@@ -54,6 +54,7 @@ export {
 	markSelection,
 	unmarkSelection,
 	promptMessage as prompt,
+	setVisible,
 	onStartPage,
 	onEndPage,
 	onLoadResource,
@@ -75,6 +76,16 @@ function promptMessage(message, defaultValue) {
 	return prompt(message, defaultValue);
 }
 
+function setVisible(visible) {
+	const maskElement = document.querySelector(MASK_TAGNAME);
+	if (maskElement) {
+		maskElement.style.setProperty("display", visible ? "block" : "none");
+	}
+	if (logsWindowElement) {
+		logsWindowElement.style.setProperty("display", visible ? "block" : "none");
+	}
+}
+
 function onStartPage(options) {
 	let maskElement = document.querySelector(MASK_TAGNAME);
 	if (!maskElement) {

+ 4 - 4
src/ui/pages/help.html

@@ -244,10 +244,10 @@
 						</p>
 					</li>
 					<li data-options-label="insertEmbeddedImageLabel">
-						<span class="option">Option: insert embedded images</span>
-						<p>Check this option to embed an image in all types of ZIP file, making them compatible with the
-							PNG format. This image is displayed when the archive renamed as a PNG file is opened in an
-							image viewer.
+						<span class="option">Option: embed image</span>
+						<p>Check one of the options to embed an image when saving the page as a ZIP file
+							(self-extracting or not). This allows you to display the saved page as a PNG image when
+							the filename extension is changed to ".png".
 						</p>
 					</li>
 				</ul>

+ 12 - 3
src/ui/pages/options.html

@@ -95,7 +95,8 @@
 				<label for="fileFormatSelectInput" id="fileFormatSelectLabel"></label>
 				<select id="fileFormatSelectInput">
 					<option id="fileFormatSelectHTMLLabel" value="html"></option>
-					<option id="fileFormatSelectSelfExtractingUniversalLabel" value="self-extracting-zip-universal"></option>
+					<option id="fileFormatSelectSelfExtractingUniversalLabel" value="self-extracting-zip-universal">
+					</option>
 					<option id="fileFormatSelectSelfExtractingLabel" value="self-extracting-zip"></option>
 					<option id="fileFormatSelectZIPLabel" value="zip"></option>
 				</select>
@@ -117,8 +118,16 @@
 				<input type="checkbox" id="insertTextBodyInput">
 			</div>
 			<div class="option">
-				<label for="insertEmbeddedImageInput" id="insertEmbeddedImageLabel"></label>
-				<input type="checkbox" id="insertEmbeddedImageInput">
+				<label id="insertEmbeddedImageLabel"></label>
+				<input type="checkbox" id="insertEmbeddedImageInput" name="embeddedImageInput">
+			</div>
+			<div class="option second-level">
+				<label for="insertEmbeddedScreenshotImageInput" id="insertEmbeddedScreenshotImageLabel"></label>
+				<input type="radio" id="insertEmbeddedScreenshotImageInput" name="embeddedImageInput">
+			</div>
+			<div class="option second-level">
+				<label for="insertEmbeddedCustomImageInput" id="insertEmbeddedCustomImageLabel"></label>
+				<input type="radio" id="insertEmbeddedCustomImageInput" name="embeddedImageInput">
 			</div>
 		</details>
 		<details>

Some files were not shown because too many files changed in this diff