tags working now. rust parser works pretty good. next is cache
This commit is contained in:
@@ -17,3 +17,4 @@ regex = "1.10"
|
|||||||
clap = { version = "4.4", features = ["derive"] }
|
clap = { version = "4.4", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
html-escape = "0.2.13"
|
html-escape = "0.2.13"
|
||||||
|
once_cell = "1.18"
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ use std::time::Duration;
|
|||||||
use syntect::highlighting::{ThemeSet, Style};
|
use syntect::highlighting::{ThemeSet, Style};
|
||||||
use syntect::parsing::SyntaxSet;
|
use syntect::parsing::SyntaxSet;
|
||||||
use syntect::html::{highlighted_html_for_string, IncludeBackground};
|
use syntect::html::{highlighted_html_for_string, IncludeBackground};
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Clone, serde::Serialize)]
|
#[derive(Debug, Deserialize, Clone, serde::Serialize)]
|
||||||
pub struct PostFrontmatter {
|
pub struct PostFrontmatter {
|
||||||
@@ -77,6 +78,39 @@ fn process_anchor_links(content: &str) -> String {
|
|||||||
}).to_string()
|
}).to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper function to strip emojis from a string
|
||||||
|
fn strip_emojis(s: &str) -> String {
|
||||||
|
// Remove all characters in the Emoji Unicode ranges
|
||||||
|
// This is a simple approach and may not cover all emojis, but works for most cases
|
||||||
|
// Unicode emoji ranges: https://unicode.org/Public/emoji/15.0/emoji-data.txt
|
||||||
|
s.chars()
|
||||||
|
.filter(|c| {
|
||||||
|
let c = *c as u32;
|
||||||
|
// Basic Emoji ranges
|
||||||
|
!( (c >= 0x1F600 && c <= 0x1F64F) // Emoticons
|
||||||
|
|| (c >= 0x1F300 && c <= 0x1F5FF) // Misc Symbols and Pictographs
|
||||||
|
|| (c >= 0x1F680 && c <= 0x1F6FF) // Transport and Map
|
||||||
|
|| (c >= 0x2600 && c <= 0x26FF) // Misc symbols
|
||||||
|
|| (c >= 0x2700 && c <= 0x27BF) // Dingbats
|
||||||
|
|| (c >= 0x1F900 && c <= 0x1F9FF) // Supplemental Symbols and Pictographs
|
||||||
|
|| (c >= 0x1FA70 && c <= 0x1FAFF) // Symbols and Pictographs Extended-A
|
||||||
|
|| (c >= 0x1F1E6 && c <= 0x1F1FF) // Regional Indicator Symbols
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
static AMMONIA: Lazy<ammonia::Builder<'static>> = Lazy::new(|| {
|
||||||
|
let mut builder = ammonia::Builder::default();
|
||||||
|
builder.add_tag_attributes("h1", &["id"]);
|
||||||
|
builder.add_tag_attributes("h2", &["id"]);
|
||||||
|
builder.add_tag_attributes("h3", &["id"]);
|
||||||
|
builder.add_tag_attributes("h4", &["id"]);
|
||||||
|
builder.add_tag_attributes("h5", &["id"]);
|
||||||
|
builder.add_tag_attributes("h6", &["id"]);
|
||||||
|
builder
|
||||||
|
});
|
||||||
|
|
||||||
pub fn get_post_by_slug(slug: &str) -> Result<Post, Box<dyn std::error::Error>> {
|
pub fn get_post_by_slug(slug: &str) -> Result<Post, Box<dyn std::error::Error>> {
|
||||||
let posts_dir = get_posts_directory();
|
let posts_dir = get_posts_directory();
|
||||||
let file_path = posts_dir.join(format!("{}.md", slug));
|
let file_path = posts_dir.join(format!("{}.md", slug));
|
||||||
@@ -122,7 +156,9 @@ pub fn get_post_by_slug(slug: &str) -> Result<Post, Box<dyn std::error::Error>>
|
|||||||
},
|
},
|
||||||
Event::End(Tag::Heading(_, _, _)) => {
|
Event::End(Tag::Heading(_, _, _)) => {
|
||||||
in_heading = false;
|
in_heading = false;
|
||||||
let id = slugify(&heading_text);
|
// Strip emojis before slugifying for the id
|
||||||
|
let heading_no_emoji = strip_emojis(&heading_text);
|
||||||
|
let id = slugify(&heading_no_emoji);
|
||||||
events.push(Event::Html(CowStr::Boxed(format!("<h{lvl} id=\"{id}\">", lvl=heading_level, id=id).into_boxed_str())));
|
events.push(Event::Html(CowStr::Boxed(format!("<h{lvl} id=\"{id}\">", lvl=heading_level, id=id).into_boxed_str())));
|
||||||
events.push(Event::Text(CowStr::Boxed(heading_text.clone().into_boxed_str())));
|
events.push(Event::Text(CowStr::Boxed(heading_text.clone().into_boxed_str())));
|
||||||
events.push(Event::Html(CowStr::Boxed(format!("</h{lvl}>", lvl=heading_level).into_boxed_str())));
|
events.push(Event::Html(CowStr::Boxed(format!("</h{lvl}>", lvl=heading_level).into_boxed_str())));
|
||||||
@@ -164,7 +200,7 @@ pub fn get_post_by_slug(slug: &str) -> Result<Post, Box<dyn std::error::Error>>
|
|||||||
}
|
}
|
||||||
html::push_html(&mut html_output, events.into_iter());
|
html::push_html(&mut html_output, events.into_iter());
|
||||||
|
|
||||||
let sanitized_html = clean(&html_output);
|
let sanitized_html = AMMONIA.clean(&html_output).to_string();
|
||||||
|
|
||||||
Ok(Post {
|
Ok(Post {
|
||||||
slug: slug.to_string(),
|
slug: slug.to_string(),
|
||||||
|
|||||||
@@ -21,6 +21,16 @@ const parserStats = {
|
|||||||
lastRustError: '',
|
lastRustError: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Add a slugify function that matches Rust's slug::slugify
|
||||||
|
function slugify(text: string): string {
|
||||||
|
return text
|
||||||
|
.toLowerCase()
|
||||||
|
.normalize('NFKD')
|
||||||
|
.replace(/[\u0300-\u036F]/g, '') // Remove diacritics
|
||||||
|
.replace(/[^a-z0-9]+/g, '-')
|
||||||
|
.replace(/^-+|-+$/g, '');
|
||||||
|
}
|
||||||
|
|
||||||
export default function PostPage({ params }: { params: { slug: string[] } }) {
|
export default function PostPage({ params }: { params: { slug: string[] } }) {
|
||||||
const [post, setPost] = useState<Post | null>(null);
|
const [post, setPost] = useState<Post | null>(null);
|
||||||
// Modal state for zoomed image
|
// Modal state for zoomed image
|
||||||
@@ -315,35 +325,49 @@ export default function PostPage({ params }: { params: { slug: string[] } }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the element, but only consider visible ones
|
// Try to find the element by the raw ID first
|
||||||
const allElements = document.querySelectorAll(`#${id}`);
|
let allElements = document.querySelectorAll(`#${id}`);
|
||||||
let element: HTMLElement | null = null;
|
let element: HTMLElement | null = null;
|
||||||
|
|
||||||
// Check if we're on desktop or mobile
|
|
||||||
const isDesktop = window.innerWidth >= 640;
|
|
||||||
|
|
||||||
for (const el of Array.from(allElements)) {
|
for (const el of Array.from(allElements)) {
|
||||||
const htmlEl = el as HTMLElement;
|
const htmlEl = el as HTMLElement;
|
||||||
// Check if the element is visible (not hidden by CSS)
|
|
||||||
const rect = htmlEl.getBoundingClientRect();
|
const rect = htmlEl.getBoundingClientRect();
|
||||||
const isVisible = rect.width > 0 && rect.height > 0;
|
const isVisible = rect.width > 0 && rect.height > 0;
|
||||||
|
|
||||||
if (isVisible) {
|
if (isVisible) {
|
||||||
element = htmlEl;
|
element = htmlEl;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (element) {
|
if (element) {
|
||||||
console.log('Found target element:', element.textContent?.substring(0, 50));
|
console.log('Found target element (raw id):', element.textContent?.substring(0, 50));
|
||||||
scrollToElement(element);
|
scrollToElement(element);
|
||||||
} else if (retryCount < 5) {
|
return;
|
||||||
|
}
|
||||||
|
// If not found, try slugified version
|
||||||
|
const slugId = slugify(id);
|
||||||
|
if (slugId !== id) {
|
||||||
|
allElements = document.querySelectorAll(`#${slugId}`);
|
||||||
|
for (const el of Array.from(allElements)) {
|
||||||
|
const htmlEl = el as HTMLElement;
|
||||||
|
const rect = htmlEl.getBoundingClientRect();
|
||||||
|
const isVisible = rect.width > 0 && rect.height > 0;
|
||||||
|
if (isVisible) {
|
||||||
|
element = htmlEl;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (element) {
|
||||||
|
console.log('Found target element (slugified id):', element.textContent?.substring(0, 50));
|
||||||
|
scrollToElement(element);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (retryCount < 5) {
|
||||||
console.log(`Element not found for anchor: ${id}, retrying... (${retryCount + 1}/5)`);
|
console.log(`Element not found for anchor: ${id}, retrying... (${retryCount + 1}/5)`);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
findAndScrollToElement(id, retryCount + 1);
|
findAndScrollToElement(id, retryCount + 1);
|
||||||
}, 100);
|
}, 100);
|
||||||
} else {
|
} else {
|
||||||
console.warn(`Element with id "${id}" not found after retries`);
|
console.warn(`Element with id "${id}" (or slugified "${slugId}") not found after retries`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user