From 5a92ee9e4a7fda2ab2967204420863ddf0d9a846 Mon Sep 17 00:00:00 2001 From: Bryan Linda <48506502+blindaa121@users.noreply.github.com> Date: Fri, 27 Jun 2025 14:18:46 -0700 Subject: [PATCH] Add icon and transition to copy markdown button --- .../src/index.ts | 10 ++++ .../src/types.ts | 5 ++ .../src/theme-openapi.d.ts | 9 +++ .../src/theme/ApiItem/index.tsx | 5 ++ .../_CopyMarkdownButton.scss | 39 ++++++++++++ .../src/theme/CopyMarkdownButton/index.tsx | 59 +++++++++++++++++++ .../src/theme/styles.scss | 1 + 7 files changed, 128 insertions(+) create mode 100644 packages/docusaurus-theme-openapi-docs/src/theme/CopyMarkdownButton/_CopyMarkdownButton.scss create mode 100644 packages/docusaurus-theme-openapi-docs/src/theme/CopyMarkdownButton/index.tsx diff --git a/packages/docusaurus-plugin-openapi-docs/src/index.ts b/packages/docusaurus-plugin-openapi-docs/src/index.ts index 6c66e7e69..102a93ada 100644 --- a/packages/docusaurus-plugin-openapi-docs/src/index.ts +++ b/packages/docusaurus-plugin-openapi-docs/src/index.ts @@ -236,6 +236,8 @@ show_extensions: true {{/frontMatter.show_extensions}} --- +export const rawMarkdown = '{{{markdown_b64}}}'; + {{{markdown}}} `; @@ -250,6 +252,8 @@ hide_title: true custom_edit_url: null --- +export const rawMarkdown = '{{{markdown_b64}}}'; + {{{markdown}}} \`\`\`mdx-code-block @@ -269,6 +273,8 @@ description: "{{{frontMatter.description}}}" custom_edit_url: null --- +export const rawMarkdown = '{{{markdown_b64}}}'; + {{{markdown}}} \`\`\`mdx-code-block @@ -295,6 +301,8 @@ sample: {{{frontMatter.sample}}} custom_edit_url: null --- +export const rawMarkdown = '{{{markdown_b64}}}'; + {{{markdown}}} `; @@ -329,6 +337,8 @@ custom_edit_url: null } const markdown = pageGeneratorByType[item.type](item as any); item.markdown = markdown; + const markdown_b64 = Buffer.from(markdown).toString("base64"); + (item as any).markdown_b64 = markdown_b64; if (item.type === "api") { // opportunity to compress JSON // const serialize = (o: any) => { diff --git a/packages/docusaurus-plugin-openapi-docs/src/types.ts b/packages/docusaurus-plugin-openapi-docs/src/types.ts index 664728f99..529557884 100644 --- a/packages/docusaurus-plugin-openapi-docs/src/types.ts +++ b/packages/docusaurus-plugin-openapi-docs/src/types.ts @@ -126,6 +126,7 @@ export interface ApiPageMetadata extends ApiMetadataBase { type: "api"; api: ApiItem; markdown?: string; + markdown_b64?: string; } export interface ApiItem extends OperationObject { @@ -145,6 +146,7 @@ export interface InfoPageMetadata extends ApiMetadataBase { type: "info"; info: ApiInfo; markdown?: string; + markdown_b64?: string; securitySchemes?: { [key: string]: SecuritySchemeObject; }; @@ -154,12 +156,14 @@ export interface TagPageMetadata extends ApiMetadataBase { type: "tag"; tag: TagObject; markdown?: string; + markdown_b64?: string; } export interface SchemaPageMetadata extends ApiMetadataBase { type: "schema"; schema: SchemaObject; markdown?: string; + markdown_b64?: string; } export interface TagGroupPageMetadata extends ApiMetadataBase { @@ -167,6 +171,7 @@ export interface TagGroupPageMetadata extends ApiMetadataBase { name: string; tags: TagObject[]; markdown?: string; + markdown_b64?: string; } export type ApiInfo = InfoObject; diff --git a/packages/docusaurus-theme-openapi-docs/src/theme-openapi.d.ts b/packages/docusaurus-theme-openapi-docs/src/theme-openapi.d.ts index 1927d6c86..226e33f23 100644 --- a/packages/docusaurus-theme-openapi-docs/src/theme-openapi.d.ts +++ b/packages/docusaurus-theme-openapi-docs/src/theme-openapi.d.ts @@ -275,6 +275,15 @@ declare module "@theme/ApiExplorer/Server" { export default function Server(): JSX.Element; } +declare module "@theme/CopyMarkdownButton" { + export interface CopyMarkdownButtonProps { + markdown: string | undefined; + } + export default function CopyMarkdownButton( + props: CopyMarkdownButtonProps + ): JSX.Element; +} + declare module "@theme/ApiExplorer/ApiCodeBlock" { export default function ApiCodeBlock(): JSX.Element; } diff --git a/packages/docusaurus-theme-openapi-docs/src/theme/ApiItem/index.tsx b/packages/docusaurus-theme-openapi-docs/src/theme/ApiItem/index.tsx index 13f2f17d8..0abebfabd 100644 --- a/packages/docusaurus-theme-openapi-docs/src/theme/ApiItem/index.tsx +++ b/packages/docusaurus-theme-openapi-docs/src/theme/ApiItem/index.tsx @@ -21,6 +21,7 @@ import type { Props } from "@theme/DocItem"; import DocItemMetadata from "@theme/DocItem/Metadata"; import SkeletonLoader from "@theme/SkeletonLoader"; import clsx from "clsx"; +import CopyMarkdownButton from "@theme/CopyMarkdownButton"; import { ParameterObject, ServerObject, @@ -159,6 +160,7 @@ export default function ApiItem(props: Props): JSX.Element { } if (api) { + const { rawMarkdown } = MDXComponent as any; return ( @@ -167,6 +169,7 @@ export default function ApiItem(props: Props): JSX.Element {
+
@@ -183,6 +186,7 @@ export default function ApiItem(props: Props): JSX.Element { ); } else if (schema) { + const { rawMarkdown } = MDXComponent as any; return ( @@ -190,6 +194,7 @@ export default function ApiItem(props: Props): JSX.Element {
+
diff --git a/packages/docusaurus-theme-openapi-docs/src/theme/CopyMarkdownButton/_CopyMarkdownButton.scss b/packages/docusaurus-theme-openapi-docs/src/theme/CopyMarkdownButton/_CopyMarkdownButton.scss new file mode 100644 index 000000000..fc8378959 --- /dev/null +++ b/packages/docusaurus-theme-openapi-docs/src/theme/CopyMarkdownButton/_CopyMarkdownButton.scss @@ -0,0 +1,39 @@ +.openapi-copy-markdown-btn-icons { + position: relative; + width: 1.125rem; + height: 1.125rem; + margin-right: 0.25rem; +} + +.openapi-copy-markdown-btn-icon, +.openapi-copy-markdown-btn-icon--success { + position: absolute; + top: 0; + left: 0; + fill: currentColor; + opacity: inherit; + width: inherit; + height: inherit; + transition: all 0.15s ease; +} + +.openapi-copy-markdown-btn-icon--success { + top: 50%; + left: 50%; + transform: translate(-50%, -50%) scale(0.33); + opacity: 0; + color: #00d600; +} + +.openapi-copy-markdown-btn--copied { + .openapi-copy-markdown-btn-icon { + transform: scale(0.33); + opacity: 0; + } + + .openapi-copy-markdown-btn-icon--success { + transform: translate(-50%, -50%) scale(1); + opacity: 1; + transition-delay: 0.075s; + } +} diff --git a/packages/docusaurus-theme-openapi-docs/src/theme/CopyMarkdownButton/index.tsx b/packages/docusaurus-theme-openapi-docs/src/theme/CopyMarkdownButton/index.tsx new file mode 100644 index 000000000..16324333a --- /dev/null +++ b/packages/docusaurus-theme-openapi-docs/src/theme/CopyMarkdownButton/index.tsx @@ -0,0 +1,59 @@ +import React, { useCallback, useRef, useState, useEffect } from "react"; +import clsx from "clsx"; +import copy from "copy-text-to-clipboard"; + +interface CopyMarkdownButtonProps { + markdown: string | undefined; +} + +export default function CopyMarkdownButton({ markdown }: CopyMarkdownButtonProps) { + const [isCopied, setIsCopied] = useState(false); + const copyTimeout = useRef(undefined); + + const handleCopy = useCallback(() => { + if (!markdown) return; + try { + const decoded = atob(markdown); + copy(decoded); + setIsCopied(true); + copyTimeout.current = window.setTimeout(() => { + setIsCopied(false); + }, 1000); + } catch { + // ignore decoding errors + } + }, [markdown]); + + useEffect(() => () => window.clearTimeout(copyTimeout.current), []); + + if (!markdown) return null; + + return ( + + ); +} diff --git a/packages/docusaurus-theme-openapi-docs/src/theme/styles.scss b/packages/docusaurus-theme-openapi-docs/src/theme/styles.scss index db8a648ee..3cddfae7f 100644 --- a/packages/docusaurus-theme-openapi-docs/src/theme/styles.scss +++ b/packages/docusaurus-theme-openapi-docs/src/theme/styles.scss @@ -27,6 +27,7 @@ @use "./ApiExplorer/ApiCodeBlock/ExpandButton/ExpandButton"; @use "./ApiExplorer/ApiCodeBlock/Line/Line"; @use "./ApiExplorer/ApiCodeBlock/WordWrapButton/WordWrapButton"; +@use "./CopyMarkdownButton/CopyMarkdownButton"; /* Schema Styling */ @use "./ParamsItem/ParamsItem";