Skip to content

Commit ee53584

Browse files
新增功能
1 parent 62a9f31 commit ee53584

4 files changed

Lines changed: 251 additions & 29 deletions

File tree

src/components/EssayCarousel.astro

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,39 @@
11
---
2-
import essayData from "../assets/essay.json";
2+
import { fetchEch0Posts } from "../utils/ech0-api";
3+
4+
// 从Ech0 API获取动态数据
5+
const essayData = await fetchEch0Posts("https://say.allen2030.com");
36
---
47

58
<div class="essay-carousel-container w-full py-4">
69
<div class="max-w-[var(--page-width)] mx-auto px-4">
7-
<div class="essay-carousel relative overflow-hidden card-base card-shadow card-glass cursor-pointer" onclick="window.location.href='/essay/'">
8-
<div class="carousel-track flex transition-transform duration-500 ease-in-out" id="carousel-track">
10+
<div class="essay-carousel relative overflow-hidden card-base card-shadow card-glass cursor-pointer h-[40px]" onclick="window.location.href='/essay/'">
11+
<div class="carousel-track flex flex-col transition-transform duration-500 ease-in-out" id="carousel-track">
912
{essayData.map((item, index) => (
1013
<div
1114
key={item.id}
12-
class="carousel-item flex-shrink-0 w-full px-6 py-4"
15+
class="carousel-item flex-shrink-0 w-full px-6 py-2 h-[40px] flex items-center justify-center"
1316
>
14-
<div class="carousel-content flex items-center justify-center h-full">
15-
<p class="text-content text-center">{item.content}</p>
17+
<div class="carousel-content w-full text-center">
18+
<p class="text-content">{item.images ? '[图片]' : item.content}</p>
1619
</div>
1720
</div>
1821
))}
1922
</div>
2023

2124
<!-- 轮播指示器 -->
22-
<div class="carousel-indicators absolute bottom-2 left-1/2 transform -translate-x-1/2 flex gap-1">
23-
{essayData.map((_, index) => (
24-
<button
25-
key={index}
26-
class={`indicator w-1.5 h-1.5 rounded-full transition-all ${index === 0 ? 'bg-primary w-4' : 'bg-muted'}`}
27-
data-index={index}
28-
onclick="event.stopPropagation();"
29-
></button>
30-
))}
31-
</div>
25+
{essayData.length > 0 && (
26+
<div class="carousel-indicators absolute bottom-1 left-1/2 transform -translate-x-1/2 flex gap-1">
27+
{essayData.map((_, index) => (
28+
<button
29+
key={index}
30+
class={`indicator w-1 h-1 rounded-full transition-all ${index === 0 ? 'bg-primary w-3' : 'bg-muted'}`}
31+
data-index={index}
32+
onclick="event.stopPropagation();"
33+
></button>
34+
))}
35+
</div>
36+
)}
3237
</div>
3338
</div>
3439
</div>
@@ -42,19 +47,19 @@ document.addEventListener('DOMContentLoaded', () => {
4247
if (!track || !items.length) return;
4348

4449
let currentIndex = 0;
45-
const itemWidth = items[0].offsetWidth + 32; // 包含左右padding
50+
const itemHeight = 40; // 固定高度,确保一次只显示一条
4651

4752
function updateCarousel() {
48-
const offset = -currentIndex * itemWidth;
49-
track.style.transform = `translateX(${offset}px)`;
53+
const offset = -currentIndex * itemHeight;
54+
track.style.transform = `translateY(${offset}px)`;
5055

5156
// 更新指示器
5257
indicators.forEach((indicator, index) => {
5358
if (index === currentIndex) {
54-
indicator.classList.add('bg-primary', 'w-4');
59+
indicator.classList.add('bg-primary', 'w-3');
5560
indicator.classList.remove('bg-muted');
5661
} else {
57-
indicator.classList.remove('bg-primary', 'w-4');
62+
indicator.classList.remove('bg-primary', 'w-3');
5863
indicator.classList.add('bg-muted');
5964
}
6065
});

src/config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,7 @@ export const beautifyConfig: BeautifyConfig = {
309309

310310
// 鼠标样式配置
311311
export const cursorConfig = {
312-
overrideDefault: true, // 是否覆盖访问者系统默认鼠标样式 (false=尊重用户设置, true=强制覆盖)
312+
overrideDefault: false, // 是否覆盖访问者系统默认鼠标样式 (false=尊重用户设置, true=强制覆盖)
313313
};
314314

315315
// 使用说明:

src/pages/essay.astro

Lines changed: 74 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
---
22
import Comment from "@components/comment/index.astro";
33
import { Icon } from "astro-icon/components";
4-
import essayData from "../assets/essay.json";
54
import I18nKey from "../i18n/i18nKey";
65
import { i18n } from "../i18n/translation";
76
import MainGridLayout from "../layouts/MainGridLayout.astro";
7+
import { fetchEch0Posts } from "../utils/ech0-api";
88
9-
// 按ID倒序排列,ID越大越靠前
10-
const essays = essayData.sort((a, b) => b.id - a.id);
9+
// 从Ech0 API获取动态数据
10+
const essays = await fetchEch0Posts("https://say.allen2030.com");
1111
1212
// 为评论区创建一个模拟的post对象
1313
const mockPost = {
@@ -22,7 +22,7 @@ const mockPost = {
2222
<div class="flex w-full rounded-[var(--radius-large)] overflow-hidden relative min-h-32 mb-8">
2323
<div class="card-base z-10 px-9 py-6 relative w-full">
2424
<h1 class="text-4xl font-bold mb-6 text-neutral-900 dark:text-white">说说</h1>
25-
<p class="text-neutral-600 dark:text-neutral-400 mb-8">Ech0动态镜像</p>
25+
<p class="text-neutral-600 dark:text-neutral-400 mb-8">Ech0 API智能获取</p>
2626

2727
<div class="space-y-6">
2828
{essays.map((essay) => (
@@ -80,7 +80,17 @@ const mockPost = {
8080
)}
8181
</div>
8282

83-
83+
<!-- 评论按钮 -->
84+
<div>
85+
<button
86+
class="flex items-center gap-1 text-sm hover:text-primary transition-colors"
87+
data-content={`${essay.content}`}
88+
onclick="window.commentOnPost(this)"
89+
>
90+
<Icon name="material-symbols:chat" class="text-base" />
91+
<span>评论</span>
92+
</button>
93+
</div>
8494
</div>
8595
</div>
8696
))}
@@ -89,7 +99,7 @@ const mockPost = {
8999
</div>
90100

91101
<!-- 评论区 -->
92-
<div id="comments-section">
102+
<div id="comments-section" class="twikoo-container">
93103
<Comment path="/essay/" />
94104
</div>
95105
</MainGridLayout>
@@ -116,4 +126,61 @@ const mockPost = {
116126
word-wrap: break-word;
117127
white-space: pre-wrap;
118128
}
119-
</style>
129+
</style>
130+
131+
<script>
132+
// 全局评论函数
133+
window.commentOnPost = function(button) {
134+
const content = button.dataset.content;
135+
console.log('commentOnPost called with content:', content);
136+
137+
// 滚动到评论区
138+
const commentsSection = document.getElementById('comments-section');
139+
if (commentsSection) {
140+
console.log('Found comments section, scrolling...');
141+
commentsSection.scrollIntoView({ behavior: 'smooth' });
142+
} else {
143+
console.log('Comments section not found, scrolling to bottom...');
144+
window.scrollTo({
145+
top: document.body.scrollHeight,
146+
behavior: 'smooth'
147+
});
148+
}
149+
150+
// 增加延迟时间,确保滚动完成且Twikoo完全加载
151+
setTimeout(() => {
152+
console.log('Trying to find textarea...');
153+
// 尝试找到Twikoo输入框
154+
const textarea = document.querySelector('.el-textarea__inner') ||
155+
document.querySelector('#twikoo textarea') ||
156+
document.querySelector('textarea');
157+
158+
if (textarea) {
159+
console.log('Found textarea:', textarea);
160+
// 填充引用内容
161+
textarea.value = `> ${content}\n\n`;
162+
// 聚焦输入框
163+
textarea.focus();
164+
// 触发输入事件
165+
textarea.dispatchEvent(new Event('input', { bubbles: true }));
166+
console.log('Content filled into textarea');
167+
} else {
168+
console.log('Textarea not found, trying again...');
169+
// 再次尝试
170+
setTimeout(() => {
171+
const retryTextarea = document.querySelector('.el-textarea__inner') ||
172+
document.querySelector('textarea');
173+
if (retryTextarea) {
174+
console.log('Found textarea on retry:', retryTextarea);
175+
retryTextarea.value = `> ${content}\n\n`;
176+
retryTextarea.focus();
177+
retryTextarea.dispatchEvent(new Event('input', { bubbles: true }));
178+
console.log('Content filled into textarea on retry');
179+
} else {
180+
console.log('Textarea still not found');
181+
}
182+
}, 500);
183+
}
184+
}, 800);
185+
};
186+
</script>

src/utils/ech0-api.ts

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
// Ech0 API 工具函数
2+
3+
interface EssayData {
4+
id: number;
5+
content: string;
6+
time: string;
7+
tags: string[];
8+
images?: string[];
9+
}
10+
11+
/**
12+
* 从Ech0 RSS获取动态数据
13+
* @param apiUrl Ech0 API地址
14+
* @returns 转换后的动态数据数组
15+
*/
16+
export async function fetchEch0Posts(apiUrl: string): Promise<EssayData[]> {
17+
try {
18+
const response = await fetch(`${apiUrl}/rss`);
19+
20+
if (!response.ok) {
21+
throw new Error(`Failed to fetch Ech0 posts: ${response.status}`);
22+
}
23+
24+
const xmlText = await response.text();
25+
return parseRssData(xmlText);
26+
} catch (error) {
27+
console.error('Error fetching Ech0 posts:', error);
28+
// 出错时返回空数组,避免页面崩溃
29+
return [];
30+
}
31+
}
32+
33+
/**
34+
* 解析RSS XML数据
35+
* @param xmlText RSS XML文本
36+
* @returns 转换后的动态数据数组
37+
*/
38+
function parseRssData(xmlText: string): EssayData[] {
39+
// 使用正则表达式解析RSS数据,避免使用DOMParser(浏览器特有API)
40+
const entryRegex = /<entry>([\s\S]*?)<\/entry>/g;
41+
const entries: EssayData[] = [];
42+
let match;
43+
44+
let index = 0;
45+
while ((match = entryRegex.exec(xmlText)) !== null) {
46+
const entryText = match[1];
47+
index++;
48+
49+
// 提取更新时间
50+
const updatedRegex = /<updated>([\s\S]*?)<\/updated>/;
51+
const updatedMatch = entryText.match(updatedRegex);
52+
const updated = updatedMatch ? updatedMatch[1] : '';
53+
54+
// 提取摘要(使用更宽松的正则表达式,支持换行符)
55+
const summaryRegex = /<summary[^>]*>([\s\S]*?)<\/summary>/i;
56+
const summaryMatch = entryText.match(summaryRegex);
57+
const summary = summaryMatch ? summaryMatch[1] : '';
58+
59+
// 提取纯文本内容
60+
const content = extractPlainText(summary);
61+
62+
// 提取图片
63+
const images = extractImages(summary);
64+
65+
entries.push({
66+
id: index,
67+
content,
68+
time: formatDate(updated),
69+
tags: ['生活'], // 默认标签
70+
images: images.length > 0 ? images : undefined
71+
});
72+
}
73+
74+
// 按ID倒序排列
75+
return entries.sort((a, b) => b.id - a.id);
76+
}
77+
78+
/**
79+
* 从HTML中提取纯文本
80+
* @param html HTML文本
81+
* @returns 纯文本
82+
*/
83+
function extractPlainText(html: string): string {
84+
// 解码HTML实体
85+
let decodedHtml = html
86+
.replace(/&lt;/g, '<')
87+
.replace(/&gt;/g, '>')
88+
.replace(/&amp;/g, '&')
89+
.replace(/&quot;/g, '"')
90+
.replace(/&#34;/g, '"')
91+
.replace(/&#39;/g, "'")
92+
.replace(/&#xA;/g, '\n');
93+
94+
// 使用正则表达式移除HTML标签
95+
let plainText = decodedHtml.replace(/<[^>]*>/g, '').trim();
96+
97+
// 如果纯文本为空,说明可能是纯图片的说说,返回一个占位符
98+
return plainText || '[图片]';
99+
}
100+
101+
/**
102+
* 从HTML中提取图片URL
103+
* @param html HTML文本
104+
* @returns 图片URL数组
105+
*/
106+
function extractImages(html: string): string[] {
107+
console.log('原始HTML:', html);
108+
109+
// 解码HTML实体
110+
let decodedHtml = html
111+
.replace(/&lt;/g, '<')
112+
.replace(/&gt;/g, '>')
113+
.replace(/&amp;/g, '&')
114+
.replace(/&quot;/g, '"')
115+
.replace(/&#34;/g, '"')
116+
.replace(/&#39;/g, "'");
117+
118+
console.log('解码后HTML:', decodedHtml);
119+
120+
// 使用更宽松的正则表达式提取图片URL
121+
const imgRegex = /<img[^>]*src=["']([^"']+)["']/gi;
122+
const images: string[] = [];
123+
let match;
124+
125+
while ((match = imgRegex.exec(decodedHtml)) !== null) {
126+
let url = match[1];
127+
console.log('找到图片URL:', url);
128+
129+
// 将HTTP URL转换为HTTPS
130+
if (url.startsWith('http://')) {
131+
url = url.replace('http://', 'https://');
132+
console.log('转换为HTTPS:', url);
133+
}
134+
135+
images.push(url);
136+
}
137+
138+
console.log('最终图片数组:', images);
139+
return images;
140+
}
141+
142+
/**
143+
* 格式化日期
144+
* @param dateString ISO日期字符串
145+
* @returns YYYY-MM-DD格式的日期字符串
146+
*/
147+
function formatDate(dateString: string): string {
148+
const date = new Date(dateString);
149+
return date.toISOString().split('T')[0];
150+
}

0 commit comments

Comments
 (0)