PluginProbe
Code Snippets / 3.10.2
Code Snippets v3.10.2
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
code-snippets / js / components / EditMenu / SnippetForm / SnippetForm.tsx

SnippetForm.tsx in Code Snippets 3.10.2, at js/components/EditMenu/SnippetForm/SnippetForm.tsx

242 lines 7.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import React, { useEffect, useRef, useState } from 'react'
2 import classnames from 'classnames'
3 import { __ } from '@wordpress/i18n'
4 import { WithRestAPIContext } from '../../../hooks/useRestAPI'
5 import { WithSnippetsAPIContext } from '../../../hooks/useSnippetsAPI'
6 import { WithSnippetsListContext, useSnippetsList } from '../../../hooks/useSnippetsList'
7 import { SubmitSnippetAction, useSubmitSnippet } from '../../../hooks/useSubmitSnippet'
8 import { handleUnknownError } from '../../../utils/errors'
9 import { createSnippetObject, getSnippetType, isCondition, validateSnippet } from '../../../utils/snippets/snippets'
10 import { buildUrl } from '../../../utils/urls'
11 import { ConfirmDialog } from '../../common/ConfirmDialog'
12 import { Toolbar } from '../../common/Toolbar'
13 import { UpsellBanner } from '../../common/UpsellBanner'
14 import { UpsellDialog } from '../../common/UpsellDialog'
15 import { EditorSidebar } from '../EditorSidebar'
16 import { WithSnippetFormContext, useSnippetForm } from './WithSnippetFormContext'
17 import { SnippetTypeInput } from './fields/SnippetTypeInput'
18 import { TagsEditor } from './fields/TagsEditor'
19 import { CodeEditor } from './fields/CodeEditor'
20 import { DescriptionEditor } from './fields/DescriptionEditor'
21 import { NameInput } from './fields/NameInput'
22 import { Notices } from './page/Notices'
23 import { PageHeading } from './page/PageHeading'
24 import type { PropsWithChildren } from 'react'
25 import type { Snippet } from '../../../types/Snippet'
26
27 const editFormClassName = ({ snippet, isReadOnly, isExpanded }: {
28 snippet: Snippet,
29 isReadOnly: boolean,
30 isExpanded: boolean
31 }) =>
32 classnames(
33 'snippet-form',
34 isExpanded ? 'snippet-form-expanded' : 'snippet-form-collapsed',
35 `${snippet.scope}-snippet`,
36 `${getSnippetType(snippet)}-snippet`,
37 `${snippet.id ? 'saved' : 'new'}-snippet`,
38 `${snippet.active ? 'active' : 'inactive'}-snippet`,
39 {
40 'erroneous-snippet': !!snippet.code_error,
41 'read-only-snippet': isReadOnly
42 }
43 )
44
45 interface ConfirmSubmitDialogProps {
46 doSubmit: (action: SubmitSnippetAction | undefined) => void
47 submitAction: SubmitSnippetAction | undefined
48 setSubmitAction: (action: SubmitSnippetAction | undefined) => void
49 validationWarning: string | undefined
50 setValidationWarning: (warning: string | undefined) => void
51 }
52
53 const ConfirmSubmitDialog: React.FC<ConfirmSubmitDialogProps> = ({
54 doSubmit,
55 submitAction,
56 setSubmitAction,
57 validationWarning,
58 setValidationWarning
59 }) =>
60 <ConfirmDialog
61 open={validationWarning !== undefined}
62 title={__('Snippet incomplete', 'code-snippets')}
63 confirmLabel={__('Continue', 'code-snippets')}
64 onCancel={() => {
65 setSubmitAction(undefined)
66 setValidationWarning(undefined)
67 }}
68 onConfirm={() => {
69 doSubmit(submitAction)
70 setSubmitAction(undefined)
71 setValidationWarning(undefined)
72 }}
73 >
74 <p>{`${validationWarning} ${__('Continue?', 'code-snippets')}`}</p>
75 </ConfirmDialog>
76
77 interface EditFormProps extends PropsWithChildren {
78 className?: string
79 }
80
81 const EditForm: React.FC<EditFormProps> = ({ children, className }) => {
82 const { submitSnippet } = useSubmitSnippet()
83 const { snippet } = useSnippetForm()
84 const { refreshSnippetsList } = useSnippetsList()
85
86 const [validationWarning, setValidationWarning] = useState<string | undefined>()
87 const [submitAction, setSubmitAction] = useState<SubmitSnippetAction | undefined>()
88
89 const doSubmit = (action?: SubmitSnippetAction) => {
90 submitSnippet(snippet, action)
91 .then(response => {
92 if (response && 0 !== response.id && window.CODE_SNIPPETS) {
93 if (window.location.href.includes(window.CODE_SNIPPETS.urls.addNew)) {
94 document.title = document.title
95 .replace(__('Create New Snippet', 'code-snippets'), __('Edit Snippet', 'code-snippets'))
96 .replace(__('Create New Condition', 'code-snippets'), __('Edit Condition', 'code-snippets'))
97
98 const newUrl = buildUrl(window.CODE_SNIPPETS.urls.edit, { id: response.id })
99 window.history.replaceState({}, document.title, newUrl)
100 }
101 }
102 })
103 .then(refreshSnippetsList)
104 .catch(handleUnknownError)
105 }
106
107 const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
108 event.preventDefault()
109
110 const action = Object.values(SubmitSnippetAction).find(actionName =>
111 actionName === document.activeElement?.getAttribute('name'))
112
113 const validationWarning = validateSnippet(snippet)
114
115 if (validationWarning) {
116 setValidationWarning(validationWarning)
117 setSubmitAction(action)
118 } else {
119 doSubmit(action)
120 }
121 }
122
123 return (
124 <>
125 <form id="snippet-form" method="post" onSubmit={handleSubmit} className={className}>
126 {children}
127 </form>
128
129 <ConfirmSubmitDialog
130 {...{ doSubmit, submitAction, setSubmitAction, validationWarning, setValidationWarning }}
131 />
132 </>
133 )
134 }
135
136 const ConditionsEditor: React.FC = () => {
137 const { snippet } = useSnippetForm()
138
139 return isCondition(snippet)
140 ? <div id="snippet_conditions" className="snippet-condition-editor-container">
141 <p>{__('This snippet type is not supported in this version of Code Snippets.')}</p>
142 </div>
143 : null
144 }
145
146 const useReloadOnPopState = (isDirty: boolean) => {
147 const currentUrl = useRef(window.location.href)
148 const skipNextUnloadPrompt = useRef(false)
149
150 useEffect(() => {
151 currentUrl.current = window.location.href
152 })
153
154 useEffect(() => {
155 const handleBeforeUnload = (event: BeforeUnloadEvent) => {
156 if (skipNextUnloadPrompt.current) {
157 skipNextUnloadPrompt.current = false
158 return
159 }
160
161 event.preventDefault()
162 // Required by Chrome and Edge versions before 119.
163 // eslint-disable-next-line @typescript-eslint/no-deprecated
164 event.returnValue = true
165 }
166
167 if (isDirty) {
168 window.addEventListener('beforeunload', handleBeforeUnload)
169 }
170
171 return () => window.removeEventListener('beforeunload', handleBeforeUnload)
172 }, [isDirty])
173
174 useEffect(() => {
175 const handlePopState = () => {
176 if (isDirty && !window.confirm(
177 __('You have unsaved changes. Leave this page and discard them?', 'code-snippets')
178 )) {
179 window.history.pushState({}, document.title, currentUrl.current)
180 return
181 }
182
183 skipNextUnloadPrompt.current = isDirty
184 window.location.reload()
185 }
186
187 window.addEventListener('popstate', handlePopState)
188 return () => window.removeEventListener('popstate', handlePopState)
189 }, [isDirty])
190 }
191
192 const EditFormWrap: React.FC = () => {
193 const { snippet, isReadOnly, isDirty } = useSnippetForm()
194 const [isExpanded, setIsExpanded] = useState(false)
195 const [isUpgradeDialogOpen, setIsUpgradeDialogOpen] = useState(false)
196
197 useReloadOnPopState(isDirty)
198
199 return (
200 <>
201 <PageHeading />
202 <Notices placement="above-form" />
203
204 <EditForm className={editFormClassName({ snippet, isReadOnly, isExpanded })}>
205 <div className="snippet-form-upper">
206 <div className="snippet-name-wrapper">
207 <NameInput />
208 <SnippetTypeInput setIsUpgradeDialogOpen={setIsUpgradeDialogOpen} />
209 </div>
210
211 <CodeEditor {...{ isExpanded, setIsExpanded }} />
212 <ConditionsEditor />
213 </div>
214
215 <div className="snippet-form-lower">
216 <UpsellBanner />
217 <DescriptionEditor />
218 <TagsEditor />
219 </div>
220
221 <EditorSidebar setIsUpgradeDialogOpen={setIsUpgradeDialogOpen} />
222 </EditForm>
223
224 <UpsellDialog isOpen={isUpgradeDialogOpen} setIsOpen={setIsUpgradeDialogOpen} />
225 </>
226 )
227 }
228
229 export const SnippetForm: React.FC = () =>
230 <WithRestAPIContext>
231 <WithSnippetsAPIContext>
232 <WithSnippetsListContext>
233 <WithSnippetFormContext
234 initialSnippet={() => createSnippetObject(window.CODE_SNIPPETS_EDIT?.snippet)}
235 >
236 <Toolbar />
237 <EditFormWrap />
238 </WithSnippetFormContext>
239 </WithSnippetsListContext>
240 </WithSnippetsAPIContext>
241 </WithRestAPIContext>
242