@@ -0,0 +1,26 @@
|
||||
"use client";
|
||||
|
||||
import Image from "next/image";
|
||||
import { PhotoProvider, PhotoView } from "react-photo-view";
|
||||
import "react-photo-view/dist/react-photo-view.css";
|
||||
|
||||
interface ZoomableImageProps {
|
||||
src: string;
|
||||
alt: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function ZoomableImage({ src, alt, className }: ZoomableImageProps) {
|
||||
return (
|
||||
<PhotoProvider>
|
||||
<PhotoView src={src}>
|
||||
<Image
|
||||
src={src}
|
||||
alt={alt}
|
||||
fill
|
||||
className={`object-cover cursor-zoom-in ${className || ""}`}
|
||||
/>
|
||||
</PhotoView>
|
||||
</PhotoProvider>
|
||||
);
|
||||
}
|
||||
303
apps/website/app/[locale]/blog/[slug]/page.tsx
Normal file
@@ -0,0 +1,303 @@
|
||||
import { getPost, getPosts } from "@/lib/ghost";
|
||||
import type { Post } from "@/lib/ghost";
|
||||
import type { Metadata, ResolvingMetadata } from "next";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
import type { DetailedHTMLProps, HTMLAttributes } from "react";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import type { Components } from "react-markdown";
|
||||
import rehypeRaw from "rehype-raw";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import { ZoomableImage } from "./components/ZoomableImage";
|
||||
|
||||
type Props = {
|
||||
params: { locale: string; slug: string };
|
||||
};
|
||||
|
||||
export async function generateMetadata(
|
||||
{ params }: Props,
|
||||
parent: ResolvingMetadata,
|
||||
): Promise<Metadata> {
|
||||
const post = await getPost(params.slug);
|
||||
|
||||
if (!post) {
|
||||
return {
|
||||
title: "Post Not Found",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
title: post.title,
|
||||
description: post.custom_excerpt || post.excerpt,
|
||||
openGraph: post.feature_image
|
||||
? {
|
||||
images: [
|
||||
{
|
||||
url: post.feature_image,
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: post.title,
|
||||
},
|
||||
],
|
||||
}
|
||||
: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
export async function generateStaticParams() {
|
||||
const posts = await getPosts();
|
||||
|
||||
return posts.map((post) => ({
|
||||
slug: post.slug,
|
||||
}));
|
||||
}
|
||||
|
||||
export default async function BlogPostPage({ params }: Props) {
|
||||
const { locale, slug } = params;
|
||||
const t = await getTranslations({ locale, namespace: "blog" });
|
||||
const post = await getPost(slug);
|
||||
const allPosts = await getPosts();
|
||||
|
||||
// Get related posts (excluding current post)
|
||||
const relatedPosts = allPosts.filter((p) => p.id !== post?.id).slice(0, 3); // Show only 3 related posts
|
||||
|
||||
if (!post) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
const formattedDate = new Date(post.published_at).toLocaleDateString(locale, {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
});
|
||||
|
||||
const components: Partial<Components> = {
|
||||
h1: ({ node, ...props }) => (
|
||||
<h1 className="text-3xl text-primary font-bold mt-8 mb-4" {...props} />
|
||||
),
|
||||
h2: ({ node, ...props }) => (
|
||||
<h2 className="text-2xl text-primary/90 font-bold mt-6 mb-3" {...props} />
|
||||
),
|
||||
h3: ({ node, ...props }) => (
|
||||
<h3 className="text-xl text-primary/90 font-bold mt-4 mb-2" {...props} />
|
||||
),
|
||||
p: ({ node, ...props }) => (
|
||||
<p
|
||||
className="text-base text-muted-foreground leading-relaxed mb-4"
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
a: ({ node, href, ...props }) => (
|
||||
<a
|
||||
href={href}
|
||||
className="text-blue-500 hover:text-blue-500/80 transition-colors"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
ul: ({ node, ...props }) => (
|
||||
<ul className="list-disc list-inside space-y-2 mb-4" {...props} />
|
||||
),
|
||||
ol: ({ node, ...props }) => (
|
||||
<ol className="list-decimal list-inside space-y-2 mb-4" {...props} />
|
||||
),
|
||||
li: ({ node, ...props }) => (
|
||||
<li className="text-base leading-relaxed" {...props} />
|
||||
),
|
||||
blockquote: ({ node, ...props }) => (
|
||||
<blockquote
|
||||
className="border-l-4 border-primary pl-4 py-2 my-4 bg-muted/50"
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
table: ({ node, ...props }) => (
|
||||
<div className="my-6 w-full overflow-x-auto border rounded-lg">
|
||||
<table className="w-full border-collapse" {...props} />
|
||||
</div>
|
||||
),
|
||||
thead: ({ node, ...props }) => (
|
||||
<thead className="bg-muted border-b border-border" {...props} />
|
||||
),
|
||||
tbody: ({ node, ...props }) => (
|
||||
<tbody className="divide-y divide-border" {...props} />
|
||||
),
|
||||
tr: ({ node, ...props }) => (
|
||||
<tr className="transition-colors hover:bg-muted/50" {...props} />
|
||||
),
|
||||
th: ({ node, ...props }) => (
|
||||
<th className="p-4 text-left font-semibold" {...props} />
|
||||
),
|
||||
td: ({ node, ...props }) => (
|
||||
<td className="p-4 text-muted-foreground" {...props} />
|
||||
),
|
||||
img: ({ node, src, alt }) => (
|
||||
<div className="relative w-full h-64 my-6 rounded-lg overflow-hidden">
|
||||
{src && <ZoomableImage src={src} alt={alt || ""} />}
|
||||
</div>
|
||||
),
|
||||
};
|
||||
|
||||
return (
|
||||
<article className="container mx-auto px-4 py-12 max-w-5xl">
|
||||
<Link
|
||||
href="/blog"
|
||||
className="inline-flex items-center mb-8 text-primary hover:text-primary/80 transition-colors"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="h-5 w-5 mr-2"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M9.707 16.707a1 1 0 01-1.414 0l-6-6a1 1 0 010-1.414l6-6a1 1 0 011.414 1.414L5.414 9H17a1 1 0 110 2H5.414l4.293 4.293a1 1 0 010 1.414z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
{t("backToBlog")}
|
||||
</Link>
|
||||
|
||||
<div className=" rounded-lg p-8 shadow-lg border border-border">
|
||||
<header className="mb-8">
|
||||
<h1 className="text-4xl font-bold mb-4">{post.title}</h1>
|
||||
<div className="flex items-center mb-6">
|
||||
{post.primary_author?.profile_image && (
|
||||
<div className="relative h-12 w-12 rounded-full overflow-hidden mr-4">
|
||||
{post.primary_author.twitter ? (
|
||||
<a
|
||||
href={`https://twitter.com/${post.primary_author.twitter}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="block cursor-pointer transition-opacity hover:opacity-90"
|
||||
>
|
||||
<Image
|
||||
src={post.primary_author.profile_image}
|
||||
alt={post.primary_author.name}
|
||||
fill
|
||||
className="object-cover"
|
||||
/>
|
||||
</a>
|
||||
) : (
|
||||
<Image
|
||||
src={post.primary_author.profile_image}
|
||||
alt={post.primary_author.name}
|
||||
fill
|
||||
className="object-cover"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<p className="font-medium">
|
||||
{post.primary_author?.twitter ? (
|
||||
<a
|
||||
href={`https://twitter.com/${post.primary_author.twitter}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="hover:text-primary transition-colors"
|
||||
>
|
||||
{post.primary_author.name || "Unknown Author"}
|
||||
</a>
|
||||
) : (
|
||||
post.primary_author?.name || "Unknown Author"
|
||||
)}
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{formattedDate} • {post.reading_time} min read
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{post.feature_image && (
|
||||
<div className="relative w-full h-[400px] mb-8">
|
||||
<ZoomableImage
|
||||
src={post.feature_image}
|
||||
alt={post.title}
|
||||
className="rounded-lg"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</header>
|
||||
|
||||
<div className="prose prose-lg max-w-none">
|
||||
<ReactMarkdown
|
||||
remarkPlugins={[remarkGfm]}
|
||||
rehypePlugins={[rehypeRaw]}
|
||||
components={components}
|
||||
>
|
||||
{post.html}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
|
||||
{post.tags && post.tags.length > 0 && (
|
||||
<div className="mt-12 pt-6 border-t border-border">
|
||||
<h2 className="text-xl font-semibold mb-4">{t("tags")}</h2>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{post.tags.map((tag) => (
|
||||
<Link
|
||||
key={tag.id}
|
||||
href={`/blog/tag/${tag.slug}`}
|
||||
className="px-4 py-2 bg-muted hover:bg-muted/80 rounded-full text-sm transition-colors"
|
||||
>
|
||||
{tag.name}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{relatedPosts.length > 0 && (
|
||||
<div className="mt-12">
|
||||
<h2 className="text-2xl font-bold mb-6">{t("relatedPosts")}</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
{relatedPosts.map((relatedPost) => {
|
||||
const relatedPostDate = new Date(
|
||||
relatedPost.published_at,
|
||||
).toLocaleDateString(locale, {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
});
|
||||
|
||||
return (
|
||||
<Link
|
||||
key={relatedPost.id}
|
||||
href={`/blog/${relatedPost.slug}`}
|
||||
className="group"
|
||||
>
|
||||
<div className="bg-card rounded-lg overflow-hidden h-full shadow-lg transition-all duration-300 hover:shadow-xl border border-border">
|
||||
{relatedPost.feature_image && (
|
||||
<div className="relative h-48 w-full">
|
||||
<Image
|
||||
src={relatedPost.feature_image}
|
||||
alt={relatedPost.title}
|
||||
fill
|
||||
className="object-cover"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="p-6">
|
||||
<h3 className="text-lg font-semibold mb-2 group-hover:text-primary transition-colors line-clamp-2">
|
||||
{relatedPost.title}
|
||||
</h3>
|
||||
<p className="text-sm text-muted-foreground mb-4">
|
||||
{relatedPostDate} • {relatedPost.reading_time} min read
|
||||
</p>
|
||||
<p className="text-muted-foreground line-clamp-2">
|
||||
{relatedPost.excerpt}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</article>
|
||||
);
|
||||
}
|
||||
99
apps/website/app/[locale]/blog/components/BlogPostCard.tsx
Normal file
@@ -0,0 +1,99 @@
|
||||
"use client";
|
||||
|
||||
import type { Post } from "@/lib/ghost";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
interface BlogPostCardProps {
|
||||
post: Post;
|
||||
locale: string;
|
||||
}
|
||||
|
||||
export function BlogPostCard({ post, locale }: BlogPostCardProps) {
|
||||
const router = useRouter();
|
||||
const formattedDate = new Date(post.published_at).toLocaleDateString(locale, {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
});
|
||||
|
||||
const handleTwitterClick = (e: React.MouseEvent) => {
|
||||
if (post.primary_author?.twitter) {
|
||||
router.push(`https://twitter.com/${post.primary_author.twitter}`);
|
||||
}
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
return (
|
||||
<Link
|
||||
href={`/blog/${post.slug}`}
|
||||
className="group block hover:bg-muted p-4 rounded-lg"
|
||||
>
|
||||
<article className="flex gap-6 items-start">
|
||||
<div className="relative h-32 w-48 shrink-0">
|
||||
<Image
|
||||
src={post.feature_image || "/og.png"}
|
||||
alt={post.feature_image ? post.title : "Default Image"}
|
||||
fill
|
||||
className="object-cover rounded-lg"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<h2 className="text-xl font-semibold mb-2 group-hover:text-primary">
|
||||
{post.title}
|
||||
</h2>
|
||||
<p className="text-muted-foreground line-clamp-2 mb-4">
|
||||
{post.custom_excerpt || post.excerpt}
|
||||
</p>
|
||||
<div className="flex items-center text-sm text-muted-foreground">
|
||||
<div className="flex items-center">
|
||||
{post.primary_author?.profile_image && (
|
||||
<div className="relative h-6 w-6 rounded-full overflow-hidden mr-2">
|
||||
{post.primary_author.twitter ? (
|
||||
<button
|
||||
className="block cursor-pointer transition-opacity hover:opacity-90"
|
||||
onClick={handleTwitterClick}
|
||||
type="button"
|
||||
>
|
||||
<Image
|
||||
src={post.primary_author.profile_image}
|
||||
alt={post.primary_author.name}
|
||||
fill
|
||||
className="object-cover"
|
||||
/>
|
||||
</button>
|
||||
) : (
|
||||
<Image
|
||||
src={post.primary_author.profile_image}
|
||||
alt={post.primary_author.name}
|
||||
fill
|
||||
className="object-cover"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{post.primary_author?.twitter ? (
|
||||
<button
|
||||
className="hover:text-primary transition-colors"
|
||||
onClick={handleTwitterClick}
|
||||
type="button"
|
||||
>
|
||||
{post.primary_author.name || "Unknown Author"}
|
||||
</button>
|
||||
) : (
|
||||
<span>{post.primary_author?.name || "Unknown Author"}</span>
|
||||
)}
|
||||
</div>
|
||||
<span className="mx-2">in</span>
|
||||
<span>{post.primary_tag?.name || "General"}</span>
|
||||
<span className="mx-2">•</span>
|
||||
<span>{post.reading_time} min read</span>
|
||||
<span className="mx-2">•</span>
|
||||
<span>{formattedDate}</span>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
106
apps/website/app/[locale]/blog/components/SearchAndFilter.tsx
Normal file
@@ -0,0 +1,106 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { useDebounce } from "@/lib/hooks/use-debounce";
|
||||
import { Search } from "lucide-react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useCallback, useTransition } from "react";
|
||||
|
||||
interface Tag {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
}
|
||||
|
||||
interface SearchAndFilterProps {
|
||||
tags: Tag[];
|
||||
initialSearch: string;
|
||||
initialTag: string;
|
||||
searchPlaceholder: string;
|
||||
allTagsText: string;
|
||||
}
|
||||
|
||||
const ALL_TAGS_VALUE = "all";
|
||||
|
||||
export function SearchAndFilter({
|
||||
tags,
|
||||
initialSearch,
|
||||
initialTag,
|
||||
searchPlaceholder,
|
||||
allTagsText,
|
||||
}: SearchAndFilterProps) {
|
||||
const router = useRouter();
|
||||
const [isPending, startTransition] = useTransition();
|
||||
|
||||
const handleTagChange = (value: string) => {
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
if (value && value !== ALL_TAGS_VALUE) {
|
||||
searchParams.set("tag", value);
|
||||
} else {
|
||||
searchParams.delete("tag");
|
||||
}
|
||||
startTransition(() => {
|
||||
router.push(`?${searchParams.toString()}`);
|
||||
});
|
||||
};
|
||||
|
||||
const debouncedCallback = useDebounce((value: string) => {
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
if (value) {
|
||||
searchParams.set("search", value);
|
||||
} else {
|
||||
searchParams.delete("search");
|
||||
}
|
||||
startTransition(() => {
|
||||
router.push(`?${searchParams.toString()}`);
|
||||
});
|
||||
}, 300);
|
||||
|
||||
const handleSearch = useCallback(
|
||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
debouncedCallback(e.target.value);
|
||||
},
|
||||
[debouncedCallback],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col md:flex-row gap-4 mb-8">
|
||||
<div className="relative flex-1">
|
||||
<div className="absolute inset-y-0 left-3 flex items-center pointer-events-none">
|
||||
<Search className="h-5 w-5 text-gray-400" />
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
defaultValue={initialSearch}
|
||||
onChange={handleSearch}
|
||||
placeholder={searchPlaceholder}
|
||||
className="w-full pl-10 pr-4 py-2 border border-border rounded-md bg-background ring-offset-background placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50"
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full md:w-64">
|
||||
<Select
|
||||
defaultValue={initialTag || ALL_TAGS_VALUE}
|
||||
onValueChange={handleTagChange}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder={allTagsText} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value={ALL_TAGS_VALUE}>{allTagsText}</SelectItem>
|
||||
{tags.map((tag) => (
|
||||
<SelectItem key={tag.id} value={tag.slug}>
|
||||
{tag.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
88
apps/website/app/[locale]/blog/page.tsx
Normal file
@@ -0,0 +1,88 @@
|
||||
import { getPosts, getTags } from "@/lib/ghost";
|
||||
import type { Post } from "@/lib/ghost";
|
||||
import { RssIcon } from "lucide-react";
|
||||
import type { Metadata } from "next";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import Link from "next/link";
|
||||
import { BlogPostCard } from "./components/BlogPostCard";
|
||||
import { SearchAndFilter } from "./components/SearchAndFilter";
|
||||
|
||||
interface Tag {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
}
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Blog | Dokploy",
|
||||
description: "Latest news, updates, and articles from Dokploy",
|
||||
};
|
||||
|
||||
export default async function BlogPage({
|
||||
params: { locale },
|
||||
searchParams,
|
||||
}: {
|
||||
params: { locale: string };
|
||||
searchParams: { [key: string]: string | string[] | undefined };
|
||||
}) {
|
||||
const t = await getTranslations({ locale, namespace: "blog" });
|
||||
const posts = await getPosts();
|
||||
const tags = (await getTags()) as Tag[];
|
||||
const search =
|
||||
typeof searchParams.search === "string" ? searchParams.search : "";
|
||||
const selectedTag =
|
||||
typeof searchParams.tag === "string" ? searchParams.tag : "";
|
||||
|
||||
const filteredPosts = posts.filter((post) => {
|
||||
const matchesSearch =
|
||||
search === "" ||
|
||||
post.title.toLowerCase().includes(search.toLowerCase()) ||
|
||||
post.excerpt.toLowerCase().includes(search.toLowerCase());
|
||||
|
||||
const matchesTag =
|
||||
selectedTag === "" || post.tags?.some((tag) => tag.slug === selectedTag);
|
||||
|
||||
return matchesSearch && matchesTag;
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="container mx-auto px-4 py-12 max-w-5xl">
|
||||
<div className="flex items-center justify-between mb-8">
|
||||
<div>
|
||||
<p className="text-sm text-muted-foreground uppercase tracking-wider mb-2">
|
||||
BLOG
|
||||
</p>
|
||||
<h1 className="text-4xl font-bold">Dokploy Latest News & Updates</h1>
|
||||
</div>
|
||||
<Link
|
||||
href="/rss.xml"
|
||||
className="text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<RssIcon className="h-5 w-5" />
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<SearchAndFilter
|
||||
tags={tags}
|
||||
initialSearch={search}
|
||||
initialTag={selectedTag}
|
||||
searchPlaceholder={t("searchPlaceholder")}
|
||||
allTagsText={t("allTags")}
|
||||
/>
|
||||
|
||||
{filteredPosts.length === 0 ? (
|
||||
<div className="text-center py-12 min-h-[20vh] flex items-center justify-center">
|
||||
<p className="text-xl text-muted-foreground">
|
||||
{search || selectedTag ? t("noResults") : t("noPosts")}
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-8">
|
||||
{filteredPosts.map((post: Post) => (
|
||||
<BlogPostCard key={post.id} post={post} locale={locale} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
135
apps/website/app/[locale]/blog/tag/[tag]/page.tsx
Normal file
@@ -0,0 +1,135 @@
|
||||
import { getPostsByTag, getTags } from "@/lib/ghost";
|
||||
import type { Post } from "@/lib/ghost";
|
||||
import type { Metadata } from "next";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
|
||||
type Props = {
|
||||
params: { locale: string; tag: string };
|
||||
};
|
||||
|
||||
export async function generateMetadata({ params }: Props): Promise<Metadata> {
|
||||
const { tag, locale } = params;
|
||||
const t = await getTranslations({ locale, namespace: "blog" });
|
||||
|
||||
return {
|
||||
title: `${t("tagTitle", { tag })}`,
|
||||
description: t("tagDescription", { tag }),
|
||||
};
|
||||
}
|
||||
|
||||
export async function generateStaticParams() {
|
||||
const tags = await getTags();
|
||||
|
||||
return tags.map((tag: { slug: string }) => ({
|
||||
tag: tag.slug,
|
||||
}));
|
||||
}
|
||||
|
||||
export default async function TagPage({ params }: Props) {
|
||||
const { locale, tag } = params;
|
||||
const t = await getTranslations({ locale, namespace: "blog" });
|
||||
const posts = await getPostsByTag(tag);
|
||||
|
||||
if (!posts || posts.length === 0) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
// Get the tag name from the first post
|
||||
const tagName =
|
||||
posts[0].tags?.find((t: { slug: string }) => t.slug === tag)?.name || tag;
|
||||
|
||||
return (
|
||||
<div className="container mx-auto px-4 py-12">
|
||||
<Link
|
||||
href="/blog"
|
||||
className="inline-flex items-center mb-8 text-primary-600 hover:text-primary-800 transition-colors"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="h-5 w-5 mr-2"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M9.707 16.707a1 1 0 01-1.414 0l-6-6a1 1 0 010-1.414l6-6a1 1 0 011.414 1.414L5.414 9H17a1 1 0 110 2H5.414l4.293 4.293a1 1 0 010 1.414z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
{t("backToBlog")}
|
||||
</Link>
|
||||
|
||||
<div className="mb-8">
|
||||
<h1 className="text-3xl font-bold mb-2">
|
||||
{t("postsTaggedWith")}{" "}
|
||||
<span className="text-primary-600">"{tagName}"</span>
|
||||
</h1>
|
||||
<p className="text-gray-600 dark:text-gray-400">
|
||||
{t("foundPosts", { count: posts.length })}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
{posts.map((post: Post) => (
|
||||
<BlogPostCard key={post.id} post={post} locale={locale} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function BlogPostCard({ post, locale }: { post: Post; locale: string }) {
|
||||
const formattedDate = new Date(post.published_at).toLocaleDateString(locale, {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
});
|
||||
|
||||
return (
|
||||
<Link href={`/blog/${post.slug}`} className="group">
|
||||
<div className="dark:bg-gray-800 rounded-lg overflow-hidden shadow-lg transition-all duration-300 hover:shadow-xl">
|
||||
{post.feature_image && (
|
||||
<div className="relative h-48 w-full">
|
||||
<Image
|
||||
src={post.feature_image}
|
||||
alt={post.title}
|
||||
fill
|
||||
className="object-cover"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="p-6">
|
||||
<h2 className="text-xl font-semibold mb-2 group-hover:text-primary-500 transition-colors">
|
||||
{post.title}
|
||||
</h2>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400 mb-4">
|
||||
{formattedDate} • {post.reading_time} min read
|
||||
</p>
|
||||
<p className="text-gray-700 dark:text-gray-300 mb-4">
|
||||
{post.custom_excerpt || post.excerpt}
|
||||
</p>
|
||||
<div className="flex items-center">
|
||||
{post.primary_author?.profile_image && (
|
||||
<div className="relative h-10 w-10 rounded-full overflow-hidden mr-3">
|
||||
<Image
|
||||
src={post.primary_author.profile_image}
|
||||
alt={post.primary_author.name}
|
||||
fill
|
||||
className="object-cover"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<p className="font-medium">
|
||||
{post.primary_author?.name || "Unknown Author"}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import { Inter, Lexend } from "next/font/google";
|
||||
import "@/styles/tailwind.css";
|
||||
import { NextIntlClientProvider } from "next-intl";
|
||||
import { getMessages } from "next-intl/server";
|
||||
|
||||
import "react-photo-view/dist/react-photo-view.css";
|
||||
import { Footer } from "@/components/Footer";
|
||||
import { Header } from "@/components/Header";
|
||||
import type { Metadata } from "next";
|
||||
|
||||
66
apps/website/app/rss.xml/route.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { getPosts } from "@/lib/ghost";
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
function escapeXml(unsafe: string): string {
|
||||
return unsafe.replace(/[<>&'"]/g, (c) => {
|
||||
switch (c) {
|
||||
case "<":
|
||||
return "<";
|
||||
case ">":
|
||||
return ">";
|
||||
case "&":
|
||||
return "&";
|
||||
case "'":
|
||||
return "'";
|
||||
case '"':
|
||||
return """;
|
||||
default:
|
||||
return c;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function GET() {
|
||||
const posts = await getPosts();
|
||||
|
||||
const rss = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<channel>
|
||||
<title>Dokploy Blog</title>
|
||||
<link>https://dokploy.com/blog</link>
|
||||
<description>Dokploy Latest News & Updates</description>
|
||||
<language>en</language>
|
||||
<lastBuildDate>${new Date().toUTCString()}</lastBuildDate>
|
||||
${posts
|
||||
.map(
|
||||
(post) => `
|
||||
<item>
|
||||
<title><![CDATA[${post.title}]]></title>
|
||||
<link>https://dokploy.com/blog/${escapeXml(post.slug)}</link>
|
||||
<guid>https://dokploy.com/blog/${escapeXml(post.slug)}</guid>
|
||||
<description><![CDATA[${post.excerpt}]]></description>
|
||||
<content:encoded><![CDATA[${post.html}]]></content:encoded>
|
||||
<pubDate>${new Date(post.published_at).toUTCString()}</pubDate>
|
||||
${
|
||||
post.feature_image
|
||||
? `<enclosure url="${escapeXml(post.feature_image)}" type="image/jpeg" />`
|
||||
: ""
|
||||
}
|
||||
${
|
||||
post.primary_author
|
||||
? `<dc:creator><![CDATA[${post.primary_author.name}]]></dc:creator>`
|
||||
: ""
|
||||
}
|
||||
</item>`,
|
||||
)
|
||||
.join("\n")}
|
||||
</channel>
|
||||
</rss>`;
|
||||
|
||||
return new NextResponse(rss, {
|
||||
headers: {
|
||||
"Content-Type": "application/xml; charset=utf-8",
|
||||
"Cache-Control": "s-maxage=3600, stale-while-revalidate",
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -127,6 +127,7 @@ function MobileNavigation() {
|
||||
<MobileNavLink href={linkT("docs.intro")} target="_blank">
|
||||
{t("navigation.docs")}
|
||||
</MobileNavLink>
|
||||
<MobileNavLink href="/blog">{t("navigation.blog")}</MobileNavLink>
|
||||
<MobileNavLink href={linkT("docs.intro")} target="_blank">
|
||||
<Button className=" w-full" asChild>
|
||||
<Link
|
||||
@@ -166,6 +167,7 @@ export function Header() {
|
||||
<NavLink href={linkT("docs.intro")} target="_blank">
|
||||
{t("navigation.docs")}
|
||||
</NavLink>
|
||||
<NavLink href="/blog">{t("navigation.blog")}</NavLink>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-x-4 md:gap-x-5">
|
||||
|
||||
72
apps/website/components/blog/BlogCard.tsx
Normal file
@@ -0,0 +1,72 @@
|
||||
import type { Post } from "@/lib/ghost";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
|
||||
interface BlogCardProps {
|
||||
post: Post;
|
||||
locale: string;
|
||||
}
|
||||
|
||||
export function BlogCard({ post, locale }: BlogCardProps) {
|
||||
const formattedDate = new Date(post.published_at).toLocaleDateString(locale, {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="flex flex-col overflow-hidden rounded-lg shadow-lg transition-all hover:shadow-xl">
|
||||
<div className="relative h-48 w-full">
|
||||
{post.feature_image ? (
|
||||
<Image
|
||||
src={post.feature_image}
|
||||
alt={post.title}
|
||||
fill
|
||||
className="object-cover"
|
||||
/>
|
||||
) : (
|
||||
<div className="flex h-full w-full items-center justify-center bg-gray-200">
|
||||
<span className="text-gray-400">No image</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-1 flex-col justify-between bg-white p-6">
|
||||
<div className="flex-1">
|
||||
{post.primary_tag && (
|
||||
<p className="text-sm font-medium text-indigo-600">
|
||||
{post.primary_tag.name}
|
||||
</p>
|
||||
)}
|
||||
<Link href={`/${locale}/blog/${post.slug}`} className="mt-2 block">
|
||||
<h3 className="text-xl font-semibold text-gray-900">
|
||||
{post.title}
|
||||
</h3>
|
||||
<p className="mt-3 text-base text-gray-500">{post.excerpt}</p>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="mt-6 flex items-center">
|
||||
{post.primary_author?.profile_image && (
|
||||
<div className="relative h-10 w-10 flex-shrink-0">
|
||||
<Image
|
||||
className="rounded-full"
|
||||
src={post.primary_author.profile_image}
|
||||
alt={post.primary_author.name}
|
||||
fill
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="ml-3">
|
||||
<p className="text-sm font-medium text-gray-900">
|
||||
{post.primary_author?.name || "Anonymous"}
|
||||
</p>
|
||||
<div className="flex space-x-1 text-sm text-gray-500">
|
||||
<time dateTime={post.published_at}>{formattedDate}</time>
|
||||
<span aria-hidden="true">·</span>
|
||||
<span>{post.reading_time} min read</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -140,7 +140,7 @@ const Feature = ({
|
||||
{title}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-sm text-neutral-300 max-w-xs relative z-10 px-10">
|
||||
<p className="text-sm text-neutral-300 lg:max-w-xs relative z-10 px-10">
|
||||
{description}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
6
apps/website/components/navigation.tsx
Normal file
@@ -0,0 +1,6 @@
|
||||
const navigation = [
|
||||
{ name: "home", href: "/" },
|
||||
{ name: "features", href: "/features" },
|
||||
{ name: "pricing", href: "/pricing" },
|
||||
{ name: "blog", href: "/blog" },
|
||||
];
|
||||
@@ -13,17 +13,22 @@ const features = [
|
||||
{
|
||||
title: "primaryFeatures.applications",
|
||||
description: "primaryFeatures.applicationsDes",
|
||||
image: "/primary/primary.png",
|
||||
image: "/dashboard.png",
|
||||
},
|
||||
{
|
||||
title: "primaryFeatures.compose",
|
||||
description: "primaryFeatures.composeDes",
|
||||
image: "/primary/compose.png",
|
||||
image: "/compose.png",
|
||||
},
|
||||
{
|
||||
title: "primaryFeatures.multiserver",
|
||||
description: "primaryFeatures.multiserverDes",
|
||||
image: "/primary/servers.png",
|
||||
image: "/remote.png",
|
||||
},
|
||||
{
|
||||
title: "primaryFeatures.logs",
|
||||
description: "primaryFeatures.logsDes",
|
||||
image: "/logs.png",
|
||||
},
|
||||
{
|
||||
title: "primaryFeatures.monitoring",
|
||||
@@ -33,12 +38,17 @@ const features = [
|
||||
{
|
||||
title: "primaryFeatures.backups",
|
||||
description: "primaryFeatures.backupsDes",
|
||||
image: "/primary/backups.png",
|
||||
image: "/backups.png",
|
||||
},
|
||||
{
|
||||
title: "primaryFeatures.traefik",
|
||||
description: "primaryFeatures.traefikDes",
|
||||
image: "/primary/traefik.png",
|
||||
image: "/traefik.png",
|
||||
},
|
||||
{
|
||||
title: "primaryFeatures.templates",
|
||||
description: "primaryFeatures.templatesDes",
|
||||
image: "/templates.png",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -174,14 +184,19 @@ export function SecondaryFeaturesSections() {
|
||||
bounce: 0.2,
|
||||
duration: 0.8,
|
||||
}}
|
||||
className="mt-10 h-[24rem] w-[45rem] overflow-hidden rounded-xl border-b shadow-xl sm:w-auto lg:mt-0 lg:h-[40rem] "
|
||||
className="mt-10 h-[24rem] w-[45rem] overflow-hidden rounded-xl border shadow-xl sm:w-auto lg:mt-0 lg:h-[40rem] "
|
||||
>
|
||||
<div className="relative">
|
||||
<Safari
|
||||
url={"Dokploy UI"}
|
||||
className="size-full"
|
||||
src={feature.image}
|
||||
/>
|
||||
<div className="relative w-full">
|
||||
<div className="mx-auto">
|
||||
<div className="w-full h-11 rounded-t-lg bg-card flex justify-start items-center space-x-1.5 px-3">
|
||||
<span className="w-3 h-3 rounded-full bg-red-400" />
|
||||
<span className="w-3 h-3 rounded-full bg-yellow-400" />
|
||||
<span className="w-3 h-3 rounded-full bg-green-400" />
|
||||
</div>
|
||||
<div className="bg-gray-100 w-full h-96">
|
||||
<img src={feature.image} alt={feature.title} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</Tab.Panel>
|
||||
|
||||
129
apps/website/lib/ghost.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
import GhostContentAPI from "@tryghost/content-api";
|
||||
|
||||
// Ghost API configuration
|
||||
const ghostConfig = {
|
||||
url: process.env.GHOST_URL || "NONE",
|
||||
key: process.env.GHOST_KEY || "NONE",
|
||||
version: "v5.0",
|
||||
};
|
||||
|
||||
// Initialize the Ghost API with your credentials
|
||||
const api = GhostContentAPI({
|
||||
url: ghostConfig.url,
|
||||
key: ghostConfig.key,
|
||||
version: ghostConfig.version,
|
||||
// @ts-ignore
|
||||
makeRequest: ({ url, method, params, headers }) => {
|
||||
const apiUrl = new URL(url);
|
||||
// @ts-ignore
|
||||
Object.keys(params).map((key) =>
|
||||
apiUrl.searchParams.set(key, encodeURIComponent(params[key])),
|
||||
);
|
||||
|
||||
return fetch(apiUrl.toString(), { method, headers })
|
||||
.then(async (res) => {
|
||||
// Check if the response was successful.
|
||||
if (!res.ok) {
|
||||
// You can handle HTTP errors here
|
||||
throw new Error(`HTTP error! status: ${res.status}`);
|
||||
}
|
||||
return { data: await res.json() };
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Fetch error:", error);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export interface Post {
|
||||
id: string;
|
||||
uuid: string;
|
||||
title: string;
|
||||
slug: string;
|
||||
html: string;
|
||||
feature_image: string | null;
|
||||
featured: boolean;
|
||||
visibility: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
published_at: string;
|
||||
custom_excerpt: string | null;
|
||||
excerpt: string;
|
||||
reading_time: number;
|
||||
primary_tag?: {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
};
|
||||
tags?: Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
}>;
|
||||
primary_author?: {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
profile_image: string | null;
|
||||
bio: string | null;
|
||||
twitter: string | null;
|
||||
};
|
||||
authors?: Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
profile_image: string | null;
|
||||
bio: string | null;
|
||||
}>;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export async function getPosts(options = {}): Promise<Post[]> {
|
||||
try {
|
||||
const result = (await api.posts.browse({
|
||||
include: "authors",
|
||||
limit: "all",
|
||||
})) as Post[];
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error("Error fetching posts:", error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export async function getPost(slug: string): Promise<Post | null> {
|
||||
try {
|
||||
const result = (await api.posts.read({
|
||||
slug,
|
||||
include: ["authors"],
|
||||
})) as Post;
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error("Error fetching post:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getTags() {
|
||||
try {
|
||||
const result = await api.tags.browse();
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error("Error fetching tags:", error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export async function getPostsByTag(tag: string) {
|
||||
try {
|
||||
const result = await api.posts.browse({
|
||||
limit: "all",
|
||||
filter: `tag:${tag}`,
|
||||
include: ["tags", "authors"],
|
||||
});
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error(`Error fetching posts with tag ${tag}:`, error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
28
apps/website/lib/hooks/use-debounce.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
export function useDebounce<T extends (...args: any[]) => any>(
|
||||
callback: T,
|
||||
delay: number,
|
||||
): T {
|
||||
const timeoutRef = useRef<NodeJS.Timeout>();
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return ((...args: Parameters<T>) => {
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current);
|
||||
}
|
||||
|
||||
return new Promise<ReturnType<T>>((resolve) => {
|
||||
timeoutRef.current = setTimeout(() => {
|
||||
resolve(callback(...args));
|
||||
}, delay);
|
||||
});
|
||||
}) as T;
|
||||
}
|
||||
44
apps/website/lib/types/ghost-content-api.d.ts
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
declare module "@tryghost/content-api" {
|
||||
interface GhostContentAPIOptions {
|
||||
url: string;
|
||||
key: string;
|
||||
version: string;
|
||||
}
|
||||
|
||||
interface BrowseOptions {
|
||||
limit?: string | number;
|
||||
page?: number;
|
||||
order?: string;
|
||||
filter?: string;
|
||||
include?: string | string[];
|
||||
fields?: string | string[];
|
||||
formats?: string | string[];
|
||||
}
|
||||
|
||||
interface ReadOptions {
|
||||
id?: string;
|
||||
slug?: string;
|
||||
include?: string | string[];
|
||||
fields?: string | string[];
|
||||
formats?: string | string[];
|
||||
}
|
||||
|
||||
interface ApiObject {
|
||||
browse<T>(options?: BrowseOptions): Promise<T[]>;
|
||||
read<T>(options: ReadOptions): Promise<T>;
|
||||
}
|
||||
|
||||
interface GhostAPI {
|
||||
posts: ApiObject;
|
||||
tags: ApiObject;
|
||||
authors: ApiObject;
|
||||
pages: ApiObject;
|
||||
settings: {
|
||||
browse<T>(): Promise<T>;
|
||||
};
|
||||
}
|
||||
|
||||
function GhostContentAPI(options: GhostContentAPIOptions): GhostAPI;
|
||||
|
||||
export default GhostContentAPI;
|
||||
}
|
||||
@@ -11,7 +11,11 @@
|
||||
"i18nButtonPlaceholder": "Language",
|
||||
"i18nFr": "Français",
|
||||
"i18nEn": "English",
|
||||
"i18nZh-Hans": "简体中文"
|
||||
"i18nZh-Hans": "简体中文",
|
||||
"blog": "Blog",
|
||||
"home": "Home",
|
||||
"login": "Login",
|
||||
"register": "Register"
|
||||
},
|
||||
"hero": {
|
||||
"cloud": "Introducing Dokploy Cloud",
|
||||
@@ -37,6 +41,10 @@
|
||||
"title": "Comprehensive Control for Your Digital Ecosystem",
|
||||
"des": "Simplify your project and data management, ensure robust monitoring, and secure your backups—all without the fuss over minute details.",
|
||||
"projects": "Projects",
|
||||
"templates": "Templates",
|
||||
"templatesDes": "One click to deploy open source templates.",
|
||||
"logs": "Logs",
|
||||
"logsDes": "Monitor and manage your applications' logs with ease, ensuring efficient troubleshooting and optimal performance.",
|
||||
"projectsDes": "Manage and organize all your projects in one place, keeping detailed track of progress and resource allocation.",
|
||||
"applications": "Applications & Databases",
|
||||
"applicationsDes": "Centralize control over your applications and databases for enhanced security and efficiency, simplifying access and management across your infrastructure.",
|
||||
@@ -69,7 +77,7 @@
|
||||
},
|
||||
"callToAction": {
|
||||
"title": "Unlock Your Deployment Potential with Dokploy Cloud",
|
||||
"des": "Say goodbye to infrastructure hassles—Dokploy Cloud handles it all. Effortlessly deploy, manage Docker containers, and secure your traffic with Traefik. Focus on building, we’ll handle the rest.",
|
||||
"des": "Say goodbye to infrastructure hassles—Dokploy Cloud handles it all. Effortlessly deploy, manage Docker containers, and secure your traffic with Traefik. Focus on building, we'll handle the rest.",
|
||||
"button": "Get Started Now"
|
||||
},
|
||||
"faq": {
|
||||
@@ -173,7 +181,7 @@
|
||||
},
|
||||
"faq": {
|
||||
"title": "Frequently asked questions",
|
||||
"description": "If you can’t find what you’re looking for, please send us an email to",
|
||||
"description": "If you can't find what you're looking for, please send us an email to",
|
||||
"q1": "How does Dokploy's Open Source plan work?",
|
||||
"a1": "You can host Dokploy UI on your own infrastructure and you will be responsible for the maintenance and updates.",
|
||||
"q2": "Do I need to provide my own server for the managed plan?",
|
||||
@@ -191,5 +199,20 @@
|
||||
"q8": "Is Dokploy open-source?",
|
||||
"a8": "Yes, Dokploy is fully open-source. You can contribute or modify it as needed for your projects."
|
||||
}
|
||||
},
|
||||
"blog": {
|
||||
"title": "Blog",
|
||||
"description": "Latest news, updates, and articles from Dokploy",
|
||||
"noPosts": "No posts available",
|
||||
"noResults": "No posts found matching your criteria",
|
||||
"searchPlaceholder": "Search posts...",
|
||||
"allTags": "All Tags",
|
||||
"relatedPosts": "Related Posts",
|
||||
"tagDescription": "Posts tagged with",
|
||||
"foundPosts": "{count, plural, =0 {No posts found} one {# post found} other {# posts found}}",
|
||||
"tagTitle": "Posts tagged with {tag}",
|
||||
"backToBlog": "Back to Blog",
|
||||
"tags": "Tags",
|
||||
"postsTaggedWith": "Posts tagged with"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,11 @@
|
||||
"i18nButtonPlaceholder": "Langue",
|
||||
"i18nFr": "Français",
|
||||
"i18nEn": "English",
|
||||
"i18nZh-Hans": "简体中文"
|
||||
"i18nZh-Hans": "简体中文",
|
||||
"blog": "Blog",
|
||||
"home": "Accueil",
|
||||
"login": "Connexion",
|
||||
"register": "S'inscrire"
|
||||
},
|
||||
"hero": {
|
||||
"cloud": "Présentation de Dokploy Cloud",
|
||||
@@ -37,6 +41,10 @@
|
||||
"title": "Contrôle complet de votre écosystème numérique",
|
||||
"des": "Simplifiez la gestion de vos projets et données, assurez une surveillance robuste et sécurisez vos sauvegardes — tout cela sans vous soucier des détails minutieux.",
|
||||
"projects": "Projets",
|
||||
"templates": "Templates",
|
||||
"templatesDes": "Un clic pour déployer des templates open source.",
|
||||
"logs": "Logs",
|
||||
"logsDes": "Surveillez et gérez les logs de vos applications avec facilité, en assurant un dépannage efficace et des performances optimales.",
|
||||
"projectsDes": "Gérez et organisez tous vos projets au même endroit, en suivant de manière détaillée l'avancement et l'allocation des ressources.",
|
||||
"applications": "Applications & Bases de données",
|
||||
"applicationsDes": "Centralisez le contrôle de vos applications et bases de données pour améliorer la sécurité et l'efficacité, simplifiant l'accès et la gestion sur toute votre infrastructure.",
|
||||
@@ -190,5 +198,15 @@
|
||||
"q8": "Dokploy est-il open-source ?",
|
||||
"a8": "Oui, Dokploy est entièrement open-source. Vous pouvez contribuer ou modifier selon vos besoins pour vos projets."
|
||||
}
|
||||
},
|
||||
"blog": {
|
||||
"title": "Blog",
|
||||
"description": "Dernières nouvelles, mises à jour et articles de Dokploy",
|
||||
"noPosts": "Aucun article disponible",
|
||||
"noResults": "Aucun article ne correspond à vos critères",
|
||||
"searchPlaceholder": "Rechercher des articles...",
|
||||
"allTags": "Tous les tags",
|
||||
"relatedPosts": "Articles similaires",
|
||||
"tagDescription": "Articles tagués avec"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,11 @@
|
||||
"i18nButtonPlaceholder": "语言",
|
||||
"i18nFr": "Français",
|
||||
"i18nEn": "English",
|
||||
"i18nZh-Hans": "简体中文"
|
||||
"i18nZh-Hans": "简体中文",
|
||||
"blog": "博客",
|
||||
"home": "首页",
|
||||
"login": "登录",
|
||||
"register": "注册"
|
||||
},
|
||||
"hero": {
|
||||
"cloud": "隆重介绍 Dokploy 云",
|
||||
@@ -37,6 +41,10 @@
|
||||
"title": "全面掌控您的基础设施",
|
||||
"des": "Dokploy 不仅简化您的项目部署和数据管理流程,同时还提供完备的数据备份,一切只在弹指间。",
|
||||
"projects": "项目",
|
||||
"logs": "日志",
|
||||
"templates": "模板",
|
||||
"templatesDes": "一键部署热门开源项目。",
|
||||
"logsDes": "轻松监控和处理您的应用程序日志,确保高效故障排除和最佳性能。",
|
||||
"projectsDes": "您所有的项目和所需的一切信息都将归集一处。",
|
||||
"applications": "应用和数据库",
|
||||
"applicationsDes": "Dokploy 通过集中管理您的应用和数据库来提升安全性和效率,并大大简化了跨基础设施的访问和管理。",
|
||||
@@ -195,5 +203,21 @@
|
||||
"q8": "Dokploy 开源吗?",
|
||||
"a8": "是的,Dokploy 完全开源,您可以参与贡献或者是 Fork 后自行修改以用于您的私人需求。"
|
||||
}
|
||||
},
|
||||
"blog": {
|
||||
"title": "博客",
|
||||
"description": "Dokploy 的最新消息、更新和文章",
|
||||
"noPosts": "暂无文章",
|
||||
"noResults": "没有找到符合条件的文章",
|
||||
"searchPlaceholder": "搜索文章...",
|
||||
"allTags": "所有标签",
|
||||
"relatedPosts": "相关文章",
|
||||
"tagDescription": "标签文章",
|
||||
"readMore": "阅读更多",
|
||||
"backToBlog": "返回博客",
|
||||
"tags": "标签",
|
||||
"postsTaggedWith": "标签为",
|
||||
"foundPosts": "{count, plural, =0 {未找到文章} other {找到 # 篇文章}}",
|
||||
"tagTitle": "标签为 {tag} 的文章"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,14 @@ const nextConfig = {
|
||||
typescript: {
|
||||
ignoreBuildErrors: true,
|
||||
},
|
||||
images: {
|
||||
domains: [
|
||||
"static.ghost.org",
|
||||
"testing-ghost-8423be-31-220-108-27.traefik.me",
|
||||
"images.unsplash.com",
|
||||
"www.gravatar.com",
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = withNextIntl(nextConfig);
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
},
|
||||
"browserslist": "defaults, not ie <= 11",
|
||||
"dependencies": {
|
||||
"react-photo-view": "^1.2.7",
|
||||
"@headlessui/react": "^1.7.17",
|
||||
"@headlessui/tailwindcss": "^0.2.0",
|
||||
"@radix-ui/react-accordion": "^1.2.1",
|
||||
@@ -23,8 +24,10 @@
|
||||
"@radix-ui/react-tabs": "1.1.1",
|
||||
"@radix-ui/react-tooltip": "^1.1.3",
|
||||
"@tabler/icons-react": "3.21.0",
|
||||
"@tryghost/content-api": "^1.11.21",
|
||||
"@types/node": "20.4.6",
|
||||
"autoprefixer": "^10.4.12",
|
||||
"axios": "^1.8.1",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.0",
|
||||
"framer-motion": "^11.3.19",
|
||||
|
||||
BIN
apps/website/public/backups.png
Normal file
|
After Width: | Height: | Size: 143 KiB |
BIN
apps/website/public/compose.png
Normal file
|
After Width: | Height: | Size: 164 KiB |
BIN
apps/website/public/dashboard.png
Normal file
|
After Width: | Height: | Size: 132 KiB |
BIN
apps/website/public/default.jpg
Normal file
|
After Width: | Height: | Size: 7.3 KiB |
BIN
apps/website/public/logs.png
Normal file
|
After Width: | Height: | Size: 289 KiB |
BIN
apps/website/public/remote.png
Normal file
|
After Width: | Height: | Size: 134 KiB |
BIN
apps/website/public/templates.png
Normal file
|
After Width: | Height: | Size: 240 KiB |
BIN
apps/website/public/traefik.png
Normal file
|
After Width: | Height: | Size: 176 KiB |
11
package.json
@@ -18,12 +18,12 @@
|
||||
"prepare": "node .husky/install.mjs"
|
||||
},
|
||||
"devDependencies": {
|
||||
"lint-staged": "^15.2.7",
|
||||
"@biomejs/biome": "1.8.3",
|
||||
"husky": "^9.1.6",
|
||||
"@commitlint/cli": "^19.3.0",
|
||||
"@commitlint/config-conventional": "^19.2.2",
|
||||
"@types/node": "^20.9.0"
|
||||
"@types/node": "^20.9.0",
|
||||
"husky": "^9.1.6",
|
||||
"lint-staged": "^15.2.7"
|
||||
},
|
||||
"packageManager": "pnpm@9.5.0",
|
||||
"engines": {
|
||||
@@ -41,5 +41,10 @@
|
||||
"resolutions": {
|
||||
"@types/react": "18.3.5",
|
||||
"@types/react-dom": "18.3.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"react-markdown": "^10.0.0",
|
||||
"rehype-raw": "^7.0.0",
|
||||
"remark-gfm": "^4.0.1"
|
||||
}
|
||||
}
|
||||
|
||||
359
pnpm-lock.yaml
generated
@@ -11,6 +11,16 @@ overrides:
|
||||
importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
react-markdown:
|
||||
specifier: ^10.0.0
|
||||
version: 10.0.0(@types/react@18.3.5)(react@18.3.1)
|
||||
rehype-raw:
|
||||
specifier: ^7.0.0
|
||||
version: 7.0.0
|
||||
remark-gfm:
|
||||
specifier: ^4.0.1
|
||||
version: 4.0.1
|
||||
devDependencies:
|
||||
'@biomejs/biome':
|
||||
specifier: 1.8.3
|
||||
@@ -124,12 +134,18 @@ importers:
|
||||
'@tabler/icons-react':
|
||||
specifier: 3.21.0
|
||||
version: 3.21.0(react@18.2.0)
|
||||
'@tryghost/content-api':
|
||||
specifier: ^1.11.21
|
||||
version: 1.11.21
|
||||
'@types/node':
|
||||
specifier: 20.4.6
|
||||
version: 20.4.6
|
||||
autoprefixer:
|
||||
specifier: ^10.4.12
|
||||
version: 10.4.19(postcss@8.4.47)
|
||||
axios:
|
||||
specifier: ^1.8.1
|
||||
version: 1.8.1
|
||||
class-variance-authority:
|
||||
specifier: ^0.7.0
|
||||
version: 0.7.0
|
||||
@@ -157,6 +173,9 @@ importers:
|
||||
react-ga4:
|
||||
specifier: ^2.1.0
|
||||
version: 2.1.0
|
||||
react-photo-view:
|
||||
specifier: ^1.2.7
|
||||
version: 1.2.7(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
tailwind-merge:
|
||||
specifier: ^2.2.2
|
||||
version: 2.4.0
|
||||
@@ -1490,6 +1509,9 @@ packages:
|
||||
'@tanstack/virtual-core@3.8.3':
|
||||
resolution: {integrity: sha512-vd2A2TnM5lbnWZnHi9B+L2gPtkSeOtJOAw358JqokIH1+v2J7vUAzFVPwB/wrye12RFOurffXu33plm4uQ+JBQ==}
|
||||
|
||||
'@tryghost/content-api@1.11.21':
|
||||
resolution: {integrity: sha512-ozJqEMHDUO7D0SGxPbUnG+RvwBbzC3zmdGOW8cFvkcKzrhe7uOAmVKyq7/J3kRAM2QthTlmiDpqp7NEo9ZLlKg==}
|
||||
|
||||
'@types/acorn@4.0.6':
|
||||
resolution: {integrity: sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ==}
|
||||
|
||||
@@ -1615,6 +1637,9 @@ packages:
|
||||
resolution: {integrity: sha512-ISvCdHdlTDlH5IpxQJIex7BWBywFWgjJSVdwst+/iQCoEYnyOaQ95+X1JGshuBjGp6nxKUy1jMgE3zPqN7fQdg==}
|
||||
hasBin: true
|
||||
|
||||
asynckit@0.4.0:
|
||||
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
|
||||
|
||||
autoprefixer@10.4.19:
|
||||
resolution: {integrity: sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
@@ -1629,6 +1654,9 @@ packages:
|
||||
peerDependencies:
|
||||
postcss: ^8.1.0
|
||||
|
||||
axios@1.8.1:
|
||||
resolution: {integrity: sha512-NN+fvwH/kV01dYUQ3PTOZns4LWtWhOFCAhQ/pHb88WQ1hNe5V/dvFwc4VJcDL11LT9xSX0QtsR8sWUuyOuOq7g==}
|
||||
|
||||
bail@2.0.2:
|
||||
resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==}
|
||||
|
||||
@@ -1660,6 +1688,10 @@ packages:
|
||||
resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==}
|
||||
engines: {node: '>=10.16.0'}
|
||||
|
||||
call-bind-apply-helpers@1.0.2:
|
||||
resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
callsites@3.1.0:
|
||||
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -1757,6 +1789,10 @@ packages:
|
||||
colorette@2.0.20:
|
||||
resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==}
|
||||
|
||||
combined-stream@1.0.8:
|
||||
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
comma-separated-tokens@2.0.3:
|
||||
resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==}
|
||||
|
||||
@@ -1841,6 +1877,10 @@ packages:
|
||||
decode-named-character-reference@1.0.2:
|
||||
resolution: {integrity: sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==}
|
||||
|
||||
delayed-stream@1.0.0:
|
||||
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
|
||||
dequal@2.0.3:
|
||||
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -1865,6 +1905,10 @@ packages:
|
||||
resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
dunder-proto@1.0.1:
|
||||
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
eastasianwidth@0.2.0:
|
||||
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
|
||||
|
||||
@@ -1883,6 +1927,10 @@ packages:
|
||||
emoji-regex@9.2.2:
|
||||
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
|
||||
|
||||
entities@4.5.0:
|
||||
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
|
||||
engines: {node: '>=0.12'}
|
||||
|
||||
env-paths@2.2.1:
|
||||
resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -1894,6 +1942,22 @@ packages:
|
||||
error-ex@1.3.2:
|
||||
resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==}
|
||||
|
||||
es-define-property@1.0.1:
|
||||
resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
es-errors@1.3.0:
|
||||
resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
es-object-atoms@1.1.1:
|
||||
resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
es-set-tostringtag@2.1.0:
|
||||
resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
esast-util-from-estree@2.0.0:
|
||||
resolution: {integrity: sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==}
|
||||
|
||||
@@ -1990,6 +2054,15 @@ packages:
|
||||
resolution: {integrity: sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
follow-redirects@1.15.9:
|
||||
resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==}
|
||||
engines: {node: '>=4.0'}
|
||||
peerDependencies:
|
||||
debug: '*'
|
||||
peerDependenciesMeta:
|
||||
debug:
|
||||
optional: true
|
||||
|
||||
foreach@2.0.6:
|
||||
resolution: {integrity: sha512-k6GAGDyqLe9JaebCsFCoudPPWfihKu8pylYXRlqP1J7ms39iPoTtk2fviNglIeQEwdh0bQeKJ01ZPyuyQvKzwg==}
|
||||
|
||||
@@ -1997,6 +2070,10 @@ packages:
|
||||
resolution: {integrity: sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
form-data@4.0.2:
|
||||
resolution: {integrity: sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
fraction.js@4.3.7:
|
||||
resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
|
||||
|
||||
@@ -2075,10 +2152,18 @@ packages:
|
||||
resolution: {integrity: sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
get-intrinsic@1.3.0:
|
||||
resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
get-nonce@1.0.1:
|
||||
resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
get-proto@1.0.1:
|
||||
resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
get-stream@8.0.1:
|
||||
resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==}
|
||||
engines: {node: '>=16'}
|
||||
@@ -2110,6 +2195,10 @@ packages:
|
||||
resolution: {integrity: sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
gopd@1.2.0:
|
||||
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
graceful-fs@4.2.11:
|
||||
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
|
||||
|
||||
@@ -2121,10 +2210,27 @@ packages:
|
||||
resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
has-symbols@1.1.0:
|
||||
resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
has-tostringtag@1.0.2:
|
||||
resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
hasown@2.0.2:
|
||||
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
hast-util-from-parse5@8.0.3:
|
||||
resolution: {integrity: sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==}
|
||||
|
||||
hast-util-parse-selector@4.0.0:
|
||||
resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==}
|
||||
|
||||
hast-util-raw@9.1.0:
|
||||
resolution: {integrity: sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==}
|
||||
|
||||
hast-util-to-estree@3.1.0:
|
||||
resolution: {integrity: sha512-lfX5g6hqVh9kjS/B9E2gSkvHH4SZNiQFiqWS0x9fENzEl+8W12RqdRxX6d/Cwxi30tPQs3bIO+aolQJNp1bIyw==}
|
||||
|
||||
@@ -2134,12 +2240,21 @@ packages:
|
||||
hast-util-to-jsx-runtime@2.3.2:
|
||||
resolution: {integrity: sha512-1ngXYb+V9UT5h+PxNRa1O1FYguZK/XL+gkeqvp7EdHlB9oHUG0eYRo/vY5inBdcqo3RkPMC58/H94HvkbfGdyg==}
|
||||
|
||||
hast-util-to-parse5@8.0.0:
|
||||
resolution: {integrity: sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==}
|
||||
|
||||
hast-util-to-string@3.0.1:
|
||||
resolution: {integrity: sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==}
|
||||
|
||||
hast-util-whitespace@3.0.0:
|
||||
resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==}
|
||||
|
||||
hastscript@9.0.1:
|
||||
resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==}
|
||||
|
||||
html-url-attributes@3.0.1:
|
||||
resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==}
|
||||
|
||||
html-void-elements@3.0.0:
|
||||
resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==}
|
||||
|
||||
@@ -2381,6 +2496,10 @@ packages:
|
||||
markdown-table@3.0.3:
|
||||
resolution: {integrity: sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==}
|
||||
|
||||
math-intrinsics@1.1.0:
|
||||
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
mdast-util-find-and-replace@3.0.1:
|
||||
resolution: {integrity: sha512-SG21kZHGC3XRTSUhtofZkBzZTJNM5ecCi0SK2IMKmSXR8vO3peL+kb1O0z7Zl83jKtutG4k5Wv/W7V3/YHvzPA==}
|
||||
|
||||
@@ -2553,6 +2672,14 @@ packages:
|
||||
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
|
||||
engines: {node: '>=8.6'}
|
||||
|
||||
mime-db@1.52.0:
|
||||
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
mime-types@2.1.35:
|
||||
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
mimic-fn@4.0.0:
|
||||
resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==}
|
||||
engines: {node: '>=12'}
|
||||
@@ -2704,6 +2831,9 @@ packages:
|
||||
resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
parse5@7.2.1:
|
||||
resolution: {integrity: sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==}
|
||||
|
||||
path-exists@5.0.0:
|
||||
resolution: {integrity: sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
@@ -2855,6 +2985,12 @@ packages:
|
||||
property-information@6.5.0:
|
||||
resolution: {integrity: sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==}
|
||||
|
||||
property-information@7.0.0:
|
||||
resolution: {integrity: sha512-7D/qOz/+Y4X/rzSB6jKxKUsQnphO046ei8qxG59mtM3RG3DHgTK81HrxrmoDVINJb8NKT5ZsRbwHvQ6B68Iyhg==}
|
||||
|
||||
proxy-from-env@1.1.0:
|
||||
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
|
||||
|
||||
queue-microtask@1.2.3:
|
||||
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
||||
|
||||
@@ -2880,12 +3016,24 @@ packages:
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17 || ^18 || ^19
|
||||
|
||||
react-markdown@10.0.0:
|
||||
resolution: {integrity: sha512-4mTz7Sya/YQ1jYOrkwO73VcFdkFJ8L8I9ehCxdcV0XrClHyOJGKbBk5FR4OOOG+HnyKw5u+C/Aby9TwinCteYA==}
|
||||
peerDependencies:
|
||||
'@types/react': 18.3.5
|
||||
react: '>=18'
|
||||
|
||||
react-medium-image-zoom@5.2.10:
|
||||
resolution: {integrity: sha512-JBYf4u0zsocezIDtrjwStD+8sX+c8XuLsdz+HxPbojRj0sCicua0XOQKysuPetoFyX+YgStfj+vEtZ+699O/pg==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
|
||||
react-photo-view@1.2.7:
|
||||
resolution: {integrity: sha512-MfOWVPxuibncRLaycZUNxqYU8D9IA+rbGDDaq6GM8RIoGJal592hEJoRAyRSI7ZxyyJNJTLMUWWL3UIXHJJOpw==}
|
||||
peerDependencies:
|
||||
react: '>=16.8.0'
|
||||
react-dom: '>=16.8.0'
|
||||
|
||||
react-remove-scroll-bar@2.3.6:
|
||||
resolution: {integrity: sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -2960,12 +3108,18 @@ packages:
|
||||
regex@4.4.0:
|
||||
resolution: {integrity: sha512-uCUSuobNVeqUupowbdZub6ggI5/JZkYyJdDogddJr60L764oxC2pMZov1fQ3wM9bdyzUILDG+Sqx6NAKAz9rKQ==}
|
||||
|
||||
rehype-raw@7.0.0:
|
||||
resolution: {integrity: sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==}
|
||||
|
||||
rehype-recma@1.0.0:
|
||||
resolution: {integrity: sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==}
|
||||
|
||||
remark-gfm@4.0.0:
|
||||
resolution: {integrity: sha512-U92vJgBPkbw4Zfu/IiW2oTZLSL3Zpv+uI7My2eq8JxKgqraFdU8YUGicEJCEgSbeaG+QDFqIcwwfMTOEelPxuA==}
|
||||
|
||||
remark-gfm@4.0.1:
|
||||
resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==}
|
||||
|
||||
remark-mdx@3.0.1:
|
||||
resolution: {integrity: sha512-3Pz3yPQ5Rht2pM5R+0J2MrGoBSrzf+tJG94N+t/ilfdh8YLyyKYtidAYwTveB20BoHAcwIopOUqhcmh2F7hGYA==}
|
||||
|
||||
@@ -3307,12 +3461,18 @@ packages:
|
||||
util-deprecate@1.0.2:
|
||||
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
||||
|
||||
vfile-location@5.0.3:
|
||||
resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==}
|
||||
|
||||
vfile-message@4.0.2:
|
||||
resolution: {integrity: sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==}
|
||||
|
||||
vfile@6.0.2:
|
||||
resolution: {integrity: sha512-zND7NlS8rJYb/sPqkb13ZvbbUoExdbi4w3SfRrMq6R3FvnLQmmfpajJNITuuYm6AZ5uao9vy4BAos3EXBPf2rg==}
|
||||
|
||||
web-namespaces@2.0.1:
|
||||
resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==}
|
||||
|
||||
which@2.0.2:
|
||||
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
|
||||
engines: {node: '>= 8'}
|
||||
@@ -4803,6 +4963,12 @@ snapshots:
|
||||
|
||||
'@tanstack/virtual-core@3.8.3': {}
|
||||
|
||||
'@tryghost/content-api@1.11.21':
|
||||
dependencies:
|
||||
axios: 1.8.1
|
||||
transitivePeerDependencies:
|
||||
- debug
|
||||
|
||||
'@types/acorn@4.0.6':
|
||||
dependencies:
|
||||
'@types/estree': 1.0.5
|
||||
@@ -4921,6 +5087,8 @@ snapshots:
|
||||
|
||||
astring@1.8.6: {}
|
||||
|
||||
asynckit@0.4.0: {}
|
||||
|
||||
autoprefixer@10.4.19(postcss@8.4.47):
|
||||
dependencies:
|
||||
browserslist: 4.23.2
|
||||
@@ -4941,6 +5109,14 @@ snapshots:
|
||||
postcss: 8.4.47
|
||||
postcss-value-parser: 4.2.0
|
||||
|
||||
axios@1.8.1:
|
||||
dependencies:
|
||||
follow-redirects: 1.15.9
|
||||
form-data: 4.0.2
|
||||
proxy-from-env: 1.1.0
|
||||
transitivePeerDependencies:
|
||||
- debug
|
||||
|
||||
bail@2.0.2: {}
|
||||
|
||||
balanced-match@1.0.2: {}
|
||||
@@ -4973,6 +5149,11 @@ snapshots:
|
||||
dependencies:
|
||||
streamsearch: 1.1.0
|
||||
|
||||
call-bind-apply-helpers@1.0.2:
|
||||
dependencies:
|
||||
es-errors: 1.3.0
|
||||
function-bind: 1.1.2
|
||||
|
||||
callsites@3.1.0: {}
|
||||
|
||||
camelcase-css@2.0.1: {}
|
||||
@@ -5068,6 +5249,10 @@ snapshots:
|
||||
|
||||
colorette@2.0.20: {}
|
||||
|
||||
combined-stream@1.0.8:
|
||||
dependencies:
|
||||
delayed-stream: 1.0.0
|
||||
|
||||
comma-separated-tokens@2.0.3: {}
|
||||
|
||||
commander@12.1.0: {}
|
||||
@@ -5136,6 +5321,8 @@ snapshots:
|
||||
dependencies:
|
||||
character-entities: 2.0.2
|
||||
|
||||
delayed-stream@1.0.0: {}
|
||||
|
||||
dequal@2.0.3: {}
|
||||
|
||||
detect-libc@2.0.3:
|
||||
@@ -5155,6 +5342,12 @@ snapshots:
|
||||
dependencies:
|
||||
is-obj: 2.0.0
|
||||
|
||||
dunder-proto@1.0.1:
|
||||
dependencies:
|
||||
call-bind-apply-helpers: 1.0.2
|
||||
es-errors: 1.3.0
|
||||
gopd: 1.2.0
|
||||
|
||||
eastasianwidth@0.2.0: {}
|
||||
|
||||
electron-to-chromium@1.5.2: {}
|
||||
@@ -5167,6 +5360,8 @@ snapshots:
|
||||
|
||||
emoji-regex@9.2.2: {}
|
||||
|
||||
entities@4.5.0: {}
|
||||
|
||||
env-paths@2.2.1: {}
|
||||
|
||||
environment@1.1.0: {}
|
||||
@@ -5175,6 +5370,21 @@ snapshots:
|
||||
dependencies:
|
||||
is-arrayish: 0.2.1
|
||||
|
||||
es-define-property@1.0.1: {}
|
||||
|
||||
es-errors@1.3.0: {}
|
||||
|
||||
es-object-atoms@1.1.1:
|
||||
dependencies:
|
||||
es-errors: 1.3.0
|
||||
|
||||
es-set-tostringtag@2.1.0:
|
||||
dependencies:
|
||||
es-errors: 1.3.0
|
||||
get-intrinsic: 1.3.0
|
||||
has-tostringtag: 1.0.2
|
||||
hasown: 2.0.2
|
||||
|
||||
esast-util-from-estree@2.0.0:
|
||||
dependencies:
|
||||
'@types/estree-jsx': 1.0.5
|
||||
@@ -5336,6 +5546,8 @@ snapshots:
|
||||
path-exists: 5.0.0
|
||||
unicorn-magic: 0.1.0
|
||||
|
||||
follow-redirects@1.15.9: {}
|
||||
|
||||
foreach@2.0.6: {}
|
||||
|
||||
foreground-child@3.2.1:
|
||||
@@ -5343,6 +5555,13 @@ snapshots:
|
||||
cross-spawn: 7.0.3
|
||||
signal-exit: 4.1.0
|
||||
|
||||
form-data@4.0.2:
|
||||
dependencies:
|
||||
asynckit: 0.4.0
|
||||
combined-stream: 1.0.8
|
||||
es-set-tostringtag: 2.1.0
|
||||
mime-types: 2.1.35
|
||||
|
||||
fraction.js@4.3.7: {}
|
||||
|
||||
framer-motion@11.3.19(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
|
||||
@@ -5461,8 +5680,26 @@ snapshots:
|
||||
|
||||
get-east-asian-width@1.2.0: {}
|
||||
|
||||
get-intrinsic@1.3.0:
|
||||
dependencies:
|
||||
call-bind-apply-helpers: 1.0.2
|
||||
es-define-property: 1.0.1
|
||||
es-errors: 1.3.0
|
||||
es-object-atoms: 1.1.1
|
||||
function-bind: 1.1.2
|
||||
get-proto: 1.0.1
|
||||
gopd: 1.2.0
|
||||
has-symbols: 1.1.0
|
||||
hasown: 2.0.2
|
||||
math-intrinsics: 1.1.0
|
||||
|
||||
get-nonce@1.0.1: {}
|
||||
|
||||
get-proto@1.0.1:
|
||||
dependencies:
|
||||
dunder-proto: 1.0.1
|
||||
es-object-atoms: 1.1.1
|
||||
|
||||
get-stream@8.0.1: {}
|
||||
|
||||
get-tsconfig@4.8.1:
|
||||
@@ -5498,6 +5735,8 @@ snapshots:
|
||||
dependencies:
|
||||
ini: 4.1.1
|
||||
|
||||
gopd@1.2.0: {}
|
||||
|
||||
graceful-fs@4.2.11: {}
|
||||
|
||||
gray-matter@4.0.3:
|
||||
@@ -5509,10 +5748,47 @@ snapshots:
|
||||
|
||||
has-flag@3.0.0: {}
|
||||
|
||||
has-symbols@1.1.0: {}
|
||||
|
||||
has-tostringtag@1.0.2:
|
||||
dependencies:
|
||||
has-symbols: 1.1.0
|
||||
|
||||
hasown@2.0.2:
|
||||
dependencies:
|
||||
function-bind: 1.1.2
|
||||
|
||||
hast-util-from-parse5@8.0.3:
|
||||
dependencies:
|
||||
'@types/hast': 3.0.4
|
||||
'@types/unist': 3.0.2
|
||||
devlop: 1.1.0
|
||||
hastscript: 9.0.1
|
||||
property-information: 7.0.0
|
||||
vfile: 6.0.2
|
||||
vfile-location: 5.0.3
|
||||
web-namespaces: 2.0.1
|
||||
|
||||
hast-util-parse-selector@4.0.0:
|
||||
dependencies:
|
||||
'@types/hast': 3.0.4
|
||||
|
||||
hast-util-raw@9.1.0:
|
||||
dependencies:
|
||||
'@types/hast': 3.0.4
|
||||
'@types/unist': 3.0.2
|
||||
'@ungap/structured-clone': 1.2.0
|
||||
hast-util-from-parse5: 8.0.3
|
||||
hast-util-to-parse5: 8.0.0
|
||||
html-void-elements: 3.0.0
|
||||
mdast-util-to-hast: 13.2.0
|
||||
parse5: 7.2.1
|
||||
unist-util-position: 5.0.0
|
||||
unist-util-visit: 5.0.0
|
||||
vfile: 6.0.2
|
||||
web-namespaces: 2.0.1
|
||||
zwitch: 2.0.4
|
||||
|
||||
hast-util-to-estree@3.1.0:
|
||||
dependencies:
|
||||
'@types/estree': 1.0.5
|
||||
@@ -5568,6 +5844,16 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
hast-util-to-parse5@8.0.0:
|
||||
dependencies:
|
||||
'@types/hast': 3.0.4
|
||||
comma-separated-tokens: 2.0.3
|
||||
devlop: 1.1.0
|
||||
property-information: 6.5.0
|
||||
space-separated-tokens: 2.0.2
|
||||
web-namespaces: 2.0.1
|
||||
zwitch: 2.0.4
|
||||
|
||||
hast-util-to-string@3.0.1:
|
||||
dependencies:
|
||||
'@types/hast': 3.0.4
|
||||
@@ -5576,6 +5862,16 @@ snapshots:
|
||||
dependencies:
|
||||
'@types/hast': 3.0.4
|
||||
|
||||
hastscript@9.0.1:
|
||||
dependencies:
|
||||
'@types/hast': 3.0.4
|
||||
comma-separated-tokens: 2.0.3
|
||||
hast-util-parse-selector: 4.0.0
|
||||
property-information: 7.0.0
|
||||
space-separated-tokens: 2.0.2
|
||||
|
||||
html-url-attributes@3.0.1: {}
|
||||
|
||||
html-void-elements@3.0.0: {}
|
||||
|
||||
human-signals@5.0.0: {}
|
||||
@@ -5783,6 +6079,8 @@ snapshots:
|
||||
|
||||
markdown-table@3.0.3: {}
|
||||
|
||||
math-intrinsics@1.1.0: {}
|
||||
|
||||
mdast-util-find-and-replace@3.0.1:
|
||||
dependencies:
|
||||
'@types/mdast': 4.0.4
|
||||
@@ -6226,6 +6524,12 @@ snapshots:
|
||||
braces: 3.0.3
|
||||
picomatch: 2.3.1
|
||||
|
||||
mime-db@1.52.0: {}
|
||||
|
||||
mime-types@2.1.35:
|
||||
dependencies:
|
||||
mime-db: 1.52.0
|
||||
|
||||
mimic-fn@4.0.0: {}
|
||||
|
||||
mimic-function@5.0.1: {}
|
||||
@@ -6380,6 +6684,10 @@ snapshots:
|
||||
json-parse-even-better-errors: 2.3.1
|
||||
lines-and-columns: 1.2.4
|
||||
|
||||
parse5@7.2.1:
|
||||
dependencies:
|
||||
entities: 4.5.0
|
||||
|
||||
path-exists@5.0.0: {}
|
||||
|
||||
path-key@3.1.1: {}
|
||||
@@ -6461,6 +6769,10 @@ snapshots:
|
||||
|
||||
property-information@6.5.0: {}
|
||||
|
||||
property-information@7.0.0: {}
|
||||
|
||||
proxy-from-env@1.1.0: {}
|
||||
|
||||
queue-microtask@1.2.3: {}
|
||||
|
||||
queue@6.0.2:
|
||||
@@ -6485,11 +6797,34 @@ snapshots:
|
||||
dependencies:
|
||||
react: 18.3.1
|
||||
|
||||
react-markdown@10.0.0(@types/react@18.3.5)(react@18.3.1):
|
||||
dependencies:
|
||||
'@types/hast': 3.0.4
|
||||
'@types/mdast': 4.0.4
|
||||
'@types/react': 18.3.5
|
||||
devlop: 1.1.0
|
||||
hast-util-to-jsx-runtime: 2.3.2
|
||||
html-url-attributes: 3.0.1
|
||||
mdast-util-to-hast: 13.2.0
|
||||
react: 18.3.1
|
||||
remark-parse: 11.0.0
|
||||
remark-rehype: 11.1.1
|
||||
unified: 11.0.5
|
||||
unist-util-visit: 5.0.0
|
||||
vfile: 6.0.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
react-medium-image-zoom@5.2.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||
dependencies:
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
|
||||
react-photo-view@1.2.7(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
|
||||
dependencies:
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
|
||||
react-remove-scroll-bar@2.3.6(@types/react@18.3.5)(react@18.2.0):
|
||||
dependencies:
|
||||
react: 18.2.0
|
||||
@@ -6596,6 +6931,12 @@ snapshots:
|
||||
|
||||
regex@4.4.0: {}
|
||||
|
||||
rehype-raw@7.0.0:
|
||||
dependencies:
|
||||
'@types/hast': 3.0.4
|
||||
hast-util-raw: 9.1.0
|
||||
vfile: 6.0.2
|
||||
|
||||
rehype-recma@1.0.0:
|
||||
dependencies:
|
||||
'@types/estree': 1.0.5
|
||||
@@ -6615,6 +6956,17 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
remark-gfm@4.0.1:
|
||||
dependencies:
|
||||
'@types/mdast': 4.0.4
|
||||
mdast-util-gfm: 3.0.0
|
||||
micromark-extension-gfm: 3.0.0
|
||||
remark-parse: 11.0.0
|
||||
remark-stringify: 11.0.0
|
||||
unified: 11.0.5
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
remark-mdx@3.0.1:
|
||||
dependencies:
|
||||
mdast-util-mdx: 3.0.0
|
||||
@@ -7036,6 +7388,11 @@ snapshots:
|
||||
|
||||
util-deprecate@1.0.2: {}
|
||||
|
||||
vfile-location@5.0.3:
|
||||
dependencies:
|
||||
'@types/unist': 3.0.2
|
||||
vfile: 6.0.2
|
||||
|
||||
vfile-message@4.0.2:
|
||||
dependencies:
|
||||
'@types/unist': 3.0.2
|
||||
@@ -7047,6 +7404,8 @@ snapshots:
|
||||
unist-util-stringify-position: 4.0.0
|
||||
vfile-message: 4.0.2
|
||||
|
||||
web-namespaces@2.0.1: {}
|
||||
|
||||
which@2.0.2:
|
||||
dependencies:
|
||||
isexe: 2.0.0
|
||||
|
||||