diff --git a/PyPost.py b/PyPost.py
index 074d0f0..77ee0f9 100644
--- a/PyPost.py
+++ b/PyPost.py
@@ -7,7 +7,6 @@ from pathlib import Path
from jinja2 import Environment, FileSystemLoader
import base64
import random
-import time
import yaml
import marko
@@ -19,6 +18,9 @@ from hashes.hashes import hash_list
from htmlhandler import htmlhandler as Handler
from lua import plugin_manager
+# Import your LaTeX extension
+from hashes.util.LaTeXRenderer import LaTeXExtension
+
plugin_manager = plugin_manager.PluginManager()
plugin_manager.load_all() # load plugins
@@ -35,8 +37,8 @@ RUST_PARSER_PATH = ROOT / "fastmd" / "target" / "release" / f"fastmd{exe_ext}"
if not RUST_PARSER_PATH.exists():
RUST_PARSER_PATH = ROOT / "fastmd" / "target" / "debug" / f"fastmd{exe_ext}"
-# Python Markdown parser with table support
-markdown_parser = marko.Markdown(extensions=[GFM])
+# Python Markdown parser with table support AND LaTeX extension
+markdown_parser = marko.Markdown(extensions=[GFM, LaTeXExtension()])
# Threshold for switching to Rust parser (number of lines)
RUST_PARSER_THRESHOLD = 1000
diff --git a/hashes/util/LaTeXRenderer.py b/hashes/util/LaTeXRenderer.py
new file mode 100644
index 0000000..0285441
--- /dev/null
+++ b/hashes/util/LaTeXRenderer.py
@@ -0,0 +1,42 @@
+# latex_extension.py
+import marko
+import marko.block
+import marko.inline
+from marko.md_renderer import MarkdownRenderer
+import re
+from log.Logger import Logger
+logger = Logger()
+
+class BlockFormula(marko.block.BlockElement):
+ pattern = re.compile(r"\$\$ *\n([\s\S]+?)^\$\$ *$", re.MULTILINE)
+ def __init__(self, match):
+ logger.log_debug("Did shit at __init__ for blockformula")
+ self.children = [marko.inline.RawText(match.group(1))]
+ @classmethod
+ def match(cls, source):
+ return source.expect_re(cls.pattern)
+ @classmethod
+ def parse(cls, source):
+ logger.log_debug("Did some shit with Latex")
+ match = source.match
+ source.consume()
+ return match
+
+class Paragraph(marko.block.Paragraph):
+ override = True
+ @classmethod
+ def break_paragraph(cls, source, lazy=False):
+ if BlockFormula.match(source):
+ return True
+ return super().break_paragraph(source, lazy=lazy)
+
+class Renderer:
+ def render_block_formula(self, element):
+ # Render as HTML with MathJax-compatible format
+ return '\n
$$\n' + self.render_children(element) + '$$
\n'
+
+class LaTeXExtension:
+ logger.log_debug("Did shit at __init__ for latexextension")
+ elements = [BlockFormula, Paragraph]
+ parser_mixins = []
+ renderer_mixins = [Renderer]
\ No newline at end of file
diff --git a/html/base/template.html b/html/base/template.html
index f2997e7..d77baa3 100644
--- a/html/base/template.html
+++ b/html/base/template.html
@@ -20,6 +20,8 @@
+
+
diff --git a/lua/luarails.lua b/lua/luarails.lua
new file mode 100644
index 0000000..79cee48
--- /dev/null
+++ b/lua/luarails.lua
@@ -0,0 +1,104 @@
+-- Guardrails and safe patterns for plugin development
+
+-- Safe string operations
+function safe_concat(...)
+ local result = {}
+ for i, v in ipairs({...}) do
+ if v ~= nil then
+ table.insert(result, tostring(v))
+ end
+ end
+ return table.concat(result)
+end
+
+-- Safe table operations
+function table_contains(tbl, value)
+ for _, v in ipairs(tbl) do
+ if v == value then return true end
+ end
+ return false
+end
+
+function table_keys(tbl)
+ local keys = {}
+ for k, _ in pairs(tbl) do
+ table.insert(keys, k)
+ end
+ return keys
+end
+
+function table_values(tbl)
+ local values = {}
+ for _, v in pairs(tbl) do
+ table.insert(values, v)
+ end
+ return values
+end
+
+-- Safe string escaping
+function escape_html(str)
+ if str == nil then return "" end
+ local s = tostring(str)
+ s = string.gsub(s, "&", "&")
+ s = string.gsub(s, "<", "<")
+ s = string.gsub(s, ">", ">")
+ s = string.gsub(s, '"', """)
+ s = string.gsub(s, "'", "'")
+ return s
+end
+
+-- Pattern validation
+function is_valid_filename(name)
+ if name == nil or name == "" then return false end
+ -- Block directory traversal
+ if string.match(name, "%.%.") then return false end
+ if string.match(name, "/") or string.match(name, "\\\\") then return false end
+ return true
+end
+
+-- Safe error handling wrapper
+function try_catch(fn, catch_fn)
+ local status, err = pcall(fn)
+ if not status and catch_fn then
+ catch_fn(err)
+ end
+ return status
+end
+
+-- Request validation
+function validate_request(req, required_fields)
+ if type(req) ~= "table" then return false, "Request must be a table" end
+ for _, field in ipairs(required_fields) do
+ if req[field] == nil then
+ return false, "Missing required field: " .. field
+ end
+ end
+ return true, nil
+end
+
+-- Rate limiting helper (simple in-memory)
+_rate_limits = _rate_limits or {}
+function check_rate_limit(key, max_calls, window_seconds)
+ local now = os.time()
+ if _rate_limits[key] == nil then
+ _rate_limits[key] = {count = 1, window_start = now}
+ return true
+ end
+
+ local rl = _rate_limits[key]
+ if now - rl.window_start > window_seconds then
+ -- Reset window
+ rl.count = 1
+ rl.window_start = now
+ return true
+ end
+
+ if rl.count >= max_calls then
+ return false
+ end
+
+ rl.count = rl.count + 1
+ return true
+end
+
+log("Lua guardrails initialized")
diff --git a/markdown/Rotation.md b/markdown/Rotation.md
new file mode 100644
index 0000000..023283a
--- /dev/null
+++ b/markdown/Rotation.md
@@ -0,0 +1,128 @@
+---
+summary: "Würfelrotation mit Matrizen die Multipliziert werden erkläret"
+---
+
+# Rotation eines Würfels um die x-Achse
+
+Wir wollen verstehen, wie man einen Würfel im Raum um die x-Achse dreht.
+
+## 1. Punkte eines Würfels
+
+Ein Würfel hat 8 Eckpunkte. Wenn wir den Würfel in der Mitte des Koordinatensystems platzieren, können wir die Punkte als Vektoren schreiben:
+
+$$
+\mathbf{A} = (1, 1, 1), \quad
+\mathbf{B} = (1, 1, -1), \quad
+\mathbf{C} = (1, -1, 1), \quad
+\mathbf{D} = (1, -1, -1)
+$$
+
+$$
+\mathbf{E} = (-1, 1, 1), \quad
+\mathbf{F} = (-1, 1, -1), \quad
+\mathbf{G} = (-1, -1, 1), \quad
+\mathbf{H} = (-1, -1, -1)
+$$
+
+Jeder Punkt hat drei Koordinaten
+$$
+(x', y', z')
+$$
+
+## 2. Rotationsmatrix um die x-Achse
+
+Wenn wir einen Punkt
+
+$$
+\mathbf{v} = \begin{pmatrix} x \\ y \\ z \end{pmatrix}
+$$
+
+um die x-Achse um einen Winkel $\theta$ drehen wollen, benutzen wir die Rotationsmatrix:
+
+$$
+R_x(\theta) =
+\begin{pmatrix}
+1 & 0 & 0 \\
+0 & \cos\theta & -\sin\theta \\
+0 & \sin\theta & \cos\theta
+\end{pmatrix}
+$$
+
+**Hinweis:**
+- Die x-Koordinate bleibt gleich, weil wir um die x-Achse drehen.
+- y und z verändern sich je nach Winkel $\theta$.
+
+## 3. Berechnung des neuen Punktes
+
+Der neue Punkt $\mathbf{v}'$ nach der Drehung ist:
+
+$$
+\mathbf{v}' = R_x(\theta) \mathbf{v} =
+\begin{pmatrix}
+1 & 0 & 0 \\
+0 & \cos\theta & -\sin\theta \\
+0 & \sin\theta & \cos\theta
+\end{pmatrix}
+\begin{pmatrix} x \\ y \\ z \end{pmatrix} =
+\begin{pmatrix}
+x \\
+y \cos\theta - z \sin\theta \\
+y \sin\theta + z \cos\theta
+\end{pmatrix}
+$$
+
+## 4. Beispiel
+
+Drehen wir den Punkt
+
+$$
+\mathbf{A} = (1,1,1)
+$$
+
+um
+
+$$
+\theta = 90^\circ = \frac{\pi}{2}
+$$
+
+Dann gilt:
+
+$$
+\cos \theta = 0, \quad \sin \theta = 1
+$$
+
+$$
+\mathbf{A}' =
+\begin{pmatrix}
+1 \\
+1 \cdot 0 - 1 \cdot 1 \\
+1 \cdot 1 + 1 \cdot 0
+\end{pmatrix} =
+\begin{pmatrix}
+1 \\ -1 \\ 1
+\end{pmatrix}
+$$
+
+## 5. Tabelle aller Punkte nach Rotation
+
+$$
+\begin{array}{c|c}
+\text{Originalpunkt} & \text{Punkt nach Rotation} \\
+\hline
+A (1,1,1) & (1,-1,1) \\
+B (1,1,-1) & (1,-1,-1) \\
+C (1,-1,1) & (1,-1,-1) \\
+D (1,-1,-1) & (1,1,-1) \\
+E (-1,1,1) & (-1,-1,1) \\
+F (-1,1,-1) & (-1,-1,-1) \\
+G (-1,-1,1) & (-1,1,1) \\
+H (-1,-1,-1) & (-1,1,-1) \\
+\end{array}
+$$
+
+## Fazit
+
+- x bleibt unverändert
+- y und z ändern sich je nach Winkel
+- Rotationsmatrizen sind ein mächtiges Werkzeug, um Objekte im 3D-Raum zu bewegen
+