Skip to content

Commit c70cda0

Browse files
authored
feat: add copy page functions (#2647)
1 parent 5a9c6f5 commit c70cda0

File tree

12 files changed

+250
-2
lines changed

12 files changed

+250
-2
lines changed

i18n/en/code.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -780,5 +780,29 @@
780780
"Enterprise Features & Licensing": {
781781
"message": "Enterprise Features & Licensing",
782782
"description": "Enterprise Features & Licensing"
783+
},
784+
"Copy Page": {
785+
"message": "Copy Page",
786+
"description": "Copy Page"
787+
},
788+
"Copy page as Markdown for LLMs": {
789+
"message": "Copy page as Markdown for LLMs",
790+
"description": "Copy page as Markdown for LLMs"
791+
},
792+
"View as Markdown": {
793+
"message": "View as Markdown",
794+
"description": "View as Markdown"
795+
},
796+
"View this page as plain text": {
797+
"message": "View this page as plain text",
798+
"description": "View this page as plain text"
799+
},
800+
"Failed to copy markdown": {
801+
"message": "Failed to copy markdown",
802+
"description": "Failed to copy markdown"
803+
},
804+
"Copying...": {
805+
"message": "Copying...",
806+
"description": "Copying..."
783807
}
784808
}

i18n/zh/code.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -804,5 +804,29 @@
804804
"Enterprise Features & Licensing": {
805805
"message": "企业功能与许可",
806806
"description": "Enterprise Features & Licensing"
807+
},
808+
"Copy Page": {
809+
"message": "复制页面",
810+
"description": "Copy Page"
811+
},
812+
"Copy page as Markdown for LLMs": {
813+
"message": "复制为 Markdown 格式,供大语言模型使用",
814+
"description": "Copy page as Markdown for LLMs"
815+
},
816+
"View as Markdown": {
817+
"message": "查看 Markdown 格式",
818+
"description": "View as Markdown"
819+
},
820+
"View this page as plain text": {
821+
"message": "查看纯文本",
822+
"description": "View this page as plain text"
823+
},
824+
"Failed to copy markdown": {
825+
"message": "无法复制 Markdown",
826+
"description": "Failed to copy markdown"
827+
},
828+
"Copying...": {
829+
"message": "正在复制...",
830+
"description": "Copying..."
807831
}
808832
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import React, { useState, useMemo, useCallback } from "react";
2+
import { Button, Dropdown, Flex, Spin } from "antd";
3+
import styles from "./styles.module.scss";
4+
import DownArrow from "@site/static/icons/down.svg";
5+
import MarkdownSvg from "@site/static/icons/markdown.svg";
6+
import CopySvg from "@site/static/icons/copy.svg";
7+
import CopiedSvg from "@site/static/icons/copied.svg";
8+
import { useDoc } from "@docusaurus/plugin-content-docs/client";
9+
import axios from "axios";
10+
import $t from "@site/src/utils/tools";
11+
// mark
12+
// import TurndownService from "turndown";
13+
// const turndownService = new TurndownService();
14+
// const getPageContentAsHtml = (): string | null => {
15+
// const contentElement = document.querySelector("article");
16+
// return contentElement ? contentElement.innerHTML : null;
17+
// };
18+
19+
// const convertHtmlToMarkdown = (html: string): string => {
20+
// return turndownService.turndown(html);
21+
// };
22+
const CopyDropdownButton: React.FC = () => {
23+
const [loading, setLoading] = useState(false);
24+
const [isCopied, setIsCopied] = useState(false);
25+
const { metadata } = useDoc();
26+
const sourceUrl = useMemo(() => {
27+
return (
28+
metadata?.source?.replace(
29+
"@site",
30+
"https://raw.githubusercontent.com/databendlabs/databend-docs/refs/heads/main"
31+
) || ""
32+
);
33+
}, [metadata]);
34+
const handleCopy = useCallback((url: string) => {
35+
if (!url) return;
36+
setLoading(true);
37+
axios
38+
.get(url)
39+
.then((response) => {
40+
setIsCopied(true);
41+
if (response.status === 200) {
42+
navigator.clipboard.writeText(response.data);
43+
} else {
44+
alert($t("Failed to copy markdown"));
45+
}
46+
})
47+
.finally(() => {
48+
setLoading(false);
49+
setTimeout(() => {
50+
setIsCopied(false);
51+
}, 3000);
52+
});
53+
}, []);
54+
const menu = useMemo(() => {
55+
const items = [
56+
{
57+
key: "copy",
58+
icon: <CopySvg width={16} />,
59+
label: $t("Copy Page"),
60+
description: $t("Copy page as Markdown for LLMs"),
61+
},
62+
{
63+
key: "markdown",
64+
icon: <MarkdownSvg width={18} />,
65+
label: $t("View as Markdown"),
66+
description: $t("View this page as plain text"),
67+
},
68+
];
69+
70+
return {
71+
items: items.map(({ key, icon, label, description }) => ({
72+
key,
73+
label: (
74+
<div>
75+
<div style={{ fontWeight: 500 }}>{label}</div>
76+
<div style={{ fontSize: 12, color: "#888" }}>{description}</div>
77+
</div>
78+
),
79+
icon: <Button icon={icon} />,
80+
})),
81+
onClick: ({ key }: { key: string }) => {
82+
if (key === "copy") handleCopy(sourceUrl);
83+
if (key === "markdown") window.open(sourceUrl, "_blank");
84+
},
85+
};
86+
}, [sourceUrl, handleCopy]);
87+
const renderButtonContent = useMemo(
88+
() => (
89+
<Flex align="center" gap={6}>
90+
{loading ? (
91+
<Spin size="small" />
92+
) : isCopied ? (
93+
<CopiedSvg width={16} />
94+
) : (
95+
<CopySvg width={16} />
96+
)}
97+
<span>{loading ? $t("Copying...") : $t("Copy Page")}</span>
98+
</Flex>
99+
),
100+
[loading, isCopied]
101+
);
102+
103+
return (
104+
<Dropdown.Button
105+
onClick={() => handleCopy(sourceUrl)}
106+
menu={menu}
107+
placement="bottomRight"
108+
icon={<DownArrow width={18} height={"auto"} />}
109+
className={styles.buttonCainter}
110+
trigger={["click"]}
111+
>
112+
{renderButtonContent}
113+
</Dropdown.Button>
114+
);
115+
};
116+
117+
export default CopyDropdownButton;
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
.buttonCainter {
2+
max-width: 140px;
3+
button {
4+
font-weight: 500;
5+
border: 1px solid var(--color-border) !important;
6+
background-color: var(--color-bg-1) !important;
7+
color: var(--color-text-0) !important;
8+
&:hover {
9+
background-color: var(--color-fill-1) !important;
10+
}
11+
&:last-child {
12+
svg {
13+
position: relative;
14+
top: 2px;
15+
}
16+
}
17+
}
18+
}

src/css/markdown.scss

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,6 @@
222222
&:hover {
223223
background-color: var(--bg-hover-copy);
224224
opacity: 1;
225-
color: #fff !important;
226225
}
227226
}
228227
.theme-code-block:hover {
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import React, { type ReactNode } from "react";
2+
import clsx from "clsx";
3+
import { ThemeClassNames } from "@docusaurus/theme-common";
4+
import { useDoc } from "@docusaurus/plugin-content-docs/client";
5+
import Heading from "@theme/Heading";
6+
import MDXContent from "@theme/MDXContent";
7+
import type { Props } from "@theme/DocItem/Content";
8+
import styles from "./styles.module.css";
9+
import CopyPageButton from "@site/src/components/CopyPageButton";
10+
11+
/**
12+
Title can be declared inside md content or declared through
13+
front matter and added manually. To make both cases consistent,
14+
the added title is added under the same div.markdown block
15+
See https://github.com/facebook/docusaurus/pull/4882#issuecomment-853021120
16+
17+
We render a "synthetic title" if:
18+
- user doesn't ask to hide it with front matter
19+
- the markdown content does not already contain a top-level h1 heading
20+
*/
21+
function useSyntheticTitle(): string | null {
22+
const { metadata, frontMatter, contentTitle } = useDoc();
23+
const shouldRender =
24+
!frontMatter.hide_title && typeof contentTitle === "undefined";
25+
if (!shouldRender) {
26+
return null;
27+
}
28+
return metadata.title;
29+
}
30+
31+
export default function DocItemContent({ children }: Props): ReactNode {
32+
const syntheticTitle = useSyntheticTitle();
33+
return (
34+
<div className={clsx(ThemeClassNames.docs.docMarkdown, "markdown")}>
35+
<section className={styles.headerSection}>
36+
{syntheticTitle ? (
37+
<header>
38+
<Heading as="h1">{syntheticTitle}</Heading>
39+
</header>
40+
) : (
41+
<i />
42+
)}
43+
<CopyPageButton />
44+
</section>
45+
<MDXContent>{children}</MDXContent>
46+
</div>
47+
);
48+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
.headerSection {
2+
display: flex;
3+
justify-content: space-between;
4+
align-items: "flex-start";
5+
width: 100%;
6+
padding-right: 12px;
7+
}
8+
9+
@media (max-width: 900px) {
10+
.headerSection {
11+
flex-direction: column-reverse;
12+
align-items: flex-start;
13+
justify-conten: flex-start;
14+
}
15+
}

src/theme/DocRoot/Layout/Main/styles.module.css

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
display: flex;
33
width: 100%;
44
}
5-
65
@media (min-width: 997px) {
76
.docMainContainer {
87
flex-grow: 1;

static/icons/copied.svg

Lines changed: 1 addition & 0 deletions
Loading

static/icons/copy.svg

Lines changed: 1 addition & 0 deletions
Loading

0 commit comments

Comments
 (0)