fixed invalid post title breaking links
This commit is contained in:
@@ -115,16 +115,22 @@ pub async fn create_post(
|
||||
return Err(AppError::Unauthorized);
|
||||
}
|
||||
|
||||
validate_slug(&payload.slug)?;
|
||||
let slug = slug::slugify(&payload.slug);
|
||||
if slug.is_empty() {
|
||||
return Err(AppError::BadRequest(
|
||||
"Slug is empty after normalization (try ASCII letters/numbers)".to_string(),
|
||||
));
|
||||
}
|
||||
validate_slug(&slug)?;
|
||||
if let Some(ref old) = payload.old_slug {
|
||||
validate_slug(old)?;
|
||||
}
|
||||
|
||||
let posts_dir = state.data_dir.join("posts");
|
||||
let file_path = posts_dir.join(format!("{}.md", payload.slug));
|
||||
let file_path = posts_dir.join(format!("{}.md", slug));
|
||||
|
||||
if let Some(ref old_slug) = payload.old_slug {
|
||||
if old_slug != &payload.slug {
|
||||
if old_slug != &slug {
|
||||
let old_path = posts_dir.join(format!("{}.md", old_slug));
|
||||
if old_path.exists() {
|
||||
if file_path.exists() {
|
||||
@@ -134,11 +140,11 @@ pub async fn create_post(
|
||||
}
|
||||
let _guard = state.post_lock.lock().await;
|
||||
fs::rename(&old_path, &file_path).map_err(|e| {
|
||||
error!("Rename error from {} to {}: {}", old_slug, payload.slug, e);
|
||||
error!("Rename error from {} to {}: {}", old_slug, slug, e);
|
||||
AppError::Internal("Rename error".to_string(), Some(e.to_string()))
|
||||
})?;
|
||||
drop(_guard);
|
||||
info!("Renamed post from {} to {}", old_slug, payload.slug);
|
||||
info!("Renamed post from {} to {}", old_slug, slug);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -150,11 +156,11 @@ pub async fn create_post(
|
||||
draft: payload.draft,
|
||||
};
|
||||
let contents = serialize_post(&meta, &payload.content)?;
|
||||
write_post_atomic(&state, &payload.slug, &contents).await?;
|
||||
write_post_atomic(&state, &slug, &contents).await?;
|
||||
|
||||
info!("Post saved: {}", payload.slug);
|
||||
info!("Post saved: {}", slug);
|
||||
Ok(Json(PostDetail {
|
||||
slug: payload.slug,
|
||||
slug,
|
||||
date: meta.date,
|
||||
summary: meta.summary,
|
||||
tags: meta.tags,
|
||||
|
||||
@@ -16,7 +16,7 @@ const formattedDate = new Date(date).toLocaleDateString('en-US', {
|
||||
});
|
||||
---
|
||||
|
||||
<a href={`/posts/${slug}`} class="group block">
|
||||
<a href={`/posts/${encodeURIComponent(slug)}`} class="group block">
|
||||
<article class="glass p-5 md:p-8 transition-colors hover:bg-surface0/80 flex flex-col md:flex-row justify-between md:items-center gap-4 md:gap-6">
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex flex-wrap items-center gap-x-3 gap-y-1 mb-2 text-xs text-subtext0">
|
||||
|
||||
@@ -78,7 +78,7 @@ export default function Dashboard() {
|
||||
<p className="text-xs text-subtext0" style={{ color: 'var(--subtext0)' }}>/posts/{post.slug}</p>
|
||||
</div>
|
||||
<div className="flex gap-3">
|
||||
<a href={`/admin/editor?edit=${post.slug}`} className="p-2 text-blue hover:bg-blue/10 rounded transition-colors" title="Edit">
|
||||
<a href={`/admin/editor?edit=${encodeURIComponent(post.slug)}`} className="p-2 text-blue hover:bg-blue/10 rounded transition-colors" title="Edit">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/><path d="m15 5 4 4"/></svg>
|
||||
</a>
|
||||
<button onClick={() => handleDelete(post.slug)} className="p-2 text-red hover:bg-red/10 rounded transition-colors" title="Delete">
|
||||
|
||||
@@ -218,7 +218,7 @@ export default function Editor({ editSlug }: Props) {
|
||||
.map(t => t.trim())
|
||||
.filter(Boolean);
|
||||
try {
|
||||
await savePost({
|
||||
const saved = await savePost({
|
||||
slug,
|
||||
old_slug: originalSlug || null,
|
||||
date,
|
||||
@@ -228,7 +228,8 @@ export default function Editor({ editSlug }: Props) {
|
||||
content,
|
||||
});
|
||||
showAlertMsg('Post saved!', 'success');
|
||||
setOriginalSlug(slug);
|
||||
if (saved?.slug && saved.slug !== slug) setSlug(saved.slug);
|
||||
setOriginalSlug(saved?.slug ?? slug);
|
||||
} catch (e) {
|
||||
showAlertMsg(e instanceof ApiError ? `Error: ${e.message}` : 'Failed to connect to server.', 'error');
|
||||
}
|
||||
@@ -274,7 +275,7 @@ export default function Editor({ editSlug }: Props) {
|
||||
</button>
|
||||
{originalSlug && (
|
||||
<a
|
||||
href={`/posts/${originalSlug}`}
|
||||
href={`/posts/${encodeURIComponent(originalSlug)}`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="bg-blue text-crust font-bold py-3 px-8 rounded-lg hover:bg-sky transition-all transform hover:scale-105 whitespace-nowrap inline-flex items-center justify-center gap-2"
|
||||
|
||||
@@ -51,7 +51,7 @@ export const GET: APIRoute = async ({ site }) => {
|
||||
const items = posts
|
||||
.filter(p => !p.draft)
|
||||
.map(p => {
|
||||
const url = `${origin}/posts/${p.slug}`;
|
||||
const url = `${origin}/posts/${encodeURIComponent(p.slug)}`;
|
||||
const description = p.summary || p.excerpt || '';
|
||||
const pubDate = new Date(p.date).toUTCString();
|
||||
const categories = p.tags.map(t => ` <category>${escapeXml(t)}</category>`).join('\n');
|
||||
|
||||
@@ -24,7 +24,7 @@ let html = '';
|
||||
let error = '';
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_URL}/api/posts/${slug}`);
|
||||
const response = await fetch(`${API_URL}/api/posts/${encodeURIComponent(slug ?? '')}`);
|
||||
if (response.ok) {
|
||||
post = await response.json();
|
||||
html = renderMarkdown(post!.content);
|
||||
@@ -98,7 +98,7 @@ const isAdmin = Astro.cookies.get('admin_session')?.value === '1';
|
||||
</div>
|
||||
<a
|
||||
id="edit-link"
|
||||
href={`/admin/editor?edit=${post.slug}`}
|
||||
href={`/admin/editor?edit=${encodeURIComponent(post.slug)}`}
|
||||
class={`bg-surface0 hover:bg-surface1 text-blue px-3 py-1.5 md:px-4 md:py-2 rounded border border-surface1 transition-colors inline-flex items-center gap-2 text-sm md:text-base self-start ${isAdmin ? '' : 'hidden'}`}
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="md:w-4 md:h-4"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/><path d="m15 5 4 4"/></svg>
|
||||
|
||||
Reference in New Issue
Block a user