This commit is contained in:
Stefan Pejcic
2024-09-18 16:30:56 +02:00
parent ae4c612987
commit b8c5011b76
1349 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,120 @@
import React from "react";
import { Admonition as RefineAdmonition } from "@site/src/refine-theme/common-admonition";
function GithubIcon() {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
>
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
</svg>
);
}
function TerminalIcon() {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<polyline points="4 17 10 11 4 5"></polyline>
<line x1="12" y1="19" x2="20" y2="19"></line>
</svg>
);
}
// Workaround because it's difficult in MDX v1 to provide a MDX title as props
// See https://github.com/facebook/docusaurus/pull/7152#issuecomment-1145779682
function extractMDXAdmonitionTitle(children) {
const items = React.Children.toArray(children);
const mdxAdmonitionTitle = items.find(
(item) =>
React.isValidElement(item) &&
item.props?.mdxType === "mdxAdmonitionTitle",
);
const rest = <>{items.filter((item) => item !== mdxAdmonitionTitle)}</>;
return {
mdxAdmonitionTitle,
rest,
};
}
function processAdmonitionProps(props) {
const { mdxAdmonitionTitle, rest } = extractMDXAdmonitionTitle(
props.children,
);
return {
...props,
title: props.title ?? mdxAdmonitionTitle,
children: rest,
};
}
const AdmonitionBase = (props) => {
const { children, type, title } = processAdmonitionProps(props);
return (
<RefineAdmonition type={type} title={title}>
{children}
</RefineAdmonition>
);
};
export default function AdmonitionWrapper(props) {
if (props.type === "info-tip") {
return <AdmonitionBase {...props} />;
}
if (props.type === "additional") {
return (
<AdmonitionBase {...props} type="note" title="ADDITIONAL INFO" />
);
}
if (props.type === "sourcecode") {
return (
<AdmonitionBase
{...props}
icon={<GithubIcon />}
title={
<a
href={props.path}
target="_blank"
rel="noreferrer"
style={{ marginLeft: "4px" }}
>
VIEW SOURCE CODE &#8594;
</a>
}
titleProps={{ style: { marginBottom: "-4px" } }}
>
{props.children}
</AdmonitionBase>
);
}
if (props.type === "create-example") {
return (
<AdmonitionBase
{...props}
type="caution"
icon={<TerminalIcon />}
title={
<span style={{ marginLeft: "4px" }}>RUN IN YOUR LOCAL</span>
}
>
{props.children}
</AdmonitionBase>
);
}
return (
<>
<AdmonitionBase {...props} />
</>
);
}

View File

@@ -0,0 +1,31 @@
.admonition {
margin-bottom: 1em;
}
.admonitionHeading {
font: var(--ifm-heading-font-weight) var(--ifm-h5-font-size) /
var(--ifm-heading-line-height) var(--ifm-heading-font-family);
text-transform: uppercase;
margin-bottom: 0.3rem;
}
.admonitionHeading code {
text-transform: none;
}
.admonitionIcon {
display: inline-block;
vertical-align: middle;
margin-right: 0.4em;
}
.admonitionIcon svg {
display: inline-block;
height: 1.6em;
width: 1.6em;
fill: var(--ifm-alert-foreground-color);
}
.admonitionContent > :last-child {
margin-bottom: 0;
}

View File

@@ -0,0 +1 @@
export { RefineBlogLayout as default } from "@site/src/refine-theme/blog-layout";

View File

@@ -0,0 +1,118 @@
import React from "react";
import clsx from "clsx";
import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
import {
PageMetadata,
HtmlClassNameProvider,
ThemeClassNames,
} from "@docusaurus/theme-common";
import BlogLayout from "@theme/BlogLayout";
import SearchMetadata from "@theme/SearchMetadata";
import BlogPostItems from "@theme/BlogPostItems";
import BlogListPaginator from "@theme/BlogListPaginator";
import { FeaturedBlogPostItems } from "../../components/blog";
function BlogListPageMetadata(props) {
const { metadata } = props;
const {
siteConfig: { title: siteTitle },
} = useDocusaurusContext();
const { blogDescription, blogTitle, permalink } = metadata;
const isBlogOnlyMode = permalink === "/";
const title = isBlogOnlyMode ? siteTitle : blogTitle;
return (
<>
<PageMetadata title={title} description={blogDescription} />
<SearchMetadata tag="blog_posts_list" />
</>
);
}
function BlogListPageContent(props) {
const { metadata, tags, items } = props;
const isFirstPage = metadata.page === 1;
const featuredPosts = items.filter(
(post) => post.content.metadata.frontMatter.is_featured === true,
);
const paginatedPosts = items.filter(
(post) => post.content.metadata.frontMatter.is_featured !== true,
);
return (
<BlogLayout showSidebarBanner={false}>
{/* <div
className={clsx(
"px-4",
"max-w-[512px]",
"blog-md:px-7",
"blog-md:max-w-screen-blog-md",
"blog-2xl:px-0",
"blog-2xl:max-w-screen-blog-md",
"w-full",
"mx-auto",
)}
>
<a
href="https://s.refine.dev/hackathon2"
target="_blank"
rel="noreferrer"
>
<img src="https://refine.ams3.cdn.digitaloceanspaces.com/hackathon-2/hackathon_cover.png" />
</a>
</div> */}
{isFirstPage && <FeaturedBlogPostItems items={featuredPosts} />}
<div
className={clsx(
"px-4",
"max-w-[512px]",
"blog-md:px-7",
"blog-md:max-w-screen-blog-md",
"blog-2xl:px-0",
"blog-2xl:max-w-screen-blog-md",
"w-full",
"mx-auto",
"blog-md:block hidden",
)}
>
<div className="border-b border-gray-100 dark:border-gray-700"></div>
</div>
<BlogPostItems
items={paginatedPosts}
tags={tags}
metadata={metadata}
/>
<div
className={clsx(
"max-w-[512px]",
"blog-md:max-w-screen-blog-md",
"blog-2xl:max-w-screen-blog-md",
"w-full",
"mx-auto",
"blog-md:border-t border-t-gray-200 dark:border-t-gray-700",
"blog-sm:mb-16 blog-2xl:mb-20 mb-10",
)}
>
<BlogListPaginator metadata={metadata} />
</div>
</BlogLayout>
);
}
export default function BlogListPage(props) {
return (
<HtmlClassNameProvider
className={clsx(
ThemeClassNames.wrapper.blogPages,
ThemeClassNames.page.blogListPage,
)}
>
<BlogListPageMetadata {...props} />
<BlogListPageContent {...props} />
</HtmlClassNameProvider>
);
}

View File

@@ -0,0 +1,130 @@
import React from "react";
import { translate } from "@docusaurus/Translate";
import Link from "@docusaurus/Link";
import { ChevronLeft, ChevronRight } from "../../components/blog/icons";
import { usePagination, DOTS } from "../../hooks/use-pagination";
import clsx from "clsx";
export default function BlogListPaginator(props) {
const { metadata, basePath = "/blog" } = props;
const { totalPages, page: currentPage } = metadata;
const paginationRange = usePagination({ totalPages, currentPage });
if (currentPage === 0 || paginationRange.length < 2) {
return null;
}
const lastPage = paginationRange[paginationRange.length - 1];
return (
<nav
className={clsx(
"blog-md:justify-end flex items-center justify-center",
)}
aria-label={translate({
id: "theme.blog.paginator.navAriaLabel",
message: "Blog list page navigation",
description: "The ARIA label for the blog pagination",
})}
>
<ul
className="flex list-none items-center gap-1"
style={{
padding: 0,
}}
>
<li>
<Link
to={
currentPage === 1
? undefined
: currentPage - 1 === 1
? basePath
: `${basePath}/page/${currentPage - 1}`
}
className={clsx(
"text-gray-500 dark:text-gray-400",
"rounded",
"hover:no-underline",
"hover:no-underline",
{
"pointer-events-none opacity-20":
currentPage === 1,
},
)}
>
<ChevronLeft />
</Link>
</li>
{paginationRange.map((pageNumber) => {
if (pageNumber === DOTS) {
return (
<li
key={`page:${pageNumber}`}
className={clsx(
"flex items-center justify-center",
"text-gray-500 dark:text-gray-400",
"rounded",
"hover:no-underline",
"no-underline",
"h-[32px] w-[32px]",
)}
>
&#8230;
</li>
);
}
return (
<li key={pageNumber}>
<Link
to={
pageNumber === 1
? basePath
: `${basePath}/page/${pageNumber}`
}
className={clsx(
"text-sm",
"flex items-center justify-center",
"text-gray-500 dark:text-gray-400",
"rounded",
"hover:no-underline",
"h-[32px] w-[32px]",
"no-underline",
pageNumber === currentPage &&
"bg-gray-100 dark:bg-gray-700",
)}
>
{pageNumber}
</Link>
</li>
);
})}
<li>
<Link
to={
currentPage === lastPage
? undefined
: `${basePath}/page/${currentPage + 1}`
}
className={clsx(
"text-gray-500 dark:text-gray-400",
"rounded",
"hover:no-underline",
{
"pointer-events-none opacity-20":
currentPage === lastPage,
},
)}
>
<ChevronRight />
</Link>
</li>
</ul>
</nav>
);
}

View File

@@ -0,0 +1,109 @@
import React from "react";
import Link from "@docusaurus/Link";
import { useBlogPost } from "@docusaurus/theme-common/internal";
import BlogPostItemContainer from "@theme/BlogPostItem/Container";
import { Date } from "@site/src/components/blog/common";
import clsx from "clsx";
export default function BlogPostItem({ className }) {
const { metadata } = useBlogPost();
const {
permalink,
title,
date,
formattedDate,
frontMatter,
description,
tags,
} = metadata;
const author = metadata.authors[0];
return (
<BlogPostItemContainer className={className}>
<div>
<Link itemProp="url" to={permalink}>
<div className="not-prose relative m-0 h-40 hover:brightness-90">
<img
src={`https://openpanel.co${frontMatter.image?.replace(
"https://openpanel.co",
"",
)}?h=160`}
alt={title}
className="absolute inset-0 mt-0 h-full w-full rounded-[10px] object-cover transition duration-150"
loading="lazy"
/>
</div>
</Link>
</div>
<div className="p-4">
<div
className={clsx(
"mb-2 flex gap-1 md:mb-4",
"flex flex-wrap items-center",
)}
>
{tags.map((tag) => (
<Link
className={clsx(
"text-xs",
"bg-gray-100 dark:bg-gray-700",
"text-gray-600 hover:text-gray-600 dark:text-gray-400 dark:hover:text-gray-400",
"no-underline",
"rounded",
"px-2 py-1",
)}
href={tag.permalink}
key={tag.permalink}
>
{tag.label}
</Link>
))}
</div>
<div className="mb-2 md:mb-4">
<Link
itemProp="url"
to={permalink}
className="no-underline hover:no-underline"
rel="noopener dofollow"
>
<div
className={clsx(
"text-xs sm:text-sm md:text-2xl lg:text-base 2xl:text-xl",
"text-gray-700 dark:text-gray-200",
"font-lg",
"font-bold",
"leading-6",
)}
>
{title}
</div>
</Link>
<div
className={clsx(
"text-xs md:text-base lg:text-sm 2xl:text-lg",
"mt-2 md:mt-4",
"line-clamp-3 text-gray-700 dark:text-gray-300",
)}
>
{description}
</div>
</div>
<div className="flex items-center gap-2">
<span
className={clsx(
"text-gray-600 hover:text-gray-600",
"dark:text-gray-400 hover:dark:text-gray-400",
"text-xs",
"no-underline",
)}
>
<Date date={date} formattedDate={formattedDate} />
</span>
</div>
</div>
</BlogPostItemContainer>
);
}

View File

@@ -0,0 +1,95 @@
import React from "react";
import { BlogPostProvider } from "@docusaurus/theme-common/internal";
import TagsList from "@theme/TagsList";
import BlogPostItem from "@theme/BlogPostItem";
import clsx from "clsx";
export default function BlogPostItems({
items,
tags,
component: BlogPostItemComponent = BlogPostItem,
isAuthorPage,
isTagsPage,
}) {
return (
<div
className={clsx(
"px-4",
"blog-md:px-7",
"blog-2xl:px-0",
!isAuthorPage &&
!isTagsPage &&
"blog-sm:pb-16 blog-md:pb-8 blog-2xl:pb-12 pb-10",
!isAuthorPage && !isTagsPage && "blog-md:pt-16 blog-2xl:pt-20",
(isAuthorPage || isTagsPage) && "py-8",
"max-w-[512px]",
"blog-md:max-w-screen-blog-md",
"blog-2xl:max-w-screen-blog-md",
"w-full",
"mx-auto",
)}
>
{!isAuthorPage && !isTagsPage && (
<>
<div
className={clsx(
"flex flex-col blog-md:flex-row items-start lg:items-center justify-between",
"mb-4 lg:mb-8",
"px-0 blog-sm:px-6",
"not-prose",
)}
>
<h2
className={clsx(
"!m-0 !mt-0 !mb-0 p-0",
"blog-lg:mb-12 blog-md:mb-8 mb-8",
"text-xl blog-sm:text-4xl blog-lg:text-5xl",
"text-gray-900 dark:text-gray-0",
"font-semibold",
)}
>
All Posts
</h2>
<p
className={clsx(
"text-sm blog-md:text-base blog-lg:text-xl",
"blog-md:max-w-[624px]",
"mt-6 lg:mt-0",
"text-gray-500 dark:text-gray-400",
)}
>
<b>OpenPanel technical blog</b> - a resource for
OpenPanel, admin and end-users.
Here, we publish insightful articles that demystify
complex concepts, explore new trends, and provide
helpful tips to improve your hosting business.
</p>
</div>
<TagsList tags={tags} />
</>
)}
<div
className={clsx(
"grid",
"grid-cols-1 blog-md:grid-cols-3",
"gap-4 blog-lg:gap-12",
"pt-6",
isAuthorPage ? "blog-md:pt-0" : "blog-md:pt-12",
)}
>
{items.map(({ content: BlogPostContent }) => (
<BlogPostProvider
key={BlogPostContent.metadata.permalink}
content={BlogPostContent}
>
<BlogPostItemComponent>
<BlogPostContent />
</BlogPostItemComponent>
</BlogPostProvider>
))}
</div>
</div>
);
}

View File

@@ -0,0 +1,39 @@
import React from "react";
import { PageMetadata } from "@docusaurus/theme-common";
import { useBlogPost } from "@docusaurus/theme-common/internal";
export default function BlogPostPageMetadata() {
const { assets, metadata } = useBlogPost();
const { title, description, date, tags, authors, frontMatter } = metadata;
const { keywords } = frontMatter;
const image = frontMatter.social_image ?? assets.image ?? frontMatter.image;
return (
<PageMetadata
title={title}
description={description}
keywords={keywords}
image={image}
>
<meta property="og:type" content="article" />
<meta property="article:published_time" content={date} />
{/* TODO double check those article meta array syntaxes, see https://ogp.me/#array */}
{authors.some((author) => author.url) && (
<meta
property="article:author"
content={authors
.map((author) => author.url)
.filter(Boolean)
.join(",")}
/>
)}
{tags.length > 0 && (
<meta
property="article:tag"
content={tags.map((tag) => tag.label).join(",")}
/>
)}
</PageMetadata>
);
}

View File

@@ -0,0 +1,46 @@
import React from "react";
import clsx from "clsx";
import {
HtmlClassNameProvider,
ThemeClassNames,
} from "@docusaurus/theme-common";
import {
BlogPostProvider,
useBlogPost,
} from "@docusaurus/theme-common/internal";
import BlogLayout from "@theme/BlogLayout";
import BlogPostPageMetadata from "@theme/BlogPostPage/Metadata";
import { BlogTOC } from "../../refine-theme/blog-toc";
import { BlogPostPageView, PostPaginator } from "../../components/blog";
function BlogPostPageContent({ children }) {
const { metadata, toc } = useBlogPost();
const { relatedPosts } = metadata;
return (
<BlogLayout toc={<BlogTOC toc={toc} />}>
<BlogPostPageView>{children}</BlogPostPageView>
<PostPaginator title="Related Articles" posts={relatedPosts} />
</BlogLayout>
);
}
export default function BlogPostPage(props) {
const BlogPostContent = props.content;
return (
<BlogPostProvider content={props.content} isBlogPostPage>
<HtmlClassNameProvider
className={clsx(
ThemeClassNames.wrapper.blogPages,
ThemeClassNames.page.blogPostPage,
)}
>
<BlogPostPageMetadata />
<BlogPostPageContent>
<BlogPostContent />
</BlogPostPageContent>
</HtmlClassNameProvider>
</BlogPostProvider>
);
}

View File

@@ -0,0 +1,30 @@
import React from "react";
import clsx from "clsx";
import {
PageMetadata,
HtmlClassNameProvider,
ThemeClassNames,
translateTagsPageTitle,
} from "@docusaurus/theme-common";
import BlogLayout from "@theme/BlogLayout";
import TagsList from "@theme/TagsList";
import SearchMetadata from "@theme/SearchMetadata";
export default function BlogTagsListPage({ tags, sidebar }) {
const title = translateTagsPageTitle();
return (
<HtmlClassNameProvider
className={clsx(
ThemeClassNames.wrapper.blogPages,
ThemeClassNames.page.blogTagsListPage,
)}
>
<PageMetadata title={title} />
<SearchMetadata tag="blog_tags_list" />
<BlogLayout showSidebarBanner={false} sidebar={sidebar}>
<h1 className="">{title}</h1>
<TagsList tags={tags} />
</BlogLayout>
</HtmlClassNameProvider>
);
}

View File

@@ -0,0 +1,108 @@
import React from "react";
import clsx from "clsx";
import { translate } from "@docusaurus/Translate";
import {
PageMetadata,
HtmlClassNameProvider,
ThemeClassNames,
usePluralForm,
} from "@docusaurus/theme-common";
import BlogLayout from "@theme/BlogLayout";
import BlogListPaginator from "@theme/BlogListPaginator";
import SearchMetadata from "@theme/SearchMetadata";
import BlogPostItems from "@theme/BlogPostItems";
import TagsList from "@theme/TagsList";
// Very simple pluralization: probably good enough for now
function useBlogPostsPlural() {
const { selectMessage } = usePluralForm();
return (count) =>
selectMessage(
count,
translate(
{
id: "theme.blog.post.plurals",
description:
'Pluralized label for "{count} posts". Use as much plural forms (separated by "|") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)',
message: "One post|{count} posts",
},
{ count },
),
);
}
function useBlogTagsPostsPageTitle(tag) {
const blogPostsPlural = useBlogPostsPlural();
return translate(
{
id: "theme.blog.tagTitle",
description: "The title of the page for a blog tag",
message: '{nPosts} tagged with "{tagName}"',
},
{ nPosts: blogPostsPlural(tag.count), tagName: tag.label },
);
}
function BlogTagsPostsPageMetadata({ tag }) {
const title = useBlogTagsPostsPageTitle(tag);
return (
<>
<PageMetadata title={title} />
<SearchMetadata tag="blog_tags_posts" />
</>
);
}
function BlogTagsPostsPageContent({ tags, tag, items, sidebar, listMetadata }) {
return (
<BlogLayout showSidebarBanner={false} sidebar={sidebar}>
<div
className={clsx(
"py-8",
"blog-md:py-16",
"px-4",
"max-w-[512px]",
"blog-md:px-7",
"blog-md:max-w-screen-blog-md",
"blog-2xl:px-0",
"blog-2xl:max-w-screen-blog-md",
"w-full",
"mx-auto",
)}
>
<TagsList tags={tags} />
<div className={clsx("pt-0 blog-md:pt-16")}>
<div className="text-gray-500 dark:text-gray-400">
Posts tagged with
</div>
<h1 className="!mb-0">{tag.label}</h1>
</div>
<BlogPostItems
items={items}
showTitle={false}
isTagsPage={true}
/>
<div className="blog-md:border-t border-t-gray-200 dark:border-t-gray-700">
<BlogListPaginator
metadata={listMetadata}
basePath={`/blog/tags/${tag.label}`}
/>
</div>
</div>
</BlogLayout>
);
}
export default function BlogTagsPostsPage(props) {
return (
<HtmlClassNameProvider
className={clsx(
ThemeClassNames.wrapper.blogPages,
ThemeClassNames.page.blogTagPostListPage,
)}
>
<BlogTagsPostsPageMetadata {...props} />
<BlogTagsPostsPageContent {...props} />
</HtmlClassNameProvider>
);
}

View File

@@ -0,0 +1,33 @@
import React, { isValidElement } from "react";
import useIsBrowser from "@docusaurus/useIsBrowser";
import ElementContent from "@theme/CodeBlock/Content/Element";
import { CodeBlockString } from "../../refine-theme/common-codeblock-string";
/**
* Best attempt to make the children a plain string so it is copyable. If there
* are react elements, we will not be able to copy the content, and it will
* return `children` as-is; otherwise, it concatenates the string children
* together.
*/
function maybeStringifyChildren(children) {
if (React.Children.toArray(children).some((el) => isValidElement(el))) {
return children;
}
// The children is now guaranteed to be one/more plain strings
return Array.isArray(children) ? children.join("") : children;
}
export const CodeBlock = ({ children: rawChildren, ...props }) => {
// The Prism theme on SSR is always the default theme but the site theme can
// be in a different mode. React hydration doesn't update DOM styles that come
// from SSR. Hence force a re-render after mounting to apply the current
// relevant styles.
const isBrowser = useIsBrowser();
const children = maybeStringifyChildren(rawChildren);
const CodeBlockComp =
typeof children === "string" ? CodeBlockString : ElementContent;
return (
<CodeBlockComp key={String(isBrowser)} {...props}>
{children}
</CodeBlockComp>
);
};

View File

@@ -0,0 +1,12 @@
import React from "react";
import { CodeBlock } from "./base";
export default function CodeBlockWrapper(
props: JSX.IntrinsicAttributes & {
live?: boolean;
shared?: boolean;
className?: string;
},
): JSX.Element {
return <CodeBlock {...(props as any)} />;
}

View File

@@ -0,0 +1 @@
export { DocCard as default } from "../../refine-theme/doc-card";

View File

@@ -0,0 +1 @@
export { DocCardList as default } from "../../refine-theme/doc-card-list";

View File

@@ -0,0 +1 @@
export { DocCategoryGeneratedIndexPage as default } from "../../refine-theme/doc-generated-index-page";

View File

@@ -0,0 +1 @@
export { DocFooter as default } from "../../../refine-theme/doc-footer";

View File

@@ -0,0 +1 @@
export { DocItemLayout as default } from "../../../refine-theme/doc-item-layout";

View File

@@ -0,0 +1,24 @@
import React from "react";
import { useDoc } from "@docusaurus/theme-common/internal";
import DocPaginator from "@theme/DocPaginator";
import { useCurrentTutorial } from "../../../hooks/use-current-tutorial";
/**
* This extra component is needed, because <DocPaginator> should remain generic.
* DocPaginator is used in non-docs contexts too: generated-index pages...
*/
export default function DocItemPaginator() {
const { metadata } = useDoc();
const tutorialData = useCurrentTutorial();
const isTutorial = !!tutorialData;
const previous = isTutorial
? tutorialData.pagination.previous
: metadata.previous;
const next = isTutorial ? tutorialData.pagination.next : metadata.next;
return <DocPaginator previous={previous} next={next} />;
}

View File

@@ -0,0 +1 @@
export { DocItem as default } from "../../refine-theme/doc-item";

View File

@@ -0,0 +1,11 @@
import React from "react";
import Layout from "@theme/Layout";
import { DocPageLayout as RefineDocPageLayout } from "@site/src/refine-theme/doc-page-layout";
export default function DocPageLayout({ children }) {
return (
<Layout>
<RefineDocPageLayout>{children}</RefineDocPageLayout>
</Layout>
);
}

View File

@@ -0,0 +1,91 @@
import React from "react";
import clsx from "clsx";
import {
HtmlClassNameProvider,
ThemeClassNames,
PageMetadata,
} from "@docusaurus/theme-common";
import {
docVersionSearchTag,
DocsSidebarProvider,
DocsVersionProvider,
useDocRouteMetadata,
} from "@docusaurus/theme-common/internal";
import DocPageLayout from "@theme/DocPage/Layout";
import NotFound from "@theme/NotFound";
import SearchMetadata from "@theme/SearchMetadata";
import { useTutorialConfig } from "../../hooks/use-tutorial-config";
function DocPageMetadata(props) {
const { versionMetadata } = props;
return (
<>
<SearchMetadata
version={versionMetadata.version}
tag={docVersionSearchTag(
versionMetadata.pluginId,
versionMetadata.version,
)}
/>
<PageMetadata>
{versionMetadata.noIndex && (
<meta name="robots" content="noindex, nofollow" />
)}
</PageMetadata>
</>
);
}
export default function DocPage(props) {
const { versionMetadata } = props;
const currentDocRouteMetadata = useDocRouteMetadata(props);
const {
tutorial: { path_prefix },
} = useTutorialConfig();
if (!currentDocRouteMetadata) {
return <NotFound />;
}
const fallbackSidebarName = Object.keys(versionMetadata.docsSidebars)[0];
const fallbackSidebarItems =
versionMetadata.docsSidebars[fallbackSidebarName];
const isTutorial = props.location.pathname.startsWith(path_prefix);
const { docElement, sidebarName, sidebarItems } = currentDocRouteMetadata;
return (
<>
<DocPageMetadata {...props} />
<HtmlClassNameProvider
className={clsx(
ThemeClassNames.wrapper.docsPages,
ThemeClassNames.page.docsDocPage,
props.versionMetadata.className,
)}
>
<DocsVersionProvider version={versionMetadata}>
<DocsSidebarProvider
name={
sidebarName ?? fallbackSidebarName
// isTutorial
// ? sidebarName ?? fallbackSidebarName
// : sidebarName
}
items={
sidebarItems ?? fallbackSidebarItems
// isTutorial
// ? sidebarItems ?? fallbackSidebarItems
// : sidebarItems
}
>
<DocPageLayout>{docElement}</DocPageLayout>
</DocsSidebarProvider>
</DocsVersionProvider>
</HtmlClassNameProvider>
</>
);
}

View File

@@ -0,0 +1 @@
export { DocPaginator as default } from "../../refine-theme/doc-paginator";

View File

@@ -0,0 +1,6 @@
import React from "react";
import { CommonLayout } from "@site/src/refine-theme/common-layout";
export default function Layout(props) {
return <CommonLayout {...props} />;
}

View File

@@ -0,0 +1,9 @@
import React from "react";
import Link from "@docusaurus/Link";
import { getLinkRel } from "@site/src/utils/link-rel";
export default function MDXA(props) {
const rel = getLinkRel(props?.href);
return <Link {...props} rel={rel} />;
}

View File

@@ -0,0 +1,53 @@
import MDXComponents from "@theme-original/MDXComponents";
import { BannerRandom } from "@site/src/components/banner/banner-random";
import DiscordBanner from "@site/src/components/blog/discord-banner";
import GithubBanner from "@site/src/components/blog/github-banner";
import PromotionBanner from "@site/src/components/blog/promotion";
import TwitterBanner from "@site/src/components/blog/twitter-banner";
import Checklist from "@site/src/components/checklist";
import ChecklistItem from "@site/src/components/checklist-item";
import GeneralConceptsLink from "@site/src/components/general-concepts-link";
import { GlobalConfigBadge } from "@site/src/components/global-config-badge";
import { GuideBadge } from "@site/src/components/guide-badge";
import PropTag from "@site/src/components/prop-tag";
import PropsTable from "@site/src/components/props-table";
import { RouterBadge } from "@site/src/components/router-badge";
import UIConditional from "@site/src/components/ui-conditional";
import CommonDetails from "@site/src/refine-theme/common-details";
import CommonSummary from "@site/src/refine-theme/common-summary";
import CommonTabItem from "@site/src/refine-theme/common-tab-item";
import CommonTabs from "@site/src/refine-theme/common-tabs";
import { Blockquote } from "@site/src/refine-theme/common-blockquote";
import { Image } from "@site/src/components/image";
import { Table, FullTable } from "@site/src/refine-theme/common-table";
import { CreateRefineAppCommand } from "@site/src/partials/npm-scripts/create-refine-app-command.tsx";
import { InstallPackagesCommand } from "@site/src/partials/npm-scripts/install-packages-commands";
export default {
...MDXComponents,
Checklist: Checklist,
ChecklistItem: ChecklistItem,
UIConditional: UIConditional,
DiscordBanner: DiscordBanner,
GithubBanner: GithubBanner,
TwitterBanner: TwitterBanner,
PropsTable: PropsTable,
PropTag: PropTag,
details: CommonDetails,
summary: CommonSummary,
PromotionBanner: PromotionBanner,
Tabs: CommonTabs,
TabItem: CommonTabItem,
blockquote: Blockquote,
GeneralConceptsLink,
BannerRandom,
GuideBadge,
RouterBadge,
GlobalConfigBadge,
Image,
table: Table,
CreateRefineAppCommand: CreateRefineAppCommand,
InstallPackagesCommand: InstallPackagesCommand,
FullTable: FullTable,
};

View File

@@ -0,0 +1,213 @@
import { useDocSearchKeyboardEvents } from "@docsearch/react";
import Head from "@docusaurus/Head";
import Link from "@docusaurus/Link";
import { useHistory } from "@docusaurus/router";
import {
isRegexpStringMatch,
useSearchLinkCreator,
} from "@docusaurus/theme-common";
import {
useAlgoliaContextualFacetFilters,
useSearchResultUrlProcessor,
} from "@docusaurus/theme-search-algolia/client";
import Translate from "@docusaurus/Translate";
import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
import { DocSearchButton } from "@site/src/refine-theme/doc-search-button";
import translations from "@theme/SearchTranslations";
import React, { useCallback, useMemo, useRef, useState } from "react";
import { createPortal } from "react-dom";
let DocSearchModal = null;
function Hit({ hit, children }) {
return <Link to={hit.url}>{children}</Link>;
}
function ResultsFooter({ state, onClose }) {
const createSearchLink = useSearchLinkCreator();
return (
<Link to={createSearchLink(state.query)} onClick={onClose}>
<Translate
id="theme.SearchBar.seeAll"
values={{ count: state.context.nbHits }}
>
{"See all {count} results"}
</Translate>
</Link>
);
}
function mergeFacetFilters(f1, f2) {
const normalize = (f) => (typeof f === "string" ? [f] : f);
return [...normalize(f1), ...normalize(f2)];
}
function DocSearch({
contextualSearch,
externalUrlRegex,
CustomButton,
...props
}: any) {
const { siteMetadata } = useDocusaurusContext();
const processSearchResultUrl = useSearchResultUrlProcessor();
const contextualSearchFacetFilters = useAlgoliaContextualFacetFilters();
const configFacetFilters = props.searchParameters?.facetFilters ?? [];
const facetFilters = contextualSearch
? // Merge contextual search filters with config filters
mergeFacetFilters(contextualSearchFacetFilters, configFacetFilters)
: // ... or use config facetFilters
configFacetFilters;
// We let user override default searchParameters if she wants to
const searchParameters = {
...props.searchParameters,
facetFilters,
};
const history = useHistory();
const searchContainer = useRef(null);
const searchButtonRef = useRef(null);
const [isOpen, setIsOpen] = useState(false);
const [initialQuery, setInitialQuery] = useState(undefined);
const importDocSearchModalIfNeeded = useCallback(async () => {
if (DocSearchModal) {
return Promise.resolve();
}
return Promise.all([
import("@docsearch/react/modal"),
import("@docsearch/react/style"),
import("./styles.css"),
]).then(([{ DocSearchModal: Modal }]) => {
DocSearchModal = Modal;
});
}, []);
const onOpen = useCallback(() => {
importDocSearchModalIfNeeded().then(() => {
searchContainer.current = document.createElement("div");
document.body.insertBefore(
searchContainer.current,
document.body.firstChild,
);
setIsOpen(true);
});
}, [importDocSearchModalIfNeeded, setIsOpen]);
const onClose = useCallback(() => {
setIsOpen(false);
searchContainer.current?.remove();
}, [setIsOpen]);
const onInput = useCallback(
(event) => {
importDocSearchModalIfNeeded().then(() => {
setIsOpen(true);
setInitialQuery(event.key);
});
},
[importDocSearchModalIfNeeded, setIsOpen, setInitialQuery],
);
const navigator = useRef({
navigate({ itemUrl }) {
// Algolia results could contain URL's from other domains which cannot
// be served through history and should navigate with window.location
if (isRegexpStringMatch(externalUrlRegex, itemUrl)) {
window.location.href = itemUrl;
} else {
history.push(itemUrl);
}
},
}).current;
const transformItems = useRef((items) =>
props.transformItems
? // Custom transformItems
props.transformItems(items)
: // Default transformItems
items.map((item) => ({
...item,
url: processSearchResultUrl(item.url),
})),
).current;
const resultsFooterComponent = useMemo(
() =>
function ResultsFooterWrapper(footerProps) {
return <ResultsFooter {...footerProps} onClose={onClose} />;
},
[onClose],
);
const transformSearchClient = useCallback(
(searchClient) => {
searchClient.addAlgoliaAgent(
"docusaurus",
siteMetadata.docusaurusVersion,
);
return searchClient;
},
[siteMetadata.docusaurusVersion],
);
useDocSearchKeyboardEvents({
isOpen,
onOpen,
onClose,
onInput,
searchButtonRef,
});
const SearchButton = CustomButton ?? DocSearchButton;
const {
apiKey: _apiKey,
appId: _appId,
searchPagePath: _searchPagePath,
indexName: _indexName,
searchParameters: _searchParameters,
...buttonProps
} = props;
return (
<>
<Head>
{/* This hints the browser that the website will load data from Algolia,
and allows it to preconnect to the DocSearch cluster. It makes the first
query faster, especially on mobile. */}
<link
rel="preconnect"
href={`https://${props.appId}-dsn.algolia.net`}
crossOrigin="anonymous"
/>
</Head>
<SearchButton
onTouchStart={importDocSearchModalIfNeeded}
onFocus={importDocSearchModalIfNeeded}
onMouseOver={importDocSearchModalIfNeeded}
onClick={onOpen}
ref={searchButtonRef}
{...buttonProps}
/>
{isOpen &&
DocSearchModal &&
searchContainer.current &&
createPortal(
<DocSearchModal
onClose={onClose}
initialScrollY={window.scrollY}
initialQuery={initialQuery}
navigator={navigator}
transformItems={transformItems}
hitComponent={Hit}
transformSearchClient={transformSearchClient}
{...(props.searchPagePath && {
resultsFooterComponent,
})}
{...props}
searchParameters={searchParameters}
placeholder={translations.placeholder}
translations={translations.modal}
/>,
searchContainer.current,
)}
</>
);
}
export default function SearchBar({ CustomButton }) {
const { siteConfig } = useDocusaurusContext();
return (
<DocSearch
{...(siteConfig.themeConfig.algolia as Record<string, any>)}
CustomButton={CustomButton}
/>
);
}

View File

@@ -0,0 +1,14 @@
:root {
--docsearch-primary-color: var(--ifm-color-primary);
--docsearch-text-color: var(--ifm-font-color-base);
}
.DocSearch-Button {
margin: 0;
transition: all var(--ifm-transition-fast)
var(--ifm-transition-timing-default);
}
.DocSearch-Container {
z-index: 1001;
}

View File

@@ -0,0 +1,24 @@
import React from "react";
import clsx from "clsx";
import Link from "@docusaurus/Link";
export default function Tag({ permalink, label, isActive }) {
return (
<Link
href={permalink}
className={clsx(
"no-underline hover:no-underline",
"text-xs",
!isActive && "bg-gray-100 dark:bg-gray-700",
!isActive && "text-gray-600 dark:text-gray-400",
"rounded",
"py-1",
"px-2",
isActive && "bg-gray-200 text-gray-500",
isActive && "dark-bg-gray-700 text-gray-300",
)}
>
{label}
</Link>
);
}

View File

@@ -0,0 +1,230 @@
import React from "react";
import Tag from "@theme/Tag";
import { titleCase } from "title-case";
import clsx from "clsx";
import { Disclosure, Transition } from "@headlessui/react";
import { TriangleDownIcon } from "@site/src/refine-theme/icons/triangle-down";
const ChevronDownIcon = () => (
<svg
width="8"
height="6"
viewBox="0 0 8 6"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M0.292893 0.792893C0.683417 0.402369 1.31658 0.402369 1.70711 0.792893L4 3.08579L6.29289 0.792893C6.68342 0.402369 7.31658 0.402369 7.70711 0.792893C8.09763 1.18342 8.09763 1.81658 7.70711 2.20711L4.70711 5.20711C4.31658 5.59763 3.68342 5.59763 3.29289 5.20711L0.292893 2.20711C-0.0976311 1.81658 -0.0976311 1.18342 0.292893 0.792893Z"
fill="currentColor"
/>
</svg>
);
const mapLabel = (label) => {
// remove `-`
label = label.replace(/-/g, " ");
// replace
const replace = [
["typescript", "TypeScript"],
["javascript", "JavaScript"],
["chakra ui", "Chakra UI"],
["material ui", "Material UI"],
["nextjs", "Next.js"],
["nestjs", "NestJS"],
["css", "CSS"],
];
replace.forEach((element) => {
label = label.replace(element[0], element[1]);
});
// title case
return titleCase(label);
};
const Desktop = ({
tags,
collapsed,
onShowMoreClick,
}: {
tags: any;
collapsed: boolean;
onShowMoreClick: (collapsed: boolean) => void;
}) => {
return (
<div
className={clsx(
"hidden lg:flex",
"not-prose",
"justify-between",
"items-start",
"bg-gray-50 dark:bg-gray-800",
"rounded-xl p-4 2xl:p-6",
)}
>
<ul
className={clsx(
"overflow-hidden",
"flex-1",
collapsed && "h-8",
)}
style={{
margin: 0,
padding: 0,
}}
>
{tags.map((tag) => (
<li
className={clsx("inline-flex", "m-1")}
key={tag.permalink}
>
<Tag {...tag} label={mapLabel(tag.label)} />
</li>
))}
</ul>
<label
onClick={() => onShowMoreClick(!collapsed)}
className={clsx(
"flex",
"items-center",
"gap-1",
"cursor-pointer",
"flex-shrink",
"no-underline hover:no-underline",
"text-xs",
"bg-gray-100 dark:bg-gray-600",
"text-gray-600 dark:text-gray-400",
"rounded",
"py-1",
"px-2",
"mt-1",
)}
>
Show More <ChevronDownIcon />
</label>
</div>
);
};
const Mobile = ({ tags }: { tags: any }) => {
return (
<div className="mb-10 block w-full lg:hidden">
<Disclosure>
{({ open }) => (
<div
className={clsx(
"rounded-[4px]",
"border border-gray-100 dark:border-gray-700",
)}
>
<Disclosure.Button
className={clsx(
"bg-gray-50 dark:bg-gray-800",
"border-b border-gray-100 dark:border-gray-700",
"w-full",
"flex items-center gap-3",
"px-2 py-2",
)}
>
<TriangleDownIcon
className={clsx(
"h-5 w-5",
"text-gray-400 dark:text-gray-500",
"transition-transform duration-200 ease-in-out",
{
"-rotate-90 transform": !open,
},
)}
/>
<span
className={clsx(
"text-sm",
"dark:text-gray-0 text-gray-900",
)}
>
Blog Post Tags
</span>
</Disclosure.Button>
<Transition
show={open}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Disclosure.Panel className="h-[200px] overflow-auto p-2 sm:h-[232px] sm:p-6">
<ul
className={clsx(
"overflow-hidden",
"flex-1",
)}
style={{
margin: 0,
padding: 0,
}}
>
{tags.map((tag) => (
<li
className={clsx(
"inline-flex",
"m-1",
)}
key={tag.permalink}
>
<Tag
{...tag}
label={mapLabel(tag.label)}
/>
</li>
))}
</ul>
</Disclosure.Panel>
</Transition>
</div>
)}
</Disclosure>
</div>
);
};
export default function TagsList({ tags }) {
const [collapsed, setCollapsed] = React.useState(true);
const priorityTags = [
"refine",
"react",
"nextjs",
"typescript",
"tutorial",
"material-ui",
"ant-design",
"docker",
"comparison",
];
const sortedTags = (tags ?? []).sort((a, b) => {
const aIndex = priorityTags.indexOf(a.label);
const bIndex = priorityTags.indexOf(b.label);
if (aIndex === -1) {
return bIndex === -1 ? 0 : 1;
} else {
return bIndex === -1 ? -1 : aIndex - bIndex;
}
});
return (
<>
<Desktop
collapsed={collapsed}
tags={sortedTags}
onShowMoreClick={(collapsed) => setCollapsed(collapsed)}
/>
<Mobile tags={sortedTags} />
</>
);
}