mirror of
https://github.com/stefanpejcic/openpanel
synced 2025-06-26 18:28:26 +00:00
pakcages
This commit is contained in:
120
website/src/theme/Admonition/index.tsx
Normal file
120
website/src/theme/Admonition/index.tsx
Normal 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 →
|
||||
</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} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
31
website/src/theme/Admonition/styles.module.css
Normal file
31
website/src/theme/Admonition/styles.module.css
Normal 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;
|
||||
}
|
||||
1
website/src/theme/BlogLayout/index.js
Normal file
1
website/src/theme/BlogLayout/index.js
Normal file
@@ -0,0 +1 @@
|
||||
export { RefineBlogLayout as default } from "@site/src/refine-theme/blog-layout";
|
||||
118
website/src/theme/BlogListPage/index.js
Normal file
118
website/src/theme/BlogListPage/index.js
Normal 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>
|
||||
);
|
||||
}
|
||||
130
website/src/theme/BlogListPaginator/index.js
Normal file
130
website/src/theme/BlogListPaginator/index.js
Normal 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]",
|
||||
)}
|
||||
>
|
||||
…
|
||||
</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>
|
||||
);
|
||||
}
|
||||
109
website/src/theme/BlogPostItem/index.js
Normal file
109
website/src/theme/BlogPostItem/index.js
Normal 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>
|
||||
);
|
||||
}
|
||||
95
website/src/theme/BlogPostItems/index.js
Normal file
95
website/src/theme/BlogPostItems/index.js
Normal 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>
|
||||
);
|
||||
}
|
||||
39
website/src/theme/BlogPostPage/Metadata/index.js
Normal file
39
website/src/theme/BlogPostPage/Metadata/index.js
Normal 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>
|
||||
);
|
||||
}
|
||||
46
website/src/theme/BlogPostPage/index.js
Normal file
46
website/src/theme/BlogPostPage/index.js
Normal 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>
|
||||
);
|
||||
}
|
||||
30
website/src/theme/BlogTagsListPage/index.js
Normal file
30
website/src/theme/BlogTagsListPage/index.js
Normal 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>
|
||||
);
|
||||
}
|
||||
108
website/src/theme/BlogTagsPostsPage/index.js
Normal file
108
website/src/theme/BlogTagsPostsPage/index.js
Normal 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>
|
||||
);
|
||||
}
|
||||
33
website/src/theme/CodeBlock/base.tsx
Normal file
33
website/src/theme/CodeBlock/base.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
12
website/src/theme/CodeBlock/index.tsx
Normal file
12
website/src/theme/CodeBlock/index.tsx
Normal 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)} />;
|
||||
}
|
||||
1
website/src/theme/DocCard/index.js
Normal file
1
website/src/theme/DocCard/index.js
Normal file
@@ -0,0 +1 @@
|
||||
export { DocCard as default } from "../../refine-theme/doc-card";
|
||||
1
website/src/theme/DocCardList/index.js
Normal file
1
website/src/theme/DocCardList/index.js
Normal file
@@ -0,0 +1 @@
|
||||
export { DocCardList as default } from "../../refine-theme/doc-card-list";
|
||||
1
website/src/theme/DocCategoryGeneratedIndexPage/index.js
Normal file
1
website/src/theme/DocCategoryGeneratedIndexPage/index.js
Normal file
@@ -0,0 +1 @@
|
||||
export { DocCategoryGeneratedIndexPage as default } from "../../refine-theme/doc-generated-index-page";
|
||||
1
website/src/theme/DocItem/Footer/index.js
Normal file
1
website/src/theme/DocItem/Footer/index.js
Normal file
@@ -0,0 +1 @@
|
||||
export { DocFooter as default } from "../../../refine-theme/doc-footer";
|
||||
1
website/src/theme/DocItem/Layout/index.js
Normal file
1
website/src/theme/DocItem/Layout/index.js
Normal file
@@ -0,0 +1 @@
|
||||
export { DocItemLayout as default } from "../../../refine-theme/doc-item-layout";
|
||||
24
website/src/theme/DocItem/Paginator/index.js
Normal file
24
website/src/theme/DocItem/Paginator/index.js
Normal 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} />;
|
||||
}
|
||||
1
website/src/theme/DocItem/index.js
Normal file
1
website/src/theme/DocItem/index.js
Normal file
@@ -0,0 +1 @@
|
||||
export { DocItem as default } from "../../refine-theme/doc-item";
|
||||
11
website/src/theme/DocPage/Layout/index.js
Normal file
11
website/src/theme/DocPage/Layout/index.js
Normal 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>
|
||||
);
|
||||
}
|
||||
91
website/src/theme/DocPage/index.js
Normal file
91
website/src/theme/DocPage/index.js
Normal 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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
1
website/src/theme/DocPaginator/index.js
Normal file
1
website/src/theme/DocPaginator/index.js
Normal file
@@ -0,0 +1 @@
|
||||
export { DocPaginator as default } from "../../refine-theme/doc-paginator";
|
||||
6
website/src/theme/Layout/index.js
Normal file
6
website/src/theme/Layout/index.js
Normal 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} />;
|
||||
}
|
||||
9
website/src/theme/MDXComponents/A.js
Normal file
9
website/src/theme/MDXComponents/A.js
Normal 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} />;
|
||||
}
|
||||
53
website/src/theme/MDXComponents/index.js
Normal file
53
website/src/theme/MDXComponents/index.js
Normal 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,
|
||||
};
|
||||
213
website/src/theme/SearchBar/index.tsx
Normal file
213
website/src/theme/SearchBar/index.tsx
Normal 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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
14
website/src/theme/SearchBar/styles.css
Normal file
14
website/src/theme/SearchBar/styles.css
Normal 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;
|
||||
}
|
||||
24
website/src/theme/Tag/index.js
Normal file
24
website/src/theme/Tag/index.js
Normal 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>
|
||||
);
|
||||
}
|
||||
230
website/src/theme/TagsList/index.tsx
Normal file
230
website/src/theme/TagsList/index.tsx
Normal 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} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user