nice visual improvments
This commit is contained in:
89
PyPost.py
89
PyPost.py
@@ -8,6 +8,7 @@ from jinja2 import Environment, FileSystemLoader
|
||||
import base64
|
||||
import random
|
||||
import time
|
||||
import yaml
|
||||
|
||||
import marko
|
||||
from marko.ext.gfm import GFM
|
||||
@@ -34,7 +35,7 @@ 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}"
|
||||
|
||||
# Create Python Markdown parser with table support (fallback for small files)
|
||||
# Python Markdown parser with table support
|
||||
markdown_parser = marko.Markdown(extensions=[GFM])
|
||||
|
||||
# Threshold for switching to Rust parser (number of lines)
|
||||
@@ -45,8 +46,22 @@ Logger = Logger()
|
||||
# Global obfuscate flag, default 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:
|
||||
"""Count the number of lines in a file."""
|
||||
try:
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
return sum(1 for _ in f)
|
||||
@@ -55,24 +70,18 @@ def count_lines_in_file(file_path: Path) -> int:
|
||||
return 0
|
||||
|
||||
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():
|
||||
return False
|
||||
|
||||
line_count = count_lines_in_file(md_path)
|
||||
use_rust = line_count > RUST_PARSER_THRESHOLD
|
||||
|
||||
if use_rust:
|
||||
Logger.log_rust_usage(f"Using Rust parser for {md_path} ({line_count} lines)")
|
||||
else:
|
||||
Logger.log_debug(f"Using Python parser for {md_path} ({line_count} lines)")
|
||||
|
||||
return use_rust
|
||||
|
||||
def parse_markdown_with_rust(md_path: Path) -> str:
|
||||
"""Parse markdown using the Rust parser."""
|
||||
try:
|
||||
# Run the Rust parser
|
||||
result = subprocess.run(
|
||||
[str(RUST_PARSER_PATH), str(md_path)],
|
||||
capture_output=True,
|
||||
@@ -90,31 +99,27 @@ def parse_markdown_with_rust(md_path: Path) -> str:
|
||||
raise
|
||||
|
||||
def render_markdown(md_path: Path):
|
||||
"""Render a single markdown file to an obfuscated HTML file."""
|
||||
try:
|
||||
text = md_path.read_text(encoding="utf-8")
|
||||
except Exception as e:
|
||||
Logger.log_error(f"Could not read {md_path}: {e}")
|
||||
return
|
||||
"""Render a single markdown file to HTML, stripping YAML front matter."""
|
||||
yaml_text, markdown_text = split_yaml_front_matter(md_path)
|
||||
|
||||
# Decide which parser to use based on file size
|
||||
# Choose parser
|
||||
if should_use_rust_parser(md_path):
|
||||
try:
|
||||
html_body = parse_markdown_with_rust(md_path)
|
||||
except Exception as 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:
|
||||
html_body = markdown_parser.convert(text)
|
||||
html_body = markdown_parser.convert(markdown_text)
|
||||
|
||||
# Extract title from filename or first H1
|
||||
title = md_path.stem
|
||||
for line in text.splitlines():
|
||||
for line in markdown_text.splitlines():
|
||||
if line.startswith("# "):
|
||||
title = line[2:].strip()
|
||||
break
|
||||
|
||||
# Call pre_template hook properly
|
||||
# Plugin pre_template hook
|
||||
Logger.log_debug(f"Calling pre_template hook for {md_path}")
|
||||
modified = plugin_manager.run_hook("pre_template", str(md_path), html_body)
|
||||
if modified is not None:
|
||||
@@ -123,13 +128,11 @@ def render_markdown(md_path: Path):
|
||||
else:
|
||||
Logger.log_debug("pre_template hook returned None")
|
||||
|
||||
# Create clean HTML structure
|
||||
# Pick two different hashes from hash_list
|
||||
# Jinja template
|
||||
env = Environment(loader=FileSystemLoader("html/base"))
|
||||
template = env.get_template("template.html")
|
||||
hash1, hash2 = random.sample(hash_list, 2)
|
||||
|
||||
# Load these variable for Jinja to use.
|
||||
clean_jinja_html = template.render(
|
||||
title=title,
|
||||
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()),
|
||||
)
|
||||
|
||||
# Plugin post_render hook
|
||||
post_mod = plugin_manager.run_hook("post_render", str(md_path), clean_jinja_html)
|
||||
if post_mod is not None:
|
||||
clean_jinja_html = post_mod
|
||||
|
||||
# Ensure html directory exists
|
||||
# Write output
|
||||
HTML_DIR.mkdir(exist_ok=True)
|
||||
|
||||
# Maintain relative directory structure in html/
|
||||
relative_path = md_path.relative_to(MARKDOWN_DIR)
|
||||
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.write_text(clean_jinja_html, encoding="utf-8")
|
||||
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):
|
||||
relative_path = md_path.relative_to(MARKDOWN_DIR)
|
||||
out_path = HTML_DIR / relative_path.with_suffix(".html")
|
||||
@@ -170,20 +186,15 @@ def initial_scan(markdown_dir: Path):
|
||||
|
||||
def build_rust_parser() -> bool:
|
||||
fastmd_dir = ROOT / "fastmd"
|
||||
|
||||
if not fastmd_dir.exists():
|
||||
Logger.log_error(f"fastmd directory not found at {fastmd_dir}")
|
||||
return False
|
||||
|
||||
cargo_toml = fastmd_dir / "Cargo.toml"
|
||||
if not cargo_toml.exists():
|
||||
Logger.log_error(f"Cargo.toml not found at {cargo_toml}")
|
||||
return False
|
||||
|
||||
Logger.log_info("Attempting to build Rust parser with 'cargo build --release'...")
|
||||
|
||||
try:
|
||||
# Run cargo build --release in the fastmd directory
|
||||
result = subprocess.run(
|
||||
["cargo", "build", "--release"],
|
||||
cwd=str(fastmd_dir),
|
||||
@@ -191,11 +202,9 @@ def build_rust_parser() -> bool:
|
||||
text=True,
|
||||
check=True
|
||||
)
|
||||
|
||||
Logger.log_info("Rust parser built successfully!")
|
||||
Logger.log_debug(f"Build output: {result.stdout}")
|
||||
return True
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
Logger.log_error(f"Failed to build Rust parser: {e}")
|
||||
Logger.log_error(f"Build stderr: {e.stderr}")
|
||||
@@ -208,7 +217,7 @@ def build_rust_parser() -> bool:
|
||||
return False
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Check for markdown directory
|
||||
# Handle alternative markdown folder
|
||||
if not MARKDOWN_DIR.exists():
|
||||
alt_root = ROOT / "PyPost"
|
||||
if alt_root.exists() and alt_root.is_dir():
|
||||
@@ -216,7 +225,6 @@ if __name__ == "__main__":
|
||||
ROOT = alt_root
|
||||
MARKDOWN_DIR = ROOT / "markdown"
|
||||
HTML_DIR = ROOT / "html"
|
||||
# Update Rust parser path for new root
|
||||
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}"
|
||||
@@ -225,17 +233,13 @@ if __name__ == "__main__":
|
||||
Logger.log_warning("Please create a 'markdown' directory or use a 'PyPost' directory with one inside it.")
|
||||
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():
|
||||
Logger.log_warning(f"Rust parser not found at {RUST_PARSER_PATH}")
|
||||
|
||||
# Try to build the Rust parser
|
||||
if build_rust_parser():
|
||||
# Update path after successful build
|
||||
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}"
|
||||
|
||||
if RUST_PARSER_PATH.exists():
|
||||
Logger.log_info(f"Rust parser built and found at: {RUST_PARSER_PATH}")
|
||||
else:
|
||||
@@ -247,7 +251,6 @@ if __name__ == "__main__":
|
||||
else:
|
||||
Logger.log_info(f"Rust parser found at: {RUST_PARSER_PATH}")
|
||||
|
||||
# Log parser strategy
|
||||
if RUST_PARSER_PATH.exists():
|
||||
Logger.log_info(f"Will use Rust parser for files with more than {RUST_PARSER_THRESHOLD} lines")
|
||||
else:
|
||||
|
||||
191
css/indexer.css
191
css/indexer.css
@@ -1,16 +1,57 @@
|
||||
/* RESET & BASE LAYOUT */
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
padding: 0;
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: #fff;
|
||||
color: #0f1111;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.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;
|
||||
font-size: clamp(1.5rem, 5vw, 2rem);
|
||||
@@ -101,12 +142,13 @@ li a {
|
||||
font-size: clamp(0.95rem, 2.3vw, 1.05rem);
|
||||
}
|
||||
|
||||
/* BUTTONS */
|
||||
button {
|
||||
margin: 5px 5px 5px 0;
|
||||
background-image: linear-gradient(#f7f8fa, #e7e9ec);
|
||||
border: 1px solid #adb1b8;
|
||||
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;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
@@ -140,7 +182,8 @@ button:focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
button:disabled, button[disabled] {
|
||||
button:disabled,
|
||||
button[disabled] {
|
||||
color: #999;
|
||||
border-color: #ccc;
|
||||
cursor: not-allowed;
|
||||
@@ -167,6 +210,71 @@ input#searchbox:focus {
|
||||
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 */
|
||||
body.dark-mode {
|
||||
background-color: #1e1e1e;
|
||||
@@ -193,7 +301,7 @@ body.dark-mode button {
|
||||
background-image: linear-gradient(#3a3a3a, #2a2a2a);
|
||||
color: #e0e0e0;
|
||||
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 {
|
||||
@@ -219,9 +327,46 @@ body.dark-mode a:visited {
|
||||
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 */
|
||||
@media (max-width: 768px) {
|
||||
body {
|
||||
main {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
@@ -240,8 +385,8 @@ body.dark-mode a:visited {
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
body {
|
||||
@media (max-width: 1400px) {
|
||||
main {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
@@ -252,6 +397,36 @@ body.dark-mode a:visited {
|
||||
#available {
|
||||
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 */
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<main style="flex:1;">
|
||||
<noscript>
|
||||
<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;" />
|
||||
@@ -37,6 +38,7 @@
|
||||
|
||||
<!-- CONTENT -->
|
||||
|
||||
|
||||
<!-- load scripts needed for indexer -->
|
||||
<script src="../../js/normal.js"></script>
|
||||
<script src="../../js/search.js" defer></script>
|
||||
|
||||
108
js/search.js
108
js/search.js
@@ -1,23 +1,24 @@
|
||||
function paginate_ul_items(page = 1) {
|
||||
const ul = document.querySelector('ul');
|
||||
if (!ul) return;
|
||||
function paginate_articles(page = 1) {
|
||||
const articles = Array.from(document.querySelectorAll('article'));
|
||||
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
|
||||
const filteredItems = items.filter(li => li.textContent.toLowerCase().includes(query));
|
||||
// Filter articles based on search
|
||||
const filteredArticles = articles.filter(article =>
|
||||
article.textContent.toLowerCase().includes(query)
|
||||
);
|
||||
|
||||
const itemsPerPage = 10;
|
||||
const totalPages = Math.ceil(filteredItems.length / itemsPerPage);
|
||||
const articlesPerPage = 5; // reduced to 5
|
||||
const totalPages = Math.ceil(filteredArticles.length / articlesPerPage);
|
||||
|
||||
// Hide all items first
|
||||
items.forEach(li => li.style.display = 'none');
|
||||
// Hide all articles first
|
||||
articles.forEach(article => article.style.display = 'none');
|
||||
|
||||
// Show only items for the current page
|
||||
const start = (page - 1) * itemsPerPage;
|
||||
const end = start + itemsPerPage;
|
||||
filteredItems.slice(start, end).forEach(li => li.style.display = 'list-item');
|
||||
// Show only articles for the current page
|
||||
const start = (page - 1) * articlesPerPage;
|
||||
const end = start + articlesPerPage;
|
||||
filteredArticles.slice(start, end).forEach(article => article.style.display = 'block');
|
||||
|
||||
// Render pagination controls
|
||||
renderPaginationControls(totalPages, page);
|
||||
@@ -26,6 +27,9 @@ function paginate_ul_items(page = 1) {
|
||||
function renderPaginationControls(totalPages, currentPage) {
|
||||
let paginationDiv = document.getElementById('pagination-controls');
|
||||
let separator = document.getElementById('vertical-seperator');
|
||||
const searchbox = document.getElementById('searchbox');
|
||||
if (!searchbox) return;
|
||||
|
||||
if (!paginationDiv) {
|
||||
paginationDiv = document.createElement('div');
|
||||
paginationDiv.id = 'pagination-controls';
|
||||
@@ -33,48 +37,43 @@ function renderPaginationControls(totalPages, currentPage) {
|
||||
paginationDiv.style.marginLeft = '16px';
|
||||
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');
|
||||
wrapper.style.display = 'flex';
|
||||
wrapper.style.alignItems = 'center';
|
||||
wrapper.style.gap = '12px'; // space around separator
|
||||
wrapper.style.gap = '12px';
|
||||
|
||||
// Move searchbox into wrapper
|
||||
searchDiv.appendChild(wrapper);
|
||||
wrapper.appendChild(document.getElementById('searchbox'));
|
||||
wrapper.appendChild(searchbox);
|
||||
|
||||
// Separator
|
||||
separator = document.createElement('div');
|
||||
separator.id = "vertical-seperator"
|
||||
separator.id = "vertical-seperator";
|
||||
separator.style.width = '1px';
|
||||
separator.style.height = '28px';
|
||||
separator.style.marginRight = "-15px";
|
||||
separator.style.backgroundColor = '#ccc';
|
||||
wrapper.appendChild(separator);
|
||||
|
||||
// Add pagination after separator
|
||||
wrapper.appendChild(paginationDiv);
|
||||
}
|
||||
|
||||
paginationDiv.innerHTML = '';
|
||||
if (separator) {
|
||||
separator.style.display = totalPages <= 1 ? 'none' : 'block';
|
||||
}
|
||||
if (separator) separator.style.display = totalPages <= 1 ? 'none' : 'block';
|
||||
|
||||
if (totalPages <= 1) {
|
||||
paginationDiv.style.display = 'none';
|
||||
|
||||
return;
|
||||
} else {
|
||||
paginationDiv.style.display = 'inline-block';
|
||||
paginationDiv.style.display = 'inline-flex';
|
||||
paginationDiv.style.alignItems = 'center';
|
||||
paginationDiv.style.gap = '6px';
|
||||
}
|
||||
|
||||
// Previous button
|
||||
const prevBtn = document.createElement('button');
|
||||
prevBtn.textContent = '<';
|
||||
prevBtn.disabled = currentPage === 1;
|
||||
prevBtn.onclick = () => paginate_ul_items(currentPage - 1);
|
||||
prevBtn.onclick = () => paginate_articles(currentPage - 1);
|
||||
paginationDiv.appendChild(prevBtn);
|
||||
|
||||
// Page numbers
|
||||
@@ -82,7 +81,8 @@ function renderPaginationControls(totalPages, currentPage) {
|
||||
const btn = document.createElement('button');
|
||||
btn.textContent = i;
|
||||
btn.disabled = i === currentPage;
|
||||
btn.onclick = () => paginate_ul_items(i);
|
||||
btn.classList.add('page-number');
|
||||
btn.onclick = () => paginate_articles(i);
|
||||
paginationDiv.appendChild(btn);
|
||||
}
|
||||
|
||||
@@ -90,17 +90,28 @@ function renderPaginationControls(totalPages, currentPage) {
|
||||
const nextBtn = document.createElement('button');
|
||||
nextBtn.textContent = '>';
|
||||
nextBtn.disabled = currentPage === totalPages;
|
||||
nextBtn.onclick = () => paginate_ul_items(currentPage + 1);
|
||||
nextBtn.onclick = () => paginate_articles(currentPage + 1);
|
||||
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
|
||||
function search_ul_items() {
|
||||
paginate_ul_items(1);
|
||||
// Search input triggers pagination
|
||||
function search_articles() {
|
||||
paginate_articles(1);
|
||||
}
|
||||
|
||||
// Create search box and insert before the available pages paragraph
|
||||
// Setup search box & initial pagination
|
||||
window.addEventListener('DOMContentLoaded', function() {
|
||||
const searchDiv = document.createElement('div');
|
||||
searchDiv.style.marginBottom = '16px';
|
||||
@@ -109,7 +120,7 @@ window.addEventListener('DOMContentLoaded', function() {
|
||||
<input
|
||||
type="text"
|
||||
id="searchbox"
|
||||
placeholder="Search pages..."
|
||||
placeholder="Search articles..."
|
||||
style="
|
||||
padding: 6px 6px 6px 28px;
|
||||
font-size: 1em;
|
||||
@@ -117,20 +128,27 @@ window.addEventListener('DOMContentLoaded', function() {
|
||||
background: url('../../css/icons/search.webp') no-repeat 6px center;
|
||||
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');
|
||||
if (searchbox) {
|
||||
searchbox.title = "Search for pages";
|
||||
searchbox.style.fontStyle = "bold";
|
||||
searchbox.addEventListener('input', search_articles);
|
||||
}
|
||||
|
||||
document.getElementById('searchbox').addEventListener('input', search_ul_items);
|
||||
|
||||
// Initial pagination setup
|
||||
paginate_ul_items(1);
|
||||
paginate_articles(1);
|
||||
});
|
||||
@@ -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);
|
||||
|
||||
BIN
requirements.txt
BIN
requirements.txt
Binary file not shown.
62
webserver.py
62
webserver.py
@@ -9,6 +9,8 @@ from pathlib import Path
|
||||
|
||||
from log.Logger import *
|
||||
from lua import plugin_manager
|
||||
from PyPost import extract_summary
|
||||
|
||||
logger = Logger()
|
||||
plugin_manager = plugin_manager.PluginManager()
|
||||
plugin_manager.load_all() # load all plugins
|
||||
@@ -27,28 +29,45 @@ 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()
|
||||
html_files = get_html_files(HTML_DIR)
|
||||
links = "\n".join(f'<li><a href="/html/{fname}">{fname}</a></li>' for fname in html_files)
|
||||
content = f"<ul>{links}</ul>"
|
||||
# Insert footer after content
|
||||
full_content = content + index_footer()
|
||||
|
||||
articles = []
|
||||
for md_path in Path(MARKDOWN_DIR).rglob("*.md"):
|
||||
try:
|
||||
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)
|
||||
|
||||
|
||||
|
||||
import base64
|
||||
import random
|
||||
|
||||
@@ -66,12 +85,8 @@ def index_footer():
|
||||
<!-- Footer styling doesnt need to work with
|
||||
flex, or anything else, because pagnation.
|
||||
-->
|
||||
<footer style="
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
">
|
||||
<hr style="border: 1px solid #ccc;" />
|
||||
<div class="footer">
|
||||
<footer>
|
||||
<p>
|
||||
<!-- Server Time -->
|
||||
<img src="../css/icons/date.webp" width="16" height="16" alt="date" loading="lazy" style="vertical-align: middle;" />
|
||||
@@ -87,7 +102,8 @@ def index_footer():
|
||||
<img src="../css/icons/script.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="{tor_link}">View Tor Site</a>
|
||||
</p>
|
||||
</footer>
|
||||
</footer>
|
||||
</div>
|
||||
"""
|
||||
|
||||
class MyHandler(BaseHTTPRequestHandler):
|
||||
|
||||
Reference in New Issue
Block a user