replace marked with react-markdown #42

Merged
ashkan-o merged 1 commit from main into main 2024-12-03 10:18:09 -05:00
14 changed files with 216 additions and 82 deletions

BIN
bun.lockb

Binary file not shown.

95
components/ui/dialog.jsx Normal file
View file

@ -0,0 +1,95 @@
import * as React from "react"
import * as DialogPrimitive from "@radix-ui/react-dialog"
import { X } from "lucide-react"
import { cn } from "@/lib/utils"
const Dialog = DialogPrimitive.Root
const DialogTrigger = DialogPrimitive.Trigger
const DialogPortal = DialogPrimitive.Portal
const DialogClose = DialogPrimitive.Close
const DialogOverlay = React.forwardRef(({ className, ...props }, ref) => (
<DialogPrimitive.Overlay
ref={ref}
className={cn(
"fixed inset-0 z-50 bg-black/80 flex animate-in fade-in-0 overflow-auto",
className
)}
{...props} />
))
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
const DialogContent = React.forwardRef(({ className, children, ...props }, ref) => (
<DialogPortal>
<DialogOverlay>
<DialogPrimitive.Content
ref={ref}
className={cn(
"relative m-auto z-50 grid w-full max-w-lg gap-4 border bg-background p-6 shadow-lg sm:rounded-lg",
className
)}
{...props}>
{children}
<DialogPrimitive.Close
className="absolute end-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none bg-accent text-muted-foreground">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogOverlay>
</DialogPortal>
))
DialogContent.displayName = DialogPrimitive.Content.displayName
const DialogHeader = ({
className,
...props
}) => (
<div
className={cn("flex flex-col space-y-1.5 text-center sm:text-start", className)}
{...props} />
)
DialogHeader.displayName = "DialogHeader"
const DialogFooter = ({
className,
...props
}) => (
<div
className={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)}
{...props} />
)
DialogFooter.displayName = "DialogFooter"
const DialogTitle = React.forwardRef(({ className, ...props }, ref) => (
<DialogPrimitive.Title
ref={ref}
className={cn("text-lg font-semibold leading-none tracking-tight", className)}
{...props} />
))
DialogTitle.displayName = DialogPrimitive.Title.displayName
const DialogDescription = React.forwardRef(({ className, ...props }, ref) => (
<DialogPrimitive.Description
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props} />
))
DialogDescription.displayName = DialogPrimitive.Description.displayName
export {
Dialog,
DialogPortal,
DialogOverlay,
DialogTrigger,
DialogClose,
DialogContent,
DialogHeader,
DialogFooter,
DialogTitle,
DialogDescription,
}

View file

@ -11,6 +11,10 @@ const category = {
en: 'Multimedia', en: 'Multimedia',
fa: 'چندرسانه‌ای' fa: 'چندرسانه‌ای'
}, },
network: {
en: 'Network',
fa: 'شبکه'
},
} }
const communityData = { const communityData = {
@ -18,39 +22,34 @@ const communityData = {
apps: [ apps: [
{ {
name: { name: {
en: 'Neovim', en: 'Carburetor',
fa: 'نئوویم', fa: 'کاربراتور',
}, },
desc: { desc: {
en: 'Powerful and extendable text editor', en: 'Browse anonymously',
fa: 'ویرایشگر متن قدرتمند و قابل تمدید', fa: 'در اینترنت به شکل ناشناس کاوش کنید',
}, },
cat: category.develop, cat: category.network,
href: '#' url: 'https://tractor.frama.io/carburetor/',
}, repo: 'https://framagit.org/tractor/carburetor',
{ page: {
name: { en: `
en: 'Discord', Discover anonymous browsing with Carburetor on your phones and computers. Tailored for GNOME, it's your hidden superhero for online adventures, powered by TOR. Carburetor provides a local TOR proxy that hides your IP, ensuring your Internet activities remain encrypted, anonymized, and untraceable. Don't get your hands dirty with system files anymore just tap into the app, keeping your online world safe and private. Customize settings if you want, but you don't have to. Carburetor is Free Software and puts you in control. No worries, just enjoy your anonymous browsing!
fa: 'دیسکورد', ## Installation
}, You can install Carburetor using this command:
desc: { \`\`\`
en: 'Voice & text chat application', sudo pacman -S carburetor
fa: 'اپلیکیشن چت صوتی و متنی', \`\`\`
}, `,
cat: category.communication, fa: `
href: '#' کاربراتور پیشانهای گرافیکی برای تراکتور است. این کاره با GTK4 و Libadwaita نوشته شده وقشر هدف آن، کاربران گنوم روی تلفنهای همراه هستند تا بگذارد به سادگی به مسیریابی پیازی (تور وصل شوند. کاربراتور میتواند روی میزکارها نیز اجرا شده تا بگذارد کاربران بدون دستکاری در سامانه، یک پیشکار تور در فضای کاربری ایجاد کنند.
}, ## نصب
{ برای نصب کاربراتور دستور زیر را در ترمینال وارد کنید:
name: { \`\`\`
en: 'OBS Studio', sudo pacman -S carburetor
fa: 'OBS استودیو', \`\`\`
}, `
desc: { }
en: 'Screen recorder and streaming software',
fa: 'نرم‌افزار ضبط صفحه نمایش و پخش زنده',
},
cat: category.multimedia,
href: '#'
}, },
] ]
} }

View file

@ -1,5 +1,3 @@
import { marked } from "marked"
const en = ` const en = `
# OS Privacy Policy # OS Privacy Policy
Check out the Privacy Policy of Parch Linux (the operating system). Check out the Privacy Policy of Parch Linux (the operating system).
@ -37,7 +35,4 @@ const fa = `
از آنجا که هیچ اطلاعاتی جمعآوری نمیشود، هیچ اطلاعاتی نیز محافظت نمیشود. از آنجا که هیچ اطلاعاتی جمعآوری نمیشود، هیچ اطلاعاتی نیز محافظت نمیشود.
` `
export default { export default { en, fa }
en: marked.parse(en),
fa: marked.parse(fa)
}

View file

@ -1,5 +1,3 @@
import { marked } from "marked"
const en = ` const en = `
# Terms of Service # Terms of Service
@ -64,7 +62,4 @@ const fa = `
برای سوالات در مورد این شرایط خدمات، لطفاً با ما یا انجمن پارچ لینوکس تماس بگیرید. برای سوالات در مورد این شرایط خدمات، لطفاً با ما یا انجمن پارچ لینوکس تماس بگیرید.
` `
export default { export default { en, fa }
en: marked.parse(en),
fa: marked.parse(fa)
}

View file

@ -1,5 +1,3 @@
import { marked } from "marked"
const en = ` const en = `
# Whitepaper # Whitepaper
Everything about Parch Linux. Everything about Parch Linux.
@ -294,7 +292,4 @@ Pacman یک مدیر بسته قدرتمند است که نصب، به‌روز
با ارائه ترکیبی متعادل از سهولت استفاده و قابلیتهای پیشرفته، پارچلینوکس هدف دارد که توزیع لینوکس مورد نظر برای طیف وسیعی از کاربران، از مبتدیان تا توسعهدهندگان، باشد. این وایتپیپر نقاط قوت و پتانسیل پارچلینوکس را برجسته میکند و از کاربران و مشارکتکنندگان دعوت میکند که بخشی از سفر آن باشند. با ارائه ترکیبی متعادل از سهولت استفاده و قابلیتهای پیشرفته، پارچلینوکس هدف دارد که توزیع لینوکس مورد نظر برای طیف وسیعی از کاربران، از مبتدیان تا توسعهدهندگان، باشد. این وایتپیپر نقاط قوت و پتانسیل پارچلینوکس را برجسته میکند و از کاربران و مشارکتکنندگان دعوت میکند که بخشی از سفر آن باشند.
` `
export default { export default { en, fa }
en: marked.parse(en),
fa: marked.parse(fa)
}

View file

@ -36,7 +36,10 @@ export default {
community: { community: {
title: 'Parch Linux Community Software', title: 'Parch Linux Community Software',
search: 'Search apps...' search: 'Search apps...',
url: 'URL',
repo: 'Repository',
no_result: 'No result :('
}, },
join: { join: {

View file

@ -36,7 +36,10 @@ export default {
community: { community: {
title: 'برنامه‌های جامعه پارچ لینوکس', title: 'برنامه‌های جامعه پارچ لینوکس',
search: 'جستجوی برنامه‌ها...' search: 'جستجوی برنامه‌ها...',
url: 'نشانی',
repo: 'مخزن',
no_result: 'نتیجه‌ای یافت نشد :('
}, },

View file

@ -10,16 +10,17 @@
}, },
"dependencies": { "dependencies": {
"@icons-pack/react-simple-icons": "^10.1.0", "@icons-pack/react-simple-icons": "^10.1.0",
"@radix-ui/react-dialog": "^1.1.2",
"@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-slot": "^1.1.0",
"@tailwindcss/typography": "^0.5.15", "@tailwindcss/typography": "^0.5.15",
"class-variance-authority": "^0.7.0", "class-variance-authority": "^0.7.0",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"lucide-react": "^0.456.0", "lucide-react": "^0.456.0",
"marked": "^15.0.0",
"next": "15.0.3", "next": "15.0.3",
"next-themes": "^0.4.3", "next-themes": "^0.4.3",
"react": "19.0.0-rc-66855b96-20241106", "react": "19.0.0-rc-66855b96-20241106",
"react-dom": "19.0.0-rc-66855b96-20241106", "react-dom": "19.0.0-rc-66855b96-20241106",
"react-markdown": "^9.0.1",
"rosetta": "^1.1.0", "rosetta": "^1.1.0",
"tailwind-merge": "^2.5.4", "tailwind-merge": "^2.5.4",
"tailwindcss-animate": "^1.0.7" "tailwindcss-animate": "^1.0.7"

View file

@ -1,22 +1,52 @@
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Card, CardHeader } from "@/components/ui/card"; import { Card, CardHeader } from "@/components/ui/card";
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"
import { SiGitlab } from "@icons-pack/react-simple-icons"; import { SiGitlab } from "@icons-pack/react-simple-icons";
import { useTranslation } from "@/utils/translation"; import { useTranslation } from "@/utils/translation";
import { useState } from "react"; import { useEffect, useState } from "react";
import Image from "next/image";
import Link from "next/link";
import Markdown from "react-markdown";
import communityData from "@/data/community"; import communityData from "@/data/community";
const ProjectDialog = ({ name, page, url, repo }) => {
const { t } = useTranslation();
return (
<DialogContent>
<DialogHeader>
<DialogTitle>{name}</DialogTitle>
</DialogHeader>
<DialogDescription className="space-y-4">
<Image src="/carburetor.png" alt="Carburetor" width={800} height={450} className="max-w-full" />
<div className="bg-muted rounded-xl shadow-inner text-lg">
<ul className="divide-y-2 divide-background *:p-4">
<li>{t("community.url")}: <Link href={url} className="text-blue-500" dir="ltr">{url}</Link></li>
<li>{t("community.repo")}: <Link href={repo} className="text-blue-500" dir="ltr">{repo}</Link></li>
</ul>
</div>
<article className="prose lg:prose-lg dark:prose-invert prose-pre:[direction:ltr]">
<Markdown>{page}</Markdown>
</article>
</DialogDescription>
</DialogContent>
)
}
export default function Community() { export default function Community() {
const { t, lang } = useTranslation(); const { t, lang } = useTranslation();
const [apps, setApps] = useState(communityData.apps); const [apps, setApps] = useState(communityData.apps);
const [q, setQ] = useState('');
const search = q => { useEffect(() => {
const qq = q.trim().toLowerCase()
setApps(communityData.apps.filter(app => ( setApps(communityData.apps.filter(app => (
app.name[lang].toLowerCase().includes(q) || app.name[lang].toLowerCase().includes(qq) ||
app.desc[lang].toLowerCase().includes(q) || app.desc[lang].toLowerCase().includes(qq) ||
app.cat[lang].toLowerCase().includes(q) app.cat[lang].toLowerCase().includes(qq)
))) )))
} }, [q])
return ( return (
<main className="py-12 md:py-24 lg:py-32"> <main className="py-12 md:py-24 lg:py-32">
@ -24,33 +54,48 @@ export default function Community() {
<h2 className="text-3xl font-bold sm:text-4xl md:text-5xl text-center mb-8"> <h2 className="text-3xl font-bold sm:text-4xl md:text-5xl text-center mb-8">
{t("community.title")} {t("community.title")}
</h2> </h2>
<div className="my-12"> <div className="my-12 max-w-2xl mx-auto">
<input type="search" <input type="search"
className="bg-background rounded-full border shadow-lg px-5 py-3 block mx-auto w-[40rem] max-w-full" className="bg-background rounded-full border shadow px-5 py-3 block mx-auto w-full"
placeholder={t("community.search")} placeholder={t("community.search")}
onInput={(e) => search(e.target.value.trim().toLowerCase())} /> value={q} onInput={(e) => setQ(e.target.value)} />
<div className="flex flex-wrap gap-2 mt-3">
{communityData.categories.map(cat => (
cat[lang] == q
? <Button className="rounded-full" onClick={() => setQ('')}>{cat[lang]}</Button>
: <Button variant="outline" className="rounded-full" onClick={() => setQ(cat[lang])}>{cat[lang]}</Button>
))}
</div>
</div> </div>
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6"> <div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6">
{apps.map(app => ( {
<Card className="animate-in fade-in zoom-in duration-300"> apps.length
<CardHeader> ? apps.map(app => (
<div className="flex gap-2"> <Dialog>
<div className="grow space-y-2"> <DialogTrigger>
<h3 className="text-lg font-bold">{app.name[lang]}</h3> <Card className="animate-in fade-in zoom-in duration-300">
<p className="text-sm text-muted-foreground mt-2">{app.desc[lang]}</p> <CardHeader>
<p className="">{app.cat[lang]}</p> <div className="space-y-2">
</div> <h3 className="text-lg font-bold">{app.name[lang]}</h3>
<div className="shrink-0"> <p className="text-sm text-muted-foreground mt-2">{app.desc[lang]}</p>
<Button variant="outline" size="icon" asChild> <p className="">{app.cat[lang]}</p>
<a href={app.href} target="_blank"> </div>
<SiGitlab className="h-4 w-4" /> </CardHeader>
</a> </Card>
</Button> </DialogTrigger>
</div> <ProjectDialog
name={app.name[lang]}
url={app.url}
repo={app.repo}
page={app.page[lang]} />
</Dialog>
))
: (
<div className="col-span-full flex">
<div className="m-auto">{t("community.no_result")}</div>
</div> </div>
</CardHeader> )
</Card> }
))}
</div> </div>
</div> </div>
</main> </main>

View file

@ -1,6 +1,7 @@
import { useTranslation } from "@/utils/translation" import { useTranslation } from "@/utils/translation"
import { Card } from "@/components/ui/card" import { Card } from "@/components/ui/card"
import privacy from "@/data/privacy" import privacy from "@/data/privacy"
import Markdown from "react-markdown";
export default function DownloadPage() { export default function DownloadPage() {
const { lang } = useTranslation(); const { lang } = useTranslation();
@ -8,7 +9,7 @@ export default function DownloadPage() {
return ( return (
<main className="p-4 md:p-8 lg:p-16"> <main className="p-4 md:p-8 lg:p-16">
<Card className="prose lg:prose-lg dark:prose-invert !container mx-auto p-12"> <Card className="prose lg:prose-lg dark:prose-invert !container mx-auto p-12">
<div className="" dangerouslySetInnerHTML={{ __html: privacy[lang] }}></div> <Markdown>{privacy[lang]}</Markdown>
</Card> </Card>
</main> </main>
) )

View file

@ -1,6 +1,7 @@
import { useTranslation } from "@/utils/translation" import { useTranslation } from "@/utils/translation"
import { Card } from "@/components/ui/card" import { Card } from "@/components/ui/card"
import tos from "@/data/tos" import tos from "@/data/tos"
import Markdown from "react-markdown";
export default function DownloadPage() { export default function DownloadPage() {
const { lang } = useTranslation(); const { lang } = useTranslation();
@ -8,7 +9,7 @@ export default function DownloadPage() {
return ( return (
<main className="p-4 md:p-8 lg:p-16"> <main className="p-4 md:p-8 lg:p-16">
<Card className="prose lg:prose-lg dark:prose-invert !container mx-auto p-12"> <Card className="prose lg:prose-lg dark:prose-invert !container mx-auto p-12">
<div className="" dangerouslySetInnerHTML={{ __html: tos[lang] }}></div> <Markdown>{tos[lang]}</Markdown>
</Card> </Card>
</main> </main>
) )

View file

@ -1,6 +1,7 @@
import { useTranslation } from "@/utils/translation" import { useTranslation } from "@/utils/translation"
import { Card } from "@/components/ui/card" import { Card } from "@/components/ui/card"
import whitepaper from "@/data/whitepaper" import whitepaper from "@/data/whitepaper"
import Markdown from "react-markdown";
export default function DownloadPage() { export default function DownloadPage() {
const { lang } = useTranslation(); const { lang } = useTranslation();
@ -8,7 +9,7 @@ export default function DownloadPage() {
return ( return (
<main className="p-4 md:p-8 lg:p-16"> <main className="p-4 md:p-8 lg:p-16">
<Card className="prose lg:prose-lg dark:prose-invert !container mx-auto p-12"> <Card className="prose lg:prose-lg dark:prose-invert !container mx-auto p-12">
<div className="" dangerouslySetInnerHTML={{ __html: whitepaper[lang] }}></div> <Markdown>{whitepaper[lang]}</Markdown>
</Card> </Card>
</main> </main>
) )

BIN
public/carburetor.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB