replace marked with react-markdown #42
14 changed files with 216 additions and 82 deletions
BIN
bun.lockb
BIN
bun.lockb
Binary file not shown.
95
components/ui/dialog.jsx
Normal file
95
components/ui/dialog.jsx
Normal 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,
|
||||
}
|
|
@ -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
|
||||
\`\`\`
|
||||
`
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -36,7 +36,10 @@ export default {
|
|||
|
||||
community: {
|
||||
title: 'برنامههای جامعه پارچ لینوکس',
|
||||
search: 'جستجوی برنامهها...'
|
||||
search: 'جستجوی برنامهها...',
|
||||
url: 'نشانی',
|
||||
repo: 'مخزن',
|
||||
no_result: 'نتیجهای یافت نشد :('
|
||||
},
|
||||
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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 => (
|
||||
{
|
||||
apps.length
|
||||
? apps.map(app => (
|
||||
<Dialog>
|
||||
<DialogTrigger>
|
||||
<Card className="animate-in fade-in zoom-in duration-300">
|
||||
<CardHeader>
|
||||
<div className="flex gap-2">
|
||||
<div className="grow space-y-2">
|
||||
<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>
|
||||
<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>
|
||||
</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>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
|
|
|
@ -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
BIN
public/carburetor.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 107 KiB |
Loading…
Add table
Add a link
Reference in a new issue