Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions docusaurus.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,36 @@ module.exports = {
logo: "https://keploy.io/docs/img/favicon.png",
}),
},
{
tagName: "script",
attributes: {
type: "application/ld+json",
},
innerHTML: JSON.stringify({
"@context": "https://schema.org/",
"@type": "Organization",
name: "Keploy",
url: "https://keploy.io/",
logo: "https://keploy.io/docs/img/favicon.png",
}),
},
{
tagName: "script",
attributes: {
type: "application/ld+json",
},
innerHTML: JSON.stringify({
"@context": "https://schema.org/",
"@type": "WebSite",
name: "Keploy Documentation",
url: "https://keploy.io/docs/",
potentialAction: {
"@type": "SearchAction",
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For this section, it would be useful in the future when we add search queries as a url similar to the blog website

target: "https://keploy.io/docs/search?q={search_term_string}",
"query-input": "required name=search_term_string",
},
}),
},
],
colorMode: {
defaultMode: "light",
Expand Down
180 changes: 180 additions & 0 deletions src/theme/DocBreadcrumbs/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

import React from "react";
import clsx from "clsx";
import {ThemeClassNames} from "@docusaurus/theme-common";
import {useSidebarBreadcrumbs} from "@docusaurus/plugin-content-docs/client";
import {useHomePageRoute} from "@docusaurus/theme-common/internal";
import {useLocation} from "@docusaurus/router";
import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
import Link from "@docusaurus/Link";
import {translate} from "@docusaurus/Translate";
import Head from "@docusaurus/Head";
import HomeBreadcrumbItem from "@theme/DocBreadcrumbs/Items/Home";

import styles from "./styles.module.css";

function BreadcrumbsItemLink({children, href, isLast}) {
const className = "breadcrumbs__link";
if (isLast) {
return (
<span className={className} itemProp="name">
{children}
</span>
);
}
return href ? (
<Link className={className} href={href} itemProp="item">
<span itemProp="name">{children}</span>
</Link>
) : (
<span className={className}>{children}</span>
);
}

function BreadcrumbsItem({children, active, index, addMicrodata}) {
return (
<li
{...(addMicrodata && {
itemScope: true,
itemProp: "itemListElement",
itemType: "https://schema.org/ListItem",
})}
className={clsx("breadcrumbs__item", {
"breadcrumbs__item--active": active,
})}
>
{children}
<meta itemProp="position" content={String(index + 1)} />
</li>
);
}

export default function DocBreadcrumbs() {
const breadcrumbs = useSidebarBreadcrumbs();
const homePageRoute = useHomePageRoute();
const {siteConfig} = useDocusaurusContext();
const {pathname} = useLocation();

if (!breadcrumbs) {
return null;
}

const toAbsoluteUrl = (baseUrl, url) => {
if (!url) {
return null;
}
if (url.startsWith("http://") || url.startsWith("https://")) {
return url;
}
const trimmedBase = baseUrl?.replace(/\/$/, "") ?? "";
const normalizedPath = url.startsWith("/") ? url : `/${url}`;
return `${trimmedBase}${normalizedPath}`;
};

const breadcrumbItems = [];
const pushBreadcrumbItem = (item) => {
if (!item?.item || !item?.name) {
return;
}
if (breadcrumbItems.some((existing) => existing.item === item.item)) {
return;
}
breadcrumbItems.push(item);
};

if (siteConfig?.url) {
pushBreadcrumbItem({name: "Home", item: siteConfig.url});
}

if (siteConfig?.url && siteConfig?.baseUrl) {
const docsUrl = toAbsoluteUrl(siteConfig.url, siteConfig.baseUrl);
if (docsUrl && docsUrl !== siteConfig.url) {
pushBreadcrumbItem({name: "Docs", item: docsUrl});
}
}

if (breadcrumbs.length > 0) {
breadcrumbs.forEach((crumb, index) => {
const isLast = index === breadcrumbs.length - 1;
const href = crumb.href || (isLast ? pathname : null);
const absoluteUrl = toAbsoluteUrl(siteConfig?.url, href);
if (!absoluteUrl) {
return;
}
pushBreadcrumbItem({
name: crumb.label,
item: absoluteUrl,
});
});
}

const breadcrumbSchema =
breadcrumbItems.length > 0
? {
"@context": "https://schema.org",
"@type": "BreadcrumbList",
itemListElement: breadcrumbItems.map((item, index) => ({
"@type": "ListItem",
position: index + 1,
name: item.name,
item: item.item,
})),
}
: null;

return (
<>
{breadcrumbSchema && (
<Head>
<script type="application/ld+json">
{JSON.stringify(breadcrumbSchema)}
</script>
</Head>
)}
<nav
className={clsx(
ThemeClassNames.docs.docBreadcrumbs,
styles.breadcrumbsContainer
)}
aria-label={translate({
id: "theme.docs.breadcrumbs.navAriaLabel",
message: "Breadcrumbs",
description: "The ARIA label for the breadcrumbs",
})}
>
<ul
className="breadcrumbs"
itemScope
itemType="https://schema.org/BreadcrumbList"
>
{homePageRoute && <HomeBreadcrumbItem />}
{breadcrumbs.map((item, idx) => {
const isLast = idx === breadcrumbs.length - 1;
const href =
item.type === "category" && item.linkUnlisted
? undefined
: item.href;
return (
<BreadcrumbsItem
key={idx}
active={isLast}
index={idx}
addMicrodata={!!href}
>
<BreadcrumbsItemLink href={href} isLast={isLast}>
{item.label}
</BreadcrumbsItemLink>
</BreadcrumbsItem>
);
})}
</ul>
</nav>
</>
);
}
11 changes: 11 additions & 0 deletions src/theme/DocBreadcrumbs/styles.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

.breadcrumbsContainer {
--ifm-breadcrumb-size-multiplier: 0.8;
margin-bottom: 0.8rem;
}
126 changes: 123 additions & 3 deletions src/theme/DocItem/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,14 @@ import DocBreadcrumbs from "@theme/DocBreadcrumbs";
import Layout from "@docusaurus/core/lib/client/theme-fallback/Layout";
import Head from "@docusaurus/Head";
import MDXContent from "@theme/MDXContent";
import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
import {KeployCloud} from "@site/src/components/KeployCloud";

export default function DocItem(props) {
const {content: DocContent} = props;
const {metadata, frontMatter, assets} = DocContent;
const {
keywords,
keywords: frontMatterKeywords,
hide_title: hideTitle,
hide_table_of_contents: hideTableOfContents,
toc_min_heading_level: tocMinHeadingLevel,
Expand All @@ -43,19 +44,138 @@ export default function DocItem(props) {
!hideTableOfContents && DocContent.toc && DocContent.toc.length > 0;
const renderTocDesktop =
canRenderTOC && (windowSize === "desktop" || windowSize === "ssr");

const {siteConfig} = useDocusaurusContext();
const toIsoDate = (value) => {
if (!value) {
return null;
}
const isEpochSeconds = Number.isFinite(value) && value < 1e12;
const date = new Date(isEpochSeconds ? value * 1000 : value);
if (Number.isNaN(date.getTime())) {
return null;
}
return date.toISOString();
};
const toAbsoluteUrl = (baseUrl, url) => {
if (!url) {
return null;
}
if (url.startsWith("http://") || url.startsWith("https://")) {
return url;
}
const trimmedBase = baseUrl?.replace(/\/$/, "") ?? "";
const normalizedPath = url.startsWith("/") ? url : `/${url}`;
return `${trimmedBase}${normalizedPath}`;
};
const toArray = (value) => {
if (!value) {
return [];
}
return Array.isArray(value) ? value : [value];
};
const toPersonList = (value) => {
return toArray(value)
.map((item) => {
if (!item) {
return null;
}
if (typeof item === "string") {
return {["@type"]: "Person", name: item};
}
if (item.name) {
return {
"@type": "Person",
name: item.name,
...(item.url ? {url: item.url} : {}),
};
}
return null;
})
.filter(Boolean);
};
const pageUrl = toAbsoluteUrl(siteConfig?.url, metadata?.permalink);
const modifiedTime = toIsoDate(
metadata?.lastUpdatedAt || frontMatter?.lastUpdatedAt
);
const publishedTime = toIsoDate(frontMatter?.date || frontMatter?.publishedAt);
const schemaTypeFromFrontMatter =
frontMatter?.schemaType || frontMatter?.schema_type;
const isApi =
frontMatter?.apiReference === true ||
frontMatter?.type === "api" ||
(frontMatter?.tags || []).includes?.("api");
const isBlog =
frontMatter?.type === "blog" ||
frontMatter?.blog === true ||
(frontMatter?.tags || []).includes?.("blog");
const schemaType = schemaTypeFromFrontMatter
? schemaTypeFromFrontMatter
: isApi
? "APIReference"
: isBlog
? "BlogPosting"
: "Article";
const authorList = toPersonList(frontMatter?.author || frontMatter?.authors);
const maintainerList = toPersonList(frontMatter?.maintainer);
const contributorList = toPersonList(frontMatter?.contributor);
const combinedContributors = [...maintainerList, ...contributorList];
const keywords = frontMatter?.keywords || metadata?.keywords;
const metaKeywords = frontMatterKeywords ?? metadata?.keywords;
const programmingLanguage =
frontMatter?.programmingLanguage || frontMatter?.programmingLanguages;
const targetPlatform = frontMatter?.targetPlatform;
const proficiencyLevel = frontMatter?.proficiencyLevel;
const articleSchema =
pageUrl && title
? {
"@context": "https://schema.org",
"@type": schemaType,
headline: title,
description,
...(modifiedTime ? {dateModified: modifiedTime} : {}),
...(publishedTime ? {datePublished: publishedTime} : {}),
...(keywords ? {keywords} : {}),
...(authorList.length ? {author: authorList} : {}),
...(combinedContributors.length
? {contributor: combinedContributors}
: {}),
...(proficiencyLevel ? {proficiencyLevel} : {}),
...(programmingLanguage ? {programmingLanguage} : {}),
...(targetPlatform ? {targetPlatform} : {}),
mainEntityOfPage: {
"@type": "WebPage",
"@id": pageUrl,
},
publisher: {
"@type": "Organization",
name: "Keploy",
logo: {
"@type": "ImageObject",
url: "https://keploy.io/docs/img/favicon.png",
},
},
}
: null;
const MDXComponent = props.content;
return (
<>
<Head>
<title>{title}</title>
{description && <meta name="description" content={description} />}
{modifiedTime && (
<meta property="article:modified_time" content={modifiedTime} />
)}
{articleSchema && (
<script type="application/ld+json">
{JSON.stringify(articleSchema)}
</script>
)}
</Head>
<Layout
{...{
title,
description,
keywords,
keywords: metaKeywords,
image,
}}
/>
Expand Down
Loading