fixed some things. and did some UI changes
This commit is contained in:
46
PyPost.py
46
PyPost.py
@@ -122,7 +122,10 @@ def render_markdown(md_path: Path):
|
||||
<title>{title}</title>
|
||||
<link rel="stylesheet" href="../css/main.css">
|
||||
<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>
|
||||
<body style="display:flex; flex-direction:column; min-height:100%; margin:0;">
|
||||
<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 />
|
||||
<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 />
|
||||
<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>
|
||||
</body>
|
||||
</html>"""
|
||||
@@ -162,11 +167,11 @@ def render_markdown(md_path: Path):
|
||||
# Create parent directories if needed
|
||||
out_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
if obfuscate:
|
||||
out_path.write_text(obfuscated_html, encoding="utf-8")
|
||||
else:
|
||||
out_path.write_text(clean_html, encoding="utf-8")
|
||||
|
||||
# if obfuscate:
|
||||
# out_path.write_text(obfuscated_html, encoding="utf-8")
|
||||
# else:
|
||||
# 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}")
|
||||
|
||||
def remove_html(md_path: Path):
|
||||
@@ -184,7 +189,6 @@ def initial_scan(markdown_dir: Path):
|
||||
|
||||
|
||||
def build_rust_parser() -> bool:
|
||||
"""Attempt to build the Rust parser using cargo."""
|
||||
fastmd_dir = ROOT / "fastmd"
|
||||
|
||||
if not fastmd_dir.exists():
|
||||
@@ -269,34 +273,6 @@ if __name__ == "__main__":
|
||||
else:
|
||||
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)
|
||||
event_handler = Handler()
|
||||
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 |
@@ -7,14 +7,18 @@
|
||||
h1 { color: #333; }
|
||||
li { list-style: none; background: url("../../css/icons/item.webp") no-repeat left center; background-size: 15px 20px; padding-left: 25px; transition: font-size 0.5s cubic-bezier(0.075, 0.82, 0.165, 1); padding-bottom: 5px; }
|
||||
li:hover { font-size: larger; }
|
||||
#available { padding-left: 40px; margin-bottom: 0.1em;}
|
||||
ul { padding-left: 100px; margin-top: 0.2em;}
|
||||
#available { padding-left: 40px; margin-bottom: 0.1em;}
|
||||
ul { padding-left: 100px; margin-top: 0.2em;}
|
||||
#nojs { display: inline-block;color: red;transition: transform 0.7s cubic-bezier(0.215, 0.610, 0.355, 1); }
|
||||
/*#nojs:hover { transform: skewX(-12deg);}*/
|
||||
#nonenormalul { list-style: disc inside; margin: 1em 0; padding-left: 40px; background: none; }
|
||||
#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; }
|
||||
</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">
|
||||
</head>
|
||||
<body>
|
||||
@@ -32,77 +36,18 @@
|
||||
</ul>
|
||||
</p>
|
||||
</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">
|
||||
<img src="../../css/icons/available.webp" width="40" height="40" style="vertical-align: middle; display: inline; /*margin-right: 8px;*/ padding-right: 5px;" />
|
||||
Available pages:
|
||||
</p>
|
||||
<!-- CONTENT -->
|
||||
|
||||
|
||||
<!-- load scripts needed for indexer -->
|
||||
<script src="../../js/normal.js"></script>
|
||||
<script type="text/javascript">
|
||||
function search_ul_items() {
|
||||
const query = document.getElementById('searchbox').value.toLowerCase();
|
||||
const ul = document.querySelector('ul'); // only one UL
|
||||
if (!ul) return;
|
||||
<script src="../../js/search.js" defer></script>
|
||||
|
||||
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.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 style=" position: absolute; bottom: 0; width: 100%;">
|
||||
<p></p>
|
||||
</footer>
|
||||
</body>
|
||||
</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
|
||||
from functools import lru_cache
|
||||
from jsmin import jsmin # pip install jsmin
|
||||
import time
|
||||
|
||||
from log.Logger import *
|
||||
logger = Logger()
|
||||
@@ -13,6 +14,7 @@ import PyPost
|
||||
|
||||
PROJECT_ROOT = os.path.dirname(os.path.abspath(__file__))
|
||||
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")
|
||||
|
||||
def get_html_files(directory=HTML_DIR):
|
||||
@@ -23,6 +25,18 @@ def get_html_files(directory=HTML_DIR):
|
||||
html_files.append(entry)
|
||||
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():
|
||||
with open(BASE_FILE, "r", encoding="utf-8") as f:
|
||||
base_html = f.read()
|
||||
@@ -63,6 +77,8 @@ def index_footer():
|
||||
class MyHandler(BaseHTTPRequestHandler):
|
||||
def do_GET(self):
|
||||
req_path = self.path.lstrip("/")
|
||||
|
||||
# Handle root/index
|
||||
if req_path == "" or req_path == "index.html":
|
||||
content = build_index_page()
|
||||
self.send_response(200)
|
||||
@@ -71,6 +87,55 @@ class MyHandler(BaseHTTPRequestHandler):
|
||||
self.wfile.write(content.encode("utf-8"))
|
||||
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))
|
||||
if not file_path.startswith(PROJECT_ROOT):
|
||||
self.send_response(403)
|
||||
|
||||
Reference in New Issue
Block a user