From 244cb3747095bdeeba400ea53eee4f9c48af8310 Mon Sep 17 00:00:00 2001 From: Dicken Date: Mon, 1 Jun 2026 13:07:04 +0200 Subject: [PATCH] =?UTF-8?q?DevTools=20Regex:=20Tastatur-Bug=20behoben=20(R?= =?UTF-8?q?uleCard=20als=20Top-Level),=20Zahlenbereich=20enth=C3=A4lt/exak?= =?UTF-8?q?t?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/tools/devtools.jsx | 156 +++++++++++++++++--------------- 1 file changed, 81 insertions(+), 75 deletions(-) diff --git a/frontend/src/tools/devtools.jsx b/frontend/src/tools/devtools.jsx index 7b3114f..6afe657 100644 --- a/frontend/src/tools/devtools.jsx +++ b/frontend/src/tools/devtools.jsx @@ -1424,7 +1424,8 @@ const RULE_TYPES = [ { id:'alnum_n', label:'Genau N Alphanumerisch',fields:['count'], hint:'8' }, { id:'minlength', label:'Mindestlänge', fields:['count'], hint:'5' }, { id:'maxlength', label:'Höchstlänge', fields:['count'], hint:'20' }, - { id:'numrange', label:'Zahlenbereich', fields:['min','max'], hint:'' }, + { id:'numrange', label:'Zahlenbereich (enthält)', fields:['min','max'], hint:'' }, + { id:'numrange_exact',label:'Zahlenbereich (exakt)', fields:['min','max'], hint:'' }, { id:'onlynums', label:'Nur Zahlen', fields:[], hint:'' }, { id:'onlyletters', label:'Nur Buchstaben', fields:[], hint:'' }, { id:'onlyalnum', label:'Nur Alphanumerisch', fields:[], hint:'' }, @@ -1460,7 +1461,8 @@ function ruleToRegex(rule) { case 'alnum_n': return { pat: `[a-zA-Z0-9]{${f.count||1}}`, desc: `genau ${f.count||1} alphanumerische Zeichen` }; case 'minlength': return { pat: `.{${f.count||1},}`, desc: `mindestens ${f.count||1} Zeichen lang` }; case 'maxlength': return { pat: `.{0,${f.count||999}}`, desc: `höchstens ${f.count||999} Zeichen lang` }; - case 'numrange': return { pat: buildNumRange(Number(f.min||0), Number(f.max||9)), desc: `Zahl zwischen ${f.min||0} und ${f.max||9}` }; + case 'numrange': return { pat: `(?:${buildNumRange(Number(f.min||0), Number(f.max||9))})`, desc: `enthält eine Zahl zwischen ${f.min||0} und ${f.max||9}` }; + case 'numrange_exact': return { pat: `^(?:${buildNumRange(Number(f.min||0), Number(f.max||9))})$`, desc: `ist exakt eine Zahl zwischen ${f.min||0} und ${f.max||9}` }; case 'onlynums': return { pat: `^\\d+$`, desc: `nur Ziffern (0–9)` }; case 'onlyletters': return { pat: `^[a-zA-ZäöüÄÖÜß]+$`, desc: `nur Buchstaben` }; case 'onlyalnum': return { pat: `^[a-zA-Z0-9]+$`, desc: `nur Buchstaben und Ziffern` }; @@ -1479,10 +1481,13 @@ function ruleToRegex(rule) { // Simple numeric range pattern builder function buildNumRange(lo, hi) { + if (isNaN(lo) || isNaN(hi)) return '\\d+'; if (lo === hi) return String(lo); - if (lo < 0 || hi < 0) return `(-?\\d+)`; // fallback for negative - return `(${lo}|${lo+1 <= hi ? `[${lo}-${hi}]` : lo})`.replace(/\((\d+)\)/, '$1'); - // For proper range, just do a simple approach: + if (lo > hi) return '\\d+'; + if (hi - lo > 9999) return '\\d+'; // too large, fallback + const vals = []; + for (let i = lo; i <= hi; i++) vals.push(String(i)); + return vals.join('|'); } // Combine all rules into final regex @@ -1621,7 +1626,73 @@ function explainRegex(pattern) { return tokens; } -function RegexBuilder({ toast, mobile }) { +// RuleCard als Top-Level-Komponente (nicht innerhalb von RegexBuilder — vermeidet Fokus-Bug) +const FIELD_LABELS_RC = {text:'Text', count:'Anzahl', min:'Von', max:'Bis', pattern:'Muster'}; +const LBL_STYLE = {color:'rgba(255,255,255,0.55)', fontFamily:'monospace', fontSize:10, marginBottom:3}; + +function RuleCard({rule, idx, onUpdate, onDelete, onMove, inpStyle}) { + const rt = RULE_TYPES.find(r=>r.id===rule.type); + const preview = ruleToRegex(rule); + return ( +
+
+ {/* Move buttons */} +
+ + +
+ {/* Rule number badge */} +
+ {idx+1} +
+ {/* Type select */} +
+
Regeltyp
+ +
+ {/* Dynamic fields */} + {rt?.fields.map(f=>( +
+
{FIELD_LABELS_RC[f]||f}
+ onUpdate(rule.id,{fields:{...rule.fields,[f]:e.target.value}})}/> +
+ ))} + {/* Preview */} +
+
Regex-Teil
+ + {preview.pat || } + +
+ {/* Delete */} + +
+
+ {preview.desc} +
+
+ ); +} + + const [mode, setMode] = useState('build'); // 'build' | 'explain' // ── Build mode ── const [rules, setRules] = useState([]); @@ -1677,83 +1748,17 @@ function RegexBuilder({ toast, mobile }) { } const inp = {...S.inp, fontSize:13, padding:'7px 9px', boxSizing:'border-box'}; - const lbl = {color:'rgba(255,255,255,0.55)', fontFamily:'monospace', fontSize:10, marginBottom:3}; - - const FIELD_LABELS = {text:'Text', count:'Anzahl', min:'Von', max:'Bis', pattern:'Muster'}; - - function RuleCard({rule, idx}) { - const rt = RULE_TYPES.find(r=>r.id===rule.type); - return ( -
-
- {/* Move buttons */} -
- - -
- {/* Rule number badge */} -
- {idx+1} -
- {/* Type select */} -
-
Regeltyp
- -
- {/* Dynamic fields */} - {rt?.fields.map(f=>( -
-
{FIELD_LABELS[f]||f}
- updateRule(rule.id,{fields:{...rule.fields,[f]:e.target.value}})}/> -
- ))} - {/* Preview of this rule's regex */} -
-
Regex-Teil
- - {ruleToRegex(rule).pat || } - -
- {/* Delete */} - -
- {/* Description of this rule */} -
- {ruleToRegex(rule).desc} -
-
- ); - } - return (
{/* Mode tabs */}
- {[['build','🔨 Regex bauen'],['explain','🔍 Regex erklären']].map(([id,lbl])=>( + {[['build','🔨 Regex bauen'],['explain','🔍 Regex erklären']].map(([id,tabLbl])=>( + fontWeight:mode===id?700:400}}>{tabLbl} ))}
@@ -1761,7 +1766,8 @@ function RegexBuilder({ toast, mobile }) {
{/* Regel-Karten */} {rules.map((rule,idx)=>( - + ))}