From 2ca299224d517ae20dfe901babd96a3a638a3e97 Mon Sep 17 00:00:00 2001 From: turbolnk Date: Tue, 25 Feb 2025 18:38:25 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(ui):=20Optimi?= =?UTF-8?q?ze=20document=20display?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/(main)/(user)/document/close-icon.tsx | 35 +++++++++++ .../(user)/document/document-button.tsx | 40 ++---------- .../(user)/document/tutorial-button.tsx | 62 ++++++------------- apps/user/components/language-switch.tsx | 1 - apps/user/package.json | 1 + apps/user/utils/tutorial.ts | 56 +++++++++++++---- 6 files changed, 101 insertions(+), 94 deletions(-) create mode 100644 apps/user/app/(main)/(user)/document/close-icon.tsx diff --git a/apps/user/app/(main)/(user)/document/close-icon.tsx b/apps/user/app/(main)/(user)/document/close-icon.tsx new file mode 100644 index 0000000..da87d32 --- /dev/null +++ b/apps/user/app/(main)/(user)/document/close-icon.tsx @@ -0,0 +1,35 @@ +import { cn } from "@workspace/ui/lib/utils"; +import { motion } from "framer-motion"; + +export const CloseIcon = ({ className }: { className?: string }) => { + return ( + + + + + + ); +}; diff --git a/apps/user/app/(main)/(user)/document/document-button.tsx b/apps/user/app/(main)/(user)/document/document-button.tsx index 46db2bb..1cf56c2 100644 --- a/apps/user/app/(main)/(user)/document/document-button.tsx +++ b/apps/user/app/(main)/(user)/document/document-button.tsx @@ -11,6 +11,7 @@ import { formatDate } from '@workspace/ui/utils'; import { AnimatePresence, motion } from 'framer-motion'; import { useTranslations } from 'next-intl'; import { RefObject, useEffect, useId, useRef, useState } from 'react'; +import { CloseIcon } from './close-icon'; export function DocumentButton({ items }: { items: API.Document[] }) { const t = useTranslations('document'); @@ -78,7 +79,7 @@ export function DocumentButton({ items }: { items: API.Document[] }) { duration: 0.05, }, }} - className='bg-foreground absolute right-2 top-2 flex h-6 w-6 items-center justify-center rounded-full' + className='bg-foreground absolute right-2 top-2 flex h-6 w-6 items-center justify-center rounded-full text-white dark:text-black' onClick={() => setActive(null)} > @@ -104,7 +105,7 @@ export function DocumentButton({ items }: { items: API.Document[] }) {
- {item.title.split('')[0]} + {item.title.split('')[0]}
@@ -135,37 +136,4 @@ export function DocumentButton({ items }: { items: API.Document[] }) { ); -} - -export const CloseIcon = () => { - return ( - - - - - - ); -}; +} \ No newline at end of file diff --git a/apps/user/app/(main)/(user)/document/tutorial-button.tsx b/apps/user/app/(main)/(user)/document/tutorial-button.tsx index e3b8ad9..4dd93a7 100644 --- a/apps/user/app/(main)/(user)/document/tutorial-button.tsx +++ b/apps/user/app/(main)/(user)/document/tutorial-button.tsx @@ -2,7 +2,7 @@ import { getTutorial } from '@/utils/tutorial'; import { useQuery } from '@tanstack/react-query'; -import { Avatar, AvatarFallback } from '@workspace/ui/components/avatar'; +import { Avatar, AvatarFallback, AvatarImage } from '@workspace/ui/components/avatar'; import { buttonVariants } from '@workspace/ui/components/button'; import { Markdown } from '@workspace/ui/custom-components/markdown'; import { useOutsideClick } from '@workspace/ui/hooks/use-outside-click'; @@ -10,10 +10,14 @@ import { cn } from '@workspace/ui/lib/utils'; import { AnimatePresence, motion } from 'framer-motion'; import { useTranslations } from 'next-intl'; import { RefObject, useEffect, useId, useRef, useState } from 'react'; +import { CloseIcon } from './close-icon'; +import { formatDate } from '@workspace/ui/utils'; interface Item { path: string; title: string; + updated_at?: string; + icon?: string; } export function TutorialButton({ items }: { items: Item[] }) { const t = useTranslations('document'); @@ -80,7 +84,7 @@ export function TutorialButton({ items }: { items: Item[] }) { duration: 0.05, }, }} - className='bg-foreground absolute right-2 top-2 flex h-6 w-6 items-center justify-center rounded-full' + className='bg-foreground absolute right-2 top-2 flex h-6 w-6 items-center justify-center rounded-full text-white dark:text-black' onClick={() => setActive(null)} > @@ -105,7 +109,7 @@ export function TutorialButton({ items }: { items: Item[] }) { }, }} > - {data || ''} + {data?.content || ''}
@@ -122,19 +126,22 @@ export function TutorialButton({ items }: { items: Item[] }) {
- {item.title.split('')[0]} + + {item.title.split('')[0]}
{item.title} - {/* - {formatDate(item.updated_at)} - */} + {item.updated_at && ( + + {formatDate(new Date(item.updated_at), false)} + + )}
); -} - -export const CloseIcon = () => { - return ( - - - - - - ); -}; +} \ No newline at end of file diff --git a/apps/user/components/language-switch.tsx b/apps/user/components/language-switch.tsx index 58fc344..5908cfd 100644 --- a/apps/user/components/language-switch.tsx +++ b/apps/user/components/language-switch.tsx @@ -43,7 +43,6 @@ const languages = { export default function LanguageSwitch() { const locale = useLocale(); const country = getCountry(locale); - const t = useTranslations('language'); const router = useRouter(); const handleLanguageChange = (value: string) => { diff --git a/apps/user/package.json b/apps/user/package.json index 171f5a6..4a562e1 100644 --- a/apps/user/package.json +++ b/apps/user/package.json @@ -18,6 +18,7 @@ "ahooks": "^3.8.4", "axios": "^1.7.9", "framer-motion": "^11.16.1", + "gray-matter": "^4.0.3", "lucide-react": "^0.469.0", "next": "^15.1.4", "next-intl": "^3.26.3", diff --git a/apps/user/utils/tutorial.ts b/apps/user/utils/tutorial.ts index ad3f582..999c4f1 100644 --- a/apps/user/utils/tutorial.ts +++ b/apps/user/utils/tutorial.ts @@ -1,6 +1,9 @@ +import matter from 'gray-matter'; + const BASE_URL = 'https://cdn.jsdelivr.net/gh/perfect-panel/ppanel-tutorial'; async function getVersion() { + // API rate limit: 60 requests per hour const response = await fetch( 'https://api.github.com/repos/perfect-panel/ppanel-tutorial/commits', ); @@ -8,17 +11,33 @@ async function getVersion() { return json[0].sha; } -export async function getTutorial(path: string): Promise { - const version = await getVersion(); +async function getVersionPath() { + return getVersion() + .then(version => `${BASE_URL}@${version}`) + .catch(error => { + console.warn('Error fetching the version:', error); + return BASE_URL; + }); +} + +export async function getTutorial(path: string): Promise<{ + config?: Record; + content: string; +}> { + const versionPath = await getVersionPath(); try { - const url = `${BASE_URL}@${version}/${path}`; + const url = `${versionPath}/${path}`; const response = await fetch(url); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const text = await response.text(); - const markdown = addPrefixToImageUrls(text, getUrlPrefix(url)); - return markdown; + const { data, content } = matter(text); + const markdown = addPrefixToImageUrls(content, getUrlPrefix(url)); + return { + config: data, + content: markdown, + }; } catch (error) { console.error('Error fetching the markdown file:', error); throw error; @@ -32,14 +51,25 @@ type TutorialItem = { }; export async function getTutorialList() { - return await getTutorial('SUMMARY.md').then((markdown) => { - const map = parseTutorialToMap(markdown); - map.forEach((value, key) => { - map.set( - key, - value.filter((item) => item.title !== 'README'), - ); - }); + return await getTutorial('SUMMARY.md').then(({ config, content }) => { + const navigation = config as Record | undefined; + let map = new Map(); + if (navigation) { + for (const value of Object.values(navigation)) { + for (const item of value) { + if (item.subItems) { + for (const subItem of item.subItems) { + if ("icon" in subItem && typeof subItem.icon === 'string' && !subItem.icon.startsWith('http')) { + subItem.icon = `${BASE_URL}/${subItem.icon}`; + } + } + } + } + } + map = new Map(Object.entries(navigation)); + } else { + map = parseTutorialToMap(content); + } return map; }); } From e11f18c0340eeec79a3a861a57bd2684795c1322 Mon Sep 17 00:00:00 2001 From: turbolnk Date: Tue, 25 Feb 2025 19:31:32 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20Reduce=20c?= =?UTF-8?q?ode=20complexity=20and=20improve=20readability?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/user/utils/tutorial.ts | 41 ++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/apps/user/utils/tutorial.ts b/apps/user/utils/tutorial.ts index 999c4f1..9f5b76b 100644 --- a/apps/user/utils/tutorial.ts +++ b/apps/user/utils/tutorial.ts @@ -50,28 +50,27 @@ type TutorialItem = { subItems?: TutorialItem[]; }; +const processIcon = (item: TutorialItem) => { + if ("icon" in item && typeof item.icon === 'string' && !item.icon.startsWith('http')) { + item.icon = `${BASE_URL}/${item.icon}`; + } +}; + export async function getTutorialList() { - return await getTutorial('SUMMARY.md').then(({ config, content }) => { - const navigation = config as Record | undefined; - let map = new Map(); - if (navigation) { - for (const value of Object.values(navigation)) { - for (const item of value) { - if (item.subItems) { - for (const subItem of item.subItems) { - if ("icon" in subItem && typeof subItem.icon === 'string' && !subItem.icon.startsWith('http')) { - subItem.icon = `${BASE_URL}/${subItem.icon}`; - } - } - } - } - } - map = new Map(Object.entries(navigation)); - } else { - map = parseTutorialToMap(content); - } - return map; - }); + const { config, content } = await getTutorial('SUMMARY.md'); + const navigation = config as Record | undefined; + + if (!navigation) { + return parseTutorialToMap(content); + } + + Object.values(navigation) + .flat() + .forEach(item => { + item.subItems?.forEach(processIcon); + }); + + return new Map(Object.entries(navigation)); } function parseTutorialToMap(markdown: string): Map {