fixed some things. and did some UI changes
This commit is contained in:
44
PyPost.py
44
PyPost.py
@@ -122,7 +122,10 @@ def render_markdown(md_path: Path):
|
|||||||
<title>{title}</title>
|
<title>{title}</title>
|
||||||
<link rel="stylesheet" href="../css/main.css">
|
<link rel="stylesheet" href="../css/main.css">
|
||||||
<link rel="icon" type="image/x-icon" href="../css/favicon/favicon.ico">
|
<link rel="icon" type="image/x-icon" href="../css/favicon/favicon.ico">
|
||||||
<script src="../js/post/normal.js"></script>
|
<script src="../js/post/download.js" defer></script>
|
||||||
|
<style>
|
||||||
|
a {{ text-decoration: none; color: #0066cc; }}
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body style="display:flex; flex-direction:column; min-height:100%; margin:0;">
|
<body style="display:flex; flex-direction:column; min-height:100%; margin:0;">
|
||||||
<main class="container" style="flex:1;">
|
<main class="container" style="flex:1;">
|
||||||
@@ -144,6 +147,8 @@ def render_markdown(md_path: Path):
|
|||||||
Hash 1 (<b>UTF-8</b>)<i>:{base64.b64encode(hash1.encode("utf-8")).decode("utf-8")}</i><br />
|
Hash 1 (<b>UTF-8</b>)<i>:{base64.b64encode(hash1.encode("utf-8")).decode("utf-8")}</i><br />
|
||||||
<img src="../css/icons/magnifier.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;" />
|
||||||
Hash 2 (<b>Windows-1252</b>)<i>:{base64.b64encode(hash2.encode("windows-1252")).decode("windows-1252")}</i><br />
|
Hash 2 (<b>Windows-1252</b>)<i>:{base64.b64encode(hash2.encode("windows-1252")).decode("windows-1252")}</i><br />
|
||||||
|
<img src="../css/icons/save.webp" width="16" height="16" alt="Hash2" loading="lazy" style="display:inline; vertical-align:middle;" />
|
||||||
|
<a id="download-md">Download as Markdown</a>
|
||||||
</footer>
|
</footer>
|
||||||
</body>
|
</body>
|
||||||
</html>"""
|
</html>"""
|
||||||
@@ -162,11 +167,11 @@ def render_markdown(md_path: Path):
|
|||||||
# Create parent directories if needed
|
# Create parent directories if needed
|
||||||
out_path.parent.mkdir(parents=True, exist_ok=True)
|
out_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
if obfuscate:
|
# if obfuscate:
|
||||||
out_path.write_text(obfuscated_html, encoding="utf-8")
|
# out_path.write_text(obfuscated_html, encoding="utf-8")
|
||||||
else:
|
# else:
|
||||||
|
# out_path.write_text(clean_html, encoding="utf-8")
|
||||||
out_path.write_text(clean_html, encoding="utf-8")
|
out_path.write_text(clean_html, encoding="utf-8")
|
||||||
|
|
||||||
Logger.log_debug(f"Rendered: {md_path} -> {out_path}")
|
Logger.log_debug(f"Rendered: {md_path} -> {out_path}")
|
||||||
|
|
||||||
def remove_html(md_path: Path):
|
def remove_html(md_path: Path):
|
||||||
@@ -184,7 +189,6 @@ def initial_scan(markdown_dir: Path):
|
|||||||
|
|
||||||
|
|
||||||
def build_rust_parser() -> bool:
|
def build_rust_parser() -> bool:
|
||||||
"""Attempt to build the Rust parser using cargo."""
|
|
||||||
fastmd_dir = ROOT / "fastmd"
|
fastmd_dir = ROOT / "fastmd"
|
||||||
|
|
||||||
if not fastmd_dir.exists():
|
if not fastmd_dir.exists():
|
||||||
@@ -269,34 +273,6 @@ if __name__ == "__main__":
|
|||||||
else:
|
else:
|
||||||
Logger.log_warning("Using Python parser for all files")
|
Logger.log_warning("Using Python parser for all files")
|
||||||
|
|
||||||
import argparse
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description="Monitor markdown directory and convert to HTML with dynamic parser selection.")
|
|
||||||
|
|
||||||
# This stores True when passed, but means "no obfuscation"
|
|
||||||
parser.add_argument(
|
|
||||||
"--no-obfuscate",
|
|
||||||
action="store_false",
|
|
||||||
help="Disable HTML obfuscation."
|
|
||||||
)
|
|
||||||
|
|
||||||
parser.add_argument(
|
|
||||||
"--rust-threshold",
|
|
||||||
type=int,
|
|
||||||
default=500,
|
|
||||||
help=f"Line count threshold for using Rust parser (default: {RUST_PARSER_THRESHOLD})"
|
|
||||||
)
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
# Invert it to get the obfuscate flag
|
|
||||||
obfuscate = not args.no_obfuscate
|
|
||||||
|
|
||||||
# Update threshold if specified
|
|
||||||
RUST_PARSER_THRESHOLD = args.rust_threshold
|
|
||||||
|
|
||||||
Logger.log_obfuscation_info(f"Obfuscation is {obfuscate}",obfuscate)
|
|
||||||
|
|
||||||
initial_scan(MARKDOWN_DIR)
|
initial_scan(MARKDOWN_DIR)
|
||||||
event_handler = Handler()
|
event_handler = Handler()
|
||||||
observer = Observer()
|
observer = Observer()
|
||||||
|
|||||||
BIN
css/icons/save.webp
Normal file
BIN
css/icons/save.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.8 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 558 KiB |
BIN
css/icons/search.webp
Normal file
BIN
css/icons/search.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
@@ -15,6 +15,10 @@
|
|||||||
#nonenormalul li { list-style: inherit; margin: 0; padding: 0; background: none; transition: font-size 0.5s cubic-bezier(0.075, 0.82, 0.165, 1); }
|
#nonenormalul li { list-style: inherit; margin: 0; padding: 0; background: none; transition: font-size 0.5s cubic-bezier(0.075, 0.82, 0.165, 1); }
|
||||||
#nonenormalul li:hover { font-size: larger; }
|
#nonenormalul li:hover { font-size: larger; }
|
||||||
</style>
|
</style>
|
||||||
|
<script>
|
||||||
|
console.log("javascript is enabled! good!")
|
||||||
|
document.write('<h1 id="nojs" style="color:black; display: flex; align-items: center;"><img src="../../css/icons/folder.webp" width="45" height="45" style="vertical-align: middle; margin-right: 8px;" />Index of PyPost</h1>');
|
||||||
|
</script>
|
||||||
<link rel="icon" type="image/x-icon" href="../../css/favicon/favicon.ico">
|
<link rel="icon" type="image/x-icon" href="../../css/favicon/favicon.ico">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -32,77 +36,18 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</p>
|
</p>
|
||||||
</noscript>
|
</noscript>
|
||||||
<script>
|
|
||||||
// we just repurpouse the nojs from css
|
|
||||||
// much easier than adding a new element
|
|
||||||
console.log("javascript is enabled! good!")
|
|
||||||
document.write('<h1 id="nojs" style="color:black; display: flex; align-items: center;"><img src="../../css/icons/folder.webp" width="45" height="45" style="vertical-align: middle; margin-right: 8px;" />Index of PyPost</h1>');
|
|
||||||
</script>
|
|
||||||
<p id="available">
|
<p id="available">
|
||||||
<img src="../../css/icons/available.webp" width="40" height="40" style="vertical-align: middle; display: inline; /*margin-right: 8px;*/ padding-right: 5px;" />
|
<img src="../../css/icons/available.webp" width="40" height="40" style="vertical-align: middle; display: inline; /*margin-right: 8px;*/ padding-right: 5px;" />
|
||||||
Available pages:
|
Available pages:
|
||||||
</p>
|
</p>
|
||||||
<!-- CONTENT -->
|
<!-- CONTENT -->
|
||||||
|
|
||||||
|
<!-- load scripts needed for indexer -->
|
||||||
<script src="../../js/normal.js"></script>
|
<script src="../../js/normal.js"></script>
|
||||||
<script type="text/javascript">
|
<script src="../../js/search.js" defer></script>
|
||||||
function search_ul_items() {
|
|
||||||
const query = document.getElementById('searchbox').value.toLowerCase();
|
|
||||||
const ul = document.querySelector('ul'); // only one UL
|
|
||||||
if (!ul) return;
|
|
||||||
|
|
||||||
const items = ul.querySelectorAll('li');
|
<footer style=" position: absolute; bottom: 0; width: 100%;">
|
||||||
items.forEach(li => {
|
<p></p>
|
||||||
if (li.textContent.toLowerCase().includes(query)) {
|
|
||||||
li.style.display = 'list-item';
|
|
||||||
} else {
|
|
||||||
li.style.display = 'none';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create search box and insert before the available pages paragraph
|
|
||||||
window.addEventListener('DOMContentLoaded', function() {
|
|
||||||
const searchDiv = document.createElement('div');
|
|
||||||
searchDiv.style.marginBottom = '16px';
|
|
||||||
searchDiv.style.paddingLeft = '19px';
|
|
||||||
searchDiv.innerHTML = `
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="searchbox"
|
|
||||||
placeholder="Search pages..."
|
|
||||||
style="
|
|
||||||
padding: 6px 6px 6px 28px;
|
|
||||||
font-size: 1em;
|
|
||||||
width: 220px;
|
|
||||||
background: url('../../css/icons/search.png') no-repeat 6px center;
|
|
||||||
background-size: 20px 20px;
|
|
||||||
"
|
|
||||||
title="Search for pages (italic)"
|
|
||||||
/>
|
|
||||||
`;
|
|
||||||
const available = document.getElementById('available');
|
|
||||||
available.parentNode.insertBefore(searchDiv, available);
|
|
||||||
|
|
||||||
const searchbox = document.getElementById('searchbox');
|
|
||||||
if (searchbox) {
|
|
||||||
searchbox.title = "Search for pages";
|
|
||||||
searchbox.style.fontStyle = "italic";
|
|
||||||
}
|
|
||||||
|
|
||||||
document.getElementById('searchbox').addEventListener('input', search_ul_items);
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<footer style="
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
width: 100%;
|
|
||||||
">
|
|
||||||
<p>
|
|
||||||
|
|
||||||
</p>
|
|
||||||
</footer>
|
</footer>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
18
js/post/download.js
Normal file
18
js/post/download.js
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
// current page URL
|
||||||
|
let url = window.location.href;
|
||||||
|
|
||||||
|
// replace `/html/` with `/markdown/`
|
||||||
|
url = url.replace("/html/", "/markdown/");
|
||||||
|
|
||||||
|
// replace `.html` with `.md`
|
||||||
|
url = url.replace(/\.html$/, ".md");
|
||||||
|
|
||||||
|
// assign to <a>
|
||||||
|
const a = document.getElementById("download-md");
|
||||||
|
a.href = url;
|
||||||
|
|
||||||
|
// suggest filename
|
||||||
|
const filename = url.split("/").pop(); // e.g. markdowntest.md
|
||||||
|
a.download = filename;
|
||||||
|
});
|
||||||
File diff suppressed because one or more lines are too long
@@ -1,31 +0,0 @@
|
|||||||
// Theme toggling script for PyPost
|
|
||||||
function toggleTheme() {
|
|
||||||
const darkStyles = document.getElementById('dark-styles');
|
|
||||||
const lightStyles = document.getElementById('light-styles');
|
|
||||||
const currentlyLight = !lightStyles.disabled;
|
|
||||||
|
|
||||||
document.body.classList.add('theme-transitioning');
|
|
||||||
|
|
||||||
if (currentlyLight) {
|
|
||||||
// Switch to dark
|
|
||||||
lightStyles.disabled = true;
|
|
||||||
darkStyles.disabled = false;
|
|
||||||
} else {
|
|
||||||
// Switch to light
|
|
||||||
lightStyles.disabled = false;
|
|
||||||
darkStyles.disabled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
document.body.classList.remove('theme-transitioning');
|
|
||||||
}, 400);
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
const darkStyles = document.getElementById('dark-styles');
|
|
||||||
const lightStyles = document.getElementById('light-styles');
|
|
||||||
|
|
||||||
// Always start in light mode
|
|
||||||
lightStyles.disabled = false;
|
|
||||||
darkStyles.disabled = true;
|
|
||||||
});
|
|
||||||
46
js/search.js
Normal file
46
js/search.js
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
function search_ul_items() {
|
||||||
|
const query = document.getElementById('searchbox').value.toLowerCase();
|
||||||
|
const ul = document.querySelector('ul'); // only one UL
|
||||||
|
if (!ul) return;
|
||||||
|
|
||||||
|
const items = ul.querySelectorAll('li');
|
||||||
|
items.forEach(li => {
|
||||||
|
if (li.textContent.toLowerCase().includes(query)) {
|
||||||
|
li.style.display = 'list-item';
|
||||||
|
} else {
|
||||||
|
li.style.display = 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create search box and insert before the available pages paragraph
|
||||||
|
window.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const searchDiv = document.createElement('div');
|
||||||
|
searchDiv.style.marginBottom = '16px';
|
||||||
|
searchDiv.style.paddingLeft = '19px';
|
||||||
|
searchDiv.innerHTML = `
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="searchbox"
|
||||||
|
placeholder="Search pages..."
|
||||||
|
style="
|
||||||
|
padding: 6px 6px 6px 28px;
|
||||||
|
font-size: 1em;
|
||||||
|
width: 220px;
|
||||||
|
background: url('../../css/icons/search.webp') no-repeat 6px center;
|
||||||
|
background-size: 20px 20px;
|
||||||
|
"
|
||||||
|
title="Search for pages"
|
||||||
|
/>
|
||||||
|
`;
|
||||||
|
const available = document.getElementById('available');
|
||||||
|
available.parentNode.insertBefore(searchDiv, available);
|
||||||
|
|
||||||
|
const searchbox = document.getElementById('searchbox');
|
||||||
|
if (searchbox) {
|
||||||
|
searchbox.title = "Search for pages";
|
||||||
|
searchbox.style.fontStyle = "bold";
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('searchbox').addEventListener('input', search_ul_items);
|
||||||
|
});
|
||||||
65
webserver.py
65
webserver.py
@@ -6,6 +6,7 @@ from http.server import BaseHTTPRequestHandler, HTTPServer
|
|||||||
import mimetypes
|
import mimetypes
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
from jsmin import jsmin # pip install jsmin
|
from jsmin import jsmin # pip install jsmin
|
||||||
|
import time
|
||||||
|
|
||||||
from log.Logger import *
|
from log.Logger import *
|
||||||
logger = Logger()
|
logger = Logger()
|
||||||
@@ -13,6 +14,7 @@ import PyPost
|
|||||||
|
|
||||||
PROJECT_ROOT = os.path.dirname(os.path.abspath(__file__))
|
PROJECT_ROOT = os.path.dirname(os.path.abspath(__file__))
|
||||||
HTML_DIR = os.path.join(PROJECT_ROOT, "html")
|
HTML_DIR = os.path.join(PROJECT_ROOT, "html")
|
||||||
|
MARKDOWN_DIR = os.path.join(PROJECT_ROOT, "markdown")
|
||||||
BASE_FILE = os.path.join(HTML_DIR, "base", "index.html")
|
BASE_FILE = os.path.join(HTML_DIR, "base", "index.html")
|
||||||
|
|
||||||
def get_html_files(directory=HTML_DIR):
|
def get_html_files(directory=HTML_DIR):
|
||||||
@@ -23,6 +25,18 @@ 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()
|
||||||
@@ -63,6 +77,8 @@ def index_footer():
|
|||||||
class MyHandler(BaseHTTPRequestHandler):
|
class MyHandler(BaseHTTPRequestHandler):
|
||||||
def do_GET(self):
|
def do_GET(self):
|
||||||
req_path = self.path.lstrip("/")
|
req_path = self.path.lstrip("/")
|
||||||
|
|
||||||
|
# Handle root/index
|
||||||
if req_path == "" or req_path == "index.html":
|
if req_path == "" or req_path == "index.html":
|
||||||
content = build_index_page()
|
content = build_index_page()
|
||||||
self.send_response(200)
|
self.send_response(200)
|
||||||
@@ -71,6 +87,55 @@ class MyHandler(BaseHTTPRequestHandler):
|
|||||||
self.wfile.write(content.encode("utf-8"))
|
self.wfile.write(content.encode("utf-8"))
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Handle markdown file downloads
|
||||||
|
if req_path.startswith("markdown/"):
|
||||||
|
markdown_filename = req_path[9:] # Remove "markdown/" prefix
|
||||||
|
|
||||||
|
# Security check: only allow .md files and prevent directory traversal
|
||||||
|
if not markdown_filename.endswith(".md") or ".." in markdown_filename or "/" in markdown_filename:
|
||||||
|
self.send_response(403)
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(b"403 - Forbidden: Only .md files allowed")
|
||||||
|
return
|
||||||
|
|
||||||
|
markdown_file_path = os.path.join(MARKDOWN_DIR, markdown_filename)
|
||||||
|
|
||||||
|
# Check if file exists and is within markdown directory
|
||||||
|
if not os.path.exists(markdown_file_path) or not os.path.isfile(markdown_file_path):
|
||||||
|
self.send_response(404)
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(b"404 - Markdown file not found")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Verify the resolved path is still within the markdown directory (extra security)
|
||||||
|
resolved_path = os.path.realpath(markdown_file_path)
|
||||||
|
resolved_markdown_dir = os.path.realpath(MARKDOWN_DIR)
|
||||||
|
if not resolved_path.startswith(resolved_markdown_dir):
|
||||||
|
self.send_response(403)
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(b"403 - Forbidden")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(markdown_file_path, "rb") as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header("Content-type", "text/markdown")
|
||||||
|
self.send_header("Content-Disposition", f'attachment; filename="{markdown_filename}"')
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(content)
|
||||||
|
logger.log_info(f"Served markdown file: {markdown_filename}")
|
||||||
|
return
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.log_error(f"Error serving markdown file {markdown_filename}: {e}")
|
||||||
|
self.send_response(500)
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(b"500 - Internal Server Error")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Handle other files (existing functionality)
|
||||||
file_path = os.path.normpath(os.path.join(PROJECT_ROOT, req_path))
|
file_path = os.path.normpath(os.path.join(PROJECT_ROOT, req_path))
|
||||||
if not file_path.startswith(PROJECT_ROOT):
|
if not file_path.startswith(PROJECT_ROOT):
|
||||||
self.send_response(403)
|
self.send_response(403)
|
||||||
|
|||||||
Reference in New Issue
Block a user