Compare commits

...

2 Commits

Author SHA1 Message Date
a74d482d7a typing to know what returns and needs to be passed, styling fix 2025-10-10 13:21:21 +02:00
ea3da89d18 cdn loaded latex math 2025-10-10 13:20:32 +02:00
9 changed files with 298 additions and 112 deletions

View File

@@ -7,7 +7,6 @@ from pathlib import Path
from jinja2 import Environment, FileSystemLoader from jinja2 import Environment, FileSystemLoader
import base64 import base64
import random import random
import time
import yaml import yaml
import marko import marko
@@ -19,6 +18,9 @@ from hashes.hashes import hash_list
from htmlhandler import htmlhandler as Handler from htmlhandler import htmlhandler as Handler
from lua import plugin_manager from lua import plugin_manager
# Import your LaTeX extension
from hashes.util.LaTeXRenderer import LaTeXExtension
plugin_manager = plugin_manager.PluginManager() plugin_manager = plugin_manager.PluginManager()
plugin_manager.load_all() # load plugins 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(): if not RUST_PARSER_PATH.exists():
RUST_PARSER_PATH = ROOT / "fastmd" / "target" / "debug" / f"fastmd{exe_ext}" RUST_PARSER_PATH = ROOT / "fastmd" / "target" / "debug" / f"fastmd{exe_ext}"
# Python Markdown parser with table support # Python Markdown parser with table support AND LaTeX extension
markdown_parser = marko.Markdown(extensions=[GFM]) markdown_parser = marko.Markdown(extensions=[GFM, LaTeXExtension()])
# Threshold for switching to Rust parser (number of lines) # Threshold for switching to Rust parser (number of lines)
RUST_PARSER_THRESHOLD = 1000 RUST_PARSER_THRESHOLD = 1000

View File

@@ -361,7 +361,7 @@ body.dark-mode td {
border-color: #444; border-color: #444;
} }
body.dark-mode th { body.dark-mode th {
background: #121212; background: #2a2a2a;
} }
body.dark-mode code, body.dark-mode code,

View File

@@ -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<div class="math-block">$$\n' + self.render_children(element) + '$$</div>\n'
class LaTeXExtension:
logger.log_debug("Did shit at __init__ for latexextension")
elements = [BlockFormula, Paragraph]
parser_mixins = []
renderer_mixins = [Renderer]

View File

@@ -20,6 +20,8 @@
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.11.0/jszip.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.11.0/jszip.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js" id="MathJax-script"></script>
<!-- remove if causing issues --> <!-- remove if causing issues -->
<script src="../js/post/lazyimg.js"></script> <script src="../js/post/lazyimg.js"></script>
<script src="../js/shared/theme.js"></script> <script src="../js/shared/theme.js"></script>

104
lua/luarails.lua Normal file
View File

@@ -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, "&", "&amp;")
s = string.gsub(s, "<", "&lt;")
s = string.gsub(s, ">", "&gt;")
s = string.gsub(s, '"', "&quot;")
s = string.gsub(s, "'", "&#39;")
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")

View File

@@ -1,106 +1,14 @@
guardrails_code = """ import os
-- Guardrails and safe patterns for plugin development
-- Safe string operations def get_file_contents(filename : str) -> str | None:
function safe_concat(...) """ Gets the Contents of a File and returns it in str format """
local result = {} script_dir = os.path.dirname(os.path.abspath(__file__))
for i, v in ipairs({...}) do path = os.path.join(script_dir, filename)
if v ~= nil then
table.insert(result, tostring(v))
end
end
return table.concat(result)
end
-- Safe table operations if not os.path.isfile(path):
function table_contains(tbl, value) print(f"File '{filename}' not found in {script_dir}")
for _, v in ipairs(tbl) do return None
if v == value then return true end
end
return false
end
function table_keys(tbl) with open(path, 'r', encoding='utf-8') as file:
local keys = {} return file.read()
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, "&", "&amp;")
s = string.gsub(s, "<", "&lt;")
s = string.gsub(s, ">", "&gt;")
s = string.gsub(s, '"', "&quot;")
s = string.gsub(s, "'", "&#39;")
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")
"""

View File

@@ -8,7 +8,7 @@ from watchdog.events import FileSystemEventHandler
from log.Logger import Logger from log.Logger import Logger
from .PluginFSHandler import PluginFSHandler from .PluginFSHandler import PluginFSHandler
import json import json
from .luarails import guardrails_code from .luarails import get_file_contents
PLUGINS_DIR = Path(__file__).parent / "plugins" PLUGINS_DIR = Path(__file__).parent / "plugins"
HTML_DIR = Path(__file__).parent / "../html" HTML_DIR = Path(__file__).parent / "../html"
@@ -112,7 +112,7 @@ class PluginManager:
def _setup_lua_guardrails(self): def _setup_lua_guardrails(self):
try: try:
self.lua.execute(guardrails_code) self.lua.execute(get_file_contents("luarails.lua"))
except LuaError as e: except LuaError as e:
Logger.log_lua_error(f"Failed to initialize Lua guardrails: {e}") Logger.log_lua_error(f"Failed to initialize Lua guardrails: {e}")

128
markdown/Rotation.md Normal file
View File

@@ -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

View File

@@ -31,7 +31,7 @@ def get_html_files(directory=HTML_DIR):
return html_files return html_files
def build_index_page(): def build_index_page() -> str:
with open(BASE_FILE, "r", encoding="utf-8") as f: with open(BASE_FILE, "r", encoding="utf-8") as f:
base_html = f.read() base_html = f.read()
@@ -80,7 +80,7 @@ H2_CANDIDATES = [h for h in hash_list if h != H1]
H2 = random.choice(H2_CANDIDATES) if H2_CANDIDATES else H1 H2 = random.choice(H2_CANDIDATES) if H2_CANDIDATES else H1
# cahcing was a bad, idea, servertime got stuck. it is now a variable ;) # cahcing was a bad, idea, servertime got stuck. it is now a variable ;)
def index_footer(): def index_footer() -> str:
tor_link = "http://7uhuxits7qfmiagkmpazxvh3rtk6aijs6pbawge3fl77y4xqjixlhkqd.onion/" tor_link = "http://7uhuxits7qfmiagkmpazxvh3rtk6aijs6pbawge3fl77y4xqjixlhkqd.onion/"
return f""" return f"""
<!-- Footer styling doesnt need to work with <!-- Footer styling doesnt need to work with
@@ -107,7 +107,7 @@ def index_footer():
</div> </div>
""" """
class MyHandler(BaseHTTPRequestHandler): class WebServerHTTPRequestHandler(BaseHTTPRequestHandler):
# This is a Helper Function for the POST Endpoints # This is a Helper Function for the POST Endpoints
def _parse_post_data(self): def _parse_post_data(self):
"""Parse POST request body""" """Parse POST request body"""
@@ -331,7 +331,7 @@ if __name__ == "__main__":
logger.log_debug("Started PyPost.py in background watcher thread.") logger.log_debug("Started PyPost.py in background watcher thread.")
server_address = ("localhost", 8000) server_address = ("localhost", 8000)
httpd: HTTPServer = HTTPServer(server_address, MyHandler) # type: ignore[arg-type] httpd: HTTPServer = HTTPServer(server_address, WebServerHTTPRequestHandler) # type: ignore[arg-type]
logger.log_info(f"Serving on http://{server_address[0]}:{server_address[1]}") logger.log_info(f"Serving on http://{server_address[0]}:{server_address[1]}")
httpd.serve_forever() httpd.serve_forever()
except (Exception, KeyboardInterrupt) as e: except (Exception, KeyboardInterrupt) as e: