nice visual improvments

This commit is contained in:
2025-10-05 19:52:43 +02:00
parent 6580d5c4bc
commit a4d8283d2a
8 changed files with 398 additions and 231 deletions

101
PyPost.py
View File

@@ -8,6 +8,7 @@ from jinja2 import Environment, FileSystemLoader
import base64 import base64
import random import random
import time import time
import yaml
import marko import marko
from marko.ext.gfm import GFM from marko.ext.gfm import GFM
@@ -19,7 +20,7 @@ from htmlhandler import htmlhandler as Handler
from lua import plugin_manager from lua import plugin_manager
plugin_manager = plugin_manager.PluginManager() plugin_manager = plugin_manager.PluginManager()
plugin_manager.load_all() # load plugins plugin_manager.load_all() # load plugins
# Use absolute paths # Use absolute paths
ROOT = Path(os.path.abspath(".")) ROOT = Path(os.path.abspath("."))
@@ -34,7 +35,7 @@ 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}"
# Create Python Markdown parser with table support (fallback for small files) # Python Markdown parser with table support
markdown_parser = marko.Markdown(extensions=[GFM]) markdown_parser = marko.Markdown(extensions=[GFM])
# Threshold for switching to Rust parser (number of lines) # Threshold for switching to Rust parser (number of lines)
@@ -45,8 +46,22 @@ Logger = Logger()
# Global obfuscate flag, default True # Global obfuscate flag, default True
obfuscate = True obfuscate = True
def split_yaml_front_matter(md_path: Path) -> tuple[str, str]:
"""Return (yaml_text, markdown_text). YAML is '' if none exists."""
try:
text = md_path.read_text(encoding="utf-8")
except Exception as e:
Logger.log_error(f"Could not read {md_path}: {e}")
return '', ''
if text.startswith("---"):
parts = text.split("---", 2)
if len(parts) >= 3:
yaml_text = parts[1].strip()
markdown_text = parts[2].lstrip("\n")
return yaml_text, markdown_text
return '', text
def count_lines_in_file(file_path: Path) -> int: def count_lines_in_file(file_path: Path) -> int:
"""Count the number of lines in a file."""
try: try:
with open(file_path, 'r', encoding='utf-8') as f: with open(file_path, 'r', encoding='utf-8') as f:
return sum(1 for _ in f) return sum(1 for _ in f)
@@ -55,24 +70,18 @@ def count_lines_in_file(file_path: Path) -> int:
return 0 return 0
def should_use_rust_parser(md_path: Path) -> bool: def should_use_rust_parser(md_path: Path) -> bool:
"""Determine if we should use the Rust parser based on file size."""
if not RUST_PARSER_PATH.exists(): if not RUST_PARSER_PATH.exists():
return False return False
line_count = count_lines_in_file(md_path) line_count = count_lines_in_file(md_path)
use_rust = line_count > RUST_PARSER_THRESHOLD use_rust = line_count > RUST_PARSER_THRESHOLD
if use_rust: if use_rust:
Logger.log_rust_usage(f"Using Rust parser for {md_path} ({line_count} lines)") Logger.log_rust_usage(f"Using Rust parser for {md_path} ({line_count} lines)")
else: else:
Logger.log_debug(f"Using Python parser for {md_path} ({line_count} lines)") Logger.log_debug(f"Using Python parser for {md_path} ({line_count} lines)")
return use_rust return use_rust
def parse_markdown_with_rust(md_path: Path) -> str: def parse_markdown_with_rust(md_path: Path) -> str:
"""Parse markdown using the Rust parser."""
try: try:
# Run the Rust parser
result = subprocess.run( result = subprocess.run(
[str(RUST_PARSER_PATH), str(md_path)], [str(RUST_PARSER_PATH), str(md_path)],
capture_output=True, capture_output=True,
@@ -90,31 +99,27 @@ def parse_markdown_with_rust(md_path: Path) -> str:
raise raise
def render_markdown(md_path: Path): def render_markdown(md_path: Path):
"""Render a single markdown file to an obfuscated HTML file.""" """Render a single markdown file to HTML, stripping YAML front matter."""
try: yaml_text, markdown_text = split_yaml_front_matter(md_path)
text = md_path.read_text(encoding="utf-8")
except Exception as e:
Logger.log_error(f"Could not read {md_path}: {e}")
return
# Decide which parser to use based on file size # Choose parser
if should_use_rust_parser(md_path): if should_use_rust_parser(md_path):
try: try:
html_body = parse_markdown_with_rust(md_path) html_body = parse_markdown_with_rust(md_path)
except Exception as e: except Exception as e:
Logger.log_warning(f"Rust parser failed for {md_path}, falling back to Python parser: {e}") Logger.log_warning(f"Rust parser failed for {md_path}, falling back to Python parser: {e}")
html_body = markdown_parser.convert(text) html_body = markdown_parser.convert(markdown_text)
else: else:
html_body = markdown_parser.convert(text) html_body = markdown_parser.convert(markdown_text)
# Extract title from filename or first H1 # Extract title from filename or first H1
title = md_path.stem title = md_path.stem
for line in text.splitlines(): for line in markdown_text.splitlines():
if line.startswith("# "): if line.startswith("# "):
title = line[2:].strip() title = line[2:].strip()
break break
# Call pre_template hook properly # Plugin pre_template hook
Logger.log_debug(f"Calling pre_template hook for {md_path}") Logger.log_debug(f"Calling pre_template hook for {md_path}")
modified = plugin_manager.run_hook("pre_template", str(md_path), html_body) modified = plugin_manager.run_hook("pre_template", str(md_path), html_body)
if modified is not None: if modified is not None:
@@ -123,13 +128,11 @@ def render_markdown(md_path: Path):
else: else:
Logger.log_debug("pre_template hook returned None") Logger.log_debug("pre_template hook returned None")
# Create clean HTML structure # Jinja template
# Pick two different hashes from hash_list
env = Environment(loader=FileSystemLoader("html/base")) env = Environment(loader=FileSystemLoader("html/base"))
template = env.get_template("template.html") template = env.get_template("template.html")
hash1, hash2 = random.sample(hash_list, 2) hash1, hash2 = random.sample(hash_list, 2)
# Load these variable for Jinja to use.
clean_jinja_html = template.render( clean_jinja_html = template.render(
title=title, title=title,
html_body=html_body, html_body=html_body,
@@ -139,23 +142,36 @@ def render_markdown(md_path: Path):
timestamp=time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()), timestamp=time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),
) )
# Plugin post_render hook
post_mod = plugin_manager.run_hook("post_render", str(md_path), clean_jinja_html) post_mod = plugin_manager.run_hook("post_render", str(md_path), clean_jinja_html)
if post_mod is not None: if post_mod is not None:
clean_jinja_html = post_mod clean_jinja_html = post_mod
# Ensure html directory exists # Write output
HTML_DIR.mkdir(exist_ok=True) HTML_DIR.mkdir(exist_ok=True)
# Maintain relative directory structure in html/
relative_path = md_path.relative_to(MARKDOWN_DIR) relative_path = md_path.relative_to(MARKDOWN_DIR)
out_path = HTML_DIR / relative_path.with_suffix(".html") out_path = HTML_DIR / relative_path.with_suffix(".html")
# Create parent directories if needed
out_path.parent.mkdir(parents=True, exist_ok=True) out_path.parent.mkdir(parents=True, exist_ok=True)
out_path.write_text(clean_jinja_html, encoding="utf-8") out_path.write_text(clean_jinja_html, encoding="utf-8")
Logger.log_debug(f"Rendered: {md_path} -> {out_path}") Logger.log_debug(f"Rendered: {md_path} -> {out_path}")
def extract_summary(md_path: Path) -> tuple[str, str] | None:
yaml_text, _ = split_yaml_front_matter(md_path)
if not yaml_text:
Logger.log_debug(f"No YAML front matter in {md_path}")
return None
try:
data = yaml.safe_load(yaml_text)
summary = data.get("summary")
if summary:
html_name = md_path.with_suffix(".html").name
return html_name, summary
else:
Logger.log_debug(f"No 'summary' key in YAML of {md_path}")
except Exception as e:
Logger.log_warning(f"Failed to parse YAML summary in {md_path}: {e}")
return None
def remove_html(md_path: Path): def remove_html(md_path: Path):
relative_path = md_path.relative_to(MARKDOWN_DIR) relative_path = md_path.relative_to(MARKDOWN_DIR)
out_path = HTML_DIR / relative_path.with_suffix(".html") out_path = HTML_DIR / relative_path.with_suffix(".html")
@@ -170,20 +186,15 @@ def initial_scan(markdown_dir: Path):
def build_rust_parser() -> bool: def build_rust_parser() -> bool:
fastmd_dir = ROOT / "fastmd" fastmd_dir = ROOT / "fastmd"
if not fastmd_dir.exists(): if not fastmd_dir.exists():
Logger.log_error(f"fastmd directory not found at {fastmd_dir}") Logger.log_error(f"fastmd directory not found at {fastmd_dir}")
return False return False
cargo_toml = fastmd_dir / "Cargo.toml" cargo_toml = fastmd_dir / "Cargo.toml"
if not cargo_toml.exists(): if not cargo_toml.exists():
Logger.log_error(f"Cargo.toml not found at {cargo_toml}") Logger.log_error(f"Cargo.toml not found at {cargo_toml}")
return False return False
Logger.log_info("Attempting to build Rust parser with 'cargo build --release'...") Logger.log_info("Attempting to build Rust parser with 'cargo build --release'...")
try: try:
# Run cargo build --release in the fastmd directory
result = subprocess.run( result = subprocess.run(
["cargo", "build", "--release"], ["cargo", "build", "--release"],
cwd=str(fastmd_dir), cwd=str(fastmd_dir),
@@ -191,11 +202,9 @@ def build_rust_parser() -> bool:
text=True, text=True,
check=True check=True
) )
Logger.log_info("Rust parser built successfully!") Logger.log_info("Rust parser built successfully!")
Logger.log_debug(f"Build output: {result.stdout}") Logger.log_debug(f"Build output: {result.stdout}")
return True return True
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
Logger.log_error(f"Failed to build Rust parser: {e}") Logger.log_error(f"Failed to build Rust parser: {e}")
Logger.log_error(f"Build stderr: {e.stderr}") Logger.log_error(f"Build stderr: {e.stderr}")
@@ -208,7 +217,7 @@ def build_rust_parser() -> bool:
return False return False
if __name__ == "__main__": if __name__ == "__main__":
# Check for markdown directory # Handle alternative markdown folder
if not MARKDOWN_DIR.exists(): if not MARKDOWN_DIR.exists():
alt_root = ROOT / "PyPost" alt_root = ROOT / "PyPost"
if alt_root.exists() and alt_root.is_dir(): if alt_root.exists() and alt_root.is_dir():
@@ -216,7 +225,6 @@ if __name__ == "__main__":
ROOT = alt_root ROOT = alt_root
MARKDOWN_DIR = ROOT / "markdown" MARKDOWN_DIR = ROOT / "markdown"
HTML_DIR = ROOT / "html" HTML_DIR = ROOT / "html"
# Update Rust parser path for new root
RUST_PARSER_PATH = ROOT / "fastmd" / "target" / "release" / f"fastmd{exe_ext}" 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}"
@@ -224,18 +232,14 @@ if __name__ == "__main__":
Logger.log_error(f"Markdown directory not found: {MARKDOWN_DIR}") Logger.log_error(f"Markdown directory not found: {MARKDOWN_DIR}")
Logger.log_warning("Please create a 'markdown' directory or use a 'PyPost' directory with one inside it.") Logger.log_warning("Please create a 'markdown' directory or use a 'PyPost' directory with one inside it.")
sys.exit(1) sys.exit(1)
# Check if Rust parser exists, if not try to build it # Build Rust parser if missing
if not RUST_PARSER_PATH.exists(): if not RUST_PARSER_PATH.exists():
Logger.log_warning(f"Rust parser not found at {RUST_PARSER_PATH}") Logger.log_warning(f"Rust parser not found at {RUST_PARSER_PATH}")
# Try to build the Rust parser
if build_rust_parser(): if build_rust_parser():
# Update path after successful build
RUST_PARSER_PATH = ROOT / "fastmd" / "target" / "release" / f"fastmd{exe_ext}" 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}"
if RUST_PARSER_PATH.exists(): if RUST_PARSER_PATH.exists():
Logger.log_info(f"Rust parser built and found at: {RUST_PARSER_PATH}") Logger.log_info(f"Rust parser built and found at: {RUST_PARSER_PATH}")
else: else:
@@ -246,13 +250,12 @@ if __name__ == "__main__":
Logger.log_warning("Will use Python parser for all files") Logger.log_warning("Will use Python parser for all files")
else: else:
Logger.log_info(f"Rust parser found at: {RUST_PARSER_PATH}") Logger.log_info(f"Rust parser found at: {RUST_PARSER_PATH}")
# Log parser strategy
if RUST_PARSER_PATH.exists(): if RUST_PARSER_PATH.exists():
Logger.log_info(f"Will use Rust parser for files with more than {RUST_PARSER_THRESHOLD} lines") Logger.log_info(f"Will use Rust parser for files with more than {RUST_PARSER_THRESHOLD} lines")
else: else:
Logger.log_warning("Using Python parser for all files") Logger.log_warning("Using Python parser for all files")
initial_scan(MARKDOWN_DIR) initial_scan(MARKDOWN_DIR)
event_handler = Handler() event_handler = Handler()
observer = Observer() observer = Observer()
@@ -264,4 +267,4 @@ if __name__ == "__main__":
time.sleep(1) time.sleep(1)
except KeyboardInterrupt: except KeyboardInterrupt:
observer.stop() observer.stop()
observer.join() observer.join()

View File

@@ -1,39 +1,80 @@
/* RESET & BASE LAYOUT */
* { * {
box-sizing: border-box; box-sizing: border-box;
} }
body { html {
font-family: Arial, sans-serif; height: 100%;
}
body {
display: flex;
flex-direction: column;
min-height: 100vh;
margin: 0; margin: 0;
padding: 20px; padding: 0;
font-family: Arial, sans-serif;
background-color: #fff; background-color: #fff;
color: #0f1111; color: #0f1111;
font-size: 16px; font-size: 16px;
} }
h1 { .page-container {
display: flex;
flex-direction: column;
flex: 1;
width: 100%;
}
main {
flex: 1;
padding: 20px;
}
/* FOOTER */
.footer {
border-top: solid#ccc 1px;
margin-top: auto;
width: 100%;
text-align: left;
padding: 1em;
font-size: 14px;
}
.footer hr {
border: 1px solid #ccc;
}
.footer a {
text-decoration: none;
color: #0066cc;
font-style: italic;
}
/* TYPOGRAPHY */
h1 {
color: #333; color: #333;
font-size: clamp(1.5rem, 5vw, 2rem); font-size: clamp(1.5rem, 5vw, 2rem);
margin: 0 0 1rem 0; margin: 0 0 1rem 0;
} }
hr { hr {
width: 100%; width: 100%;
border: none; border: none;
border-top: 1px solid #ddd; border-top: 1px solid #ddd;
} }
/* LIST STYLES */ /* LIST STYLES */
ul { ul {
padding-left: clamp(20px, 10vw, 100px); padding-left: clamp(20px, 10vw, 100px);
margin-top: 0.5em; margin-top: 0.5em;
list-style: none; list-style: none;
} }
li { li {
list-style: none; list-style: none;
background: url("../../css/icons/item.webp") no-repeat left center; background: url("../../css/icons/item.webp") no-repeat left center;
background-size: 15px 20px; background-size: 15px 20px;
padding: 12px 0 12px 30px; padding: 12px 0 12px 30px;
font-size: clamp(1rem, 2.5vw, 1.125rem); font-size: clamp(1rem, 2.5vw, 1.125rem);
line-height: 1.5; line-height: 1.5;
@@ -44,7 +85,7 @@ li {
align-items: center; align-items: center;
} }
li:hover { li:hover {
font-size: clamp(1.05rem, 2.6vw, 1.2rem); font-size: clamp(1.05rem, 2.6vw, 1.2rem);
padding-left: 35px; padding-left: 35px;
} }
@@ -55,7 +96,7 @@ li a {
word-break: break-word; word-break: break-word;
} }
#available { #available {
padding-left: clamp(20px, 5vw, 40px); padding-left: clamp(20px, 5vw, 40px);
margin-bottom: 0.5em; margin-bottom: 0.5em;
font-size: clamp(1rem, 2.5vw, 1.125rem); font-size: clamp(1rem, 2.5vw, 1.125rem);
@@ -67,7 +108,7 @@ li a {
margin-right: 8px; margin-right: 8px;
} }
#nojs { #nojs {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
color: #333; color: #333;
@@ -82,14 +123,14 @@ li a {
} }
/* NOSCRIPT LIST */ /* NOSCRIPT LIST */
#nonenormalul { #nonenormalul {
list-style: disc inside; list-style: disc inside;
margin: 1em 0; margin: 1em 0;
padding-left: clamp(20px, 5vw, 40px); padding-left: clamp(20px, 5vw, 40px);
} }
#nonenormalul li { #nonenormalul li {
list-style: inherit; list-style: inherit;
margin: 0.5em 0; margin: 0.5em 0;
padding: 8px 0; padding: 8px 0;
background: none; background: none;
@@ -97,16 +138,17 @@ li a {
font-size: clamp(0.9rem, 2.2vw, 1rem); font-size: clamp(0.9rem, 2.2vw, 1rem);
} }
#nonenormalul li:hover { #nonenormalul li:hover {
font-size: clamp(0.95rem, 2.3vw, 1.05rem); font-size: clamp(0.95rem, 2.3vw, 1.05rem);
} }
/* BUTTONS */
button { button {
margin: 5px 5px 5px 0; margin: 5px 5px 5px 0;
background-image: linear-gradient(#f7f8fa, #e7e9ec); background-image: linear-gradient(#f7f8fa, #e7e9ec);
border: 1px solid #adb1b8; border: 1px solid #adb1b8;
border-radius: 3px; border-radius: 3px;
box-shadow: rgba(255,255,255,.6) 0 1px 0 inset; box-shadow: rgba(255, 255, 255, .6) 0 1px 0 inset;
color: #0f1111; color: #0f1111;
cursor: pointer; cursor: pointer;
display: inline-block; display: inline-block;
@@ -124,23 +166,24 @@ button {
transition: all 0.2s ease; transition: all 0.2s ease;
} }
button:active { button:active {
border-color: #a2a6ac; border-color: #a2a6ac;
transform: translateY(1px); transform: translateY(1px);
} }
button:hover { button:hover {
border-color: #979aa1; border-color: #979aa1;
background-image: linear-gradient(#e7e9ec, #d7d9dc); background-image: linear-gradient(#e7e9ec, #d7d9dc);
} }
button:focus { button:focus {
border-color: #e77600; border-color: #e77600;
box-shadow: rgba(228, 121, 17, .5) 0 0 3px 2px; box-shadow: rgba(228, 121, 17, .5) 0 0 3px 2px;
outline: 0; outline: 0;
} }
button:disabled, button[disabled] { button:disabled,
button[disabled] {
color: #999; color: #999;
border-color: #ccc; border-color: #ccc;
cursor: not-allowed; cursor: not-allowed;
@@ -167,13 +210,78 @@ input#searchbox:focus {
outline: 0; outline: 0;
} }
/* ARTICLE STYLING */
article {
max-width: 800px;
margin: 1rem 0 1rem 4em;
padding: 1rem;
background-image: linear-gradient(#f7f8fa, #e7e9ec);
border: 1px solid #adb1b8;
box-shadow: rgba(255, 255, 255, .6) 0 1px 0 inset, 0 1px 3px rgba(0, 0, 0, 0.05);
border-radius: 6px;
line-height: 1.6;
font-size: clamp(0.95rem, 2vw, 1.1rem);
transition: background-color 0.3s ease, color 0.3s ease;
}
article h2 {
font-size: clamp(1.15rem, 3.5vw, 1.5rem);
margin: 0.5rem 0 0.75rem 0;
color: #333;
}
article p {
margin: 0.5rem 0;
}
article a {
color: #1a73e8;
text-decoration: underline;
transition: color 0.2s ease;
}
article a:hover {
color: #0f52ba;
}
article blockquote {
margin: 0.75rem 0;
padding: 0.5rem 1rem;
border-left: 3px solid #e77600;
background-color: #fff9f2;
font-style: italic;
color: #555;
}
article code,
article pre {
font-family: "Courier New", Courier, monospace;
background-color: #f0f0f0;
padding: 0.15rem 0.3rem;
border-radius: 3px;
font-size: 0.9em;
overflow-x: auto;
}
article pre {
padding: 0.75rem;
background-color: #f4f4f4;
}
article img {
max-width: 100%;
height: auto;
margin: 0.5rem 0;
border-radius: 4px;
}
/* DARK MODE */ /* DARK MODE */
body.dark-mode { body.dark-mode {
background-color: #1e1e1e; background-color: #1e1e1e;
color: #e0e0e0; color: #e0e0e0;
} }
body.dark-mode h1 { body.dark-mode h1 {
color: #f0f0f0; color: #f0f0f0;
} }
@@ -181,11 +289,11 @@ body.dark-mode hr {
border-top-color: #444; border-top-color: #444;
} }
body.dark-mode #nojs { body.dark-mode #nojs {
color: #f0f0f0; color: #f0f0f0;
} }
body.dark-mode li { body.dark-mode li {
background-image: url("../../css/icons/item.webp"); background-image: url("../../css/icons/item.webp");
} }
@@ -193,7 +301,7 @@ body.dark-mode button {
background-image: linear-gradient(#3a3a3a, #2a2a2a); background-image: linear-gradient(#3a3a3a, #2a2a2a);
color: #e0e0e0; color: #e0e0e0;
border-color: #555; border-color: #555;
box-shadow: rgba(0,0,0,.6) 0 1px 0 inset; box-shadow: rgba(0, 0, 0, .6) 0 1px 0 inset;
} }
body.dark-mode button:hover { body.dark-mode button:hover {
@@ -219,44 +327,111 @@ body.dark-mode a:visited {
color: #9a7aff; color: #9a7aff;
} }
body.dark-mode article {
background-image: none;
background-color: #2e2e2e;
color: #e0e0e0;
border-color: #555;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
}
body.dark-mode article h1,
body.dark-mode article h2,
body.dark-mode article h3,
body.dark-mode article h4,
body.dark-mode article h5,
body.dark-mode article h6 {
color: #f0f0f0;
}
body.dark-mode article a {
color: #4ea1ff;
}
body.dark-mode article a:hover {
color: #1e90ff;
}
body.dark-mode article blockquote {
border-left-color: #e77600;
background-color: #3a3a3a;
color: #ccc;
}
body.dark-mode article code,
body.dark-mode article pre {
background-color: #3a3a3a;
color: #e0e0e0;
}
/* MOBILE OPTIMIZATIONS */ /* MOBILE OPTIMIZATIONS */
@media (max-width: 768px) { @media (max-width: 768px) {
body { main {
padding: 15px; padding: 15px;
} }
li { li {
padding: 14px 0 14px 35px; padding: 14px 0 14px 35px;
background-size: 18px 23px; background-size: 18px 23px;
} }
button { button {
width: 100%; width: 100%;
margin: 5px 0; margin: 5px 0;
} }
input#searchbox { input#searchbox {
max-width: 100%; max-width: 100%;
} }
} }
@media (max-width: 480px) { @media (max-width: 1400px) {
body { main {
padding: 10px; padding: 10px;
} }
ul { ul {
padding-left: 10px; padding-left: 10px;
} }
#available { #available {
padding-left: 10px; padding-left: 10px;
}
article {
margin-left: auto;
}
button {
font-size: clamp(0.8rem, 2vw, 0.9rem);
padding: 10px;
min-height: auto;
margin: 5px 0;
white-space: normal;
}
button.page-number {
display: none;
}
#pagination-controls {
display: flex;
justify-content: flex-start;
gap: 8px;
}
#pagination-controls button {
width: auto;
padding: 8px 12px;
min-height: auto;
white-space: nowrap;
margin: 0;
} }
} }
/* HIGH DPI DISPLAYS */ /* HIGH DPI DISPLAYS */
@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) { @media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
li { li {
background-size: 15px 20px; background-size: 15px 20px;
} }
} }

View File

@@ -13,6 +13,7 @@
</script> </script>
</head> </head>
<body> <body>
<main style="flex:1;">
<noscript> <noscript>
<div style="display: inline-flex; align-items: center; flex-wrap: wrap;"> <div style="display: inline-flex; align-items: center; flex-wrap: wrap;">
<img src="../../css/icons/script.webp" width="45" height="45" alt="Script icon" style="margin-right: 8px;" /> <img src="../../css/icons/script.webp" width="45" height="45" alt="Script icon" style="margin-right: 8px;" />
@@ -37,6 +38,7 @@
<!-- CONTENT --> <!-- CONTENT -->
<!-- load scripts needed for indexer --> <!-- load scripts needed for indexer -->
<script src="../../js/normal.js"></script> <script src="../../js/normal.js"></script>
<script src="../../js/search.js" defer></script> <script src="../../js/search.js" defer></script>

View File

@@ -1,23 +1,24 @@
function paginate_ul_items(page = 1) { function paginate_articles(page = 1) {
const ul = document.querySelector('ul'); const articles = Array.from(document.querySelectorAll('article'));
if (!ul) return; if (!articles.length) return;
const items = Array.from(ul.querySelectorAll('li')); const query = document.getElementById('searchbox')?.value.toLowerCase() || '';
const query = document.getElementById('searchbox').value.toLowerCase();
// Filter items based on search // Filter articles based on search
const filteredItems = items.filter(li => li.textContent.toLowerCase().includes(query)); const filteredArticles = articles.filter(article =>
article.textContent.toLowerCase().includes(query)
);
const itemsPerPage = 10; const articlesPerPage = 5; // reduced to 5
const totalPages = Math.ceil(filteredItems.length / itemsPerPage); const totalPages = Math.ceil(filteredArticles.length / articlesPerPage);
// Hide all items first // Hide all articles first
items.forEach(li => li.style.display = 'none'); articles.forEach(article => article.style.display = 'none');
// Show only items for the current page // Show only articles for the current page
const start = (page - 1) * itemsPerPage; const start = (page - 1) * articlesPerPage;
const end = start + itemsPerPage; const end = start + articlesPerPage;
filteredItems.slice(start, end).forEach(li => li.style.display = 'list-item'); filteredArticles.slice(start, end).forEach(article => article.style.display = 'block');
// Render pagination controls // Render pagination controls
renderPaginationControls(totalPages, page); renderPaginationControls(totalPages, page);
@@ -26,6 +27,9 @@ function paginate_ul_items(page = 1) {
function renderPaginationControls(totalPages, currentPage) { function renderPaginationControls(totalPages, currentPage) {
let paginationDiv = document.getElementById('pagination-controls'); let paginationDiv = document.getElementById('pagination-controls');
let separator = document.getElementById('vertical-seperator'); let separator = document.getElementById('vertical-seperator');
const searchbox = document.getElementById('searchbox');
if (!searchbox) return;
if (!paginationDiv) { if (!paginationDiv) {
paginationDiv = document.createElement('div'); paginationDiv = document.createElement('div');
paginationDiv.id = 'pagination-controls'; paginationDiv.id = 'pagination-controls';
@@ -33,48 +37,43 @@ function renderPaginationControls(totalPages, currentPage) {
paginationDiv.style.marginLeft = '16px'; paginationDiv.style.marginLeft = '16px';
paginationDiv.style.verticalAlign = 'middle'; paginationDiv.style.verticalAlign = 'middle';
const searchDiv = document.getElementById('searchbox').parentNode; const searchDiv = searchbox.parentNode;
// Create a wrapper so we can put a separator between search and pagination
const wrapper = document.createElement('div'); const wrapper = document.createElement('div');
wrapper.style.display = 'flex'; wrapper.style.display = 'flex';
wrapper.style.alignItems = 'center'; wrapper.style.alignItems = 'center';
wrapper.style.gap = '12px'; // space around separator wrapper.style.gap = '12px';
// Move searchbox into wrapper
searchDiv.appendChild(wrapper); searchDiv.appendChild(wrapper);
wrapper.appendChild(document.getElementById('searchbox')); wrapper.appendChild(searchbox);
// Separator
separator = document.createElement('div'); separator = document.createElement('div');
separator.id = "vertical-seperator" separator.id = "vertical-seperator";
separator.style.width = '1px'; separator.style.width = '1px';
separator.style.height = '28px'; separator.style.height = '28px';
separator.style.marginRight = "-15px";
separator.style.backgroundColor = '#ccc'; separator.style.backgroundColor = '#ccc';
wrapper.appendChild(separator); wrapper.appendChild(separator);
// Add pagination after separator
wrapper.appendChild(paginationDiv); wrapper.appendChild(paginationDiv);
} }
paginationDiv.innerHTML = ''; paginationDiv.innerHTML = '';
if (separator) { if (separator) separator.style.display = totalPages <= 1 ? 'none' : 'block';
separator.style.display = totalPages <= 1 ? 'none' : 'block';
}
if (totalPages <= 1) { if (totalPages <= 1) {
paginationDiv.style.display = 'none'; paginationDiv.style.display = 'none';
return; return;
} else { } else {
paginationDiv.style.display = 'inline-block'; paginationDiv.style.display = 'inline-flex';
paginationDiv.style.alignItems = 'center';
paginationDiv.style.gap = '6px';
} }
// Previous button // Previous button
const prevBtn = document.createElement('button'); const prevBtn = document.createElement('button');
prevBtn.textContent = '<'; prevBtn.textContent = '<';
prevBtn.disabled = currentPage === 1; prevBtn.disabled = currentPage === 1;
prevBtn.onclick = () => paginate_ul_items(currentPage - 1); prevBtn.onclick = () => paginate_articles(currentPage - 1);
paginationDiv.appendChild(prevBtn); paginationDiv.appendChild(prevBtn);
// Page numbers // Page numbers
@@ -82,7 +81,8 @@ function renderPaginationControls(totalPages, currentPage) {
const btn = document.createElement('button'); const btn = document.createElement('button');
btn.textContent = i; btn.textContent = i;
btn.disabled = i === currentPage; btn.disabled = i === currentPage;
btn.onclick = () => paginate_ul_items(i); btn.classList.add('page-number');
btn.onclick = () => paginate_articles(i);
paginationDiv.appendChild(btn); paginationDiv.appendChild(btn);
} }
@@ -90,47 +90,65 @@ function renderPaginationControls(totalPages, currentPage) {
const nextBtn = document.createElement('button'); const nextBtn = document.createElement('button');
nextBtn.textContent = '>'; nextBtn.textContent = '>';
nextBtn.disabled = currentPage === totalPages; nextBtn.disabled = currentPage === totalPages;
nextBtn.onclick = () => paginate_ul_items(currentPage + 1); nextBtn.onclick = () => paginate_articles(currentPage + 1);
paginationDiv.appendChild(nextBtn); paginationDiv.appendChild(nextBtn);
// Mobile current page span
let mobilePageSpan = document.getElementById('mobile-page-span');
if (!mobilePageSpan) {
mobilePageSpan = document.createElement('span');
mobilePageSpan.id = 'mobile-page-span';
mobilePageSpan.style.fontStyle = "italic"
mobilePageSpan.style.margin = '0 6px';
paginationDiv.appendChild(mobilePageSpan);
}
mobilePageSpan.textContent = `${currentPage}/${totalPages}`;
} }
// Update search function to use pagination // Search input triggers pagination
function search_ul_items() { function search_articles() {
paginate_ul_items(1); paginate_articles(1);
} }
// Create search box and insert before the available pages paragraph // Setup search box & initial pagination
window.addEventListener('DOMContentLoaded', function() { window.addEventListener('DOMContentLoaded', function() {
const searchDiv = document.createElement('div'); const searchDiv = document.createElement('div');
searchDiv.style.marginBottom = '16px'; searchDiv.style.marginBottom = '16px';
searchDiv.style.paddingLeft = '19px'; searchDiv.style.paddingLeft = '19px';
searchDiv.innerHTML = ` searchDiv.innerHTML = `
<input <input
type="text" type="text"
id="searchbox" id="searchbox"
placeholder="Search pages..." placeholder="Search articles..."
style=" style="
padding: 6px 6px 6px 28px; padding: 6px 6px 6px 28px;
font-size: 1em; font-size: 1em;
width: 220px; width: 220px;
background: url('../../css/icons/search.webp') no-repeat 6px center; background: url('../../css/icons/search.webp') no-repeat 6px center;
background-size: 20px 20px; background-size: 20px 20px;
" "
title="Search for pages" title="Search for articles"
/> />
`; `;
const available = document.getElementById('available');
available.parentNode.insertBefore(searchDiv, available); // Insert after the h1 header but before "Available pages"
const availablePara = document.getElementById('available');
if (availablePara) {
availablePara.parentNode.insertBefore(searchDiv, availablePara);
} else {
// Fallback: insert at the beginning of main
const container = document.querySelector('main');
if (container && container.firstElementChild) {
container.insertBefore(searchDiv, container.firstElementChild);
}
}
const searchbox = document.getElementById('searchbox'); const searchbox = document.getElementById('searchbox');
if (searchbox) { if (searchbox) {
searchbox.title = "Search for pages"; searchbox.addEventListener('input', search_articles);
searchbox.style.fontStyle = "bold";
} }
document.getElementById('searchbox').addEventListener('input', search_ul_items);
// Initial pagination setup // Initial pagination setup
paginate_ul_items(1); paginate_articles(1);
}); });

View File

@@ -1,47 +0,0 @@
// sortLists.js
// Function to detect if a string contains numbers
function extractNumber(text) {
const match = text.match(/\d+/);
return match ? parseInt(match[0], 10) : null;
}
// Sorting function
function sortListItems(a, b) {
const textA = a.textContent.trim().toLowerCase();
const textB = b.textContent.trim().toLowerCase();
const numA = extractNumber(textA);
const numB = extractNumber(textB);
if (numA !== null && numB !== null) {
// Both contain numbers -> sort numerically
if (numA !== numB) return numA - numB;
return textA.localeCompare(textB);
}
if (numA !== null) {
// A has number, B doesn't -> numbers first
return -1;
}
if (numB !== null) {
// B has number, A doesn't
return 1;
}
// Otherwise sort alphabetically
return textA.localeCompare(textB);
}
// Main function to sort all ULs
function sortAllULs() {
const uls = document.querySelectorAll("ul");
uls.forEach(ul => {
const items = Array.from(ul.querySelectorAll("li"));
items.sort(sortListItems);
items.forEach(item => ul.appendChild(item)); // reattach in sorted order
});
}
// Run after page load
document.addEventListener("DOMContentLoaded", sortAllULs);

Binary file not shown.

View File

@@ -9,6 +9,8 @@ from pathlib import Path
from log.Logger import * from log.Logger import *
from lua import plugin_manager from lua import plugin_manager
from PyPost import extract_summary
logger = Logger() logger = Logger()
plugin_manager = plugin_manager.PluginManager() plugin_manager = plugin_manager.PluginManager()
plugin_manager.load_all() # load all plugins plugin_manager.load_all() # load all plugins
@@ -27,28 +29,45 @@ def get_html_files(directory=HTML_DIR):
html_files.append(entry) html_files.append(entry)
return html_files return html_files
def get_markdown_files():
"""Get list of .md files from the markdown directory."""
if not os.path.exists(MARKDOWN_DIR):
return []
markdown_files = []
for entry in os.listdir(MARKDOWN_DIR):
full_path = os.path.join(MARKDOWN_DIR, entry)
if os.path.isfile(full_path) and entry.endswith(".md"):
markdown_files.append(entry)
return markdown_files
def build_index_page(): def build_index_page():
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()
html_files = get_html_files(HTML_DIR)
links = "\n".join(f'<li><a href="/html/{fname}">{fname}</a></li>' for fname in html_files) articles = []
content = f"<ul>{links}</ul>" for md_path in Path(MARKDOWN_DIR).rglob("*.md"):
# Insert footer after content try:
full_content = content + index_footer() summary_data = extract_summary(md_path)
if summary_data:
html_name, summary = summary_data
else:
html_name = md_path.stem + ".html"
summary = "No Summary for this Article!"
text = md_path.read_text(encoding="utf-8")
title = md_path.stem
for line in text.splitlines():
if line.startswith("# "):
title = line[2:].strip()
break
article_html = f"""
<article>
<h3><a href="/html/{html_name}">{title}</a></h3>
<p>{summary}</p>
</article>
"""
articles.append(article_html)
except Exception as e:
logger.log_warning(f"Exception with summary: {e} at {md_path}")
continue
full_content = "\n".join(articles) + "</main>" + index_footer()
return base_html.replace("<!-- CONTENT -->", full_content) return base_html.replace("<!-- CONTENT -->", full_content)
import base64 import base64
import random import random
@@ -66,28 +85,25 @@ def index_footer():
<!-- Footer styling doesnt need to work with <!-- Footer styling doesnt need to work with
flex, or anything else, because pagnation. flex, or anything else, because pagnation.
--> -->
<footer style=" <div class="footer">
position: absolute; <footer>
bottom: 0; <p>
width: 100%; <!-- Server Time -->
"> <img src="../css/icons/date.webp" width="16" height="16" alt="date" loading="lazy" style="vertical-align: middle;" />
<hr style="border: 1px solid #ccc;" /> Server-Time (CET ; GMT+2): <i>{time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())}</i><br />
<p> <!-- Hashes -->
<!-- Server Time --> <img src="../css/icons/magnifier.webp" width="16" height="16" alt="Hash2" loading="lazy" style="display:inline; vertical-align:middle;" />
<img src="../css/icons/date.webp" width="16" height="16" alt="date" loading="lazy" style="vertical-align: middle;" /> Hash 1 (<b>UTF-8</b>)<i>:{base64.b64encode(H1.encode("utf-8")).decode("utf-8")}</i><br />
Server-Time (CET ; GMT+2): <i>{time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())}</i><br /> <img src="../css/icons/magnifier.webp" width="16" height="16" alt="Hash2" loading="lazy" style="display:inline; vertical-align:middle;" />
<!-- Hashes --> Hash 2 (<b>Windows-1252</b>)<i>:{base64.b64encode(H2.encode("windows-1252")).decode("windows-1252")}</i><br />
<img src="../css/icons/magnifier.webp" width="16" height="16" alt="Hash2" loading="lazy" style="display:inline; vertical-align:middle;" /> <!-- Git Repository Link -->
Hash 1 (<b>UTF-8</b>)<i>:{base64.b64encode(H1.encode("utf-8")).decode("utf-8")}</i><br /> <img src="../css/icons/written.webp" width="16" height="16" alt="Hash2" loading="lazy" style="display:inline; vertical-align:middle;" />
<img src="../css/icons/magnifier.webp" width="16" height="16" alt="Hash2" loading="lazy" style="display:inline; vertical-align:middle;" /> <a style="text-decoration:none;color:#0066cc;font-style:italic;padding-top:5px;" href="https://rattatwinko.servecounterstrike.com/gitea/rattatwinko/PyPost">View Git-Repository</a><br />
Hash 2 (<b>Windows-1252</b>)<i>:{base64.b64encode(H2.encode("windows-1252")).decode("windows-1252")}</i><br /> <img src="../css/icons/script.webp" width="16" height="16" alt="Hash2" loading="lazy" style="display:inline; vertical-align:middle;" />
<!-- Git Repository Link --> <a style="text-decoration:none;color:#0066cc;font-style:italic;padding-top:5px;" href="{tor_link}">View Tor Site</a>
<img src="../css/icons/written.webp" width="16" height="16" alt="Hash2" loading="lazy" style="display:inline; vertical-align:middle;" /> </p>
<a style="text-decoration:none;color:#0066cc;font-style:italic;padding-top:5px;" href="https://rattatwinko.servecounterstrike.com/gitea/rattatwinko/PyPost">View Git-Repository</a><br /> </footer>
<img src="../css/icons/script.webp" width="16" height="16" alt="Hash2" loading="lazy" style="display:inline; vertical-align:middle;" /> </div>
<a style="text-decoration:none;color:#0066cc;font-style:italic;padding-top:5px;" href="{tor_link}">View Tor Site</a>
</p>
</footer>
""" """
class MyHandler(BaseHTTPRequestHandler): class MyHandler(BaseHTTPRequestHandler):