diff --git a/demo/examples/tests/docCards.yaml b/demo/examples/tests/docCards.yaml new file mode 100644 index 000000000..cc1fb992c --- /dev/null +++ b/demo/examples/tests/docCards.yaml @@ -0,0 +1,19 @@ +openapi: 3.0.3 +info: + title: DocCard Markdown Example + version: 1.0.0 +paths: + /docs: + get: + summary: Doc card operation + description: > + Returns documentation with **markdown** and HTML in descriptions. + tags: + - docCards + responses: + "200": + description: OK + +tags: + - name: docCards + description: This tag shows **markdown** and HTML in the doc card. diff --git a/packages/docusaurus-plugin-openapi-docs/src/openapi/__fixtures__/docCards/openapi.yaml b/packages/docusaurus-plugin-openapi-docs/src/openapi/__fixtures__/docCards/openapi.yaml new file mode 100644 index 000000000..cc1fb992c --- /dev/null +++ b/packages/docusaurus-plugin-openapi-docs/src/openapi/__fixtures__/docCards/openapi.yaml @@ -0,0 +1,19 @@ +openapi: 3.0.3 +info: + title: DocCard Markdown Example + version: 1.0.0 +paths: + /docs: + get: + summary: Doc card operation + description: > + Returns documentation with **markdown** and HTML in descriptions. + tags: + - docCards + responses: + "200": + description: OK + +tags: + - name: docCards + description: This tag shows **markdown** and HTML in the doc card. diff --git a/packages/docusaurus-plugin-openapi-docs/src/openapi/openapi.test.ts b/packages/docusaurus-plugin-openapi-docs/src/openapi/openapi.test.ts index 678a003ff..c3958b22e 100644 --- a/packages/docusaurus-plugin-openapi-docs/src/openapi/openapi.test.ts +++ b/packages/docusaurus-plugin-openapi-docs/src/openapi/openapi.test.ts @@ -37,4 +37,22 @@ describe("openapi", () => { ).toBeDefined(); }); }); + + describe("doc card markdown", () => { + it("preserves markdown and html in tag descriptions", async () => { + const results = await readOpenapiFiles( + posixPath(path.join(__dirname, "__fixtures__/docCards")) + ); + const yaml = results.find((x) => x.source.endsWith("openapi.yaml")); + expect(yaml).toBeTruthy(); + const tag = yaml?.data.tags?.find((t) => t.name === "docCards"); + expect(tag?.description).toBe( + "This tag shows **markdown** and HTML in the doc card." + ); + const op = yaml?.data.paths?.["/docs"]?.get; + expect(op?.description?.trim()).toBe( + "Returns documentation with **markdown** and HTML in descriptions." + ); + }); + }); }); diff --git a/packages/docusaurus-theme-openapi-docs/src/theme/DocCard/index.tsx b/packages/docusaurus-theme-openapi-docs/src/theme/DocCard/index.tsx new file mode 100644 index 000000000..10188fb4c --- /dev/null +++ b/packages/docusaurus-theme-openapi-docs/src/theme/DocCard/index.tsx @@ -0,0 +1,142 @@ +/* ============================================================================ + * Copyright (c) Palo Alto Networks + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * ========================================================================== */ + +import React, { type ReactNode } from "react"; + +import isInternalUrl from "@docusaurus/isInternalUrl"; +import Link from "@docusaurus/Link"; +import type { + PropSidebarItemCategory, + PropSidebarItemLink, +} from "@docusaurus/plugin-content-docs"; +import { + useDocById, + findFirstSidebarItemLink, +} from "@docusaurus/plugin-content-docs/lib/client/docsUtils"; +import { usePluralForm } from "@docusaurus/theme-common"; +import { translate } from "@docusaurus/Translate"; +import type { Props } from "@theme/DocCard"; +import Heading from "@theme/Heading"; +import Markdown from "@theme/Markdown"; +import clsx from "clsx"; + +import styles from "./styles.module.css"; + +function useCategoryItemsPlural() { + const { selectMessage } = usePluralForm(); + return (count: number) => + selectMessage( + count, + translate( + { + message: "1 item|{count} items", + id: "theme.docs.DocCard.categoryDescription.plurals", + description: + "The default description for a category card in the generated index about how many items this category includes", + }, + { count } + ) + ); +} + +function CardContainer({ + className, + href, + children, +}: { + className?: string; + href: string; + children: ReactNode; +}): ReactNode { + return ( + + {children} + + ); +} + +function CardLayout({ + className, + href, + icon, + title, + description, +}: { + className?: string; + href: string; + icon: ReactNode; + title: string; + description?: string; +}): ReactNode { + return ( + + + {icon} {title} + + {description && ( + + {description} + + )} + + ); +} + +function CardCategory({ item }: { item: PropSidebarItemCategory }): ReactNode { + const href = findFirstSidebarItemLink(item); + const categoryItemsPlural = useCategoryItemsPlural(); + + // Unexpected: categories that don't have a link have been filtered upfront + if (!href) { + return null; + } + + return ( + + ); +} + +function CardLink({ item }: { item: PropSidebarItemLink }): ReactNode { + const icon = isInternalUrl(item.href) ? "📄️" : "🔗"; + const doc = useDocById(item.docId ?? undefined); + return ( + + ); +} + +export default function DocCard({ item }: Props): ReactNode { + switch (item.type) { + case "link": + return ; + case "category": + return ; + default: + throw new Error(`unknown item type ${JSON.stringify(item)}`); + } +} diff --git a/packages/docusaurus-theme-openapi-docs/src/theme/DocCard/styles.module.css b/packages/docusaurus-theme-openapi-docs/src/theme/DocCard/styles.module.css new file mode 100644 index 000000000..1c2ea4382 --- /dev/null +++ b/packages/docusaurus-theme-openapi-docs/src/theme/DocCard/styles.module.css @@ -0,0 +1,34 @@ +/* ========================================================================== */ +/* Copyright (c) Palo Alto Networks + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * ========================================================================== */ + +.cardContainer { + --ifm-link-color: var(--ifm-color-emphasis-800); + --ifm-link-hover-color: var(--ifm-color-emphasis-700); + --ifm-link-hover-decoration: none; + + box-shadow: 0 1.5px 3px 0 rgb(0 0 0 / 15%); + border: 1px solid var(--ifm-color-emphasis-200); + transition: all var(--ifm-transition-fast) ease; + transition-property: border, box-shadow; +} + +.cardContainer:hover { + border-color: var(--ifm-color-primary); + box-shadow: 0 3px 6px 0 rgb(0 0 0 / 20%); +} + +.cardContainer *:last-child { + margin-bottom: 0; +} + +.cardTitle { + font-size: 1.2rem; +} + +.cardDescription { + font-size: 0.8rem; +}