From 4f6bc78110743fbc65ef37807fcd24d6d88401ee Mon Sep 17 00:00:00 2001 From: HongwuQz Date: Wed, 12 Nov 2025 22:44:04 +0800 Subject: [PATCH 1/2] refactor: x-markdown mermaid actions --- .../plugins/Mermaid/__test__/index.test.tsx | 36 +++++--- .../x-markdown/src/plugins/Mermaid/index.tsx | 86 +++++++++++-------- packages/x/components/locale/zh_CN.ts | 4 +- 3 files changed, 75 insertions(+), 51 deletions(-) diff --git a/packages/x-markdown/src/plugins/Mermaid/__test__/index.test.tsx b/packages/x-markdown/src/plugins/Mermaid/__test__/index.test.tsx index ef8d09195..a892bcfc4 100644 --- a/packages/x-markdown/src/plugins/Mermaid/__test__/index.test.tsx +++ b/packages/x-markdown/src/plugins/Mermaid/__test__/index.test.tsx @@ -135,7 +135,8 @@ describe('Mermaid Plugin', () => { render({mermaidContent}); - const copyButton = screen.getByRole('button', { name: 'copy' }); + const copyIcon = screen.getByLabelText('copy'); + const copyButton = copyIcon.closest('.ant-actions-item') as HTMLElement; fireEvent.click(copyButton); await waitFor(() => { @@ -154,7 +155,8 @@ describe('Mermaid Plugin', () => { render({mermaidContent}); - const copyButton = screen.getByRole('button', { name: 'copy' }); + const copyIcon = screen.getByLabelText('copy'); + const copyButton = copyIcon.closest('.ant-actions-item') as HTMLElement; // 确保点击不会抛出错误 expect(() => fireEvent.click(copyButton)).not.toThrow(); @@ -177,7 +179,8 @@ describe('Mermaid Plugin', () => { render({mermaidContent}); - const copyButton = screen.getByRole('button', { name: 'copy' }); + const copyIcon = screen.getByLabelText('copy'); + const copyButton = copyIcon.closest('.ant-actions-item') as HTMLElement; fireEvent.click(copyButton); await waitFor(() => { @@ -192,23 +195,25 @@ describe('Mermaid Plugin', () => { it('should show zoom controls only in image mode', () => { render({mermaidContent}); - expect(screen.getByRole('button', { name: 'zoom-in' })).toBeInTheDocument(); - expect(screen.getByRole('button', { name: 'zoom-out' })).toBeInTheDocument(); - expect(screen.getByRole('button', { name: 'Reset' })).toBeInTheDocument(); - expect(screen.getByRole('button', { name: 'download' })).toBeInTheDocument(); + expect(screen.getByLabelText('zoom-in')).toBeInTheDocument(); + expect(screen.getByLabelText('zoom-out')).toBeInTheDocument(); + expect(screen.getByLabelText('undo')).toBeInTheDocument(); + expect(screen.getByLabelText('download')).toBeInTheDocument(); const codeButton = screen.getByText('Code'); fireEvent.click(codeButton); - expect(screen.queryByRole('button', { name: 'zoom-in' })).not.toBeInTheDocument(); - expect(screen.queryByRole('button', { name: 'zoom-out' })).not.toBeInTheDocument(); + expect(screen.queryByLabelText('zoom-in')).not.toBeInTheDocument(); + expect(screen.queryByLabelText('zoom-out')).not.toBeInTheDocument(); }); it('should handle zoom in/out', () => { render({mermaidContent}); - const zoomInButton = screen.getByRole('button', { name: 'zoom-in' }); - const zoomOutButton = screen.getByRole('button', { name: 'zoom-out' }); + const zoomInIcon = screen.getByLabelText('zoom-in'); + const zoomOutIcon = screen.getByLabelText('zoom-out'); + const zoomInButton = zoomInIcon.closest('.ant-actions-item') as HTMLElement; + const zoomOutButton = zoomOutIcon.closest('.ant-actions-item') as HTMLElement; fireEvent.click(zoomInButton); fireEvent.click(zoomOutButton); @@ -217,7 +222,8 @@ describe('Mermaid Plugin', () => { it('should handle reset functionality', () => { render({mermaidContent}); - const resetButton = screen.getByRole('button', { name: 'Reset' }); + const resetIcon = screen.getByLabelText('undo'); + const resetButton = resetIcon.closest('.ant-actions-item') as HTMLElement; fireEvent.click(resetButton); }); }); @@ -500,7 +506,8 @@ describe('Mermaid Plugin', () => { container.querySelector = mockQuerySelector; } - const downloadButton = screen.getByRole('button', { name: 'download' }); + const downloadIcon = screen.getByLabelText('download'); + const downloadButton = downloadIcon.closest('.ant-actions-item') as HTMLElement; fireEvent.click(downloadButton); // Wait for async operations @@ -542,7 +549,8 @@ describe('Mermaid Plugin', () => { container.querySelector = mockQuerySelector; } - const downloadButton = screen.getByRole('button', { name: 'download' }); + const downloadIcon = screen.getByLabelText('download'); + const downloadButton = downloadIcon.closest('.ant-actions-item') as HTMLElement; fireEvent.click(downloadButton); // Should not throw and should return early diff --git a/packages/x-markdown/src/plugins/Mermaid/index.tsx b/packages/x-markdown/src/plugins/Mermaid/index.tsx index bb1639b9d..99f98d00d 100644 --- a/packages/x-markdown/src/plugins/Mermaid/index.tsx +++ b/packages/x-markdown/src/plugins/Mermaid/index.tsx @@ -1,9 +1,17 @@ -import { CopyOutlined, DownloadOutlined, ZoomInOutlined, ZoomOutOutlined } from '@ant-design/icons'; +import { + CopyOutlined, + DownloadOutlined, + UndoOutlined, + ZoomInOutlined, + ZoomOutOutlined, +} from '@ant-design/icons'; import useXComponentConfig from '@ant-design/x/es/_util/hooks/use-x-component-config'; +import Actions from '@ant-design/x/es/actions'; +import type { ItemType } from '@ant-design/x/es/actions/interface'; import useLocale from '@ant-design/x/es/locale/useLocale'; import useXProviderContext from '@ant-design/x/es/x-provider/hooks/use-x-provider-context'; import locale_EN from '@ant-design/x/locale/en_US'; -import { Button, message, Segmented, Space, Tooltip } from 'antd'; +import { message, Segmented } from 'antd'; import classnames from 'classnames'; import throttle from 'lodash.throttle'; import mermaid from 'mermaid'; @@ -222,6 +230,46 @@ const Mermaid: React.FC = React.memo((props) => { } }; + // ============================ Action Items ============================ + const baseItems: ItemType[] = [ + { + key: 'copy', + icon: , + label: contextLocale.copy, + onItemClick: handleCopyCode, + }, + ]; + + const imageItems: ItemType[] = [ + ...baseItems, + { + key: 'zoomIn', + icon: , + label: contextLocale.zoomIn, + onItemClick: handleZoomIn, + }, + { + key: 'zoomOut', + icon: , + label: contextLocale.zoomOut, + onItemClick: handleZoomOut, + }, + { + key: 'zoomReset', + icon: , + label: contextLocale.zoomReset, + onItemClick: handleReset, + }, + { + key: 'download', + icon: , + label: contextLocale.download, + onItemClick: handleDownload, + }, + ]; + + const actionItems = renderType === RenderType.Image ? imageItems : baseItems; + const renderHeader = () => { if (header === null) return null; if (header) return header; @@ -244,39 +292,7 @@ const Mermaid: React.FC = React.memo((props) => { value={renderType} onChange={setRenderType} /> - - - - - -