diff --git a/src/css/admonition.css b/src/css/admonition.css
index 2bcab01d58..9d624ab581 100644
--- a/src/css/admonition.css
+++ b/src/css/admonition.css
@@ -61,6 +61,12 @@
[data-theme='dark'] {
--if-alert-color: white;
}
+
+.alert {
+ width: max-content;
+ max-width: 100%;
+}
+
[data-theme='dark'] .alert {
--ifm-link-hover-color: #5196d7;
}
diff --git a/src/theme/CodeBlock/Container/index.tsx b/src/theme/CodeBlock/Container/index.tsx
new file mode 100644
index 0000000000..682d928a3e
--- /dev/null
+++ b/src/theme/CodeBlock/Container/index.tsx
@@ -0,0 +1,21 @@
+import React from 'react';
+import clsx from 'clsx';
+import {ThemeClassNames, usePrismTheme} from '@docusaurus/theme-common';
+import {getPrismCssVariables} from '@docusaurus/theme-common/internal';
+import styles from './styles.module.scss';
+export default function CodeBlockContainer({as: As, ...props}) {
+ const prismTheme = usePrismTheme();
+ const prismCssVariables = getPrismCssVariables(prismTheme);
+ return (
+
tags in markdown map to CodeBlocks. They may contain JSX children. When
+// the children is not a simple string, we just return a styled block without
+// actually highlighting.
+export default function CodeBlockJSX({children, className}) {
+ return (
+
+ {children}
+
+ );
+}
diff --git a/src/theme/CodeBlock/Content/String.tsx b/src/theme/CodeBlock/Content/String.tsx
new file mode 100644
index 0000000000..dcf66bbc20
--- /dev/null
+++ b/src/theme/CodeBlock/Content/String.tsx
@@ -0,0 +1,101 @@
+import React from 'react';
+import clsx from 'clsx';
+import {useThemeConfig, usePrismTheme} from '@docusaurus/theme-common';
+import {
+ parseCodeBlockTitle,
+ parseLanguage,
+ parseLines,
+ containsLineNumbers,
+ useCodeWordWrap,
+} from '@docusaurus/theme-common/internal';
+import {Highlight} from 'prism-react-renderer';
+import styles from './styles.module.css';
+import Container from '../Container';
+import WordWrapButton from '../WordWrapButton';
+import CopyButton from '../CopyButton';
+import Line from '../Line';
+// Prism languages are always lowercase
+// We want to fail-safe and allow both "php" and "PHP"
+// See https://github.com/facebook/docusaurus/issues/9012
+function normalizeLanguage(language) {
+ return language?.toLowerCase();
+}
+export default function CodeBlockString({
+ children,
+ className: blockClassName = '',
+ metastring,
+ title: titleProp,
+ showLineNumbers: showLineNumbersProp,
+ language: languageProp,
+}) {
+ const {
+ prism: {defaultLanguage, magicComments},
+ } = useThemeConfig();
+ const language = normalizeLanguage(
+ languageProp ?? parseLanguage(blockClassName) ?? defaultLanguage,
+ );
+ const prismTheme = usePrismTheme();
+ const wordWrap = useCodeWordWrap();
+ // We still parse the metastring in case we want to support more syntax in the
+ // future. Note that MDX doesn't strip quotes when parsing metastring:
+ // "title=\"xyz\"" => title: "\"xyz\""
+ const title = parseCodeBlockTitle(metastring) || titleProp;
+ const {lineClassNames, code} = parseLines(children, {
+ metastring,
+ language,
+ magicComments,
+ });
+ const showLineNumbers =
+ showLineNumbersProp ?? containsLineNumbers(metastring);
+ return (
+
+ {title && {title}}
+
+
+ {({className, style, tokens, getLineProps, getTokenProps}) => (
+
+
+ {tokens.map((line, i) => (
+
+ ))}
+
+
+ )}
+
+
+ {(wordWrap.isEnabled || wordWrap.isCodeScrollable) && (
+ wordWrap.toggle()}
+ isEnabled={wordWrap.isEnabled}
+ />
+ )}
+
+
+
+
+ );
+}
diff --git a/src/theme/CodeBlock/Content/styles.module.css b/src/theme/CodeBlock/Content/styles.module.css
new file mode 100644
index 0000000000..31eda1e028
--- /dev/null
+++ b/src/theme/CodeBlock/Content/styles.module.css
@@ -0,0 +1,80 @@
+.codeBlockContent {
+ position: relative;
+ /* rtl:ignore */
+ direction: ltr;
+ border-radius: inherit;
+}
+
+.codeBlockTitle {
+ border-bottom: 1px solid var(--ifm-color-emphasis-300);
+ font-size: var(--ifm-code-font-size);
+ font-weight: 500;
+ padding: 0.75rem var(--ifm-pre-padding);
+ border-top-left-radius: inherit;
+ border-top-right-radius: inherit;
+}
+
+.codeBlock {
+ --ifm-pre-background: var(--prism-background-color);
+ margin: 0;
+ padding-bottom: 0 !important;
+}
+
+.codeBlockTitle + .codeBlockContent .codeBlock {
+ border-top-left-radius: 0;
+ border-top-right-radius: 0;
+}
+
+.codeBlockStandalone {
+ padding: 0;
+}
+
+.codeBlockLines {
+ font: inherit;
+ /* rtl:ignore */
+ float: left;
+ min-width: 100%;
+ padding: var(--ifm-pre-padding);
+}
+
+.codeBlockLinesWithNumbering {
+ display: table;
+ padding: var(--ifm-pre-padding) 0;
+}
+
+@media print {
+ .codeBlockLines {
+ white-space: pre-wrap;
+ }
+}
+
+.buttonGroup {
+ display: flex;
+ column-gap: 0.2rem;
+ position: absolute;
+ /* rtl:ignore */
+ right: calc(var(--ifm-pre-padding) / 2);
+ top: calc(var(--ifm-pre-padding) / 2);
+}
+
+.buttonGroup button {
+ display: flex;
+ align-items: center;
+ background: var(--prism-background-color);
+ color: var(--prism-color);
+ border: 1px solid var(--ifm-color-emphasis-300);
+ border-radius: var(--ifm-global-radius);
+ padding: 0.4rem;
+ line-height: 0;
+ transition: opacity var(--ifm-transition-fast) ease-in-out;
+ opacity: 0;
+}
+
+.buttonGroup button:focus-visible,
+.buttonGroup button:hover {
+ opacity: 1 !important;
+}
+
+:global(.theme-code-block:hover) .buttonGroup button {
+ opacity: 0.4;
+}
diff --git a/src/theme/CodeBlock/CopyButton/index.js b/src/theme/CodeBlock/CopyButton/index.js
new file mode 100644
index 0000000000..fd430b473c
--- /dev/null
+++ b/src/theme/CodeBlock/CopyButton/index.js
@@ -0,0 +1,53 @@
+import React, {useCallback, useState, useRef, useEffect} from 'react';
+import clsx from 'clsx';
+import copy from 'copy-text-to-clipboard';
+import {translate} from '@docusaurus/Translate';
+import IconCopy from '@theme/Icon/Copy';
+import IconSuccess from '@theme/Icon/Success';
+import styles from './styles.module.css';
+export default function CopyButton({code, className}) {
+ const [isCopied, setIsCopied] = useState(false);
+ const copyTimeout = useRef(undefined);
+ const handleCopyCode = useCallback(() => {
+ copy(code);
+ setIsCopied(true);
+ copyTimeout.current = window.setTimeout(() => {
+ setIsCopied(false);
+ }, 1000);
+ }, [code]);
+ useEffect(() => () => window.clearTimeout(copyTimeout.current), []);
+ return (
+
+ );
+}
diff --git a/src/theme/CodeBlock/CopyButton/styles.module.css b/src/theme/CodeBlock/CopyButton/styles.module.css
new file mode 100644
index 0000000000..d5268e900b
--- /dev/null
+++ b/src/theme/CodeBlock/CopyButton/styles.module.css
@@ -0,0 +1,40 @@
+:global(.theme-code-block:hover) .copyButtonCopied {
+ opacity: 1 !important;
+}
+
+.copyButtonIcons {
+ position: relative;
+ width: 1.125rem;
+ height: 1.125rem;
+}
+
+.copyButtonIcon,
+.copyButtonSuccessIcon {
+ position: absolute;
+ top: 0;
+ left: 0;
+ fill: currentColor;
+ opacity: inherit;
+ width: inherit;
+ height: inherit;
+ transition: all var(--ifm-transition-fast) ease;
+}
+
+.copyButtonSuccessIcon {
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%) scale(0.33);
+ opacity: 0;
+ color: #00d600;
+}
+
+.copyButtonCopied .copyButtonIcon {
+ transform: scale(0.33);
+ opacity: 0;
+}
+
+.copyButtonCopied .copyButtonSuccessIcon {
+ transform: translate(-50%, -50%) scale(1);
+ opacity: 1;
+ transition-delay: 0.075s;
+}
diff --git a/src/theme/CodeBlock/Line/index.js b/src/theme/CodeBlock/Line/index.js
new file mode 100644
index 0000000000..f36761806a
--- /dev/null
+++ b/src/theme/CodeBlock/Line/index.js
@@ -0,0 +1,34 @@
+import React from 'react';
+import clsx from 'clsx';
+import styles from './styles.module.css';
+export default function CodeBlockLine({
+ line,
+ classNames,
+ showLineNumbers,
+ getLineProps,
+ getTokenProps,
+}) {
+ if (line.length === 1 && line[0].content === '\n') {
+ line[0].content = '';
+ }
+ const lineProps = getLineProps({
+ line,
+ className: clsx(classNames, showLineNumbers && styles.codeLine),
+ });
+ const lineTokens = line.map((token, key) => (
+
+ ));
+ return (
+
+ {showLineNumbers ? (
+ <>
+
+ {lineTokens}
+ >
+ ) : (
+ lineTokens
+ )}
+
+
+ );
+}
diff --git a/src/theme/CodeBlock/Line/styles.module.css b/src/theme/CodeBlock/Line/styles.module.css
new file mode 100644
index 0000000000..7c28ed9aa3
--- /dev/null
+++ b/src/theme/CodeBlock/Line/styles.module.css
@@ -0,0 +1,45 @@
+/* Intentionally has zero specificity, so that to be able to override
+the background in custom CSS file due bug https://github.com/facebook/docusaurus/issues/3678 */
+:where(:root) {
+ --docusaurus-highlighted-code-line-bg: rgb(72 77 91);
+}
+
+:where([data-theme='dark']) {
+ --docusaurus-highlighted-code-line-bg: rgb(100 100 100);
+}
+
+:global(.theme-code-block-highlighted-line) {
+ background-color: var(--docusaurus-highlighted-code-line-bg);
+ display: block;
+ margin: 0 calc(-1 * var(--ifm-pre-padding));
+ padding: 0 var(--ifm-pre-padding);
+}
+
+.codeLine {
+ display: table-row;
+ counter-increment: line-count;
+}
+
+.codeLineNumber {
+ display: table-cell;
+ text-align: right;
+ width: 1%;
+ position: sticky;
+ left: 0;
+ padding: 0 var(--ifm-pre-padding);
+ background: var(--ifm-pre-background);
+ overflow-wrap: normal;
+}
+
+.codeLineNumber::before {
+ content: counter(line-count);
+ opacity: 0.4;
+}
+
+:global(.theme-code-block-highlighted-line) .codeLineNumber::before {
+ opacity: 0.8;
+}
+
+.codeLineContent {
+ padding-right: var(--ifm-pre-padding);
+}
diff --git a/src/theme/CodeBlock/WordWrapButton/index.js b/src/theme/CodeBlock/WordWrapButton/index.js
new file mode 100644
index 0000000000..3a4136b630
--- /dev/null
+++ b/src/theme/CodeBlock/WordWrapButton/index.js
@@ -0,0 +1,27 @@
+import React from 'react';
+import clsx from 'clsx';
+import {translate} from '@docusaurus/Translate';
+import IconWordWrap from '@theme/Icon/WordWrap';
+import styles from './styles.module.css';
+export default function WordWrapButton({className, onClick, isEnabled}) {
+ const title = translate({
+ id: 'theme.CodeBlock.wordWrapToggle',
+ message: 'Toggle word wrap',
+ description:
+ 'The title attribute for toggle word wrapping button of code block lines',
+ });
+ return (
+
+ );
+}
diff --git a/src/theme/CodeBlock/WordWrapButton/styles.module.css b/src/theme/CodeBlock/WordWrapButton/styles.module.css
new file mode 100644
index 0000000000..fdbc894646
--- /dev/null
+++ b/src/theme/CodeBlock/WordWrapButton/styles.module.css
@@ -0,0 +1,8 @@
+.wordWrapButtonIcon {
+ width: 1.2rem;
+ height: 1.2rem;
+}
+
+.wordWrapButtonEnabled .wordWrapButtonIcon {
+ color: var(--ifm-color-primary);
+}
diff --git a/src/theme/CodeBlock/index.tsx b/src/theme/CodeBlock/index.tsx
new file mode 100644
index 0000000000..dc5e34ecd9
--- /dev/null
+++ b/src/theme/CodeBlock/index.tsx
@@ -0,0 +1,33 @@
+import React, {isValidElement} from 'react';
+import useIsBrowser from '@docusaurus/useIsBrowser';
+import CodeBlockString from './Content/String';
+import CodeBlockJSX from './Content/Element';
+/**
+ * Best attempt to make the children a plain string so it is copyable. If there
+ * are react elements, we will not be able to copy the content, and it will
+ * return `children` as-is; otherwise, it concatenates the string children
+ * together.
+ */
+function maybeStringifyChildren(children) {
+ if (React.Children.toArray(children).some((el) => isValidElement(el))) {
+ return children;
+ }
+ // The children is now guaranteed to be one/more plain strings
+ return Array.isArray(children) ? children.join('') : children;
+}
+export default function CodeBlock({children: rawChildren, ...props}) {
+ // The Prism theme on SSR is always the default theme but the site theme can
+ // be in a different mode. React hydration doesn't update DOM styles that come
+ // from SSR. Hence force a re-render after mounting to apply the current
+ // relevant styles.
+ const isBrowser = useIsBrowser();
+ const children = maybeStringifyChildren(rawChildren);
+ const CodeBlockComp =
+ typeof children === 'string' ? CodeBlockString : CodeBlockJSX;
+ return (
+ //@ts-ignore
+
+ {children}
+
+ );
+}