diff --git a/manifest.firefox.json b/manifest.firefox.json index fab6932..064a3a4 100644 --- a/manifest.firefox.json +++ b/manifest.firefox.json @@ -54,7 +54,7 @@ } ], "content_security_policy": { - "extension_pages": "script-src 'self'; object-src 'self'" + "extension_pages": "script-src 'self'; object-src 'self'; img-src 'self' https: data: blob:" }, "icons": { "16": "assets/icons/icon16.png", diff --git a/manifest.json b/manifest.json index cc5ed6f..917322f 100644 --- a/manifest.json +++ b/manifest.json @@ -36,7 +36,7 @@ } ], "content_security_policy": { - "extension_pages": "script-src 'self'; object-src 'self'" + "extension_pages": "script-src 'self'; object-src 'self'; img-src 'self' https: data: blob:" }, "icons": { "16": "assets/icons/icon16.png", diff --git a/manifest.opera.json b/manifest.opera.json index 942930d..8e2f2e8 100644 --- a/manifest.opera.json +++ b/manifest.opera.json @@ -51,7 +51,7 @@ }, "content_security_policy": { - "extension_pages": "script-src 'self'; object-src 'self'" + "extension_pages": "script-src 'self'; object-src 'self'; img-src 'self' https: data: blob:" }, "icons": { "16": "assets/icons/icon16.png", diff --git a/newtab.html b/newtab.html index 0826aab..04bea63 100644 --- a/newtab.html +++ b/newtab.html @@ -381,9 +381,10 @@ +
Datei hochladen diff --git a/src/css/main.css b/src/css/main.css index ccb0bab..72a9f65 100644 --- a/src/css/main.css +++ b/src/css/main.css @@ -2387,6 +2387,10 @@ body.show-desc .bm-desc { display: block; } .theme-modal-section .setting-row { padding: 8px 0; } +.bg-url-hint { + padding: 2px 0 6px; + line-height: 1.4; +} /* ============================================ ACCORDION SETTINGS diff --git a/src/js/i18n.js b/src/js/i18n.js index f1c3c74..8aeaa95 100644 --- a/src/js/i18n.js +++ b/src/js/i18n.js @@ -385,7 +385,9 @@ const STRINGS = { 'settings.visible_count': 'Sichtbare Bookmarks', 'settings.visible_count.desc': 'Anzahl vor dem Ausblenden', 'settings.bg_url': 'Bild-URL', - 'settings.bg_url.desc': 'Eigenes Hintergrundbild per URL', + 'settings.bg_url.desc': 'Eigenes Bild per https-URL oder lokalem Upload', + 'settings.bg_url.placeholder': 'https://… oder leer für Standard', + 'settings.bg_url.privacy_hint': 'Hinweis: Ein per URL eingebundenes Bild wird bei jedem Öffnen vom fremden Server geladen.', 'settings.bg_change': 'Ändern', 'settings.bg_apply': 'Übernehmen', 'settings.bg_upload': 'Datei hochladen', @@ -396,7 +398,7 @@ const STRINGS = { 'settings.onboarding_btn': 'Start', 'settings.reset_btn': 'Reset', 'settings.bg_upload_btn': 'Upload', - 'settings.bg_invalid_url': 'Nur lokale Bilder (Upload) sind als Hintergrund erlaubt.', + 'settings.bg_invalid_url': 'Nur https-URLs oder lokale Bilder (Upload) sind als Hintergrund erlaubt.', 'settings.bg_invalid_url.title': 'Ungültige URL', // Modals @@ -846,7 +848,9 @@ const STRINGS = { 'settings.visible_count': 'Visible bookmarks', 'settings.visible_count.desc': 'Number before hiding', 'settings.bg_url': 'Image URL', - 'settings.bg_url.desc': 'Custom background image via URL', + 'settings.bg_url.desc': 'Custom image via https URL or local upload', + 'settings.bg_url.placeholder': 'https://… or empty for default', + 'settings.bg_url.privacy_hint': 'Note: an image loaded via URL is fetched from the remote server every time you open a tab.', 'settings.bg_change': 'Change', 'settings.bg_apply': 'Apply', 'settings.bg_upload': 'Upload file', @@ -857,7 +861,7 @@ const STRINGS = { 'settings.onboarding_btn': 'Start', 'settings.reset_btn': 'Reset', 'settings.bg_upload_btn': 'Upload', - 'settings.bg_invalid_url': 'Only local images (upload) are allowed as background.', + 'settings.bg_invalid_url': 'Only https URLs or local images (upload) are allowed as background.', 'settings.bg_invalid_url.title': 'Invalid URL', // Modals diff --git a/src/js/settings.js b/src/js/settings.js index ec94088..358dbfb 100644 --- a/src/js/settings.js +++ b/src/js/settings.js @@ -113,7 +113,37 @@ function switchTheme(name) { */ function isValidBgUrl(url) { return typeof url === 'string' && url.length > 0 && - (url.startsWith('blob:') || url.startsWith('data:image/')); + (url.startsWith('blob:') || url.startsWith('data:image/') || url.startsWith('https://')); +} + +// Eigenes Upload-Bild Quota-schonend verkleinern: auf die laengste Bildschirmkante +// (× devicePixelRatio, gedeckelt) herunterrechnen und als WebP neu kodieren. Das spart +// gegenueber dem rohen Base64-Upload locker den Grossteil der chrome.storage.local-Quota. +// Greift nur beim lokalen Upload (data:-URL ist same-origin, Canvas wird nicht getainted); +// https-Hintergruende liegen remote und kosten keine Quota. +function downscaleBgImage(dataUrl) { + const MAX_DIM = Math.min(2560, Math.round(Math.max(screen.width, screen.height) * (window.devicePixelRatio || 1))); + const QUALITY = 0.82; + return new Promise((resolve, reject) => { + const img = new Image(); + img.onload = () => { + const scale = Math.min(1, MAX_DIM / Math.max(img.naturalWidth, img.naturalHeight)); + const w = Math.max(1, Math.round(img.naturalWidth * scale)); + const h = Math.max(1, Math.round(img.naturalHeight * scale)); + const canvas = document.createElement('canvas'); + canvas.width = w; + canvas.height = h; + const ctx = canvas.getContext('2d'); + if (!ctx) { resolve(dataUrl); return; } // kein 2D-Context -> Original behalten + ctx.drawImage(img, 0, 0, w, h); + // WebP wo verfuegbar (Chrome/Opera/FF142+); sonst faellt toDataURL auf PNG zurueck -> dann JPEG + let out = canvas.toDataURL('image/webp', QUALITY); + if (!out.startsWith('data:image/webp')) out = canvas.toDataURL('image/jpeg', QUALITY); + resolve(out); + }; + img.onerror = () => reject(new Error('image decode failed')); + img.src = dataUrl; + }); } // ---- ACCORDION ---- @@ -434,7 +464,9 @@ function bindSettingsEvents() { // Background URL (im Theme-Modal) document.getElementById('btnChangeBg').addEventListener('click', () => { - document.getElementById('bgInputRow').classList.toggle('hidden'); + // toggle() liefert true, wenn 'hidden' jetzt gesetzt ist -> Hinweis exakt parallel schalten + const isNowHidden = document.getElementById('bgInputRow').classList.toggle('hidden'); + document.getElementById('bgUrlHint').classList.toggle('hidden', isNowHidden); }); document.getElementById('btnApplyBg').addEventListener('click', async () => { const url = document.getElementById('bgUrlInput').value.trim(); @@ -458,8 +490,14 @@ function bindSettingsEvents() { const reader = new FileReader(); reader.onload = async ev => { if (!isValidBgUrl(ev.target.result)) return; - settings.bgUrl = ev.target.result; - document.getElementById('bgLayer').style.backgroundImage = `url('${ev.target.result}')`; + let bg = ev.target.result; + try { + bg = await downscaleBgImage(bg); // Quota-Schutz: verkleinern + WebP + } catch { + // Downscale fehlgeschlagen -> Original-Upload nutzen (besser als gar kein Bild) + } + settings.bgUrl = bg; + document.getElementById('bgLayer').style.backgroundImage = `url('${bg}')`; await saveSettings(); }; reader.onerror = () => {