import { Modal } from '@wordpress/components' import { __ } from '@wordpress/i18n' import React, { useEffect, useRef, useState } from 'react' import { useSnippetsAPI } from '../../../hooks/useSnippetsAPI' import { useSnippetsList } from '../../../hooks/useSnippetsList' import { handleUnknownError } from '../../../utils/errors' import { downloadSnippetExportFile } from '../../../utils/files' import { canModifySnippet, cloneSnippetObject, getSnippetDisplayName, getSnippetEditUrl, getSnippetType } from '../../../utils/snippets/snippets' import { Badge } from '../Badge' import { Button } from '../Button' import { CloudSnippetDownloadButton } from '../cloud/CloudSnippetDownloadButton' import { ConfirmDeleteDialog, useDeleteSnippet } from './ConfirmDeleteDialog' import type { CloudSnippetSchema } from '../../../types/schema/CloudSnippetSchema' import type { EditorConfiguration, EditorFromTextArea } from 'codemirror' import type { ReactNode } from 'react' import type { Snippet, SnippetType } from '../../../types/Snippet' const EDITOR_MODES: Record = { css: 'text/css', js: 'javascript', php: 'text/x-php', html: 'application/x-httpd-php' } const getClipboard = (): Clipboard | undefined => window.isSecureContext ? navigator.clipboard as Clipboard | undefined : undefined const getPreviewEditorSettings = (type: string): EditorConfiguration => ({ extraKeys: { 'Tab': false, 'Shift-Tab': false }, readOnly: true, lineNumbers: true, theme: window.CODE_SNIPPETS_MANAGE?.editorTheme ?? 'default', mode: EDITOR_MODES[type] ?? EDITOR_MODES.php, screenReaderLabel: __('Snippet code preview', 'code-snippets') }) /** * Tracks whether a footer action is in flight. The ref mirrors the state so * `beginWorking` can reject re-entry within the same tick, before React * re-renders with the disabled buttons. */ const useWorkingState = () => { const [isWorking, setIsWorking] = useState(false) const isWorkingRef = useRef(false) const updateWorking = (value: boolean) => { isWorkingRef.current = value setIsWorking(value) } return { isWorking, setIsWorking: updateWorking } } enum CopyStatus { Ready, Copied, Failed} const CopyCodeButton: React.FC<{ code: string }> = ({ code }) => { const [copyStatus, setCopyStatus] = useState(CopyStatus.Ready) const handleCopy = () => { const clipboard = getClipboard() if (!clipboard) { setCopyStatus(CopyStatus.Failed) return } void clipboard.writeText(code) .then(() => setCopyStatus(CopyStatus.Copied)) .catch(() => setCopyStatus(CopyStatus.Failed)) } return ( ) } interface PreviewModalProps { onRequestClose: VoidFunction title: string type: SnippetType code: string children: ReactNode } const PreviewModal: React.FC = ({ onRequestClose, title, type, code, children }) => { const textareaRef = useRef(null) useEffect(() => { if (!textareaRef.current || !window.wp.codeEditor) { return undefined } const instance = window.wp.codeEditor.initialize( textareaRef.current, { codemirror: getPreviewEditorSettings(type) } ) // CodeMirror hides the labeled source textarea and creates an unlabelled // internal input. The screenReaderLabel option only exists from CodeMirror // 5.59, while WordPress 5.5 ships 5.29, so label the input directly. instance.codemirror.getInputField().setAttribute('aria-label', __('Snippet code preview', 'code-snippets')) return () => { (instance.codemirror as EditorFromTextArea).toTextArea() } }, [type]) return ( } >