import { CheckOutlined, LinkOutlined, SnippetsOutlined, ThunderboltOutlined, } from '@ant-design/icons'; import type { Project } from '@stackblitz/sdk'; import stackblitzSdk from '@stackblitz/sdk'; import { Alert, Badge, Space, Tooltip } from 'antd'; import classNames from 'classnames'; import LZString from 'lz-string'; import React, { useContext, useEffect, useRef, useState } from 'react'; import CopyToClipboard from 'react-copy-to-clipboard'; import type { IPreviewerProps } from 'dumi'; import { FormattedMessage, useSiteData } from 'dumi'; import Prism from 'prismjs'; import JsonML from 'jsonml.js/lib/utils'; import toReactElement from 'jsonml-to-react-element'; import { ping } from '../../utils'; import ClientOnly from '../../common/ClientOnly'; import BrowserFrame from '../../common/BrowserFrame'; import EditButton from '../../common/EditButton'; import CodePenIcon from '../../common/CodePenIcon'; import CodePreview from '../../common/CodePreview'; import CodeSandboxIcon from '../../common/CodeSandboxIcon'; import RiddleIcon from '../../common/RiddleIcon'; import ExternalLinkIcon from '../../common/ExternalLinkIcon'; import type { SiteContextProps } from '../../slots/SiteContext'; import SiteContext from '../../slots/SiteContext'; import useLocation from '../../../hooks/useLocation'; const { ErrorBoundary } = Alert; function toReactComponent(jsonML: any) { return toReactElement(jsonML, [ [ (node: any) => JsonML.isElement(node) && JsonML.getTagName(node) === 'pre', (node: any, index: any) => { // ref: https://github.com/benjycui/bisheng/blob/master/packages/bisheng/src/bisheng-plugin-highlight/lib/browser.js#L7 const attr = JsonML.getAttributes(node); return React.createElement( 'pre', { key: index, className: `language-${attr.lang}`, }, React.createElement('code', { dangerouslySetInnerHTML: { __html: attr.highlighted }, }), ); }, ], ]); } function compress(string: string): string { return LZString.compressToBase64(string) .replace(/\+/g, '-') // Convert '+' to '-' .replace(/\//g, '_') // Convert '/' to '_' .replace(/=+$/, ''); // Remove ending '=' } const track = ({ type, demo }: { type: string; demo: string }) => { if (!window.gtag) { return; } window.gtag('event', 'demo', { event_category: type, event_label: demo }); }; let pingDeferrer: PromiseLike; function useShowRiddleButton() { const [showRiddleButton, setShowRiddleButton] = useState(false); useEffect(() => { pingDeferrer ??= new Promise((resolve) => { ping((status) => { if (status !== 'timeout' && status !== 'error') { return resolve(true); } return resolve(false); }); }); pingDeferrer.then(setShowRiddleButton); }, []); return showRiddleButton; } const CodePreviewer: React.FC = (props) => { const { asset, expand, iframe, demoUrl, children, title, description, debug, jsx, style, compact, background, filePath, } = props; const { pkg } = useSiteData(); const location = useLocation(); const entryCode = asset.dependencies['index.tsx'].value; const showRiddleButton = useShowRiddleButton(); const liveDemo = useRef(null); const anchorRef = useRef(null); const codeSandboxIconRef = useRef(null); const riddleIconRef = useRef(null); const codepenIconRef = useRef(null); const [codeExpand, setCodeExpand] = useState(false); const [copyTooltipOpen, setCopyTooltipOpen] = useState(false); const [copied, setCopied] = useState(false); const [codeType, setCodeType] = useState('tsx'); const { theme } = useContext(SiteContext); const { hash, pathname, search } = location; const docsOnlineUrl = `https://ant.design${pathname}${search}#${asset.id}`; const [showOnlineUrl, setShowOnlineUrl] = useState(false); const highlightedCodes = { jsx: Prism.highlight(jsx, Prism.languages.javascript, 'jsx'), tsx: Prism.highlight(entryCode, Prism.languages.javascript, 'jsx'), }; const highlightedStyle = style ? Prism.highlight(style, Prism.languages.css, 'css') : ''; useEffect(() => { const regexp = /preview-(\d+)-ant-design/; // matching PR preview addresses setShowOnlineUrl( process.env.NODE_ENV === 'development' || regexp.test(window.location.hostname), ); }, []); const handleCodeExpand = (demo: string) => { setCodeExpand((prev) => !prev); track({ type: 'expand', demo }); }; const handleCodeCopied = (demo: string) => { setCopied(true); track({ type: 'copy', demo }); }; const onCopyTooltipOpenChange = (open: boolean) => { setCopyTooltipOpen(open); if (open) { setCopied(false); } }; useEffect(() => { if (asset.id === hash.slice(1)) { anchorRef.current?.click(); } }, []); useEffect(() => { setCodeExpand(expand); }, [expand]); if (!liveDemo.current) { liveDemo.current = iframe ? (