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',
fa: 'چندرسانه‌ای'
},
network: {
en: 'Network',
fa: 'شبکه'
},
}
const communityData = {
@ -18,39 +22,34 @@ const communityData = {
apps: [
{
name: {
en: 'Neovim',
fa: 'نئوویم',
en: 'Carburetor',
fa: 'کاربراتور',
},
desc: {
en: 'Powerful and extendable text editor',
fa: 'ویرایشگر متن قدرتمند و قابل تمدید',
en: 'Browse anonymously',
fa: 'در اینترنت به شکل ناشناس کاوش کنید',
},
cat: category.develop,
href: '#'
},
{
name: {
en: 'Discord',
fa: 'دیسکورد',
},
desc: {
en: 'Voice & text chat application',
fa: 'اپلیکیشن چت صوتی و متنی',
},
cat: category.communication,
href: '#'
},
{
name: {
en: 'OBS Studio',
fa: 'OBS استودیو',
},
desc: {
en: 'Screen recorder and streaming software',
fa: 'نرم‌افزار ضبط صفحه نمایش و پخش زنده',
},
cat: category.multimedia,
href: '#'
cat: category.network,
url: 'https://tractor.frama.io/carburetor/',
repo: 'https://framagit.org/tractor/carburetor',
page: {
en: `
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!
## Installation
You can install Carburetor using this command:
\`\`\`
sudo pacman -S carburetor
\`\`\`
`,
fa: `
کاربراتور پیشانهای گرافیکی برای تراکتور است. این کاره با GTK4 و Libadwaita نوشته شده وقشر هدف آن، کاربران گنوم روی تلفنهای همراه هستند تا بگذارد به سادگی به مسیریابی پیازی (تور وصل شوند. کاربراتور میتواند روی میزکارها نیز اجرا شده تا بگذارد کاربران بدون دستکاری در سامانه، یک پیشکار تور در فضای کاربری ایجاد کنند.
## نصب
برای نصب کاربراتور دستور زیر را در ترمینال وارد کنید:
\`\`\`
sudo pacman -S carburetor
\`\`\`
`
}
},
]
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,22 +1,52 @@
import { Button } from "@/components/ui/button";
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 { 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";
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() {
const { t, lang } = useTranslation();
const [apps, setApps] = useState(communityData.apps);
const [q, setQ] = useState('');
const search = q => {
useEffect(() => {
const qq = q.trim().toLowerCase()
setApps(communityData.apps.filter(app => (
app.name[lang].toLowerCase().includes(q) ||
app.desc[lang].toLowerCase().includes(q) ||
app.cat[lang].toLowerCase().includes(q)
app.name[lang].toLowerCase().includes(qq) ||
app.desc[lang].toLowerCase().includes(qq) ||
app.cat[lang].toLowerCase().includes(qq)
)))
}
}, [q])
return (
<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">
{t("community.title")}
</h2>
<div className="my-12">
<div className="my-12 max-w-2xl mx-auto">
<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")}
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 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">
<CardHeader>
<div className="flex gap-2">
<div className="grow space-y-2">
<h3 className="text-lg font-bold">{app.name[lang]}</h3>
<p className="text-sm text-muted-foreground mt-2">{app.desc[lang]}</p>
<p className="">{app.cat[lang]}</p>
</div>
<div className="shrink-0">
<Button variant="outline" size="icon" asChild>
<a href={app.href} target="_blank">
<SiGitlab className="h-4 w-4" />
</a>
</Button>
</div>
{
apps.length
? apps.map(app => (
<Dialog>
<DialogTrigger>
<Card className="animate-in fade-in zoom-in duration-300">
<CardHeader>
<div className="space-y-2">
<h3 className="text-lg font-bold">{app.name[lang]}</h3>
<p className="text-sm text-muted-foreground mt-2">{app.desc[lang]}</p>
<p className="">{app.cat[lang]}</p>
</div>
</CardHeader>
</Card>
</DialogTrigger>
<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>
</CardHeader>
</Card>
))}
)
}
</div>
</div>
</main>

View file

@ -1,6 +1,7 @@
import { useTranslation } from "@/utils/translation"
import { Card } from "@/components/ui/card"
import privacy from "@/data/privacy"
import Markdown from "react-markdown";
export default function DownloadPage() {
const { lang } = useTranslation();
@ -8,7 +9,7 @@ export default function DownloadPage() {
return (
<main className="p-4 md:p-8 lg:p-16">
<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>
</main>
)

View file

@ -1,6 +1,7 @@
import { useTranslation } from "@/utils/translation"
import { Card } from "@/components/ui/card"
import tos from "@/data/tos"
import Markdown from "react-markdown";
export default function DownloadPage() {
const { lang } = useTranslation();
@ -8,7 +9,7 @@ export default function DownloadPage() {
return (
<main className="p-4 md:p-8 lg:p-16">
<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>
</main>
)

View file

@ -1,6 +1,7 @@
import { useTranslation } from "@/utils/translation"
import { Card } from "@/components/ui/card"
import whitepaper from "@/data/whitepaper"
import Markdown from "react-markdown";
export default function DownloadPage() {
const { lang } = useTranslation();
@ -8,7 +9,7 @@ export default function DownloadPage() {
return (
<main className="p-4 md:p-8 lg:p-16">
<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>
</main>
)

BIN
public/carburetor.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB