From 8d4d9d1c11e67b45f96b9be2a279e5b1c8c91a0b Mon Sep 17 00:00:00 2001 From: canisminor1990 Date: Tue, 3 Dec 2024 17:20:45 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=85=20test:=20Add=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locales/ar/changelog.json | 4 + locales/bg-BG/changelog.json | 4 + locales/de-DE/changelog.json | 4 + locales/en-US/changelog.json | 4 + locales/es-ES/changelog.json | 4 + locales/fa-IR/changelog.json | 4 + locales/fr-FR/changelog.json | 4 + locales/it-IT/changelog.json | 4 + locales/ja-JP/changelog.json | 4 + locales/ko-KR/changelog.json | 4 + locales/nl-NL/changelog.json | 4 + locales/pl-PL/changelog.json | 4 + locales/pt-BR/changelog.json | 4 + locales/ru-RU/changelog.json | 4 + locales/tr-TR/changelog.json | 4 + locales/vi-VN/changelog.json | 4 + locales/zh-CN/changelog.json | 4 + locales/zh-TW/changelog.json | 4 + package.json | 1 + src/app/(main)/changelog/_layout/Desktop.tsx | 8 +- .../(main)/changelog/features/GridLayout.tsx | 39 ++-- src/app/(main)/changelog/features/Post.tsx | 60 ++++--- src/app/(main)/changelog/page.tsx | 21 ++- .../@modal/(.)changelog/features/Cover.tsx | 21 +-- .../(.)changelog/features/Pagination.tsx | 54 ++++++ src/app/@modal/(.)changelog/features/Post.tsx | 11 +- src/app/@modal/(.)changelog/layout.tsx | 4 + src/app/@modal/(.)changelog/loading.tsx | 2 +- src/app/@modal/(.)changelog/page.tsx | 3 +- src/components/mdx/Image.tsx | 50 ++++++ src/components/mdx/index.tsx | 2 + src/locales/default/changelog.ts | 4 + src/server/services/changelog/index.test.ts | 167 ++++++++++++++++++ .../services/changelog/index.ts} | 41 +---- src/server/services/discover/index.test.ts | 1 - src/store/global/action.ts | 6 +- src/types/changelog.ts | 6 + vitest.config.ts | 2 +- 38 files changed, 456 insertions(+), 119 deletions(-) create mode 100644 src/app/@modal/(.)changelog/features/Pagination.tsx create mode 100644 src/components/mdx/Image.tsx create mode 100644 src/server/services/changelog/index.test.ts rename src/{services/changelog.ts => server/services/changelog/index.ts} (83%) create mode 100644 src/types/changelog.ts diff --git a/locales/ar/changelog.json b/locales/ar/changelog.json index 8997f54eecb8..ff12b6c86033 100644 --- a/locales/ar/changelog.json +++ b/locales/ar/changelog.json @@ -7,6 +7,10 @@ "addedWhileAway": "لقد أضفنا ميزات جديدة أثناء غيابك.", "allChangelog": "عرض جميع سجلات التحديثات", "description": "تابع الميزات الجديدة والتحسينات في {{appName}}", + "pagination": { + "older": "عرض التغييرات السابقة", + "prev": "الصفحة السابقة" + }, "readDetails": "اقرأ التفاصيل", "title": "سجل التحديثات", "versionDetails": "تفاصيل الإصدار", diff --git a/locales/bg-BG/changelog.json b/locales/bg-BG/changelog.json index 136252e17464..e7fcce524eb5 100644 --- a/locales/bg-BG/changelog.json +++ b/locales/bg-BG/changelog.json @@ -7,6 +7,10 @@ "addedWhileAway": "Докато ви нямаше, добавихме нови функции.", "allChangelog": "Вижте всички актуализации", "description": "Следете новите функции и подобрения на {{appName}}", + "pagination": { + "older": "Преглед на историческите промени", + "prev": "Предишна страница" + }, "readDetails": "Прочетете подробности", "title": "Актуализации", "versionDetails": "Детайли за версиите", diff --git a/locales/de-DE/changelog.json b/locales/de-DE/changelog.json index 1980e18b2863..e397bb9fdb41 100644 --- a/locales/de-DE/changelog.json +++ b/locales/de-DE/changelog.json @@ -7,6 +7,10 @@ "addedWhileAway": "Wir haben neue Funktionen hinzugefügt, während Sie weg waren.", "allChangelog": "Alle Änderungsprotokolle anzeigen", "description": "Verfolgen Sie die neuen Funktionen und Verbesserungen von {{appName}} kontinuierlich", + "pagination": { + "older": "Ältere Änderungen anzeigen", + "prev": "Vorherige Seite" + }, "readDetails": "Details lesen", "title": "Änderungsprotokoll", "versionDetails": "Versionsdetails", diff --git a/locales/en-US/changelog.json b/locales/en-US/changelog.json index b42d51d11f52..bba44698e436 100644 --- a/locales/en-US/changelog.json +++ b/locales/en-US/changelog.json @@ -7,6 +7,10 @@ "addedWhileAway": "We've introduced new features while you were away.", "allChangelog": "View all changelogs", "description": "Stay updated on the new features and improvements of {{appName}}", + "pagination": { + "older": "View Historical Changes", + "prev": "Previous Page" + }, "readDetails": "Read details", "title": "Changelog", "versionDetails": "Version details", diff --git a/locales/es-ES/changelog.json b/locales/es-ES/changelog.json index 5180dfc75b62..2b35adebf5bb 100644 --- a/locales/es-ES/changelog.json +++ b/locales/es-ES/changelog.json @@ -7,6 +7,10 @@ "addedWhileAway": "Hemos traído nuevas características mientras estabas ausente.", "allChangelog": "Ver todos los registros de cambios", "description": "Sigue las nuevas funciones y mejoras de {{appName}}", + "pagination": { + "older": "Ver cambios anteriores", + "prev": "Página anterior" + }, "readDetails": "Leer detalles", "title": "Registro de cambios", "versionDetails": "Detalles de la versión", diff --git a/locales/fa-IR/changelog.json b/locales/fa-IR/changelog.json index fa84589cc465..bad13c844875 100644 --- a/locales/fa-IR/changelog.json +++ b/locales/fa-IR/changelog.json @@ -7,6 +7,10 @@ "addedWhileAway": "در زمان غیبت شما، ویژگی‌های جدیدی اضافه کردیم.", "allChangelog": "تمام تغییرات را مشاهده کنید", "description": "به‌روزرسانی‌های جدید و بهبودهای {{appName}} را دنبال کنید", + "pagination": { + "older": "مشاهده تغییرات قبلی", + "prev": "صفحه قبلی" + }, "readDetails": "جزئیات را بخوانید", "title": "تغییرات", "versionDetails": "جزئیات نسخه", diff --git a/locales/fr-FR/changelog.json b/locales/fr-FR/changelog.json index edd7603d4626..2f9f68c59c73 100644 --- a/locales/fr-FR/changelog.json +++ b/locales/fr-FR/changelog.json @@ -7,6 +7,10 @@ "addedWhileAway": "Nous avons ajouté de nouvelles fonctionnalités pendant votre absence.", "allChangelog": "Voir tous les journaux de mise à jour", "description": "Suivez en continu les nouvelles fonctionnalités et améliorations de {{appName}}", + "pagination": { + "older": "Voir les modifications antérieures", + "prev": "Page précédente" + }, "readDetails": "Lire les détails", "title": "Journal des mises à jour", "versionDetails": "Détails de la version", diff --git a/locales/it-IT/changelog.json b/locales/it-IT/changelog.json index 00816ff32e22..7f7549af6c6f 100644 --- a/locales/it-IT/changelog.json +++ b/locales/it-IT/changelog.json @@ -7,6 +7,10 @@ "addedWhileAway": "Abbiamo introdotto nuove funzionalità mentre eri via.", "allChangelog": "Visualizza tutti i registri delle modifiche", "description": "Tieni traccia delle nuove funzionalità e miglioramenti di {{appName}}", + "pagination": { + "older": "Visualizza le modifiche precedenti", + "prev": "Pagina precedente" + }, "readDetails": "Leggi i dettagli", "title": "Registro delle modifiche", "versionDetails": "Dettagli versione", diff --git a/locales/ja-JP/changelog.json b/locales/ja-JP/changelog.json index 3660b206e28f..386c567b042d 100644 --- a/locales/ja-JP/changelog.json +++ b/locales/ja-JP/changelog.json @@ -7,6 +7,10 @@ "addedWhileAway": "あなたが離れている間に、新しい機能を追加しました。", "allChangelog": "すべての更新ログを見る", "description": "{{appName}}の新機能と改善を継続的に追跡", + "pagination": { + "older": "履歴の変更を表示", + "prev": "前のページ" + }, "readDetails": "詳細を読む", "title": "更新ログ", "versionDetails": "バージョンの詳細", diff --git a/locales/ko-KR/changelog.json b/locales/ko-KR/changelog.json index a0c1e9335121..cc70e7a8b689 100644 --- a/locales/ko-KR/changelog.json +++ b/locales/ko-KR/changelog.json @@ -7,6 +7,10 @@ "addedWhileAway": "귀하가 떠나 있는 동안 새로운 기능이 추가되었습니다.", "allChangelog": "모든 업데이트 로그 보기", "description": "{{appName}}의 새로운 기능과 개선 사항을 지속적으로 추적하세요", + "pagination": { + "older": "이전 변경 사항 보기", + "prev": "이전 페이지" + }, "readDetails": "자세히 읽기", "title": "업데이트 로그", "versionDetails": "버전 세부정보", diff --git a/locales/nl-NL/changelog.json b/locales/nl-NL/changelog.json index 40d7a3fa1e35..0076f83087cc 100644 --- a/locales/nl-NL/changelog.json +++ b/locales/nl-NL/changelog.json @@ -7,6 +7,10 @@ "addedWhileAway": "We hebben nieuwe functies toegevoegd terwijl je weg was.", "allChangelog": "Bekijk alle changelogs", "description": "Blijf op de hoogte van nieuwe functies en verbeteringen van {{appName}}", + "pagination": { + "older": "Bekijk eerdere wijzigingen", + "prev": "Vorige pagina" + }, "readDetails": "Lees meer", "title": "Changelog", "versionDetails": "Versie details", diff --git a/locales/pl-PL/changelog.json b/locales/pl-PL/changelog.json index 1e08315fa930..b6a277dc3707 100644 --- a/locales/pl-PL/changelog.json +++ b/locales/pl-PL/changelog.json @@ -7,6 +7,10 @@ "addedWhileAway": "W czasie Twojej nieobecności wprowadziliśmy nowe funkcje.", "allChangelog": "Zobacz wszystkie dzienniki zmian", "description": "Na bieżąco śledź nowe funkcje i ulepszenia {{appName}}", + "pagination": { + "older": "Zobacz wcześniejsze zmiany", + "prev": "Poprzednia strona" + }, "readDetails": "Przeczytaj szczegóły", "title": "Dziennik zmian", "versionDetails": "Szczegóły wersji", diff --git a/locales/pt-BR/changelog.json b/locales/pt-BR/changelog.json index 4a055007b947..50b34c1dd02c 100644 --- a/locales/pt-BR/changelog.json +++ b/locales/pt-BR/changelog.json @@ -7,6 +7,10 @@ "addedWhileAway": "Trouxemos novos recursos enquanto você estava ausente.", "allChangelog": "Veja todos os registros de alterações", "description": "Acompanhe as novas funcionalidades e melhorias do {{appName}}", + "pagination": { + "older": "Ver alterações anteriores", + "prev": "Página anterior" + }, "readDetails": "Leia os detalhes", "title": "Registro de Atualizações", "versionDetails": "Detalhes da versão", diff --git a/locales/ru-RU/changelog.json b/locales/ru-RU/changelog.json index 48983bb6be21..42a31753681a 100644 --- a/locales/ru-RU/changelog.json +++ b/locales/ru-RU/changelog.json @@ -7,6 +7,10 @@ "addedWhileAway": "Мы добавили новые функции, пока вы отсутствовали.", "allChangelog": "Просмотреть все журналы изменений", "description": "Постоянно следите за новыми функциями и улучшениями {{appName}}", + "pagination": { + "older": "Посмотреть историю изменений", + "prev": "Предыдущая страница" + }, "readDetails": "Читать детали", "title": "Журнал изменений", "versionDetails": "Детали версий", diff --git a/locales/tr-TR/changelog.json b/locales/tr-TR/changelog.json index 2aade975e932..8ca7fee5b03f 100644 --- a/locales/tr-TR/changelog.json +++ b/locales/tr-TR/changelog.json @@ -7,6 +7,10 @@ "addedWhileAway": "Siz yokken yeni özellikler ekledik.", "allChangelog": "Tüm güncelleme günlüklerini görüntüle", "description": "{{appName}}'in yeni özelliklerini ve iyileştirmelerini sürekli takip edin", + "pagination": { + "older": "Geçmiş değişiklikleri görüntüle", + "prev": "Önceki sayfa" + }, "readDetails": "Detayları okuyun", "title": "Güncelleme Günlüğü", "versionDetails": "Sürüm detayları", diff --git a/locales/vi-VN/changelog.json b/locales/vi-VN/changelog.json index fff4e2cf8440..dea0fbeb1097 100644 --- a/locales/vi-VN/changelog.json +++ b/locales/vi-VN/changelog.json @@ -7,6 +7,10 @@ "addedWhileAway": "Chúng tôi đã mang đến những tính năng mới trong thời gian bạn vắng mặt.", "allChangelog": "Xem tất cả nhật ký cập nhật", "description": "Theo dõi các tính năng và cải tiến mới của {{appName}}", + "pagination": { + "older": "Xem thay đổi lịch sử", + "prev": "Trang trước" + }, "readDetails": "Đọc chi tiết", "title": "Nhật ký cập nhật", "versionDetails": "Chi tiết phiên bản", diff --git a/locales/zh-CN/changelog.json b/locales/zh-CN/changelog.json index b6dc4100dcaf..c139ed36954b 100644 --- a/locales/zh-CN/changelog.json +++ b/locales/zh-CN/changelog.json @@ -7,6 +7,10 @@ "addedWhileAway": "在您离开期间,我们带来了新的特性。", "allChangelog": "查看所有更新日志", "description": "持续追踪 {{appName}} 的新功能和改进", + "pagination": { + "older": "查看历史变更", + "prev": "上一页" + }, "readDetails": "阅读详情", "title": "更新日志", "versionDetails": "版本详情", diff --git a/locales/zh-TW/changelog.json b/locales/zh-TW/changelog.json index 2cde0ee7b45e..e7d265ca8fe8 100644 --- a/locales/zh-TW/changelog.json +++ b/locales/zh-TW/changelog.json @@ -7,6 +7,10 @@ "addedWhileAway": "在您離開期間,我們帶來了新的特性。", "allChangelog": "查看所有更新日誌", "description": "持續追蹤 {{appName}} 的新功能和改進", + "pagination": { + "older": "查看歷史變更", + "prev": "上一頁" + }, "readDetails": "閱讀詳情", "title": "更新日誌", "versionDetails": "版本詳情", diff --git a/package.json b/package.json index 4e718205bada..db13ab3802e3 100644 --- a/package.json +++ b/package.json @@ -189,6 +189,7 @@ "pdfjs-dist": "4.4.168", "pg": "^8.13.0", "pino": "^9.5.0", + "plaiceholder": "^3.0.0", "polished": "^4.3.1", "posthog-js": "^1.174.2", "pwa-install-handler": "^2.6.1", diff --git a/src/app/(main)/changelog/_layout/Desktop.tsx b/src/app/(main)/changelog/_layout/Desktop.tsx index 47a37fb980f5..900faa2ddcb9 100644 --- a/src/app/(main)/changelog/_layout/Desktop.tsx +++ b/src/app/(main)/changelog/_layout/Desktop.tsx @@ -7,8 +7,12 @@ type Props = { children: ReactNode }; const Layout = ({ children }: Props) => { return ( - - + + {children} diff --git a/src/app/(main)/changelog/features/GridLayout.tsx b/src/app/(main)/changelog/features/GridLayout.tsx index c8b321955705..d0023a9744bc 100644 --- a/src/app/(main)/changelog/features/GridLayout.tsx +++ b/src/app/(main)/changelog/features/GridLayout.tsx @@ -1,27 +1,22 @@ -'use client'; - -import { useResponsive } from 'antd-style'; -import { PropsWithChildren, ReactNode, memo } from 'react'; +import { FC, PropsWithChildren, ReactNode } from 'react'; import { Flexbox } from 'react-layout-kit'; -const GridLayout = memo>( - ({ mobile, children, date }) => { - const { md } = useResponsive(); - - const isMobile = mobile || !md; - - return ( - - - {date} - - - {children} - - {!isMobile && } +const GridLayout: FC> = ({ + mobile, + children, + date, +}) => { + return ( + + + {date} + + + {children} - ); - }, -); + {!mobile && } + + ); +}; export default GridLayout; diff --git a/src/app/(main)/changelog/features/Post.tsx b/src/app/(main)/changelog/features/Post.tsx index bbc8eaa0b6aa..0857cf4e56a1 100644 --- a/src/app/(main)/changelog/features/Post.tsx +++ b/src/app/(main)/changelog/features/Post.tsx @@ -1,11 +1,14 @@ -import { Image, Typography } from '@lobehub/ui'; +import { Typography } from '@lobehub/ui'; +import { Divider } from 'antd'; import Link from 'next/link'; import urlJoin from 'url-join'; import { CustomMDX } from '@/components/mdx'; +import Image from '@/components/mdx/Image'; import { OFFICIAL_SITE } from '@/const/url'; import { Locales } from '@/locales/resources'; -import { ChangelogIndexItem, changelogService } from '@/services/changelog'; +import { ChangelogService } from '@/server/services/changelog'; +import { ChangelogIndexItem } from '@/types/changelog'; import GridLayout from './GridLayout'; import PublishedTime from './PublishedTime'; @@ -17,35 +20,36 @@ const Post = async ({ versionRange, locale, }: ChangelogIndexItem & { branch?: string; locale: Locales; mobile?: boolean }) => { + const changelogService = new ChangelogService(); const data = await changelogService.getPostById(id, { locale }); + if (!data || !data.title) return null; + return ( - - } - mobile={mobile} - > - - -

{data.rawTitle || data.title}

- - {data.title} - - - - -
-
+ <> + + + } + mobile={mobile} + > + + +

{data.rawTitle || data.title}

+ + {data.title} + + + + +
+
+ ); }; diff --git a/src/app/(main)/changelog/page.tsx b/src/app/(main)/changelog/page.tsx index 341b1a62188c..366c8bafef8d 100644 --- a/src/app/(main)/changelog/page.tsx +++ b/src/app/(main)/changelog/page.tsx @@ -2,14 +2,16 @@ import { Divider, Skeleton } from 'antd'; import { Fragment, Suspense } from 'react'; import { Flexbox } from 'react-layout-kit'; +import Pagination from '@/app/@modal/(.)changelog/features/Pagination'; import StructuredData from '@/components/StructuredData'; import { BRANDING_NAME } from '@/const/branding'; import { ldModule } from '@/server/ld'; import { metadataModule } from '@/server/metadata'; +import { ChangelogService } from '@/server/services/changelog'; import { translation } from '@/server/translation'; -import { changelogService } from '@/services/changelog'; import { isMobileDevice } from '@/utils/server/responsive'; +import GridLayout from './features/GridLayout'; import Post from './features/Post'; export const generateMetadata = async () => { @@ -24,6 +26,7 @@ export const generateMetadata = async () => { const Page = async () => { const mobile = isMobileDevice(); const { t, locale } = await translation('metadata'); + const changelogService = new ChangelogService(); const data = await changelogService.getChangelogIndex(); const ld = ldModule.generate({ @@ -36,16 +39,24 @@ const Page = async () => { <> - {data.map((item, index) => ( + {data.map((item) => ( - {!mobile && } - {mobile && index > 0 && } - }> + + + + + } + > ))} + + + ); }; diff --git a/src/app/@modal/(.)changelog/features/Cover.tsx b/src/app/@modal/(.)changelog/features/Cover.tsx index 467973515937..8cb6f3b27824 100644 --- a/src/app/@modal/(.)changelog/features/Cover.tsx +++ b/src/app/@modal/(.)changelog/features/Cover.tsx @@ -1,8 +1,7 @@ 'use client'; import { createStyles } from 'antd-style'; -import Image from 'next/image'; -import { memo } from 'react'; +import { PropsWithChildren, memo } from 'react'; import { Flexbox } from 'react-layout-kit'; const useStyles = createStyles( @@ -41,23 +40,9 @@ const useStyles = createStyles( `, ); -const Cover = memo<{ alt: string; mobile?: boolean; src: string }>(({ alt, mobile, src }) => { +const Cover = memo(({ children }) => { const { styles } = useStyles(); - return ( - - {alt} - - ); + return {children}; }); export default Cover; diff --git a/src/app/@modal/(.)changelog/features/Pagination.tsx b/src/app/@modal/(.)changelog/features/Pagination.tsx new file mode 100644 index 000000000000..344c8d2a7f17 --- /dev/null +++ b/src/app/@modal/(.)changelog/features/Pagination.tsx @@ -0,0 +1,54 @@ +'use client'; + +import { Icon } from '@lobehub/ui'; +import { createStyles } from 'antd-style'; +import { ChevronRightIcon } from 'lucide-react'; +import Link from 'next/link'; +import { memo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Flexbox } from 'react-layout-kit'; +import urlJoin from 'url-join'; + +import { OFFICIAL_SITE } from '@/const/url'; + +const useStyles = createStyles(({ css, token }) => ({ + button: css` + border: 1px solid ${token.colorBorderSecondary}; + border-radius: ${token.borderRadiusLG}px; + + &:hover { + background: ${token.colorFillTertiary}; + } + `, + desc: css` + color: ${token.colorTextSecondary}; + `, + title: css` + font-size: 16px; + font-weight: 500; + `, +})); + +const Pagination = memo(() => { + const { t } = useTranslation('changelog'); + const { styles } = useStyles(); + return ( + + + + + {t('pagination.prev')} + + +
{t('pagination.older')}
+
+ +
+ ); +}); + +export default Pagination; diff --git a/src/app/@modal/(.)changelog/features/Post.tsx b/src/app/@modal/(.)changelog/features/Post.tsx index ca995b2c2291..1c15cd3d25c7 100644 --- a/src/app/@modal/(.)changelog/features/Post.tsx +++ b/src/app/@modal/(.)changelog/features/Post.tsx @@ -4,9 +4,11 @@ import { Flexbox } from 'react-layout-kit'; import urlJoin from 'url-join'; import { CustomMDX } from '@/components/mdx'; +import Image from '@/components/mdx/Image'; import { OFFICIAL_SITE } from '@/const/url'; import { Locales } from '@/locales/resources'; -import { ChangelogIndexItem, changelogService } from '@/services/changelog'; +import { ChangelogService } from '@/server/services/changelog'; +import { ChangelogIndexItem } from '@/types/changelog'; import Cover from './Cover'; import PublishedTime from './PublishedTime'; @@ -18,13 +20,18 @@ const Post = async ({ versionRange, locale, }: ChangelogIndexItem & { branch?: string; locale: Locales; mobile?: boolean }) => { + const changelogService = new ChangelogService(); const data = await changelogService.getPostById(id, { locale }); const url = urlJoin(OFFICIAL_SITE, 'changelog', id); + if (!data) return null; + return ( - + + {data.title} + diff --git a/src/app/@modal/(.)changelog/layout.tsx b/src/app/@modal/(.)changelog/layout.tsx index ff748244a1c9..3fd6070cc750 100644 --- a/src/app/@modal/(.)changelog/layout.tsx +++ b/src/app/@modal/(.)changelog/layout.tsx @@ -7,6 +7,7 @@ import { useGlobalStore } from '@/store/global'; import ModalLayout from '../_layout/ModalLayout'; import Hero from './features/Hero'; +import Pagination from './features/Pagination'; const Layout: FC = ({ children }) => { const [useCheckLatestChangelogId, updateSystemStatus] = useGlobalStore((s) => [ @@ -36,6 +37,9 @@ const Layout: FC = ({ children }) => { > {children} + + + ); diff --git a/src/app/@modal/(.)changelog/loading.tsx b/src/app/@modal/(.)changelog/loading.tsx index b04d59deffab..f5a6dcb53b46 100644 --- a/src/app/@modal/(.)changelog/loading.tsx +++ b/src/app/@modal/(.)changelog/loading.tsx @@ -3,7 +3,7 @@ import { Flexbox } from 'react-layout-kit'; export default () => { return ( - + ); diff --git a/src/app/@modal/(.)changelog/page.tsx b/src/app/@modal/(.)changelog/page.tsx index 3a217926b77d..44b1b8c007fd 100644 --- a/src/app/@modal/(.)changelog/page.tsx +++ b/src/app/@modal/(.)changelog/page.tsx @@ -1,7 +1,7 @@ import { Suspense } from 'react'; +import { ChangelogService } from '@/server/services/changelog'; import { getLocale } from '@/server/translation'; -import { changelogService } from '@/services/changelog'; import { isMobileDevice } from '@/utils/server/responsive'; import Post from './features/Post'; @@ -10,6 +10,7 @@ import Loading from './loading'; const Page = async () => { const locale = await getLocale(); const mobile = isMobileDevice(); + const changelogService = new ChangelogService(); const data = await changelogService.getChangelogIndex(); return data.map((item) => ( diff --git a/src/components/mdx/Image.tsx b/src/components/mdx/Image.tsx new file mode 100644 index 000000000000..0eeffd854811 --- /dev/null +++ b/src/components/mdx/Image.tsx @@ -0,0 +1,50 @@ +'use server'; + +import { Image } from '@lobehub/ui/mdx'; +import Img from 'next/image'; +import { getPlaiceholder } from 'plaiceholder'; +import { FC } from 'react'; + +const DEFAULT_WIDTH = 800; + +const fetchImage = async (url: string) => { + const buffer = await fetch(url, { cache: 'force-cache' }).then(async (res) => + Buffer.from(await res.arrayBuffer()), + ); + const { + base64, + metadata: { height, width }, + } = await getPlaiceholder(buffer, { format: ['webp'] }); + return { + base64, + height: (DEFAULT_WIDTH / width) * height, + }; +}; + +const ImageWrapper: FC<{ alt: string; src: string }> = async ({ alt, src, ...rest }) => { + try { + const { base64, height } = await fetchImage(src); + return ( + {alt} + } + src={src} + width={DEFAULT_WIDTH} + /> + ); + } catch { + return {alt}; + } +}; + +export default ImageWrapper; diff --git a/src/components/mdx/index.tsx b/src/components/mdx/index.tsx index 7b1655f6dfac..1e83333315e1 100644 --- a/src/components/mdx/index.tsx +++ b/src/components/mdx/index.tsx @@ -5,6 +5,7 @@ import { FC } from 'react'; import remarkGfm from 'remark-gfm'; import CodeBlock from './CodeBlock'; +import Image from './Image'; import Link from './Link'; export const Typography = ({ @@ -31,6 +32,7 @@ export const CustomMDX: FC = ({ mobile, . const list: any = {}; Object.entries({ ...mdxComponents, + Image: Image, a: Link, pre: CodeBlock, ...rest.components, diff --git a/src/locales/default/changelog.ts b/src/locales/default/changelog.ts index 05eaa44596a6..a76a427554af 100644 --- a/src/locales/default/changelog.ts +++ b/src/locales/default/changelog.ts @@ -7,6 +7,10 @@ export default { addedWhileAway: '在您离开期间,我们带来了新的特性。', allChangelog: '查看所有更新日志', description: '持续追踪 {{appName}} 的新功能和改进', + pagination: { + older: '查看历史变更', + prev: '上一页', + }, readDetails: '阅读详情', title: '更新日志', versionDetails: '版本详情', diff --git a/src/server/services/changelog/index.test.ts b/src/server/services/changelog/index.test.ts new file mode 100644 index 000000000000..d7d805c7b1fb --- /dev/null +++ b/src/server/services/changelog/index.test.ts @@ -0,0 +1,167 @@ +// @vitest-environment node +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import { ChangelogIndexItem } from '@/types/changelog'; + +import { ChangelogService } from './index'; + +// Mock external dependencies +vi.mock('dayjs', () => ({ + default: (date: string) => ({ + format: vi.fn().mockReturnValue(date), + }), +})); + +vi.mock('gray-matter', () => ({ + default: vi.fn().mockImplementation((text) => ({ + data: { date: '2023-01-01' }, + content: text, + })), +})); + +vi.mock('markdown-to-txt', () => ({ + markdownToTxt: vi.fn().mockImplementation((text) => text), +})); + +vi.mock('semver', async (importOriginal) => { + const actual: any = await importOriginal(); + return { + ...actual, + rcompare: vi.fn().mockImplementation((a, b) => b.localeCompare(a)), + lt: vi.fn().mockImplementation((a, b) => a < b), + gt: vi.fn().mockImplementation((a, b) => a > b), + parse: vi.fn().mockImplementation((v) => ({ toString: () => v })), + }; +}); +describe('ChangelogService', () => { + let service: ChangelogService; + + beforeEach(() => { + service = new ChangelogService(); + // Mock fetch globally + global.fetch = vi.fn(); + }); + + describe('getLatestChangelogId', () => { + it('should return the id of the first changelog item', async () => { + const mockIndex = [{ id: 'latest' }, { id: 'older' }]; + vi.spyOn(service, 'getChangelogIndex').mockResolvedValue(mockIndex as ChangelogIndexItem[]); + + const result = await service.getLatestChangelogId(); + expect(result).toBe('latest'); + }); + + it('should return undefined if the index is empty', async () => { + vi.spyOn(service, 'getChangelogIndex').mockResolvedValue([]); + + const result = await service.getLatestChangelogId(); + expect(result).toBeUndefined(); + }); + }); + + describe('getChangelogIndex', () => { + it('should fetch and merge changelog data', async () => { + const mockResponse = { + json: vi.fn().mockResolvedValue({ + cloud: [{ id: 'cloud1', date: '2023-01-01', versionRange: ['1.0.0'] }], + community: [{ id: 'community1', date: '2023-01-02', versionRange: ['1.1.0'] }], + }), + }; + (global.fetch as any).mockResolvedValue(mockResponse); + + const result = await service.getChangelogIndex(); + expect(result).toHaveLength(2); + expect(result[0].id).toBe('community1'); + expect(result[1].id).toBe('cloud1'); + }); + + it('should handle fetch errors', async () => { + (global.fetch as any).mockRejectedValue(new Error('Fetch failed')); + + const result = await service.getChangelogIndex(); + expect(result).toBe(false); + }); + }); + + describe('getIndexItemById', () => { + it('should return the correct item by id', async () => { + const mockIndex = [ + { id: 'item1', date: '2023-01-01', versionRange: ['1.0.0'] }, + { id: 'item2', date: '2023-01-02', versionRange: ['1.1.0'] }, + ]; + vi.spyOn(service, 'getChangelogIndex').mockResolvedValue(mockIndex as ChangelogIndexItem[]); + + const result = await service.getIndexItemById('item2'); + expect(result).toEqual({ id: 'item2', date: '2023-01-02', versionRange: ['1.1.0'] }); + }); + + it('should return undefined for non-existent id', async () => { + vi.spyOn(service, 'getChangelogIndex').mockResolvedValue([]); + + const result = await service.getIndexItemById('nonexistent'); + expect(result).toBeUndefined(); + }); + }); + + describe('getPostById', () => { + it('should fetch and parse post content', async () => { + vi.spyOn(service, 'getIndexItemById').mockResolvedValue({ + id: 'post1', + date: '2023-01-01', + versionRange: ['1.0.0'], + } as ChangelogIndexItem); + + const mockResponse = { + text: vi.fn().mockResolvedValue('# Post Title\nPost content'), + }; + (global.fetch as any).mockResolvedValue(mockResponse); + + const result = await service.getPostById('post1'); + expect(result).toMatchObject({ + content: 'Post content', + date: '2023-01-01', + description: 'Post content', + image: undefined, + rawTitle: 'Post Title', + tags: ['changelog'], + title: 'Post Title', + }); + }); + + it('should handle fetch errors', async () => { + vi.spyOn(service, 'getIndexItemById').mockResolvedValue({} as ChangelogIndexItem); + (global.fetch as any).mockRejectedValue(new Error('Fetch failed')); + + const result = await service.getPostById('error'); + expect(result).toBe(false); + }); + }); + + // Additional tests for private methods if they were public + describe('mergeChangelogs', () => { + it('should merge and sort changelogs correctly', () => { + const cloud = [{ id: 'cloud1', date: '2023-01-01', versionRange: ['1.0.0'] }]; + const community = [{ id: 'community1', date: '2023-01-02', versionRange: ['1.1.0'] }]; + + // @ts-ignore - accessing private method for testing + const result = service.mergeChangelogs(cloud, community); + expect(result).toHaveLength(2); + expect(result[0].id).toBe('community1'); + expect(result[1].id).toBe('cloud1'); + }); + }); + + describe('formatVersionRange', () => { + it('should format version range correctly', () => { + // @ts-ignore - accessing private method for testing + const result = service.formatVersionRange(['1.0.0', '1.1.0']); + expect(result).toEqual(['1.1.0', '1.0.0']); + }); + + it('should return single version as is', () => { + // @ts-ignore - accessing private method for testing + const result = service.formatVersionRange(['1.0.0']); + expect(result).toEqual(['1.0.0']); + }); + }); +}); diff --git a/src/services/changelog.ts b/src/server/services/changelog/index.ts similarity index 83% rename from src/services/changelog.ts rename to src/server/services/changelog/index.ts index faa21b8e3eea..217b63fbc80c 100644 --- a/src/services/changelog.ts +++ b/src/server/services/changelog/index.ts @@ -5,16 +5,12 @@ import semver from 'semver'; import urlJoin from 'url-join'; import { Locales } from '@/locales/resources'; +import { ChangelogIndexItem } from '@/types/changelog'; const BASE_URL = 'https://raw.githubusercontent.com'; const LAST_MODIFIED = new Date().toISOString(); -export interface ChangelogIndexItem { - date: string; - id: string; - image?: string; - versionRange: string[]; -} +const revalidate: number = 12 * 3600; export interface ChangelogConfig { branch: string; @@ -26,28 +22,7 @@ export interface ChangelogConfig { user: string; } -export interface StaticChangelogItem { - children: { - features?: string[]; - fixes?: string[]; - improvements?: string[]; - }; - date: string; - version: string; -} - -export interface ChangelogDetailsItem { - children: string[]; - version: string; -} - -export interface ChangelogDetails { - features: ChangelogDetailsItem[]; - fixes: ChangelogDetailsItem[]; - improvements: ChangelogDetailsItem[]; -} - -class ChangelogService { +export class ChangelogService { config: ChangelogConfig = { branch: process.env.DOCS_BRANCH || 'main', changelogPath: 'changelog', @@ -58,8 +33,6 @@ class ChangelogService { user: 'lobehub', }; - revalidate: number = 12 * 3600; - async getLatestChangelogId() { const index = await this.getChangelogIndex(); return index[0]?.id; @@ -70,12 +43,12 @@ class ChangelogService { const url = this.genUrl(urlJoin(this.config.docsPath, 'index.json')); const res = await fetch(url, { - next: { revalidate: this.revalidate }, + next: { revalidate }, }); const data = await res.json(); - return this.mergeChangelogs(data.cloud, data.community); + return this.mergeChangelogs(data.cloud, data.community).slice(0, 5); } catch { console.error('Error getting changlog index'); return false as any; @@ -95,7 +68,7 @@ class ChangelogService { const url = this.genUrl(urlJoin(this.config.docsPath, filename)); const response = await fetch(url, { - next: { revalidate: this.revalidate }, + next: { revalidate }, }); const text = await response.text(); const { data, content } = matter(text); @@ -180,5 +153,3 @@ class ChangelogService { return urlJoin(BASE_URL, this.config.user, this.config.repo, this.config.branch, path); } } - -export const changelogService = new ChangelogService(); diff --git a/src/server/services/discover/index.test.ts b/src/server/services/discover/index.test.ts index 9a123ed778eb..e1715aa0ca30 100644 --- a/src/server/services/discover/index.test.ts +++ b/src/server/services/discover/index.test.ts @@ -1,7 +1,6 @@ // @vitest-environment node import { beforeEach, describe, expect, it, vi } from 'vitest'; -import { DEFAULT_LANG } from '@/const/locale'; import { AssistantCategory, PluginCategory } from '@/types/discover'; import { DiscoverService } from './index'; diff --git a/src/store/global/action.ts b/src/store/global/action.ts index e5a1d75da729..86e02212e31b 100644 --- a/src/store/global/action.ts +++ b/src/store/global/action.ts @@ -8,7 +8,6 @@ import { INBOX_SESSION_ID } from '@/const/session'; import { SESSION_CHAT_URL } from '@/const/url'; import { CURRENT_VERSION } from '@/const/version'; import { useOnlyFetchOnceSWR } from '@/libs/swr'; -import { changelogService } from '@/services/changelog'; import { globalService } from '@/services/global'; import type { GlobalStore } from '@/store/global/index'; import { merge } from '@/utils/merge'; @@ -98,8 +97,9 @@ export const globalActionSlice: StateCreator< get().statusStorage.saveToLocalStorage(nextStatus); }, - useCheckLatestChangelogId: () => - useSWR('changelog', async () => changelogService.getLatestChangelogId()), + + // TODO: 从初始化请求获取 + useCheckLatestChangelogId: () => useSWR('changelog', async () => []), useCheckLatestVersion: (enabledCheck = true) => useSWR(enabledCheck ? 'checkLatestVersion' : null, globalService.getLatestVersion, { diff --git a/src/types/changelog.ts b/src/types/changelog.ts new file mode 100644 index 000000000000..37f54c28714a --- /dev/null +++ b/src/types/changelog.ts @@ -0,0 +1,6 @@ +export interface ChangelogIndexItem { + date: string; + id: string; + image?: string; + versionRange: string[]; +} diff --git a/vitest.config.ts b/vitest.config.ts index 54e43f578dea..c0434eb18f82 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -31,7 +31,7 @@ export default defineConfig({ '**/dist/**', '**/build/**', 'src/database/server/**/**', - 'src/server/services/!(discover)/**/**', + 'src/server/services/!(discover|changelog)/**/**', ], globals: true, server: {