Compare commits
2 Commits
ca9e083399
...
b8a8cc474b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b8a8cc474b | ||
|
|
78c468a7d2 |
@@ -2,6 +2,7 @@ import { useEffect, useRef } from 'react';
|
|||||||
import type { WalineInstance, WalineInitOptions } from '@waline/client';
|
import type { WalineInstance, WalineInitOptions } from '@waline/client';
|
||||||
import { init } from '@waline/client';
|
import { init } from '@waline/client';
|
||||||
import '@waline/client/style';
|
import '@waline/client/style';
|
||||||
|
import '@/styles/waline-custom.css';
|
||||||
|
|
||||||
import type { Lang } from '@/types/i18n';
|
import type { Lang } from '@/types/i18n';
|
||||||
|
|
||||||
@@ -28,6 +29,8 @@ export default function Comments({ lang = 'en', ...props }: CommentsProps) {
|
|||||||
serverURL: import.meta.env.PUBLIC_WALINE_SERVER_URL,
|
serverURL: import.meta.env.PUBLIC_WALINE_SERVER_URL,
|
||||||
lang: walineLang,
|
lang: walineLang,
|
||||||
dark: getTheme(),
|
dark: getTheme(),
|
||||||
|
pageview: true,
|
||||||
|
comment: true,
|
||||||
path,
|
path,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
342
src/styles/waline-custom.css
Normal file
342
src/styles/waline-custom.css
Normal file
@@ -0,0 +1,342 @@
|
|||||||
|
/**
|
||||||
|
* Waline Custom Styles
|
||||||
|
* 适配网站主题风格 - 紫色渐变主题
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* ========== 基础变量覆盖 ========== */
|
||||||
|
:root {
|
||||||
|
/* 主题色 - 使用网站的紫色渐变accent */
|
||||||
|
--waline-theme-color: #8B5CF6;
|
||||||
|
--waline-active-color: #EC4899;
|
||||||
|
|
||||||
|
/* 背景色 */
|
||||||
|
--waline-bg-color: oklch(1 0 0);
|
||||||
|
--waline-bg-color-light: oklch(0.97 0 0);
|
||||||
|
--waline-bg-color-hover: oklch(0.93 0 0);
|
||||||
|
|
||||||
|
/* 文字颜色 */
|
||||||
|
--waline-color: oklch(0.4 0 0);
|
||||||
|
--waline-light-grey: oklch(0.65 0 0);
|
||||||
|
--waline-dark-grey: oklch(0.3 0 0);
|
||||||
|
|
||||||
|
/* 边框颜色 */
|
||||||
|
--waline-border-color: oklch(0.87 0 0);
|
||||||
|
|
||||||
|
/* 其他颜色 */
|
||||||
|
--waline-badge-color: #8B5CF6;
|
||||||
|
--waline-info-bg-color: oklch(0.97 0 0);
|
||||||
|
--waline-info-color: oklch(0.55 0 0);
|
||||||
|
--waline-bq-color: oklch(0.93 0 0);
|
||||||
|
|
||||||
|
/* 代码块背景 */
|
||||||
|
--waline-code-bg-color: oklch(0.25 0 0);
|
||||||
|
|
||||||
|
/* 圆角 - 与网站一致 */
|
||||||
|
--waline-border-radius: var(--radius, 0.75rem);
|
||||||
|
|
||||||
|
/* 阴影 */
|
||||||
|
--waline-box-shadow: var(--shadow-md, 0 4px 12px rgba(0, 0, 0, 0.08));
|
||||||
|
|
||||||
|
/* 头像圆角 - 使用网站风格 */
|
||||||
|
--waline-avatar-radius: var(--radius-md, 0.5rem);
|
||||||
|
|
||||||
|
/* 按钮样式 */
|
||||||
|
--waline-btn-radius: var(--radius, 0.75rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========== 暗黑模式 ========== */
|
||||||
|
.dark {
|
||||||
|
--waline-bg-color: oklch(0.205 0 0);
|
||||||
|
--waline-bg-color-light: oklch(0.269 0 0);
|
||||||
|
--waline-bg-color-hover: oklch(0.32 0 0);
|
||||||
|
|
||||||
|
--waline-color: oklch(0.9 0 0);
|
||||||
|
--waline-light-grey: oklch(0.6 0 0);
|
||||||
|
--waline-dark-grey: oklch(0.75 0 0);
|
||||||
|
|
||||||
|
--waline-border-color: oklch(1 0 0 / 15%);
|
||||||
|
|
||||||
|
--waline-info-bg-color: oklch(0.269 0 0);
|
||||||
|
--waline-info-color: oklch(0.6 0 0);
|
||||||
|
--waline-bq-color: oklch(0.269 0 0);
|
||||||
|
|
||||||
|
--waline-code-bg-color: oklch(0.12 0 0);
|
||||||
|
|
||||||
|
--waline-box-shadow: var(--shadow-md, 0 4px 12px rgba(0, 0, 0, 0.25));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========== 评论面板样式 ========== */
|
||||||
|
.wl-panel {
|
||||||
|
border-radius: var(--waline-border-radius) !important;
|
||||||
|
border: 1px solid var(--waline-border-color) !important;
|
||||||
|
box-shadow: var(--waline-box-shadow) !important;
|
||||||
|
background: var(--waline-bg-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========== 输入框样式 ========== */
|
||||||
|
.wl-header {
|
||||||
|
border-bottom-color: var(--waline-border-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wl-header label {
|
||||||
|
color: var(--waline-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wl-header input {
|
||||||
|
background: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wl-editor {
|
||||||
|
background: var(--waline-bg-color-light) !important;
|
||||||
|
border-radius: var(--waline-btn-radius) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wl-editor:focus {
|
||||||
|
background: var(--waline-bg-color-hover) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========== 按钮样式 ========== */
|
||||||
|
.wl-btn {
|
||||||
|
border-radius: var(--waline-btn-radius) !important;
|
||||||
|
font-size: 0.875em !important;
|
||||||
|
transition: all 0.2s ease !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wl-btn:hover {
|
||||||
|
border-color: var(--waline-theme-color) !important;
|
||||||
|
color: var(--waline-theme-color) !important;
|
||||||
|
background: var(--waline-bg-color-light) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wl-btn.primary {
|
||||||
|
background: linear-gradient(135deg, #8B5CF6, #EC4899) !important;
|
||||||
|
border: none !important;
|
||||||
|
color: white !important;
|
||||||
|
font-weight: 500 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wl-btn.primary:hover {
|
||||||
|
background: linear-gradient(135deg, #7C3AED, #DB2777) !important;
|
||||||
|
border: none !important;
|
||||||
|
color: white !important;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 4px 12px rgba(139, 92, 246, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========== 头部标签页 ========== */
|
||||||
|
.wl-header label {
|
||||||
|
transition: all 0.2s ease !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wl-header label:hover {
|
||||||
|
background: var(--waline-bg-color-light) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========== 评论卡片样式 ========== */
|
||||||
|
.wl-card {
|
||||||
|
border-bottom-color: var(--waline-border-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wl-card:first-child {
|
||||||
|
margin-inline-start: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wl-nick {
|
||||||
|
color: var(--waline-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wl-time {
|
||||||
|
color: var(--waline-info-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wl-content {
|
||||||
|
color: var(--waline-color) !important;
|
||||||
|
line-height: 1.8 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========== 徽章样式 ========== */
|
||||||
|
.wl-badge {
|
||||||
|
background: linear-gradient(135deg, #8B5CF6, #EC4899) !important;
|
||||||
|
border: none !important;
|
||||||
|
color: white !important;
|
||||||
|
font-size: 0.7em !important;
|
||||||
|
padding: 2px 8px !important;
|
||||||
|
border-radius: 9999px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========== 操作按钮 ========== */
|
||||||
|
.wl-action {
|
||||||
|
color: var(--waline-light-grey) !important;
|
||||||
|
transition: all 0.2s ease !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wl-action:hover {
|
||||||
|
color: var(--waline-theme-color) !important;
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wl-action.active {
|
||||||
|
color: var(--waline-active-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========== 评论操作 ========== */
|
||||||
|
.wl-delete:hover,
|
||||||
|
.wl-like:hover,
|
||||||
|
.wl-reply:hover,
|
||||||
|
.wl-edit:hover {
|
||||||
|
color: var(--waline-theme-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wl-delete.active,
|
||||||
|
.wl-like.active,
|
||||||
|
.wl-reply.active,
|
||||||
|
.wl-edit.active {
|
||||||
|
color: var(--waline-active-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========== 排序按钮 ========== */
|
||||||
|
.wl-sort li {
|
||||||
|
color: var(--waline-info-color) !important;
|
||||||
|
transition: all 0.2s ease !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wl-sort li:hover {
|
||||||
|
color: var(--waline-theme-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wl-sort li.active {
|
||||||
|
color: var(--waline-theme-color) !important;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========== 表情弹窗 ========== */
|
||||||
|
.wl-emoji-popup {
|
||||||
|
border-radius: var(--waline-border-radius) !important;
|
||||||
|
box-shadow: var(--waline-box-shadow) !important;
|
||||||
|
background: var(--waline-bg-color) !important;
|
||||||
|
border: 1px solid var(--waline-border-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wl-emoji-popup .wl-tab.active {
|
||||||
|
background: var(--waline-bg-color-light) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wl-emoji-popup button:hover {
|
||||||
|
background: var(--waline-bg-color-hover) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========== GIF 弹窗 ========== */
|
||||||
|
.wl-gif-popup {
|
||||||
|
border-radius: var(--waline-border-radius) !important;
|
||||||
|
box-shadow: var(--waline-box-shadow) !important;
|
||||||
|
background: var(--waline-bg-color) !important;
|
||||||
|
border: 1px solid var(--waline-border-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========== 评论统计 ========== */
|
||||||
|
.wl-count {
|
||||||
|
color: var(--waline-theme-color) !important;
|
||||||
|
font-size: 1.5em !important;
|
||||||
|
font-weight: 700 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========== 空状态 ========== */
|
||||||
|
.wl-empty {
|
||||||
|
color: var(--waline-info-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========== 头像样式 ========== */
|
||||||
|
.wl-avatar {
|
||||||
|
border-radius: var(--waline-avatar-radius) !important;
|
||||||
|
border: 2px solid var(--waline-border-color) !important;
|
||||||
|
box-shadow: var(--waline-box-shadow) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========== 回复引用框 ========== */
|
||||||
|
.wl-quote {
|
||||||
|
border-left-color: var(--waline-border-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========== 预览区域 ========== */
|
||||||
|
.wl-preview .wl-content {
|
||||||
|
background: var(--waline-bg-color-light) !important;
|
||||||
|
border-radius: var(--waline-btn-radius) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========== 反应区域 ========== */
|
||||||
|
.wl-reaction-title {
|
||||||
|
color: var(--waline-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wl-reaction-item.active .wl-reaction-text {
|
||||||
|
color: var(--waline-theme-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wl-reaction-votes {
|
||||||
|
border-color: var(--waline-theme-color) !important;
|
||||||
|
color: var(--waline-theme-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wl-reaction-item.active .wl-reaction-votes {
|
||||||
|
background: var(--waline-theme-color) !important;
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========== 加载动画 ========== */
|
||||||
|
.wl-loading svg {
|
||||||
|
color: var(--waline-theme-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========== 滚动条样式 ========== */
|
||||||
|
[data-waline] ::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-waline] ::-webkit-scrollbar-track-piece {
|
||||||
|
background: var(--waline-bg-color-light);
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-waline] ::-webkit-scrollbar-thumb {
|
||||||
|
background: var(--waline-theme-color);
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-waline] ::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: var(--waline-active-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========== 链接样式 ========== */
|
||||||
|
[data-waline] a {
|
||||||
|
color: var(--waline-theme-color) !important;
|
||||||
|
text-decoration: none !important;
|
||||||
|
transition: all 0.2s ease !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-waline] a:hover {
|
||||||
|
color: var(--waline-active-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========== 代码块样式 ========== */
|
||||||
|
.wl-content pre,
|
||||||
|
.wl-content pre[class*="language-"] {
|
||||||
|
background: var(--waline-code-bg-color) !important;
|
||||||
|
border-radius: var(--waline-btn-radius) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========== 移动端适配 ========== */
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.wl-panel {
|
||||||
|
border-radius: var(--radius-md, 0.5rem) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wl-btn {
|
||||||
|
font-size: 0.8em !important;
|
||||||
|
padding: 0.4em 0.8em !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wl-editor {
|
||||||
|
min-height: 6em !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,309 +0,0 @@
|
|||||||
/**
|
|
||||||
* Blog utility functions
|
|
||||||
* This file contains common functions used across blog components
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { type BlogPost, type Lang } from '@/types';
|
|
||||||
import { defaultLang } from '@/i18n/ui';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get blog posts based on language
|
|
||||||
* @param lang - Current language
|
|
||||||
* @param postsGlob - Object containing imported posts from glob
|
|
||||||
* @returns Processed blog posts array
|
|
||||||
*/
|
|
||||||
export async function getBlogPosts(lang: Lang, postsGlob: Record<string, any>): Promise<BlogPost[]> {
|
|
||||||
// Get posts based on language
|
|
||||||
let allPosts = [];
|
|
||||||
|
|
||||||
if (lang === 'zh') {
|
|
||||||
allPosts = postsGlob.zh;
|
|
||||||
} else {
|
|
||||||
allPosts = postsGlob.en;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process blog post data
|
|
||||||
const posts: BlogPost[] = allPosts.map((post: any) => {
|
|
||||||
const slug = post.url?.split('/').filter(Boolean).pop() || '';
|
|
||||||
|
|
||||||
// Default image if not specified in frontmatter
|
|
||||||
const defaultImage = "https://images.unsplash.com/photo-1516321318423-f06f85e504b3?w=400&h=250&fit=crop&crop=center";
|
|
||||||
|
|
||||||
return {
|
|
||||||
title: post.frontmatter.title,
|
|
||||||
description: post.frontmatter.description || '',
|
|
||||||
image: post.frontmatter.image || defaultImage,
|
|
||||||
slug: slug,
|
|
||||||
tags: post.frontmatter.tags || [],
|
|
||||||
tagId: post.frontmatter.tagId || [],
|
|
||||||
category: Array.isArray(post.frontmatter.category) ? post.frontmatter.category : post.frontmatter.category ? [post.frontmatter.category] : [],
|
|
||||||
categoryId: Array.isArray(post.frontmatter.categoryId) ? post.frontmatter.categoryId : post.frontmatter.categoryId ? [post.frontmatter.categoryId] : [],
|
|
||||||
date: post.frontmatter.date || post.frontmatter.pubDate || '',
|
|
||||||
readTime: post.frontmatter.readTime || post.frontmatter.readingTime || '5 min read',
|
|
||||||
url: post.url || '',
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return posts;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sort blog posts by date (newest first)
|
|
||||||
* @param posts - Array of blog posts
|
|
||||||
* @returns Sorted array of blog posts
|
|
||||||
*/
|
|
||||||
export function sortPostsByDate(posts: BlogPost[]): BlogPost[] {
|
|
||||||
return posts
|
|
||||||
.filter(post => post.date) // Filter out posts without dates
|
|
||||||
.sort((a, b) => {
|
|
||||||
const dateA = new Date(a.date).getTime();
|
|
||||||
const dateB = new Date(b.date).getTime();
|
|
||||||
return dateB - dateA; // Descending order, newest first
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Filter posts by category
|
|
||||||
* @param posts - Array of blog posts
|
|
||||||
* @param category - Category to filter by
|
|
||||||
* @returns Filtered array of blog posts
|
|
||||||
*/
|
|
||||||
export function filterPostsByCategory(posts: BlogPost[], category: string): BlogPost[] {
|
|
||||||
if (!category) return posts;
|
|
||||||
|
|
||||||
return posts.filter(post => {
|
|
||||||
// First check if post contains the current category ID
|
|
||||||
if (post.categoryId && Array.isArray(post.categoryId)) {
|
|
||||||
return post.categoryId.some(catId =>
|
|
||||||
catId.toLowerCase() === category.toLowerCase()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// If no categoryId, check category
|
|
||||||
else if (post.category && Array.isArray(post.category)) {
|
|
||||||
return post.category.some(cat =>
|
|
||||||
cat.toLowerCase() === category.toLowerCase()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Filter posts by tag
|
|
||||||
* @param posts - Array of blog posts
|
|
||||||
* @param tag - Tag to filter by
|
|
||||||
* @returns Filtered array of blog posts
|
|
||||||
*/
|
|
||||||
export function filterPostsByTag(posts: BlogPost[], tag: string): BlogPost[] {
|
|
||||||
if (!tag) return posts;
|
|
||||||
|
|
||||||
return posts.filter(post => {
|
|
||||||
// First check if post contains the current tag ID
|
|
||||||
if (post.tagId && Array.isArray(post.tagId)) {
|
|
||||||
return post.tagId.some(tagId =>
|
|
||||||
tagId.toLowerCase() === tag.toLowerCase()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// If no tagId, check tags
|
|
||||||
else if (post.tags && Array.isArray(post.tags)) {
|
|
||||||
return post.tags.some(postTag =>
|
|
||||||
postTag.toLowerCase() === tag.toLowerCase()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extract categories from posts
|
|
||||||
* @param posts - Array of blog posts
|
|
||||||
* @returns Map of category names to category IDs
|
|
||||||
*/
|
|
||||||
export function extractCategories(posts: any[]): Map<string, string> {
|
|
||||||
const allCategories = new Set<string>();
|
|
||||||
const categoryMap = new Map<string, string>(); // Map of category name to ID
|
|
||||||
|
|
||||||
posts.forEach(post => {
|
|
||||||
// Process categories
|
|
||||||
if (post.frontmatter?.category) {
|
|
||||||
const categories = Array.isArray(post.frontmatter.category)
|
|
||||||
? post.frontmatter.category
|
|
||||||
: [post.frontmatter.category];
|
|
||||||
|
|
||||||
const categoryIds = Array.isArray(post.frontmatter.categoryId)
|
|
||||||
? post.frontmatter.categoryId
|
|
||||||
: post.frontmatter.categoryId ? [post.frontmatter.categoryId] : [];
|
|
||||||
|
|
||||||
// If categoryId exists, use mapping between categoryId and category
|
|
||||||
if (categoryIds.length > 0 && categoryIds.length === categories.length) {
|
|
||||||
categories.forEach((cat: string, index: number) => {
|
|
||||||
if (cat && categoryIds[index]) {
|
|
||||||
allCategories.add(cat);
|
|
||||||
categoryMap.set(cat, categoryIds[index]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// If no categoryId or lengths don't match, use category name as ID
|
|
||||||
categories.forEach((cat: string) => {
|
|
||||||
if (cat) {
|
|
||||||
allCategories.add(cat);
|
|
||||||
categoryMap.set(cat, cat.toLowerCase());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return categoryMap;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extract tags from posts
|
|
||||||
* @param posts - Array of blog posts
|
|
||||||
* @returns Map of tag names to tag IDs
|
|
||||||
*/
|
|
||||||
export function extractTags(posts: any[]): Map<string, string> {
|
|
||||||
const allTags = new Set<string>();
|
|
||||||
const tagMap = new Map<string, string>(); // Map of tag name to ID
|
|
||||||
|
|
||||||
posts.forEach(post => {
|
|
||||||
// Process tags
|
|
||||||
if (post.frontmatter?.tags && Array.isArray(post.frontmatter.tags)) {
|
|
||||||
const tags = post.frontmatter.tags;
|
|
||||||
const tagIds = Array.isArray(post.frontmatter.tagId)
|
|
||||||
? post.frontmatter.tagId
|
|
||||||
: post.frontmatter.tagId ? [post.frontmatter.tagId] : [];
|
|
||||||
|
|
||||||
// If tagId exists, use mapping between tagId and tag
|
|
||||||
if (tagIds.length > 0 && tagIds.length === tags.length) {
|
|
||||||
tags.forEach((tag: string, index: number) => {
|
|
||||||
if (tag && tagIds[index]) {
|
|
||||||
allTags.add(tag);
|
|
||||||
tagMap.set(tag, tagIds[index]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// If no tagId or lengths don't match, use tag name as ID
|
|
||||||
tags.forEach((tag: string) => {
|
|
||||||
if (tag) {
|
|
||||||
allTags.add(tag);
|
|
||||||
tagMap.set(tag, tag.toLowerCase());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return tagMap;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get base URL for blog based on language
|
|
||||||
* @param lang - Current language
|
|
||||||
* @returns Base URL string
|
|
||||||
*/
|
|
||||||
export function getBlogBaseUrl(lang: Lang): string {
|
|
||||||
return lang === defaultLang ? '/blog' : `/${lang}/blog`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Process raw blog post data from import.meta.glob
|
|
||||||
* @param allPosts - Raw posts data from import.meta.glob
|
|
||||||
* @param lang - Current language
|
|
||||||
* @returns Processed blog posts array
|
|
||||||
*/
|
|
||||||
export function processRawBlogPosts(allPosts: Record<string, any>[] | any[], lang: Lang): BlogPost[] {
|
|
||||||
// Process blog post data
|
|
||||||
const posts: BlogPost[] = allPosts.map((post: any) => {
|
|
||||||
const slug = post.url?.split('/').filter(Boolean).pop() || '';
|
|
||||||
|
|
||||||
// Default image if not specified in frontmatter
|
|
||||||
const defaultImage = "https://images.unsplash.com/photo-1516321318423-f06f85e504b3?w=400&h=250&fit=crop&crop=center";
|
|
||||||
|
|
||||||
// Default read time text based on language
|
|
||||||
const defaultReadTime = lang === 'zh' ? '5 分钟阅读' : '5 min read';
|
|
||||||
|
|
||||||
return {
|
|
||||||
title: post.frontmatter.title,
|
|
||||||
description: post.frontmatter.description || '',
|
|
||||||
image: post.frontmatter.image || defaultImage,
|
|
||||||
slug: slug,
|
|
||||||
tags: post.frontmatter.tags || [],
|
|
||||||
tagId: post.frontmatter.tagId || [],
|
|
||||||
category: Array.isArray(post.frontmatter.category) ? post.frontmatter.category : post.frontmatter.category ? [post.frontmatter.category] : [],
|
|
||||||
categoryId: Array.isArray(post.frontmatter.categoryId) ? post.frontmatter.categoryId : post.frontmatter.categoryId ? [post.frontmatter.categoryId] : [],
|
|
||||||
date: post.frontmatter.date || post.frontmatter.pubDate || '',
|
|
||||||
readTime: post.frontmatter.readTime || post.frontmatter.readingTime || defaultReadTime,
|
|
||||||
url: post.url || '',
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return posts;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extract post metadata for categories and tags
|
|
||||||
* @param posts - Array of blog posts
|
|
||||||
* @returns Array of post metadata
|
|
||||||
*/
|
|
||||||
export function extractPostMetadata(posts: any[]) {
|
|
||||||
return posts.map((post: any) => ({
|
|
||||||
category: post.frontmatter?.category || [],
|
|
||||||
categoryId: post.frontmatter?.categoryId || [],
|
|
||||||
tags: post.frontmatter?.tags || [],
|
|
||||||
tagId: post.frontmatter?.tagId || []
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all blog posts with processing for a taxonomy page (category or tag)
|
|
||||||
* @param lang - Current language
|
|
||||||
* @param options - Additional options for processing
|
|
||||||
* @returns Object containing processed posts and metadata
|
|
||||||
*/
|
|
||||||
export async function getTaxonomyPageData(lang: Lang, options?: {
|
|
||||||
category?: string;
|
|
||||||
tag?: string;
|
|
||||||
}) {
|
|
||||||
// Read all blog posts
|
|
||||||
// Note: import.meta.glob only accepts literal strings, not variables
|
|
||||||
let allPosts;
|
|
||||||
|
|
||||||
// Use specific glob patterns based on language
|
|
||||||
if (lang === 'zh') {
|
|
||||||
allPosts = await import.meta.glob('/src/pages/zh/blog/posts/*.{md,mdx}', { eager: true });
|
|
||||||
} else {
|
|
||||||
allPosts = await import.meta.glob('/src/pages/blog/posts/*.{md,mdx}', { eager: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process blog post data
|
|
||||||
const blogPosts = processRawBlogPosts(Object.values(allPosts), lang);
|
|
||||||
|
|
||||||
// Apply filters if specified
|
|
||||||
let filteredPosts = blogPosts;
|
|
||||||
|
|
||||||
if (options?.category) {
|
|
||||||
filteredPosts = filterPostsByCategory(filteredPosts, options.category);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options?.tag) {
|
|
||||||
filteredPosts = filterPostsByTag(filteredPosts, options.tag);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort posts by date
|
|
||||||
const sortedBlogPosts = sortPostsByDate(filteredPosts);
|
|
||||||
|
|
||||||
// Extract categories and tags from all posts for sidebar
|
|
||||||
const allPostsMetadata = extractPostMetadata(Object.values(allPosts));
|
|
||||||
|
|
||||||
// Get categories and tags for sidebar
|
|
||||||
const categories = extractCategories(allPostsMetadata);
|
|
||||||
const tags = extractTags(allPostsMetadata);
|
|
||||||
|
|
||||||
return {
|
|
||||||
posts: sortedBlogPosts,
|
|
||||||
categories,
|
|
||||||
tags,
|
|
||||||
allPosts
|
|
||||||
};
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user