← All changes
|
js/components/EditMenu/SnippetForm/SnippetForm.tsx
+59
-5
4.0.0-beta.1
→
trunk
View file →
| @@ -1,5 +1,5 @@ | ||
| 1 | -import React, { useState } from 'react' | |
| 1 | +import React, { useEffect, useRef, useState } from 'react' | |
| 2 | 2 | import classnames from 'classnames' |
| 3 | 3 | import { __ } from '@wordpress/i18n' |
| 4 | 4 | import { WithRestAPIContext } from '../../../hooks/useRestAPI' |
| 5 | 5 | import { WithSnippetsAPIContext } from '../../../hooks/useSnippetsAPI' |
| @@ -8,8 +8,9 @@ | ||
| 8 | 8 | import { handleUnknownError } from '../../../utils/errors' |
| 9 | 9 | import { createSnippetObject, getSnippetType, isCondition, validateSnippet } from '../../../utils/snippets/snippets' |
| 10 | 10 | import { buildUrl } from '../../../utils/urls' |
| 11 | 11 | import { ConfirmDialog } from '../../common/ConfirmDialog' |
| 12 | +import { ScreenMetaSlot } from '../../common/ScreenMetaSlot' | |
| 12 | 13 | import { Toolbar } from '../../common/Toolbar' |
| 13 | 14 | import { UpsellBanner } from '../../common/UpsellBanner' |
| 14 | 15 | import { UpsellDialog } from '../../common/UpsellDialog' |
| 15 | 16 | import { EditorSidebar } from '../EditorSidebar' |
| @@ -95,9 +96,9 @@ | ||
| 95 | 96 | .replace(__('Create New Snippet', 'code-snippets'), __('Edit Snippet', 'code-snippets')) |
| 96 | 97 | .replace(__('Create New Condition', 'code-snippets'), __('Edit Condition', 'code-snippets')) |
| 97 | 98 | |
| 98 | 99 | const newUrl = buildUrl(window.CODE_SNIPPETS.urls.edit, { id: response.id }) |
| 99 | - window.history.pushState({}, document.title, newUrl) | |
| 100 | + window.history.replaceState({}, document.title, newUrl) | |
| 100 | 101 | } |
| 101 | 102 | } |
| 102 | 103 | }) |
| 103 | 104 | .then(refreshSnippetsList) |
| @@ -125,9 +126,11 @@ | ||
| 125 | 126 | <form id="snippet-form" method="post" onSubmit={handleSubmit} className={className}> |
| 126 | 127 | {children} |
| 127 | 128 | </form> |
| 128 | 129 | |
| 129 | - <ConfirmSubmitDialog {...{ doSubmit, submitAction, setSubmitAction, validationWarning, setValidationWarning }} /> | |
| 130 | + <ConfirmSubmitDialog | |
| 131 | + {...{ doSubmit, submitAction, setSubmitAction, validationWarning, setValidationWarning }} | |
| 132 | + /> | |
| 130 | 133 | </> |
| 131 | 134 | ) |
| 132 | 135 | } |
| 133 | 136 | |
| @@ -140,13 +143,61 @@ | ||
| 140 | 143 | </div> |
| 141 | 144 | : null |
| 142 | 145 | } |
| 143 | 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 | + | |
| 144 | 193 | const EditFormWrap: React.FC = () => { |
| 145 | - const { snippet, isReadOnly } = useSnippetForm() | |
| 194 | + const { snippet, isReadOnly, isDirty } = useSnippetForm() | |
| 146 | 195 | const [isExpanded, setIsExpanded] = useState(false) |
| 147 | 196 | const [isUpgradeDialogOpen, setIsUpgradeDialogOpen] = useState(false) |
| 148 | 197 | |
| 198 | + useReloadOnPopState(isDirty) | |
| 199 | + | |
| 149 | 200 | return ( |
| 150 | 201 | <> |
| 151 | 202 | <PageHeading /> |
| 152 | 203 | <Notices placement="above-form" /> |
| @@ -179,10 +230,13 @@ | ||
| 179 | 230 | export const SnippetForm: React.FC = () => |
| 180 | 231 | <WithRestAPIContext> |
| 181 | 232 | <WithSnippetsAPIContext> |
| 182 | 233 | <WithSnippetsListContext> |
| 183 | - <WithSnippetFormContext initialSnippet={() => createSnippetObject(window.CODE_SNIPPETS_EDIT?.snippet)}> | |
| 234 | + <WithSnippetFormContext | |
| 235 | + initialSnippet={() => createSnippetObject(window.CODE_SNIPPETS_EDIT?.snippet)} | |
| 236 | + > | |
| 184 | 237 | <Toolbar /> |
| 238 | + <ScreenMetaSlot /> | |
| 185 | 239 | <EditFormWrap /> |
| 186 | 240 | </WithSnippetFormContext> |
| 187 | 241 | </WithSnippetsListContext> |
| 188 | 242 | </WithSnippetsAPIContext> |