diff --git a/docusaurus.config.js b/docusaurus.config.js index fbed23c6c..522306cc0 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -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", + target: "https://keploy.io/docs/search?q={search_term_string}", + "query-input": "required name=search_term_string", + }, + }), + }, ], colorMode: { defaultMode: "light", diff --git a/src/theme/DocBreadcrumbs/index.js b/src/theme/DocBreadcrumbs/index.js new file mode 100644 index 000000000..a4c375751 --- /dev/null +++ b/src/theme/DocBreadcrumbs/index.js @@ -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 ( + + {children} + + ); + } + return href ? ( + + {children} + + ) : ( + {children} + ); +} + +function BreadcrumbsItem({children, active, index, addMicrodata}) { + return ( +
  • + {children} + +
  • + ); +} + +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 && ( + + + + )} + + + ); +} diff --git a/src/theme/DocBreadcrumbs/styles.module.css b/src/theme/DocBreadcrumbs/styles.module.css new file mode 100644 index 000000000..a400c5d9e --- /dev/null +++ b/src/theme/DocBreadcrumbs/styles.module.css @@ -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; +} diff --git a/src/theme/DocItem/index.js b/src/theme/DocItem/index.js index 34a12f682..89547be39 100644 --- a/src/theme/DocItem/index.js +++ b/src/theme/DocItem/index.js @@ -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, @@ -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 ( <> {title} {description && } + {modifiedTime && ( + + )} + {articleSchema && ( + + )}