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
code-snippets / js / components / EditMenu / SnippetForm / SnippetForm.tsx

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

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