diff --git a/i18n/en/code.json b/i18n/en/code.json
index de4dd2f009..cbf0cef42d 100644
--- a/i18n/en/code.json
+++ b/i18n/en/code.json
@@ -780,5 +780,29 @@
"Enterprise Features & Licensing": {
"message": "Enterprise Features & Licensing",
"description": "Enterprise Features & Licensing"
+ },
+ "Copy Page": {
+ "message": "Copy Page",
+ "description": "Copy Page"
+ },
+ "Copy page as Markdown for LLMs": {
+ "message": "Copy page as Markdown for LLMs",
+ "description": "Copy page as Markdown for LLMs"
+ },
+ "View as Markdown": {
+ "message": "View as Markdown",
+ "description": "View as Markdown"
+ },
+ "View this page as plain text": {
+ "message": "View this page as plain text",
+ "description": "View this page as plain text"
+ },
+ "Failed to copy markdown": {
+ "message": "Failed to copy markdown",
+ "description": "Failed to copy markdown"
+ },
+ "Copying...": {
+ "message": "Copying...",
+ "description": "Copying..."
}
}
diff --git a/i18n/zh/code.json b/i18n/zh/code.json
index ff69649140..784e0e3318 100644
--- a/i18n/zh/code.json
+++ b/i18n/zh/code.json
@@ -804,5 +804,29 @@
"Enterprise Features & Licensing": {
"message": "企业功能与许可",
"description": "Enterprise Features & Licensing"
+ },
+ "Copy Page": {
+ "message": "复制页面",
+ "description": "Copy Page"
+ },
+ "Copy page as Markdown for LLMs": {
+ "message": "复制为 Markdown 格式,供大语言模型使用",
+ "description": "Copy page as Markdown for LLMs"
+ },
+ "View as Markdown": {
+ "message": "查看 Markdown 格式",
+ "description": "View as Markdown"
+ },
+ "View this page as plain text": {
+ "message": "查看纯文本",
+ "description": "View this page as plain text"
+ },
+ "Failed to copy markdown": {
+ "message": "无法复制 Markdown",
+ "description": "Failed to copy markdown"
+ },
+ "Copying...": {
+ "message": "正在复制...",
+ "description": "Copying..."
}
}
diff --git a/src/components/CopyPageButton/index.tsx b/src/components/CopyPageButton/index.tsx
new file mode 100644
index 0000000000..ef8805d75f
--- /dev/null
+++ b/src/components/CopyPageButton/index.tsx
@@ -0,0 +1,117 @@
+import React, { useState, useMemo, useCallback } from "react";
+import { Button, Dropdown, Flex, Spin } from "antd";
+import styles from "./styles.module.scss";
+import DownArrow from "@site/static/icons/down.svg";
+import MarkdownSvg from "@site/static/icons/markdown.svg";
+import CopySvg from "@site/static/icons/copy.svg";
+import CopiedSvg from "@site/static/icons/copied.svg";
+import { useDoc } from "@docusaurus/plugin-content-docs/client";
+import axios from "axios";
+import $t from "@site/src/utils/tools";
+// mark
+// import TurndownService from "turndown";
+// const turndownService = new TurndownService();
+// const getPageContentAsHtml = (): string | null => {
+// const contentElement = document.querySelector("article");
+// return contentElement ? contentElement.innerHTML : null;
+// };
+
+// const convertHtmlToMarkdown = (html: string): string => {
+// return turndownService.turndown(html);
+// };
+const CopyDropdownButton: React.FC = () => {
+ const [loading, setLoading] = useState(false);
+ const [isCopied, setIsCopied] = useState(false);
+ const { metadata } = useDoc();
+ const sourceUrl = useMemo(() => {
+ return (
+ metadata?.source?.replace(
+ "@site",
+ "https://raw.githubusercontent.com/databendlabs/databend-docs/refs/heads/main"
+ ) || ""
+ );
+ }, [metadata]);
+ const handleCopy = useCallback((url: string) => {
+ if (!url) return;
+ setLoading(true);
+ axios
+ .get(url)
+ .then((response) => {
+ setIsCopied(true);
+ if (response.status === 200) {
+ navigator.clipboard.writeText(response.data);
+ } else {
+ alert($t("Failed to copy markdown"));
+ }
+ })
+ .finally(() => {
+ setLoading(false);
+ setTimeout(() => {
+ setIsCopied(false);
+ }, 3000);
+ });
+ }, []);
+ const menu = useMemo(() => {
+ const items = [
+ {
+ key: "copy",
+ icon: ,
+ label: $t("Copy Page"),
+ description: $t("Copy page as Markdown for LLMs"),
+ },
+ {
+ key: "markdown",
+ icon: ,
+ label: $t("View as Markdown"),
+ description: $t("View this page as plain text"),
+ },
+ ];
+
+ return {
+ items: items.map(({ key, icon, label, description }) => ({
+ key,
+ label: (
+
+
{label}
+
{description}
+
+ ),
+ icon: ,
+ })),
+ onClick: ({ key }: { key: string }) => {
+ if (key === "copy") handleCopy(sourceUrl);
+ if (key === "markdown") window.open(sourceUrl, "_blank");
+ },
+ };
+ }, [sourceUrl, handleCopy]);
+ const renderButtonContent = useMemo(
+ () => (
+
+ {loading ? (
+
+ ) : isCopied ? (
+
+ ) : (
+
+ )}
+ {loading ? $t("Copying...") : $t("Copy Page")}
+
+ ),
+ [loading, isCopied]
+ );
+
+ return (
+ handleCopy(sourceUrl)}
+ menu={menu}
+ placement="bottomRight"
+ icon={}
+ className={styles.buttonCainter}
+ trigger={["click"]}
+ >
+ {renderButtonContent}
+
+ );
+};
+
+export default CopyDropdownButton;
diff --git a/src/components/CopyPageButton/styles.module.scss b/src/components/CopyPageButton/styles.module.scss
new file mode 100644
index 0000000000..df51154bd6
--- /dev/null
+++ b/src/components/CopyPageButton/styles.module.scss
@@ -0,0 +1,18 @@
+.buttonCainter {
+ max-width: 140px;
+ button {
+ font-weight: 500;
+ border: 1px solid var(--color-border) !important;
+ background-color: var(--color-bg-1) !important;
+ color: var(--color-text-0) !important;
+ &:hover {
+ background-color: var(--color-fill-1) !important;
+ }
+ &:last-child {
+ svg {
+ position: relative;
+ top: 2px;
+ }
+ }
+ }
+}
diff --git a/src/css/markdown.scss b/src/css/markdown.scss
index a33d9a3bbe..17930c1308 100644
--- a/src/css/markdown.scss
+++ b/src/css/markdown.scss
@@ -222,7 +222,6 @@
&:hover {
background-color: var(--bg-hover-copy);
opacity: 1;
- color: #fff !important;
}
}
.theme-code-block:hover {
diff --git a/src/theme/DocItem/Content/index.tsx b/src/theme/DocItem/Content/index.tsx
new file mode 100644
index 0000000000..c076ecd35c
--- /dev/null
+++ b/src/theme/DocItem/Content/index.tsx
@@ -0,0 +1,48 @@
+import React, { type ReactNode } from "react";
+import clsx from "clsx";
+import { ThemeClassNames } from "@docusaurus/theme-common";
+import { useDoc } from "@docusaurus/plugin-content-docs/client";
+import Heading from "@theme/Heading";
+import MDXContent from "@theme/MDXContent";
+import type { Props } from "@theme/DocItem/Content";
+import styles from "./styles.module.css";
+import CopyPageButton from "@site/src/components/CopyPageButton";
+
+/**
+ Title can be declared inside md content or declared through
+ front matter and added manually. To make both cases consistent,
+ the added title is added under the same div.markdown block
+ See https://github.com/facebook/docusaurus/pull/4882#issuecomment-853021120
+
+ We render a "synthetic title" if:
+ - user doesn't ask to hide it with front matter
+ - the markdown content does not already contain a top-level h1 heading
+*/
+function useSyntheticTitle(): string | null {
+ const { metadata, frontMatter, contentTitle } = useDoc();
+ const shouldRender =
+ !frontMatter.hide_title && typeof contentTitle === "undefined";
+ if (!shouldRender) {
+ return null;
+ }
+ return metadata.title;
+}
+
+export default function DocItemContent({ children }: Props): ReactNode {
+ const syntheticTitle = useSyntheticTitle();
+ return (
+
+
+ {syntheticTitle ? (
+
+ ) : (
+
+ )}
+
+
+ {children}
+
+ );
+}
diff --git a/src/theme/DocItem/Content/styles.module.css b/src/theme/DocItem/Content/styles.module.css
new file mode 100644
index 0000000000..6c883490fa
--- /dev/null
+++ b/src/theme/DocItem/Content/styles.module.css
@@ -0,0 +1,15 @@
+.headerSection {
+ display: flex;
+ justify-content: space-between;
+ align-items: "flex-start";
+ width: 100%;
+ padding-right: 12px;
+}
+
+@media (max-width: 900px) {
+ .headerSection {
+ flex-direction: column-reverse;
+ align-items: flex-start;
+ justify-conten: flex-start;
+ }
+}
diff --git a/src/theme/DocRoot/Layout/Main/styles.module.css b/src/theme/DocRoot/Layout/Main/styles.module.css
index 096eb06490..224ab937bc 100644
--- a/src/theme/DocRoot/Layout/Main/styles.module.css
+++ b/src/theme/DocRoot/Layout/Main/styles.module.css
@@ -2,7 +2,6 @@
display: flex;
width: 100%;
}
-
@media (min-width: 997px) {
.docMainContainer {
flex-grow: 1;
diff --git a/static/icons/copied.svg b/static/icons/copied.svg
new file mode 100644
index 0000000000..1742fd0e3a
--- /dev/null
+++ b/static/icons/copied.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/static/icons/copy.svg b/static/icons/copy.svg
new file mode 100644
index 0000000000..6c755bb849
--- /dev/null
+++ b/static/icons/copy.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/static/icons/down.svg b/static/icons/down.svg
new file mode 100644
index 0000000000..9b9537721f
--- /dev/null
+++ b/static/icons/down.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/static/icons/markdown.svg b/static/icons/markdown.svg
new file mode 100644
index 0000000000..8a7df5b37d
--- /dev/null
+++ b/static/icons/markdown.svg
@@ -0,0 +1 @@
+
\ No newline at end of file