From 916d4b7c7ffa563413c6a6eac2357c4f6c8b68d5 Mon Sep 17 00:00:00 2001
From: Till Heidrich
Date: Tue, 15 Sep 2026 12:24:59 +0200
Subject: [PATCH] Logo als Pfade, Undo-Fix beim Einfuegen, Drop-Schutz,
CSP/HSTS, Datenschutz praezisiert
---
datenschutz.html | 8 ++++++++
icon-mono.svg | 7 +++++++
icon.svg | 10 ++++++----
index.html | 26 +++++++++++++++++++-------
nginx.conf | 13 +++++++++++--
5 files changed, 51 insertions(+), 13 deletions(-)
create mode 100644 icon-mono.svg
diff --git a/datenschutz.html b/datenschutz.html
index 96e2618..a261cec 100644
--- a/datenschutz.html
+++ b/datenschutz.html
@@ -116,6 +116,14 @@
insbesondere keine Schriftarten und keine Skripte von Content-Delivery-Netzwerken. Eine
Einwilligungsabfrage ist deshalb nicht erforderlich.
+
+ Eine Ausnahme betrifft die optionale Vorschau: Enthält der von Ihnen eingefuegte Text
+ Bilder, die auf fremde Server verweisen, lädt Ihr Browser diese Bilder beim Anzeigen der
+ Vorschau von dort. Dabei erfährt der jeweilige fremde Server Ihre IP-Adresse. Übertragen
+ wird dabei nichts von Ihrem Text; die Herkunft wird durch die Einstellung
+ Referrer-Policy: no-referrer nicht mitgesendet. Wenn Sie das
+ vermeiden möchten, lassen Sie die Vorschau ausgeschaltet.
+
8. Ihre Rechte
diff --git a/icon-mono.svg b/icon-mono.svg
new file mode 100644
index 0000000..b1f1129
--- /dev/null
+++ b/icon-mono.svg
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/icon.svg b/icon.svg
index 7cd5a88..29fc33d 100644
--- a/icon.svg
+++ b/icon.svg
@@ -1,6 +1,8 @@
-
+
- m↓
+
+
+
+
+
diff --git a/index.html b/index.html
index 8346f90..9cac78f 100644
--- a/index.html
+++ b/index.html
@@ -472,7 +472,7 @@
pattern: LS.get('pattern', 'title')
};
- var filenameEdited = false, forceRaw = false, toastTimer = null, previewOn = LS.get('preview', false);
+ var filenameEdited = false, forceRaw = false, forceRawTimer = null, toastTimer = null, previewOn = LS.get('preview', false);
// ---------- Turndown (rebuilt on settings change) ----------
var td;
@@ -497,7 +497,7 @@
var UML={ 'ä':'ae','ö':'oe','ü':'ue','ß':'ss','Ä':'ae','Ö':'oe','Ü':'ue' };
function slugify(s){ if(!s) return '';
- s=s.replace(/[äöüßÄÖÜ]/g,function(c){return UML[c]||c;}).normalize('NFKD').replace(/[̀-ͯ]/g,'');
+ s=s.replace(/[äöüßÄÖÜ]/g,function(c){return UML[c]||c;}).normalize('NFKD').replace(/[\u0300-\u036f]/g,'');
return s.toLowerCase().replace(/[^a-z0-9]+/g,'-').replace(/^-+|-+$/g,'').slice(0,60).replace(/-+$/g,''); }
function deriveTitle(md){ var L=md.split(/\r?\n/), i, m, t;
for(i=0;i"]/g, function(c){ return {'&':'&','<':'<','>':'>','"':'"'}[c]; }); }
+ function escapeHtml(s){ return s.replace(/[&<>"']/g, function(c){ return {'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]; }); }
function renderPreview(){ if(!previewOn) return;
var md=editor.value, fmHtml='';
// Show YAML front matter as a compact metadata block, not as body text.
@@ -564,8 +564,15 @@
forceRaw=false; if(out==null) return;
e.preventDefault(); insertAtCursor(out); showSource(key,ok,true); refreshAll(true);
});
- function insertAtCursor(str){ var s=editor.selectionStart,en=editor.selectionEnd;
- editor.value=editor.value.slice(0,s)+str+editor.value.slice(en); var p=s+str.length; editor.selectionStart=editor.selectionEnd=p; }
+ function insertAtCursor(str){
+ // execCommand haelt den nativen Undo-Stack der Textarea intakt (Cmd+Z nach dem Einfuegen).
+ editor.focus();
+ var ok=false;
+ try { ok = document.execCommand('insertText', false, str); } catch(e){ ok=false; }
+ if(ok) return;
+ var s=editor.selectionStart,en=editor.selectionEnd;
+ editor.value=editor.value.slice(0,s)+str+editor.value.slice(en);
+ var p=s+str.length; editor.selectionStart=editor.selectionEnd=p; }
editor.addEventListener('input', function(){ hideSource(); refreshAll(false); });
@@ -575,10 +582,15 @@
editor.addEventListener('drop', function(e){
var f=e.dataTransfer&&e.dataTransfer.files&&e.dataTransfer.files[0]; main.classList.remove('drag'); if(!f) return;
e.preventDefault(); var r=new FileReader();
+ if(f.size > 10*1024*1024){ toast(lang==='de'?'Datei zu gross (max. 10 MB)':'File too large (max 10 MB)', true); return; }
+ if(editor.value.trim() && !confirm(lang==='de'?'Editor-Inhalt durch die Datei ersetzen?':'Replace the editor content with this file?')) return;
+ r.onerror=function(){ toast(lang==='de'?'Datei nicht lesbar':'Could not read file', true); };
r.onload=function(){ var txt=String(r.result||''); var name=f.name.toLowerCase();
if(/\.html?$/.test(name)){ try{ txt=td.turndown(txt).replace(/\n{3,}/g,'\n\n').trim(); showSource('src_conv',true,true); }catch(_){ showSource('src_fail',false,true);} }
else { showSource('src_raw',false,true); }
- editor.value=txt; if(!filenameEdited && /\.(md|markdown|txt|html?)$/.test(name)){ } refreshAll(true); toast(t('loaded')); };
+ editor.value=txt;
+ if(!filenameEdited){ var base=f.name.replace(/\.[^.]+$/,''); if(base){ filename.value=slugify(base)||computeFilename(txt); } }
+ refreshAll(true); toast(t('loaded')); };
r.readAsText(f);
});
@@ -608,7 +620,7 @@
// ---------- Keyboard ----------
document.addEventListener('keydown', function(e){ var mod=e.metaKey||e.ctrlKey;
if(mod && !e.shiftKey && !e.altKey && (e.key==='s'||e.key==='S')){ e.preventDefault(); save(); return; }
- if(mod && e.shiftKey && (e.key==='v'||e.key==='V')){ forceRaw=true; }
+ if(mod && e.shiftKey && (e.key==='v'||e.key==='V')){ forceRaw=true; clearTimeout(forceRawTimer); forceRawTimer=setTimeout(function(){ forceRaw=false; },3000); }
if(e.key==='Escape'){ closeSettings(); } });
// ---------- Preview toggle ----------
diff --git a/nginx.conf b/nginx.conf
index f50bdd8..f030789 100644
--- a/nginx.conf
+++ b/nginx.conf
@@ -40,8 +40,17 @@ server {
try_files $uri $uri.html $uri/ /index.html;
}
- # Minimal hardening headers.
+ # Hardening headers.
add_header X-Content-Type-Options "nosniff" always;
- add_header X-Frame-Options "SAMEORIGIN" always;
+ add_header X-Frame-Options "DENY" always;
add_header Referrer-Policy "no-referrer" always;
+ add_header Strict-Transport-Security "max-age=31536000" always;
+ add_header Permissions-Policy "geolocation=(), camera=(), microphone=(), payment=(), usb=(), interest-cohort=()" always;
+
+ # Content Security Policy.
+ # connect-src 'none' ist hier das eigentliche Versprechen: der Browser selbst
+ # unterbindet jede fetch/XHR/WebSocket-Verbindung. "Nothing is uploaded" ist damit
+ # nicht nur behauptet, sondern erzwungen. img-src erlaubt https, damit Bilder aus
+ # eingefuegtem Markdown in der Vorschau sichtbar bleiben.
+ add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https:; font-src 'self'; connect-src 'none'; object-src 'none'; frame-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none'; manifest-src 'self'; worker-src 'self'" always;
}