Procházet zdrojové kódy

add option `File format > insert embedded image`

Gildas před 2 roky
rodič
revize
ed05be3188

+ 16 - 0
_locales/de/messages.json

@@ -299,6 +299,10 @@
 		"message": "Text durchsuchbar machen",
 		"description": "Options page label: 'make text searchable'"
 	},
+	"optionInsertEmbeddedImage": {
+		"message": "insert embedded image",
+		"description": "Options page label: 'insert embedded image'"
+	},
 	"optionsInfobarSubTitle": {
 		"message": "Infoknopf",
 		"description": "Options sub-title: 'Infobar'"
@@ -739,6 +743,10 @@
 		"message": "Rahmeninhalte",
 		"description": "Label 'Frame contents' in the log panel"
 	},
+	"logPanelEmbeddedImage": {
+		"message": "Eingebettetes Bild",
+		"description": "Label 'Embedded image' in the log panel"
+	},
 	"logPanelStep": {
 		"message": "Schritt",
 		"description": "Label 'Step' (for 'Step x / 4') in the log panel"
@@ -747,6 +755,14 @@
 		"message": "150",
 		"description": "Width of the log panel in pixels, it should be adjusted for the longest label beginning with 'log' (e.g. 'logPanelDeferredImages')"
 	},
+	"topPanelError": {
+		"message": "SingleFile Fehler : ",
+		"description": "Label 'SingleFile error: ' in the top panel when displaying an error message"
+	},
+	"topPanelEmbeddedImageButton": {
+		"message": "Bild öffnen...",
+		"description": "Top panel button 'Open image...' when embedding an image"
+	},
 	"profileDefaultSettings": {
 		"message": "Standardeinstellungen",
 		"description": "Label 'Default settings' of the default settings in the options page"

+ 16 - 0
_locales/en/messages.json

@@ -299,6 +299,10 @@
 		"message": "make text searchable",
 		"description": "Options page label: 'make text searchable'"
 	},
+	"optionInsertEmbeddedImage": {
+		"message": "insert embedded image",
+		"description": "Options page label: 'insert embedded image'"
+	},
 	"optionsInfobarSubTitle": {
 		"message": "Infobar",
 		"description": "Options sub-title: 'Infobar'"
@@ -739,6 +743,10 @@
 		"message": "Frame contents",
 		"description": "Label 'Frame contents' in the log panel"
 	},
+	"logPanelEmbeddedImage": {
+		"message": "Embedded image",
+		"description": "Label 'Embedded image' in the log panel"
+	},
 	"logPanelStep": {
 		"message": "Step",
 		"description": "Label 'Step' (for 'Step x / 4') in the log panel"
@@ -747,6 +755,14 @@
 		"message": "122",
 		"description": "Width of the log panel in pixels, it should be adjusted for the longest label beginning with 'log' (e.g. 'logPanelDeferredImages')"
 	},
+	"topPanelError": {
+		"message": "SingleFile error: ",
+		"description": "Label 'SingleFile error: ' in the top panel when displaying an error message"
+	},
+	"topPanelEmbeddedImageButton": {
+		"message": "Open image...",
+		"description": "Top panel button 'Open image...' when embedding an image"
+	},
 	"profileDefaultSettings": {
 		"message": "Default settings",
 		"description": "Label 'Default settings' of the default settings in the options page"

+ 16 - 0
_locales/es/messages.json

@@ -299,6 +299,10 @@
 		"message": "hacer buscable el texto",
 		"description": "Options page label: 'make text searchable'"
 	},
+	"optionInsertEmbeddedImage": {
+		"message": "insert embedded image",
+		"description": "Options page label: 'insert embedded image'"
+	},
 	"optionsInfobarSubTitle": {
 		"message": "Barra informativa",
 		"description": "Options sub-title: 'Infobar'"
@@ -739,6 +743,10 @@
 		"message": "Contenidos de marco (frame)",
 		"description": "Label 'Frame contents' in the log panel"
 	},
+	"logPanelEmbeddedImage": {
+		"message": "Imagen incrustada",
+		"description": "Label 'Embedded image' in the log panel"
+	},
 	"logPanelStep": {
 		"message": "Nivel",
 		"description": "Label 'Step' (for 'Step x / 4') in the log panel"
@@ -747,6 +755,14 @@
 		"message": "192",
 		"description": "Width of the log panel in pixels, it should be adjusted for the longest label beginning with 'log' (e.g. 'logPanelDeferredImages')"
 	},
+	"topPanelError": {
+		"message": "Error de SingleFile: ",
+		"description": "Label 'SingleFile error: ' in the top panel when displaying an error message"
+	},
+	"topPanelEmbeddedImageButton": {
+		"message": "Abrir imagen...",
+		"description": "Top panel button 'Open image...' when embedding an image"
+	},
 	"profileDefaultSettings": {
 		"message": "Configuración predeterminada",
 		"description": "Label 'Default settings' of the default settings in the options page"

+ 16 - 0
_locales/fr/messages.json

@@ -299,6 +299,10 @@
 		"message": "rendre le texte indexable",
 		"description": "Options page label: 'make text searchable'"
 	},
+	"optionInsertEmbeddedImage": {
+		"message": "insert embedded image",
+		"description": "Options page label: 'insert embedded image'"
+	},
 	"optionsInfobarSubTitle": {
 		"message": "Barre d'information",
 		"description": "Options sub-title: 'Infobar'"
@@ -739,6 +743,10 @@
 		"message": "Contenus des cadres",
 		"description": "Label 'Frame contents' in the log panel"
 	},
+	"logPanelEmbeddedImage": {
+		"message": "Image intégrée",
+		"description": "Label 'Embedded image' in the log panel"
+	},
 	"logPanelStep": {
 		"message": "Étape",
 		"description": "Label 'Step' (for 'Step x / 4') in the log panel"
@@ -747,6 +755,14 @@
 		"message": "147",
 		"description": "Width of the log panel in pixels, it should be adjusted for the longest label beginning with 'log' (e.g. 'logPanelDeferredImages')"
 	},
+	"topPanelError": {
+		"message": "Erreur SingleFile : ",
+		"description": "Label 'SingleFile error: ' in the top panel when displaying an error message"
+	},
+	"topPanelEmbeddedImageButton": {
+		"message": "Ouvrir image...",
+		"description": "Top panel button 'Open image...' when embedding an image"
+	},
 	"profileDefaultSettings": {
 		"message": "Configuration par défaut",
 		"description": "Label 'Default settings' of the default settings in the options page"

+ 16 - 0
_locales/it/messages.json

@@ -299,6 +299,10 @@
 		"message": "rendi il testo ricercabile",
 		"description": "Options page label: 'make text searchable'"
 	},
+	"optionInsertEmbeddedImage": {
+		"message": "insert embedded image",
+		"description": "Options page label: 'insert embedded image'"
+	},
 	"optionsInfobarSubTitle": {
 		"message": "Barra informativa",
 		"description": "Options sub-title: 'Infobar'"
@@ -739,6 +743,10 @@
 		"message": "Contenuti frame",
 		"description": "Label 'Frame contents' in the log panel"
 	},
+	"logPanelEmbeddedImage": {
+		"message": "Immagine incorporata",
+		"description": "Label 'Embedded image' in the log panel"
+	},
 	"logPanelStep": {
 		"message": "Passo",
 		"description": "Label 'Step' (for 'Step x / 4') in the log panel"
@@ -747,6 +755,14 @@
 		"message": "122",
 		"description": "Width of the log panel in pixels, it should be adjusted for the longest label beginning with 'log' (e.g. 'logPanelDeferredImages')"
 	},
+	"topPanelError": {
+		"message": "Errore di SingleFile: ",
+		"description": "Label 'SingleFile error: ' in the top panel when displaying an error message"
+	},
+	"topPanelEmbeddedImageButton": {
+		"message": "Apri immagine...",
+		"description": "Top panel button 'Open image...' when embedding an image"
+	},
 	"profileDefaultSettings": {
 		"message": "Impostazioni predefinite",
 		"description": "Label 'Default settings' of the default settings in the options page"

+ 16 - 0
_locales/ja/messages.json

@@ -299,6 +299,10 @@
 		"message": "テキストを検索可能にする",
 		"description": "Options page label: 'make text searchable'"
 	},
+	"optionInsertEmbeddedImage": {
+		"message": "insert embedded image",
+		"description": "Options page label: 'insert embedded image'"
+	},
 	"optionsInfobarSubTitle": {
 		"message": "インフォバー",
 		"description": "Options sub-title: 'Infobar'"
@@ -739,6 +743,10 @@
 		"message": "フレームの内容",
 		"description": "Label 'Frame contents' in the log panel"
 	},
+	"logPanelEmbeddedImage": {
+		"message": "埋め込み画像",
+		"description": "Label 'Embedded image' in the log panel"
+	},
 	"logPanelStep": {
 		"message": "ステップ",
 		"description": "Label 'Step' (for 'Step x / 4') in the log panel"
@@ -747,6 +755,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')"
 	},
+	"topPanelError": {
+		"message": "SingleFile エラー: ",
+		"description": "Label 'SingleFile error: ' in the top panel when displaying an error message"
+	},
+	"topPanelEmbeddedImageButton": {
+		"message": "画像を開く...",
+		"description": "Top panel button 'Open image...' when embedding an image"
+	},
 	"profileDefaultSettings": {
 		"message": "デフォルトの設定",
 		"description": "Label 'Default settings' of the default settings in the options page"

+ 16 - 0
_locales/pl/messages.json

@@ -299,6 +299,10 @@
 		"message": "uczyń tekst przeszukiwalnym",
 		"description": "Options page label: 'make text searchable'"
 	},
+	"optionInsertEmbeddedImage": {
+		"message": "insert embedded image",
+		"description": "Options page label: 'insert embedded image'"
+	},
 	"optionsInfobarSubTitle": {
 		"message": "Pasek informacyjny",
 		"description": "Options sub-title: 'Infobar'"
@@ -739,6 +743,10 @@
 		"message": "Zawartość ramki",
 		"description": "Label 'Frame contents' in the log panel"
 	},
+	"logPanelEmbeddedImage": {
+		"message": "Embedded image",
+		"description": "Label 'Embedded image' in the log panel"
+	},
 	"logPanelStep": {
 		"message": "Krok",
 		"description": "Label 'Step' (for 'Step x / 4') in the log panel"
@@ -747,6 +755,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')"
 	},
+	"topPanelError": {
+		"message": "SingleFile error: ",
+		"description": "Label 'SingleFile error: ' in the top panel when displaying an error message"
+	},
+	"topPanelEmbeddedImageButton": {
+		"message": "Open image...",
+		"description": "Top panel button 'Open image...' when embedding an image"
+	},
 	"profileDefaultSettings": {
 		"message": "Ustawienia domyślne",
 		"description": "Label 'Default settings' of the default settings in the options page"

+ 16 - 0
_locales/pt_PT/messages.json

@@ -299,6 +299,10 @@
 		"message": "tornar o texto pesquisável",
 		"description": "Options page label: 'make text searchable'"
 	},
+	"optionInsertEmbeddedImage": {
+		"message": "insert embedded image",
+		"description": "Options page label: 'insert embedded image'"
+	},
 	"optionsInfobarSubTitle": {
 		"message": "Barra de Informações",
 		"description": "Options sub-title: 'Infobar'"
@@ -739,6 +743,10 @@
 		"message": "Conteúdo do frame",
 		"description": "Label 'Frame contents' in the log panel"
 	},
+	"logPanelEmbeddedImage": {
+		"message": "Imagem incorporada",
+		"description": "Label 'Embedded image' in the log panel"
+	},
 	"logPanelStep": {
 		"message": "Passo",
 		"description": "Label 'Step' (for 'Step x / 4') in the log panel"
@@ -747,6 +755,14 @@
 		"message": "122",
 		"description": "Width of the log panel in pixels, it should be adjusted for the longest label beginning with 'log' (e.g. 'logPanelDeferredImages')"
 	},
+	"topPanelError": {
+		"message": "Erro do SingleFile: ",
+		"description": "Label 'SingleFile error: ' in the top panel when displaying an error message"
+	},
+	"topPanelEmbeddedImageButton": {
+		"message": "Abrir imagem...",
+		"description": "Top panel button 'Open image...' when embedding an image"
+	},
 	"profileDefaultSettings": {
 		"message": "Definições predefinidas",
 		"description": "Label 'Default settings' of the default settings in the options page"

+ 16 - 0
_locales/pt_br/messages.json

@@ -299,6 +299,10 @@
 		"message": "tornar texto pesquisável",
 		"description": "Options page label: 'make text searchable'"
 	},
+	"optionInsertEmbeddedImage": {
+		"message": "insert embedded image",
+		"description": "Options page label: 'insert embedded image'"
+	},
 	"optionsInfobarSubTitle": {
 		"message": "Infobar",
 		"description": "Options sub-title: 'Infobar'"
@@ -739,6 +743,10 @@
 		"message": "Conteúdo do frame",
 		"description": "Label 'Frame contents' in the log panel"
 	},
+	"logPanelEmbeddedImage": {
+		"message": "Imagem incorporada",
+		"description": "Label 'Embedded image' in the log panel"
+	},
 	"logPanelStep": {
 		"message": "Passo",
 		"description": "Label 'Step' (for 'Step x / 4') in the log panel"
@@ -747,6 +755,14 @@
 		"message": "122",
 		"description": "Width of the log panel in pixels, it should be adjusted for the longest label beginning with 'log' (e.g. 'logPanelDeferredImages')"
 	},
+	"topPanelError": {
+		"message": "Erro do SingleFile: ",
+		"description": "Label 'SingleFile error: ' in the top panel when displaying an error message"
+	},
+	"topPanelEmbeddedImageButton": {
+		"message": "Abrir imagem...",
+		"description": "Top panel button 'Open image...' when embedding an image"
+	},
 	"profileDefaultSettings": {
 		"message": "Configurações padrão",
 		"description": "Label 'Default settings' of the default settings in the options page"

+ 16 - 0
_locales/ru/messages.json

@@ -299,6 +299,10 @@
 		"message": "делать текст доступным для поиска",
 		"description": "Options page label: 'make text searchable'"
 	},
+	"optionInsertEmbeddedImage": {
+		"message": "insert embedded image",
+		"description": "Options page label: 'insert embedded image'"
+	},
 	"optionsInfobarSubTitle": {
 		"message": "Информационная панель",
 		"description": "Options sub-title: 'Infobar'"
@@ -739,6 +743,10 @@
 		"message": "Содержимое фреймов",
 		"description": "Label 'Frame contents' in the log panel"
 	},
+	"logPanelEmbeddedImage": {
+		"message": "Embedded image",
+		"description": "Label 'Embedded image' in the log panel"
+	},
 	"logPanelStep": {
 		"message": "Шаг",
 		"description": "Label 'Step' (for 'Step x / 4') in the log panel"
@@ -747,6 +755,14 @@
 		"message": "190",
 		"description": "Width of the log panel in pixels, it should be adjusted for the longest label beginning with 'log' (e.g. 'logPanelDeferredImages')"
 	},
+	"topPanelError": {
+		"message": "SingleFile error: ",
+		"description": "Label 'SingleFile error: ' in the top panel when displaying an error message"
+	},
+	"topPanelEmbeddedImageButton": {
+		"message": "Open image...",
+		"description": "Top panel button 'Open image...' when embedding an image"
+	},
 	"profileDefaultSettings": {
 		"message": "Настройки по умолчанию",
 		"description": "Label 'Default settings' of the default settings in the options page"

+ 16 - 0
_locales/tr/messages.json

@@ -299,6 +299,10 @@
 		"message": "metni aranabilir yap",
 		"description": "Options page label: 'make text searchable'"
 	},
+	"optionInsertEmbeddedImage": {
+		"message": "insert embedded image",
+		"description": "Options page label: 'insert embedded image'"
+	},
 	"optionsInfobarSubTitle": {
 		"message": "Bilgi çubuğu",
 		"description": "Options sub-title: 'Infobar'"
@@ -739,6 +743,10 @@
 		"message": "çerçeve içeriği",
 		"description": "Label 'Frame contents' in the log panel"
 	},
+	"logPanelEmbeddedImage": {
+		"message": "Gömülü resim",
+		"description": "Label 'Embedded image' in the log panel"
+	},
 	"logPanelStep": {
 		"message": "Adım",
 		"description": "Label 'Step' (for 'Step x / 4') in the log panel"
@@ -747,6 +755,14 @@
 		"message": "122",
 		"description": "Width of the log panel in pixels, it should be adjusted for the longest label beginning with 'log' (e.g. 'logPanelDeferredImages')"
 	},
+	"topPanelError": {
+		"message": "SingleFile hata: ",
+		"description": "Label 'SingleFile error: ' in the top panel when displaying an error message"
+	},
+	"topPanelEmbeddedImageButton": {
+		"message": "Resmi aç...",
+		"description": "Top panel button 'Open image...' when embedding an image"
+	},
 	"profileDefaultSettings": {
 		"message": "Varsayılan ayarlar",
 		"description": "Label 'Default settings' of the default settings in the options page"

+ 16 - 0
_locales/uk/messages.json

@@ -299,6 +299,10 @@
 		"message": "зробити текст доступним для пошуку",
 		"description": "Options page label: 'make text searchable'"
 	},
+	"optionInsertEmbeddedImage": {
+		"message": "insert embedded image",
+		"description": "Options page label: 'insert embedded image'"
+	},
 	"optionsInfobarSubTitle": {
 		"message": "Інфобар",
 		"description": "Options sub-title: 'Infobar'"
@@ -739,6 +743,10 @@
 		"message": "Вміст фрейму",
 		"description": "Label 'Frame contents' in the log panel"
 	},
+	"logPanelEmbeddedImage": {
+		"message": "Вбудоване зображення",
+		"description": "Label 'Embedded image' in the log panel"
+	},
 	"logPanelStep": {
 		"message": "Крок",
 		"description": "Label 'Step' (for 'Step x / 4') in the log panel"
@@ -747,6 +755,14 @@
 		"message": "Сто двадцять і два,",
 		"description": "Width of the log panel in pixels, it should be adjusted for the longest label beginning with 'log' (e.g. 'logPanelDeferredImages')"
 	},
+	"topPanelError": {
+		"message": "Помилка SingleFile: ",
+		"description": "Label 'SingleFile error: ' in the top panel when displaying an error message"
+	},
+	"topPanelEmbeddedImageButton": {
+		"message": "Відкрити зображення..",
+		"description": "Top panel button 'Open image...' when embedding an image"
+	},
 	"profileDefaultSettings": {
 		"message": "Типові налаштування",
 		"description": "Label 'Default settings' of the default settings in the options page"

+ 16 - 0
_locales/zh_CN/messages.json

@@ -299,6 +299,10 @@
 		"message": "使文本可被搜索",
 		"description": "Options page label: 'make text searchable'"
 	},
+	"optionInsertEmbeddedImage": {
+		"message": "insert embedded image",
+		"description": "Options page label: 'insert embedded image'"
+	},
 	"optionsInfobarSubTitle": {
 		"message": "信息栏",
 		"description": "Options sub-title: 'Infobar'"
@@ -739,6 +743,10 @@
 		"message": "框架内容",
 		"description": "Label 'Frame contents' in the log panel"
 	},
+	"logPanelEmbeddedImage": {
+		"message": "嵌入式图像",
+		"description": "Label 'Embedded image' in the log panel"
+	},
 	"logPanelStep": {
 		"message": "步骤",
 		"description": "Label 'Step' (for 'Step x / 4') in the log panel"
@@ -747,6 +755,14 @@
 		"message": "122",
 		"description": "Width of the log panel in pixels, it should be adjusted for the longest label beginning with 'log' (e.g. 'logPanelDeferredImages')"
 	},
+	"topPanelError": {
+		"message": "SingleFile 错误: ",
+		"description": "Label 'SingleFile error: ' in the top panel when displaying an error message"
+	},
+	"topPanelEmbeddedImageButton": {
+		"message": "打开图片...",
+		"description": "Top panel button 'Open image...' when embedding an image"
+	},
 	"profileDefaultSettings": {
 		"message": "默认设置",
 		"description": "Label 'Default settings' of the default settings in the options page"

+ 16 - 0
_locales/zh_TW/messages.json

@@ -299,6 +299,10 @@
 		"message": "使文本可被搜索",
 		"description": "Options page label: 'make text searchable'"
 	},
+	"optionInsertEmbeddedImage": {
+		"message": "insert embedded image",
+		"description": "Options page label: 'insert embedded image'"
+	},
 	"optionsInfobarSubTitle": {
 		"message": "信息欄",
 		"description": "Options sub-title: 'Infobar'"
@@ -739,6 +743,10 @@
 		"message": "框架內容",
 		"description": "Label 'Frame contents' in the log panel"
 	},
+	"logPanelEmbeddedImage": {
+		"message": "嵌入式圖像",
+		"description": "Label 'Embedded image' in the log panel"
+	},
 	"logPanelStep": {
 		"message": "步驟",
 		"description": "Label 'Step' (for 'Step x / 4') in the log panel"
@@ -747,6 +755,14 @@
 		"message": "122",
 		"description": "Width of the log panel in pixels, it should be adjusted for the longest label beginning with 'log' (e.g. 'logPanelDeferredImages')"
 	},
+	"topPanelError": {
+		"message": "SingleFile 錯誤: ",
+		"description": "Label 'SingleFile error: ' in the top panel when displaying an error message"
+	},
+	"topPanelEmbeddedImageButton": {
+		"message": "開啟圖片...",
+		"description": "Top panel button 'Open image...' when embedding an image"
+	},
 	"profileDefaultSettings": {
 		"message": "默認設置",
 		"description": "Label 'Default settings' of the default settings in the options page"

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

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

+ 4 - 2
src/core/bg/downloads.js

@@ -270,7 +270,8 @@ async function downloadCompressedContent(message, tab) {
 				preventAppendedData: message.preventAppendedData,
 				insertCanonicalLink: message.insertCanonicalLink,
 				insertMetaNoIndex: message.insertMetaNoIndex,
-				password: message.password
+				password: message.password,
+				embeddedImage: message.embeddedImage
 			});
 			let response;
 			if (message.openEditor) {
@@ -282,7 +283,8 @@ async function downloadCompressedContent(message, tab) {
 					compressContent: message.compressContent,
 					selfExtractingArchive: message.selfExtractingArchive,
 					extractDataFromPage: message.extractDataFromPage,
-					insertTextBody: message.insertTextBody
+					insertTextBody: message.insertTextBody,
+					embeddedImage: message.embeddedImage
 				});
 			} else if (message.foregroundSave) {
 				await downloadPageForeground(message.taskId, message.filename, blob, tabId, message.foregroundSave);

+ 5 - 3
src/core/bg/editor.js

@@ -39,7 +39,7 @@ export {
 	EDITOR_URL
 };
 
-async function open({ tabIndex, content, filename, compressContent, selfExtractingArchive, extractDataFromPage, insertTextBody }) {
+async function open({ tabIndex, content, filename, compressContent, selfExtractingArchive, extractDataFromPage, insertTextBody, embeddedImage }) {
 	const createTabProperties = { active: true, url: EDITOR_PAGE_URL };
 	if (tabIndex != null) {
 		createTabProperties.index = tabIndex;
@@ -51,7 +51,8 @@ async function open({ tabIndex, content, filename, compressContent, selfExtracti
 		compressContent, 
 		selfExtractingArchive, 
 		extractDataFromPage,
-		insertTextBody
+		insertTextBody,
+		embeddedImage
 	});
 }
 
@@ -118,7 +119,8 @@ async function onMessage(message, sender) {
 				compressContent: message.compressContent,
 				selfExtractingArchive: message.selfExtractingArchive,
 				extractDataFromPageTags: message.extractDataFromPageTags,
-				insertTextBody: message.insertTextBody
+				insertTextBody: message.insertTextBody,
+				embeddedImage: message.embeddedImage
 			});
 		}
 		return {};

+ 1 - 0
src/core/common/download.js

@@ -69,6 +69,7 @@ async function downloadPage(pageData, options) {
 		warnUnsavedPage: options.warnUnsavedPage,
 		createRootDirectory: options.createRootDirectory,
 		selfExtractingArchive: options.selfExtractingArchive,
+		embeddedImage: options.embeddedImage,
 		preventAppendedData: options.preventAppendedData,
 		extractDataFromPage: options.extractDataFromPage,
 		insertCanonicalLink: options.insertCanonicalLink,

+ 23 - 4
src/core/content/content.js

@@ -26,7 +26,7 @@
 import * as download from "./../common/download.js";
 import { fetch, frameFetch } from "./../../lib/single-file/fetch/content/content-fetch.js";
 import * as ui from "./../../ui/content/content-ui.js";
-import { onError } from "./../../ui/common/content-error.js";
+import { onError, getOpenFileBar, openFile } from "./../../ui/common/common-content-ui.js";
 import * as yabson from "./../../lib/yabson/yabson.js";
 
 const singlefile = globalThis.singlefile;
@@ -34,7 +34,7 @@ const bootstrap = globalThis.singlefileBootstrap;
 
 const MOZ_EXTENSION_PROTOCOL = "moz-extension:";
 
-let processor, processing, downloadParser;
+let processor, processing, downloadParser, openFileInfobar;
 
 if (!bootstrap || !bootstrap.initializedSingleFile) {
 	singlefile.init({ fetch, frameFetch });
@@ -60,6 +60,10 @@ async function onMessage(message) {
 			if (processor) {
 				processor.cancel();
 				ui.onEndPage();
+				if (openFileInfobar) {
+					openFileInfobar.cancel();
+					openFileInfobar = null;
+				}
 				browser.runtime.sendMessage({ method: "ui.processCancelled" });
 			}
 			if (message.options.loadDeferredImages) {
@@ -188,7 +192,22 @@ async function processPage(options) {
 		}
 	};
 	const cancelProcessor = processor.cancel.bind(processor);
-	if (!options.saveRawPage) {
+	if (options.insertEmbeddedImage) {
+		ui.onInsertingEmbeddedImage(options);
+		openFileInfobar = getOpenFileBar();
+		const cancelled = await openFileInfobar.display();
+		if (cancelled) {
+			browser.runtime.sendMessage({ method: "downloads.cancel", taskId: options.taskId });
+		} else {
+			const embeddedImage = await openFile({ accept: "image/*" });
+			if (embeddedImage) {
+				options.embeddedImage = Array.from(embeddedImage);
+			}
+			openFileInfobar.hide();
+			ui.onInsertEmbeddedImage(options);
+		}
+	}
+	if (!options.saveRawPage && !processor.cancelled) {
 		let lazyLoadPromise;
 		if (options.loadDeferredImages) {
 			lazyLoadPromise = singlefile.processors.lazy.process(options);
@@ -231,7 +250,7 @@ async function processPage(options) {
 			preInitializationPromises.push(lazyLoadPromise);
 		}
 	}
-	if (!options.loadDeferredImagesBeforeFrames) {
+	if (!options.loadDeferredImagesBeforeFrames && !processor.cancelled) {
 		[options.frames] = await new Promise(resolve => {
 			const preInitializationAllPromises = Promise.all(preInitializationPromises);
 			processor.cancel = function () {

+ 4 - 1
src/ui/bg/ui-editor.js

@@ -24,7 +24,7 @@
 /* global browser, document, matchMedia, addEventListener, navigator, prompt, URL, MouseEvent, Blob, setInterval, DOMParser */
 
 import * as download from "../../core/common/download.js";
-import { onError } from "./../common/content-error.js";
+import { onError } from "./../common/common-content-ui.js";
 import * as zip from "./../../../lib/single-file-zip.js";
 import * as yabson from "./../../lib/yabson/yabson.js";
 
@@ -285,6 +285,9 @@ addEventListener("message", event => {
 			if (tabData.insertTextBody !== undefined) {
 				tabData.options.insertTextBody = tabData.insertTextBody;
 			}
+			if (tabData.embeddedImage !== undefined) {
+				tabData.options.embeddedImage = tabData.embeddedImage;
+			}
 			getContentPageData(tabData.content, message.content, { password: tabData.options.password })
 				.then(pageData => {
 					pageData.content = message.content;

+ 6 - 0
src/ui/bg/ui-options.js

@@ -91,6 +91,7 @@ const githubBranchLabel = document.getElementById("githubBranchLabel");
 const saveWithCompanionLabel = document.getElementById("saveWithCompanionLabel");
 const compressHTMLLabel = document.getElementById("compressHTMLLabel");
 const insertTextBodyLabel = document.getElementById("insertTextBodyLabel");
+const insertEmbeddedImageLabel = document.getElementById("insertEmbeddedImageLabel");
 const compressCSSLabel = document.getElementById("compressCSSLabel");
 const moveStylesInHeadLabel = document.getElementById("moveStylesInHeadLabel");
 const loadDeferredImagesLabel = document.getElementById("loadDeferredImagesLabel");
@@ -230,6 +231,7 @@ const saveWithCompanionInput = document.getElementById("saveWithCompanionInput")
 const saveToFilesystemInput = document.getElementById("saveToFilesystemInput");
 const compressHTMLInput = document.getElementById("compressHTMLInput");
 const insertTextBodyInput = document.getElementById("insertTextBodyInput");
+const insertEmbeddedImageInput = document.getElementById("insertEmbeddedImageInput");
 const compressCSSInput = document.getElementById("compressCSSInput");
 const moveStylesInHeadInput = document.getElementById("moveStylesInHeadInput");
 const loadDeferredImagesInput = document.getElementById("loadDeferredImagesInput");
@@ -626,6 +628,7 @@ githubBranchLabel.textContent = browser.i18n.getMessage("optionGitHubBranch");
 saveWithCompanionLabel.textContent = browser.i18n.getMessage("optionSaveWithCompanion");
 compressHTMLLabel.textContent = browser.i18n.getMessage("optionCompressHTML");
 insertTextBodyLabel.textContent = browser.i18n.getMessage("optionInsertTextBody");
+insertEmbeddedImageLabel.textContent = browser.i18n.getMessage("optionInsertEmbeddedImage");
 compressCSSLabel.textContent = browser.i18n.getMessage("optionCompressCSS");
 moveStylesInHeadLabel.textContent = browser.i18n.getMessage("optionMoveStylesInHead");
 loadDeferredImagesLabel.textContent = browser.i18n.getMessage("optionLoadDeferredImages");
@@ -985,6 +988,8 @@ async function refresh(profileName) {
 	passwordInput.disabled = !profileOptions.compressContent;
 	insertTextBodyInput.checked = profileOptions.insertTextBody;
 	insertTextBodyInput.disabled = !profileOptions.compressContent || (!profileOptions.selfExtractingArchive && !profileOptions.extractDataFromPage);
+	insertEmbeddedImageInput.checked = profileOptions.insertEmbeddedImage;
+	insertEmbeddedImageInput.disabled = !profileOptions.compressContent;
 	infobarTemplateInput.value = profileOptions.infobarTemplate;
 	blockMixedContentInput.checked = profileOptions.blockMixedContent;
 	saveOriginalURLsInput.checked = profileOptions.saveOriginalURLs;
@@ -1053,6 +1058,7 @@ async function update() {
 			saveWithCompanion: saveWithCompanionInput.checked,
 			compressHTML: compressHTMLInput.checked,
 			insertTextBody: insertTextBodyInput.checked,
+			insertEmbeddedImage: insertEmbeddedImageInput.checked,
 			compressCSS: compressCSSInput.checked,
 			moveStylesInHead: moveStylesInHeadInput.checked,
 			loadDeferredImages: loadDeferredImagesInput.checked,

+ 238 - 0
src/ui/common/common-content-ui.js

@@ -0,0 +1,238 @@
+/*
+ * 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, document, globalThis, getComputedStyle, FileReader, Image, OffscreenCanvas */
+
+const singlefile = globalThis.singlefile;
+
+const CLOSE_ICON = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAABhmlDQ1BJQ0MgcHJvZmlsZQAAKJF9kT1Iw0AYht+mSlUqHewg4hChOogFURFHqWIRLJS2QqsOJpf+CE0akhQXR8G14ODPYtXBxVlXB1dBEPwBcXNzUnSREr9LCi1ivOO4h/e+9+XuO0Col5lqdowDqmYZqXhMzOZWxMAruhGiOYohiZl6Ir2Qgef4uoeP73dRnuVd9+foVfImA3wi8SzTDYt4nXh609I57xOHWUlSiM+Jxwy6IPEj12WX3zgXHRZ4ZtjIpOaIw8RisY3lNmYlQyWeIo4oqkb5QtZlhfMWZ7VcZc178hcG89pymuu0BhHHIhJIQoSMKjZQhoUo7RopJlJ0HvPwDzj+JLlkcm2AkWMeFaiQHD/4H/zurVmYnHCTgjGg88W2P4aBwC7QqNn297FtN04A/zNwpbX8lTow80l6raVFjoDQNnBx3dLkPeByB+h/0iVDciQ/LaFQAN7P6JtyQN8t0LPq9q15jtMHIEO9WroBDg6BkSJlr3m8u6u9b//WNPv3A6mTcr3f/E/sAAAABmJLR0QAigCKAIrj2uckAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAB3RJTUUH5QkPDysvCdPVuwAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAELSURBVHja7ZpLFsIwDAPj3v/OsGHDe1BIa8tKO7Mnlkw+dpoxAAAAAGCfx4ur6Yx/B337UUS4mp/VuWUEcjSfOgO+BXCZCWe0hSqQo/npBLglIUNLdAV2MH84Ad1JyIwdLkK6YoabIHWscBWmihHuAqvHtv+XqmdXOK9TxdKy3axUm2vZkXXGgPJksTuz1bVFeeU2Y6ijsLIpXbtKa1kDs2ews69o7+A+ihJ2lvI+/lcS1G21zUVG18XKNm4OS4BNkGOQQohSmGaIdpgLESvzyiRwKepsXjE2H0ZWMF8Zi4+jK5mviM0DiRXNZ2rhkdTK5jO0xermz2o8dCnq+FS2XNNVH0sDAAAA3JYnre9cH8BZmhEAAAAASUVORK5CYII=";
+
+const SINGLE_FILE_UI_ELEMENT_CLASS = singlefile.helper.SINGLE_FILE_UI_ELEMENT_CLASS;
+const ERROR_BAR_TAGNAME = "singlefile-error-bar";
+const OPEN_FILE_BAR_TAGNAME = "singlefile-open-file-bar";
+const EMBEDDED_IMAGE_BUTTON_MESSAGE = browser.i18n.getMessage("topPanelEmbeddedImageButton");
+const ERROR_TITLE_MESSAGE = browser.i18n.getMessage("topPanelError");
+
+const CSS_PROPERTIES = new Set(Array.from(getComputedStyle(document.documentElement)));
+
+export {
+	openFile,
+	getOpenFileBar,
+	onError
+};
+
+function onError(message, link) {
+	console.error("SingleFile", message, link); // eslint-disable-line no-console
+	displayBar(ERROR_BAR_TAGNAME, ERROR_TITLE_MESSAGE + message, { link });
+}
+
+function getOpenFileBar() {
+	let resolvePromise;
+	return {
+		display: async function () {
+			return new Promise(resolve => {
+				resolvePromise = resolve;
+				displayBar(OPEN_FILE_BAR_TAGNAME, "", { buttonLabel: EMBEDDED_IMAGE_BUTTON_MESSAGE, buttonOnclick: resolve });
+			});
+		},
+		hide: function () {
+			const barElement = document.querySelector(OPEN_FILE_BAR_TAGNAME);
+			if (barElement) {
+				barElement.remove();
+			}
+		},
+		cancel: function () {
+			this.hide();
+			if (resolvePromise) {
+				resolvePromise(true);
+			}
+		}
+	};
+}
+
+function openFile({ accept } = { accept: "image/*" }) {
+	const inputElement = document.createElement("input");
+	inputElement.type = "file";
+	inputElement.accept = accept;
+	inputElement.click();
+	return new Promise(resolve => {
+		inputElement.addEventListener("change", event => {
+			if (event.target.files.length) {
+				const file = event.target.files[0];
+				const fileReader = new FileReader();
+				fileReader.addEventListener("load", async () => {
+					let mimeType = file.type;
+					if (mimeType == "image/png") {
+						resolve(new Uint8Array(fileReader.result));
+					} else {
+						const dataURI = await new Promise(resolve => {
+							const fileReader = new FileReader();
+							fileReader.addEventListener("load", () => resolve(fileReader.result));
+							fileReader.addEventListener("error", () => resolve());
+							fileReader.readAsDataURL(file);
+						});
+						if (dataURI) {
+							const image = new Image();
+							image.src = dataURI;
+							image.addEventListener("error", () => resolve());
+							await new Promise(resolve => image.addEventListener("load", resolve));
+							const canvas = new OffscreenCanvas(image.width, image.height);
+							const context = canvas.getContext("2d");
+							context.drawImage(image, 0, 0);
+							const blob = await canvas.convertToBlob({ type: "image/png" });
+							const fileReader = new FileReader();
+							fileReader.addEventListener("load", () => resolve(new Uint8Array(fileReader.result)));
+							fileReader.addEventListener("error", () => resolve());
+							fileReader.readAsArrayBuffer(blob);
+						} else {
+							resolve();
+						}
+					}
+					resolve(Array.from(new Uint8Array(fileReader.result)));
+				});
+				fileReader.addEventListener("error", () => resolve());
+				fileReader.readAsArrayBuffer(file);
+			} else {
+				resolve();
+			}
+		});
+		inputElement.addEventListener("cancel", () => resolve());
+	});
+}
+
+function displayBar(tagName, message, { link, buttonLabel, buttonOnclick } = {}) {
+	try {
+		const barElement = document.querySelector(tagName);
+		if (!barElement) {
+			const barElement = createElement(tagName);
+			const shadowRoot = barElement.attachShadow({ mode: "open" });
+			const styleElement = document.createElement("style");
+			styleElement.textContent = `
+				.container {
+					background-color: #ff6c00;
+					color: white;
+					display: flex;
+					position: fixed;
+					top: 0px;
+					left: 0px;
+					right: 0px;
+					height: auto;
+					width: auto;
+					min-height: 24px;
+					min-width: 24px;					
+					z-index: 2147483647;
+					margin: 0;
+					padding: 2px;
+					font-family: Arial;
+				}
+				.singlefile-open-file-bar.container {
+					background-color: whitesmoke;
+				}
+				.text {
+					flex: 1;
+					padding-top: 4px;
+					padding-bottom: 4px;
+					padding-left: 8px;					
+				}
+				button {
+					background-color: grey;
+					color: white;
+					border: 1px solid darkgrey;
+					padding: 3px;
+					padding-left: 8px;
+					padding-right: 8px;
+					border-radius: 4px;
+					cursor: pointer;
+				}
+				.close-button {
+					opacity: .7;
+					padding-left: 8px;
+					padding-right: 8px;
+					cursor: pointer;
+					transition: opacity 250ms;
+					height: 16px;
+					font-size: .8rem;
+					align-self: center;
+				}
+				.singlefile-open-file-bar .close-button {
+					filter: invert(1);
+				}
+				a {
+					color: #303036;
+				}
+				.close-button:hover {
+					opacity: 1;
+				}
+			`;
+			shadowRoot.appendChild(styleElement);
+			const containerElement = document.createElement("div");
+			containerElement.classList.add(tagName);
+			containerElement.classList.add("container");
+			const textElement = document.createElement("span");
+			textElement.classList.add("text");
+			const content = message.split("__DOC_LINK__");
+			textElement.textContent = content[0];
+			if (link && content.length == 2) {
+				const linkElement = document.createElement("a");
+				linkElement.textContent = link;
+				linkElement.href = link;
+				linkElement.target = "_blank";
+				textElement.appendChild(linkElement);
+				textElement.appendChild(document.createTextNode(content[1]));
+			}
+			if (buttonLabel && buttonOnclick) {
+				const buttonElement = document.createElement("button");
+				buttonElement.textContent = buttonLabel;
+				buttonElement.onclick = () => buttonOnclick();
+				textElement.appendChild(buttonElement);
+			}
+			containerElement.appendChild(textElement);
+			const closeElement = document.createElement("img");
+			closeElement.classList.add("close-button");
+			containerElement.appendChild(closeElement);
+			shadowRoot.appendChild(containerElement);
+			closeElement.src = CLOSE_ICON;
+			closeElement.onclick = event => {
+				if (event.button === 0) {
+					if (buttonOnclick) {
+						buttonOnclick(true);
+					}
+					barElement.remove();
+				}
+			};
+			document.body.appendChild(barElement);
+		}
+	} catch (error) {
+		// iignored
+	}
+}
+
+function createElement(tagName, parentElement) {
+	const element = document.createElement(tagName);
+	element.className = SINGLE_FILE_UI_ELEMENT_CLASS;
+	if (parentElement) {
+		parentElement.appendChild(element);
+	}
+	CSS_PROPERTIES.forEach(property => element.style.setProperty(property, "initial", "important"));
+	return element;
+}

+ 0 - 130
src/ui/common/content-error.js

@@ -1,130 +0,0 @@
-/*
- * 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 document, globalThis, getComputedStyle */
-
-const singlefile = globalThis.singlefile;
-
-const CLOSE_ICON = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAABhmlDQ1BJQ0MgcHJvZmlsZQAAKJF9kT1Iw0AYht+mSlUqHewg4hChOogFURFHqWIRLJS2QqsOJpf+CE0akhQXR8G14ODPYtXBxVlXB1dBEPwBcXNzUnSREr9LCi1ivOO4h/e+9+XuO0Col5lqdowDqmYZqXhMzOZWxMAruhGiOYohiZl6Ir2Qgef4uoeP73dRnuVd9+foVfImA3wi8SzTDYt4nXh609I57xOHWUlSiM+Jxwy6IPEj12WX3zgXHRZ4ZtjIpOaIw8RisY3lNmYlQyWeIo4oqkb5QtZlhfMWZ7VcZc178hcG89pymuu0BhHHIhJIQoSMKjZQhoUo7RopJlJ0HvPwDzj+JLlkcm2AkWMeFaiQHD/4H/zurVmYnHCTgjGg88W2P4aBwC7QqNn297FtN04A/zNwpbX8lTow80l6raVFjoDQNnBx3dLkPeByB+h/0iVDciQ/LaFQAN7P6JtyQN8t0LPq9q15jtMHIEO9WroBDg6BkSJlr3m8u6u9b//WNPv3A6mTcr3f/E/sAAAABmJLR0QAigCKAIrj2uckAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAB3RJTUUH5QkPDysvCdPVuwAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAELSURBVHja7ZpLFsIwDAPj3v/OsGHDe1BIa8tKO7Mnlkw+dpoxAAAAAGCfx4ur6Yx/B337UUS4mp/VuWUEcjSfOgO+BXCZCWe0hSqQo/npBLglIUNLdAV2MH84Ad1JyIwdLkK6YoabIHWscBWmihHuAqvHtv+XqmdXOK9TxdKy3axUm2vZkXXGgPJksTuz1bVFeeU2Y6ijsLIpXbtKa1kDs2ews69o7+A+ihJ2lvI+/lcS1G21zUVG18XKNm4OS4BNkGOQQohSmGaIdpgLESvzyiRwKepsXjE2H0ZWMF8Zi4+jK5mviM0DiRXNZ2rhkdTK5jO0xermz2o8dCnq+FS2XNNVH0sDAAAA3JYnre9cH8BZmhEAAAAASUVORK5CYII=";
-
-const SINGLE_FILE_UI_ELEMENT_CLASS = singlefile.helper.SINGLE_FILE_UI_ELEMENT_CLASS;
-const ERROR_BAR_TAGNAME = "singlefile-error-bar";
-
-const CSS_PROPERTIES = new Set(Array.from(getComputedStyle(document.documentElement)));
-
-let errorBarElement;
-
-export {
-	onError
-};
-
-function onError(message, link) {
-	try {
-		console.error("SingleFile", message, link); // eslint-disable-line no-console
-		errorBarElement = document.querySelector(ERROR_BAR_TAGNAME);
-		if (!errorBarElement) {
-			errorBarElement = createElement(ERROR_BAR_TAGNAME);
-			const shadowRoot = errorBarElement.attachShadow({ mode: "open" });
-			const styleElement = document.createElement("style");
-			styleElement.textContent = `
-				.container {
-					background-color: #ff6c00;
-					color: white;
-					display: flex;
-					position: fixed;
-					top: 0px;
-					left: 0px;
-					right: 0px;
-					height: auto;
-					width: auto;
-					min-height: 24px;
-					min-width: 24px;					
-					z-index: 2147483647;
-					margin: 0;
-					padding: 2px;
-					font-family: Arial;
-				}
-				.text {
-					flex: 1;
-					padding-top: 4px;
-					padding-bottom: 4px;
-					padding-left: 8px;					
-				}
-				.close-button {
-					opacity: .7;
-					padding-top: 4px;
-					padding-left: 8px;
-					padding-right: 8px;
-					cursor: pointer;
-					transition: opacity 250ms;
-					height: 16px;
-				}
-				a {
-					color: #303036;
-				}
-				.close-button:hover {
-					opacity: 1;
-				}
-			`;
-			shadowRoot.appendChild(styleElement);
-			const containerElement = document.createElement("div");
-			containerElement.className = "container";
-			const errorTextElement = document.createElement("span");
-			errorTextElement.classList.add("text");
-			const content = message.split("__DOC_LINK__");
-			errorTextElement.textContent = "SingleFile error: " + content[0];
-			if (link && content.length == 2) {
-				const linkElement = document.createElement("a");
-				linkElement.textContent = link;
-				linkElement.href = link;
-				linkElement.target = "_blank";
-				errorTextElement.appendChild(linkElement);
-				errorTextElement.appendChild(document.createTextNode(content[1]));
-			}
-			containerElement.appendChild(errorTextElement);
-			const closeElement = document.createElement("img");
-			closeElement.classList.add("close-button");
-			containerElement.appendChild(closeElement);
-			shadowRoot.appendChild(containerElement);
-			closeElement.src = CLOSE_ICON;
-			closeElement.onclick = event => {
-				if (event.button === 0) {
-					errorBarElement.remove();
-				}
-			};
-			document.body.appendChild(errorBarElement);
-		}
-	} catch (error) {
-		// iignored
-	}
-}
-
-function createElement(tagName, parentElement) {
-	const element = document.createElement(tagName);
-	element.className = SINGLE_FILE_UI_ELEMENT_CLASS;
-	if (parentElement) {
-		parentElement.appendChild(element);
-	}
-	CSS_PROPERTIES.forEach(property => element.style.setProperty(property, "initial", "important"));
-	return element;
-}

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

@@ -41,6 +41,7 @@ const SINGLE_FILE_UI_ELEMENT_CLASS = singlefile.helper.SINGLE_FILE_UI_ELEMENT_CL
 const SELECT_PX_THRESHOLD = 8;
 const LOG_PANEL_DEFERRED_IMAGES_MESSAGE = browser.i18n.getMessage("logPanelDeferredImages");
 const LOG_PANEL_FRAME_CONTENTS_MESSAGE = browser.i18n.getMessage("logPanelFrameContents");
+const LOG_PANEL_EMBEDDED_IMAGE_MESSAGE = browser.i18n.getMessage("logPanelEmbeddedImage");
 const LOG_PANEL_STEP_MESSAGE = browser.i18n.getMessage("logPanelStep");
 const LOG_PANEL_WIDTH = browser.i18n.getMessage("logPanelWidth");
 const CSS_PROPERTIES = new Set(Array.from(getComputedStyle(document.documentElement)));
@@ -56,6 +57,8 @@ export {
 	onStartPage,
 	onEndPage,
 	onLoadResource,
+	onInsertingEmbeddedImage,
+	onInsertEmbeddedImage,
 	onLoadingDeferResources,
 	onLoadDeferResources,
 	onLoadingFrames,
@@ -110,6 +113,14 @@ function onLoadDeferResources(options) {
 	updateLog("load-deferred-images", LOG_PANEL_DEFERRED_IMAGES_MESSAGE, "✓", options);
 }
 
+function onInsertingEmbeddedImage(options) {
+	updateLog("insert-embedded-image", LOG_PANEL_EMBEDDED_IMAGE_MESSAGE, "…", options);
+}
+
+function onInsertEmbeddedImage(options) {
+	updateLog("insert-embedded-image", LOG_PANEL_EMBEDDED_IMAGE_MESSAGE, "✓", options);
+}
+
 function onLoadingFrames(options) {
 	updateLog("load-frames", LOG_PANEL_FRAME_CONTENTS_MESSAGE, "…", options);
 }

+ 7 - 0
src/ui/pages/help.html

@@ -243,6 +243,13 @@
 							encoded in UTF-8 but the page is declared in ISO-8859-1.
 						</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.
+						</p>
+					</li>
 				</ul>
 				<p>HTML content</p>
 				<ul>

+ 4 - 0
src/ui/pages/options.html

@@ -116,6 +116,10 @@
 				<label for="insertTextBodyInput" id="insertTextBodyLabel"></label>
 				<input type="checkbox" id="insertTextBodyInput">
 			</div>
+			<div class="option">
+				<label for="insertEmbeddedImageInput" id="insertEmbeddedImageLabel"></label>
+				<input type="checkbox" id="insertEmbeddedImageInput">
+			</div>
 		</details>
 		<details>
 			<summary id="htmlContentLabel"></summary>