Add 5 new tools: QR-Code, Markdown, Color Converter, JSON Formatter, Regex Tester

- backend/tools/qrcode_gen.py: POST /api/qrcode/generate, returns base64 PNG (qrcode[pil])
- backend/tools/markdown_tool.py: POST /api/markdown/render, extensions: tables/fenced_code/nl2br
- backend/tools/colorconverter.py: POST /api/color/convert, HEX/RGB/HSL via colorsys (no deps)
- backend/tools/jsonformatter.py: POST /api/json/format, returns formatted JSON with line/col errors
- backend/tools/regextester.py: POST /api/regex/test, flags i/m/s, returns matches with positions
- QrCodeTool.jsx: generate + download PNG button
- MarkdownTool.jsx: split editor/preview, debounce 500ms, white preview bg
- ColorConverterTool.jsx: color swatch preview, per-format copy buttons
- JsonFormatterTool.jsx: indent toggle 2/4, pre result box with copy
- RegexTesterTool.jsx: debounce 400ms, yellow match highlighting, flag checkboxes
- All blueprints registered in app.py; qrcode[pil] + markdown added to requirements.txt

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Nirodan
2026-04-24 18:19:34 +02:00
parent 45e1934bee
commit 34c82f3dca
15 changed files with 629 additions and 2 deletions
+5
View File
@@ -5,3 +5,8 @@ from .jwtdecoder import jwt_decoder_blueprint
from .passwordgen import passwordgen_blueprint
from .timestamp import timestamp_blueprint
from .textdiff import textdiff_blueprint
from .qrcode_gen import qrcode_blueprint
from .markdown_tool import markdown_blueprint
from .colorconverter import color_blueprint
from .jsonformatter import json_formatter_blueprint
from .regextester import regex_blueprint
+88
View File
@@ -0,0 +1,88 @@
from flask import Blueprint, request, jsonify
import colorsys
import re
from util.logger import logger
from auth.token import verify_token
color_blueprint = Blueprint('color_tool', __name__)
def hex_to_rgb(hex_str):
hex_str = hex_str.strip().lstrip('#')
if len(hex_str) == 3:
hex_str = ''.join(c * 2 for c in hex_str)
if len(hex_str) != 6:
raise ValueError("Ungültiger HEX-Wert")
return int(hex_str[0:2], 16), int(hex_str[2:4], 16), int(hex_str[4:6], 16)
def rgb_to_hex(r, g, b):
return f"#{r:02x}{g:02x}{b:02x}"
def rgb_to_hsl(r, g, b):
# colorsys uses HLS order (hue, lightness, saturation)
h, l, s = colorsys.rgb_to_hls(r / 255, g / 255, b / 255)
return round(h * 360), round(s * 100), round(l * 100)
def hsl_to_rgb(h, s, l):
# CSS HSL: hue 0-360, saturation 0-100, lightness 0-100
r, g, b = colorsys.hls_to_rgb(h / 360, l / 100, s / 100)
return round(r * 255), round(g * 255), round(b * 255)
def parse_rgb(value):
nums = re.findall(r'\d+', value)
if len(nums) < 3:
raise ValueError("Ungültiger RGB-Wert")
r, g, b = int(nums[0]), int(nums[1]), int(nums[2])
if not all(0 <= x <= 255 for x in (r, g, b)):
raise ValueError("RGB-Werte müssen zwischen 0 und 255 liegen")
return r, g, b
def parse_hsl(value):
nums = re.findall(r'[\d.]+', value)
if len(nums) < 3:
raise ValueError("Ungültiger HSL-Wert")
h, s, l = float(nums[0]), float(nums[1]), float(nums[2])
if not (0 <= h <= 360 and 0 <= s <= 100 and 0 <= l <= 100):
raise ValueError("HSL-Werte außerhalb des gültigen Bereichs")
return h, s, l
@color_blueprint.route('/api/color/convert', methods=['POST'])
def convert_color():
user = verify_token()
if not user:
return jsonify({"message": "Nicht autorisiert"}), 401
try:
data = request.get_json(silent=True) or {}
value = data.get("value", "").strip()
from_format = data.get("from_format", "hex").lower()
if from_format == "hex":
r, g, b = hex_to_rgb(value)
elif from_format == "rgb":
r, g, b = parse_rgb(value)
elif from_format == "hsl":
h, s, l = parse_hsl(value)
r, g, b = hsl_to_rgb(h, s, l)
else:
return jsonify({"message": "Unbekanntes Format"}), 400
hex_val = rgb_to_hex(r, g, b)
hue, sat, lig = rgb_to_hsl(r, g, b)
logger.info(f"Farbe konvertiert von {user['username']}")
return jsonify({
"hex": hex_val,
"rgb": f"rgb({r}, {g}, {b})",
"hsl": f"hsl({hue}, {sat}%, {lig}%)",
})
except ValueError as e:
return jsonify({"message": str(e)}), 400
except Exception as e:
logger.error(f"Fehler Farbkonverter: {e}")
return jsonify({"message": "Fehler bei der Konvertierung"}), 500
+26
View File
@@ -0,0 +1,26 @@
from flask import Blueprint, request, jsonify
import json
from util.logger import logger
from auth.token import verify_token
json_formatter_blueprint = Blueprint('json_formatter_tool', __name__)
@json_formatter_blueprint.route('/api/json/format', methods=['POST'])
def format_json():
user = verify_token()
if not user:
return jsonify({"message": "Nicht autorisiert"}), 401
try:
data = request.get_json(silent=True) or {}
text = data.get("text", "")
indent = int(data.get("indent", 2))
parsed = json.loads(text)
formatted = json.dumps(parsed, indent=indent, ensure_ascii=False)
return jsonify({"result": formatted})
except json.JSONDecodeError as e:
return jsonify({"message": f"JSON-Fehler in Zeile {e.lineno}, Spalte {e.colno}: {e.msg}"}), 400
except Exception as e:
logger.error(f"Fehler JSON-Formatter: {e}")
return jsonify({"message": "Fehler beim Formatieren"}), 500
+21
View File
@@ -0,0 +1,21 @@
from flask import Blueprint, request, jsonify
import markdown
from util.logger import logger
from auth.token import verify_token
markdown_blueprint = Blueprint('markdown_tool', __name__)
@markdown_blueprint.route('/api/markdown/render', methods=['POST'])
def render_markdown():
user = verify_token()
if not user:
return jsonify({"message": "Nicht autorisiert"}), 401
try:
data = request.get_json(silent=True) or {}
text = data.get("text", "")
html = markdown.markdown(text, extensions=["tables", "fenced_code", "nl2br"])
return jsonify({"html": html})
except Exception as e:
logger.error(f"Fehler Markdown: {e}")
return jsonify({"message": "Fehler beim Rendern"}), 500
+35
View File
@@ -0,0 +1,35 @@
from flask import Blueprint, request, jsonify
import qrcode
import io
import base64
from util.logger import logger
from auth.token import verify_token
qrcode_blueprint = Blueprint('qrcode_tool', __name__)
@qrcode_blueprint.route('/api/qrcode/generate', methods=['POST'])
def generate_qrcode():
user = verify_token()
if not user:
return jsonify({"message": "Nicht autorisiert"}), 401
try:
data = request.get_json(silent=True) or {}
text = data.get("text", "").strip()
if not text:
return jsonify({"message": "Text darf nicht leer sein"}), 400
qr = qrcode.QRCode(box_size=10, border=4)
qr.add_data(text)
qr.make(fit=True)
img = qr.make_image(fill_color="black", back_color="white")
buffer = io.BytesIO()
img.save(buffer, format="PNG")
b64 = base64.b64encode(buffer.getvalue()).decode()
logger.info(f"QR-Code generiert von {user['username']}")
return jsonify({"image": f"data:image/png;base64,{b64}"})
except Exception as e:
logger.error(f"Fehler QR-Code: {e}")
return jsonify({"message": "Fehler beim Generieren"}), 500
+41
View File
@@ -0,0 +1,41 @@
from flask import Blueprint, request, jsonify
import re
from util.logger import logger
from auth.token import verify_token
regex_blueprint = Blueprint('regex_tool', __name__)
_FLAG_MAP = {"i": re.IGNORECASE, "m": re.MULTILINE, "s": re.DOTALL}
@regex_blueprint.route('/api/regex/test', methods=['POST'])
def test_regex():
user = verify_token()
if not user:
return jsonify({"message": "Nicht autorisiert"}), 401
try:
data = request.get_json(silent=True) or {}
pattern = data.get("pattern", "")
text = data.get("text", "")
flags_list = data.get("flags", [])
combined = 0
for f in flags_list:
if f in _FLAG_MAP:
combined |= _FLAG_MAP[f]
try:
compiled = re.compile(pattern, combined)
except re.error as e:
return jsonify({"matches": [], "count": 0, "error": str(e)})
matches = [
{"match": m.group(), "start": m.start(), "end": m.end(), "groups": list(m.groups())}
for m in compiled.finditer(text)
]
logger.info(f"Regex getestet von {user['username']}")
return jsonify({"matches": matches, "count": len(matches), "error": None})
except Exception as e:
logger.error(f"Fehler Regex-Tester: {e}")
return jsonify({"message": "Fehler beim Testen"}), 500