feat(migration): complete WordPress migration and UX refinements

- Migrate historical posts, localized assets, and 103 comments
- Colocate media into posts/<slug>/images/ and remove wp-content
- Add native bilingual support for ai-can-change post (English default, /zh variant)
- Support clean taxonomy routing (/category/:category and /tag/:tag)
- Dynamically populate categories and tags in sitemap.xml
- Fix header scroll padding/margin offsets for anchor and reference links
- Constrain image and media widths to prevent content overflow
- Redesign homepage recent posts to 4 compact editorial cards
- Restore valid binary favicon.ico and high-res icon assets
- Remove placeholder content and sample test comments
This commit is contained in:
2026-09-16 23:09:09 -07:00
parent 8c0b04db0e
commit f52b3fe9e4
87 changed files with 3417 additions and 190 deletions
+11 -2
View File
@@ -16,7 +16,7 @@ function renderPost(req, res, next, slug, lang) {
res.locals.seo = {
title: `${post.title} - ${res.locals.site.title}`,
description: post.excerpt || res.locals.site.tagline,
canonicalUrl: `${res.locals.site.url}/${post.slug}${post.lang !== 'en' && !post.isDefault ? `/${post.lang}` : ''}`,
canonicalUrl: `${res.locals.site.url}/${post.slug}${post.isDefault ? '' : `/${post.lang}`}`,
ogImage: post.cover ? (post.cover.startsWith('http') ? post.cover : `${res.locals.site.url}${post.cover}`) : res.locals.seo.ogImage,
type: 'article',
publishedTime: post.date.toISOString(),
@@ -36,13 +36,22 @@ function renderPost(req, res, next, slug, lang) {
// Route for bilingual variant: /<post-slug>/<lang> (e.g. /my-post/zh)
router.get('/:slug/:lang([a-zA-Z]{2,5})', (req, res, next) => {
const post = contentService.getPost(req.params.slug);
if (post && post.defaultLang === req.params.lang) {
return res.redirect(301, `/${req.params.slug}`);
}
renderPost(req, res, next, req.params.slug, req.params.lang);
});
// Legacy redirects for multilingual posts
router.get('/ai-can-change-coding-for-kids-en', (req, res) => {
res.redirect(301, '/ai-can-change-coding-for-kids');
});
// Route for default language: /<post-slug>
router.get('/:slug', (req, res, next) => {
// Exclude reserved top-level prefixes if any
const reserved = ['blogs', 'search', 'quick-links', 'feed', 'rss.xml', 'sitemap.xml', 'robots.txt', 'api', 'plugins', 'public', 'posts-assets', 'guanli', 'admin'];
const reserved = ['blogs', 'category', 'tag', 'tags', 'search', 'quick-links', 'feed', 'rss.xml', 'sitemap.xml', 'robots.txt', 'api', 'plugins', 'public', 'posts-assets', 'guanli', 'admin'];
if (reserved.includes(req.params.slug)) {
return next();
}