PluginProbe
Code Snippets / trunk
Code Snippets vtrunk
4.0.0-beta.2 3.10.2 3.10.1 3.10.0 3.10.0-beta.2 3.10.0-beta.1 4.0.0-beta.1 3.9.6 trunk 2.10.0 2.10.1 2.12.0 2.12.1 2.13.0 2.13.1 2.13.2 2.13.3 2.14.0 2.14.1 2.14.2 2.14.3 2.14.4 2.14.5 2.14.6 3.0.0 All 65 releases
← All changes | js/components/EditMenu/SnippetForm/fields/CodeEditor.tsx +51 -6 3.10.2 → trunk View file →
@@ -1,9 +1,11 @@
1 1 import React, { useEffect, useId, useRef } from 'react'
2 -import { __ } from '@wordpress/i18n'
2 +import { __, sprintf } from '@wordpress/i18n'
3 3 import { useSubmitSnippet } from '../../../../hooks/useSubmitSnippet'
4 4 import { handleUnknownError } from '../../../../utils/errors'
5 5 import { isMacOS } from '../../../../utils/screen'
6 +import { getSnippetType } from '../../../../utils/snippets/snippets'
7 +import { stripWrapperTags } from '../../../../utils/snippets/tags'
6 8 import { useSnippetForm } from '../WithSnippetFormContext'
7 9 import { Button } from '../../../common/Button'
8 10 import { ExpandIcon } from '../../../common/icons/ExpandIcon'
9 11 import { MinimiseIcon } from '../../../common/icons/MinimiseIcon'
@@ -8,8 +10,10 @@
8 10 import { ExpandIcon } from '../../../common/icons/ExpandIcon'
9 11 import { MinimiseIcon } from '../../../common/icons/MinimiseIcon'
10 12 import { CodeEditorShortcuts } from './CodeEditorShortcuts'
11 13 import type { Dispatch, RefObject, SetStateAction } from 'react'
14 +import type { ScreenNotice } from '../../../../types/ScreenNotice'
15 +import type { Snippet } from '../../../../types/Snippet'
12 16
13 17 interface EditorTextareaProps {
14 18 textareaRef: RefObject<HTMLTextAreaElement>
15 19 snippetCodeId: string
@@ -50,10 +54,48 @@
50 54 isExpanded: boolean
51 55 setIsExpanded: Dispatch<SetStateAction<boolean>>
52 56 }
53 57
58 +/**
59 + * Keep the editor's contents in step with the snippet being edited.
60 + *
61 + * Code pasted from a chat window or a file usually arrives wrapped in the tags
62 + * for its language. Those are removed here rather than silently on save, so the
63 + * editor shows what will actually be stored and does not flag an error for
64 + * markup we were going to strip anyway.
65 + */
66 +const handleEditorChanges = (
67 + instance: CodeMirror.Editor,
68 + changes: readonly CodeMirror.EditorChange[],
69 + setSnippet: Dispatch<SetStateAction<Snippet>>,
70 + setCurrentNotice: Dispatch<SetStateAction<ScreenNotice | undefined>>
71 +) => {
72 + const pasted = changes.some(change => 'paste' === change.origin)
73 +
74 + setSnippet(previous => {
75 + const value = instance.getValue()
76 +
77 + if (!pasted) {
78 + return { ...previous, code: value }
79 + }
80 +
81 + const { code, removed } = stripWrapperTags(value, getSnippetType(previous))
82 +
83 + if (removed) {
84 + instance.setValue(code)
85 + setCurrentNotice(['updated', sprintf(
86 + /* translators: %s: markup that was removed, such as "opening PHP tag". */
87 + __('Removed the %s from the pasted code. Snippets do not need them.', 'code-snippets'),
88 + removed
89 + )])
90 + }
91 +
92 + return { ...previous, code }
93 + })
94 +}
95 +
54 96 export const CodeEditor: React.FC<CodeEditorProps> = ({ isExpanded, setIsExpanded }) => {
55 - const { snippet, setSnippet, codeEditorInstance, setCodeEditorInstance } = useSnippetForm()
97 + const { snippet, setSnippet, codeEditorInstance, setCodeEditorInstance, setCurrentNotice } = useSnippetForm()
56 98 const { submitSnippet } = useSubmitSnippet()
57 99 const textareaRef = useRef<HTMLTextAreaElement>(null)
58 100 const snippetCodeId = useId()
59 101
@@ -61,16 +103,19 @@
61 103 setCodeEditorInstance(editorInstance => {
62 104 if (textareaRef.current && !editorInstance && window.wp.codeEditor) {
63 105 editorInstance = window.wp.codeEditor.initialize(textareaRef.current)
64 106
65 - editorInstance.codemirror.on('changes', instance => {
66 - setSnippet(previous => ({ ...previous, code: instance.getValue() }))
67 - })
107 + // CodeMirror hides the labelled textarea and types into an unlabelled one
108 + // of its own, so the name has to be put on that input directly.
109 + editorInstance.codemirror.getInputField().setAttribute('aria-label', __('Snippet code', 'code-snippets'))
110 +
111 + editorInstance.codemirror.on('changes', (instance, changes) =>
112 + handleEditorChanges(instance, changes, setSnippet, setCurrentNotice))
68 113 }
69 114
70 115 return editorInstance
71 116 })
72 - }, [setCodeEditorInstance, textareaRef, setSnippet])
117 + }, [setCodeEditorInstance, textareaRef, setSnippet, setCurrentNotice])
73 118
74 119 useEffect(() => {
75 120 if (codeEditorInstance) {
76 121 const extraKeys = codeEditorInstance.codemirror.getOption('extraKeys') ?? {}