| 1 |
/** |
| 2 |
* JavaScript code for the "Edit" section integration of the "Header Bar". |
| 3 |
* |
| 4 |
* @package TablePress |
| 5 |
* @subpackage Views JavaScript |
| 6 |
* @author Tobias Bäthge |
| 7 |
* @since 3.3.0 |
| 8 |
*/ |
| 9 |
|
| 10 |
/** |
| 11 |
* WordPress dependencies. |
| 12 |
*/ |
| 13 |
import { useCallback, useEffect, useRef, useState } from 'react'; |
| 14 |
import { |
| 15 |
Button, |
| 16 |
DropdownMenu, |
| 17 |
__experimentalHStack as HStack, // eslint-disable-line @wordpress/no-unsafe-wp-apis |
| 18 |
Icon, |
| 19 |
KeyboardShortcuts, |
| 20 |
MenuGroup, |
| 21 |
MenuItem, |
| 22 |
Modal, |
| 23 |
Spinner, |
| 24 |
Tooltip, |
| 25 |
__experimentalVStack as VStack, // eslint-disable-line @wordpress/no-unsafe-wp-apis |
| 26 |
withNotices, |
| 27 |
} from '@wordpress/components'; |
| 28 |
import { useCopyToClipboard } from '@wordpress/compose'; |
| 29 |
import { useDispatch } from '@wordpress/data'; |
| 30 |
import { applyFilters } from '@wordpress/hooks'; |
| 31 |
import { __, _n, sprintf } from '@wordpress/i18n'; |
| 32 |
import { arrowUp, blockTable, copy, download, info, moreVertical, pencil, settings, shortcode, siteLogo, trash } from '@wordpress/icons'; |
| 33 |
import { displayShortcut, shortcutAriaLabel } from '@wordpress/keycodes'; |
| 34 |
import { store as noticesStore } from '@wordpress/notices'; |
| 35 |
|
| 36 |
/** |
| 37 |
* Internal dependencies. |
| 38 |
*/ |
| 39 |
import { initializeReactComponentInPortal } from '../common/react-loader'; |
| 40 |
import processAjaxRequest from '../common/ajax-request'; |
| 41 |
import { Notifications } from '../common/notifications'; |
| 42 |
import { TablePressIcon, TablePressIconSimple } from '../../img/tablepress-icon'; |
| 43 |
|
| 44 |
/** |
| 45 |
* Saves the table changes to the server. |
| 46 |
* |
| 47 |
* @param {Object} props Function parameters. |
| 48 |
* @param {Object} props.screenData Screen data. |
| 49 |
* @param {Function} props.updateScreenData Callback to update the screen data. |
| 50 |
* @param {Object} props.tableOptions Table options. |
| 51 |
* @param {Object} props.tableMeta Table meta data. |
| 52 |
* @param {Function} props.updateTableMeta Callback to update the table meta. |
| 53 |
* @param {Function} props.noticeOperations Callbacks for working with notices. |
| 54 |
* @param {Object} props.noticesStoreDispatch Dispatch function for the notices store. (Optional, only for notices created by keyboard shortcuts.) |
| 55 |
*/ |
| 56 |
const saveTableChanges = ( { screenData, updateScreenData, tableOptions, tableMeta, updateTableMeta, noticeOperations, noticesStoreDispatch } ) => { |
| 57 |
// Validate input fields. |
| 58 |
if ( ! applyFilters( 'tablepress.optionsValidateFields', true, tableOptions ) ) { |
| 59 |
return; |
| 60 |
} |
| 61 |
|
| 62 |
// Collect information about hidden rows and columns. |
| 63 |
tp.helpers.visibility.update(); |
| 64 |
|
| 65 |
// Prepare the data for the AJAX request. |
| 66 |
const requestData = { |
| 67 |
action: 'tablepress_save_table', |
| 68 |
_ajax_nonce: tp.nonces.edit_table, |
| 69 |
tablepress: { |
| 70 |
id: tp.table.id, |
| 71 |
new_id: tableMeta.id, |
| 72 |
name: tableMeta.name, |
| 73 |
description: tableMeta.description, |
| 74 |
data: JSON.stringify( tp.editor.options.data ), |
| 75 |
options: JSON.stringify( tableOptions ), |
| 76 |
visibility: JSON.stringify( tp.table.visibility ), |
| 77 |
number: { |
| 78 |
rows: tp.editor.options.data.length, |
| 79 |
columns: tp.editor.options.columns.length, |
| 80 |
}, |
| 81 |
}, |
| 82 |
}; |
| 83 |
|
| 84 |
/** |
| 85 |
* Callback for handling specifics of a successful save on this screen. |
| 86 |
* |
| 87 |
* @param {Object} data |
| 88 |
*/ |
| 89 |
const onSuccessfulRequest = ( data ) => { |
| 90 |
// Saving was successful, so the original ID has changed to the (maybe) new ID -> we need to adjust all occurrences. |
| 91 |
if ( tp.table.id !== data.table_id && window?.history?.pushState ) { |
| 92 |
// Update URL, but only if the table ID changed, to not get dummy entries in the browser history. |
| 93 |
window.history.pushState( '', '', window.location.href.replace( /table_id=[a-zA-Z0-9_-]+/gi, `table_id=${ data.table_id }` ) ); |
| 94 |
} |
| 95 |
|
| 96 |
tp.table.id = data.table_id; |
| 97 |
|
| 98 |
updateTableMeta( { |
| 99 |
id: data.table_id, |
| 100 |
lastModified: data.last_modified, |
| 101 |
lastEditor: data.last_editor, |
| 102 |
} ); |
| 103 |
|
| 104 |
// Update the nonces. |
| 105 |
[ 'copy', 'delete', 'edit', 'preview' ].forEach( action => { |
| 106 |
tp.nonces[ `${action}_table` ] = data[ `new_${action}_nonce` ]; |
| 107 |
} ); |
| 108 |
|
| 109 |
// Update the "Export", "Copy", "Delete", and "Preview" URLs in the screen data. |
| 110 |
const updatedUrls = {}; |
| 111 |
updatedUrls.exportUrl = screenData.exportUrl.replace( /table_id=[a-zA-Z0-9_-]+/g, `table_id=${ data.table_id }` ); |
| 112 |
[ 'copy', 'delete', 'preview' ].forEach( ( action ) => { |
| 113 |
updatedUrls[ `${ action }Url` ] = screenData[ `${ action }Url` ] |
| 114 |
.replace( /item=[a-zA-Z0-9_-]+/g, `item=${ data.table_id }` ) // Updates both the "item" and the "return_item" parameters. |
| 115 |
.replace( /&_wpnonce=[a-zA-Z0-9]+/g, `&_wpnonce=${ tp.nonces[ `${ action }_table` ] }` ); |
| 116 |
} ); |
| 117 |
updateScreenData( updatedUrls ); |
| 118 |
|
| 119 |
tp.helpers.unsaved_changes.unset(); |
| 120 |
|
| 121 |
const actionMessages = {}; |
| 122 |
actionMessages.success_save = __( 'The table was saved successfully.', 'tablepress' ); |
| 123 |
actionMessages.success_save_success_id_change = actionMessages.success_save + ' ' + __( 'The table ID was changed.', 'tablepress' ); |
| 124 |
actionMessages.success_save_error_id_change = actionMessages.success_save + ' ' + __( 'The table ID could not be changed, probably because the new ID is already in use!', 'tablepress' ); |
| 125 |
|
| 126 |
if ( 'success_save_error_id_change' === data.message && data.error_details ) { |
| 127 |
const errorIntroduction = __( 'These errors were encountered:', 'tablepress' ); |
| 128 |
actionMessages.success_save_error_id_change = `<p>${ actionMessages.success_save_error_id_change }</p><p>${ errorIntroduction }</p><pre>${ data.error_details }</pre><p>`; |
| 129 |
} |
| 130 |
|
| 131 |
const notice = { |
| 132 |
status: ( data.message.includes( 'error' ) ) ? 'error' : 'success', |
| 133 |
content: actionMessages[ data.message ], |
| 134 |
type: ( data.message.includes( 'error' ) ) ? 'notice' : 'snackbar', |
| 135 |
}; |
| 136 |
|
| 137 |
return { notice }; |
| 138 |
}; |
| 139 |
|
| 140 |
const setBusyState = ( isBusy ) => updateScreenData( { isSaving: isBusy } ); |
| 141 |
|
| 142 |
processAjaxRequest( { requestData, onSuccessfulRequest, setBusyState, noticeOperations, noticesStoreDispatch } ); |
| 143 |
}; |
| 144 |
|
| 145 |
/** |
| 146 |
* Shows a preview of the table. |
| 147 |
* |
| 148 |
* @param {Object} props Function parameters. |
| 149 |
* @param {Function} props.updateScreenData Callback to update the screen data. |
| 150 |
* @param {Object} props.tableOptions Table options. |
| 151 |
* @param {Object} props.tableMeta Table meta data. |
| 152 |
* @param {Function} props.noticeOperations Callbacks for working with notices. |
| 153 |
*/ |
| 154 |
const showPreview = ( { updateScreenData, tableOptions, tableMeta, noticeOperations } ) => { |
| 155 |
// For tables without unsaved changes, directly show an externally rendered table from a URL in an iframe in a Modal. |
| 156 |
if ( ! tp.made_changes ) { |
| 157 |
updateScreenData( { |
| 158 |
previewIsOpen: true, |
| 159 |
previewSrcDoc: '', |
| 160 |
} ); |
| 161 |
return; |
| 162 |
} |
| 163 |
|
| 164 |
/* For tables with unsaved changes, get the table preview HTML code for the iframe via AJAX. */ |
| 165 |
|
| 166 |
// Update information about hidden rows and columns. |
| 167 |
tp.helpers.visibility.update(); |
| 168 |
|
| 169 |
// Prepare the data for the AJAX request. |
| 170 |
const requestData = { |
| 171 |
action: 'tablepress_preview_table', |
| 172 |
_ajax_nonce: tp.nonces.preview_table, |
| 173 |
tablepress: { |
| 174 |
id: tp.table.id, |
| 175 |
new_id: tableMeta.id, |
| 176 |
name: tableMeta.name, |
| 177 |
description: tableMeta.description, |
| 178 |
data: JSON.stringify( tp.editor.options.data ), |
| 179 |
options: JSON.stringify( tableOptions ), |
| 180 |
visibility: JSON.stringify( tp.table.visibility ), |
| 181 |
number: { |
| 182 |
rows: tp.editor.options.data.length, |
| 183 |
columns: tp.editor.options.columns.length, |
| 184 |
}, |
| 185 |
}, |
| 186 |
}; |
| 187 |
|
| 188 |
/** |
| 189 |
* Callback for handling specifics of a successful save on this screen. |
| 190 |
* |
| 191 |
* @param {Object} data |
| 192 |
*/ |
| 193 |
const onSuccessfulRequest = ( data ) => { |
| 194 |
updateScreenData( { |
| 195 |
previewIsOpen: true, |
| 196 |
previewSrcDoc: `<!DOCTYPE html><html><head>${ data.head_html }</head><body>${ data.body_html }</body></html>`, |
| 197 |
} ); |
| 198 |
|
| 199 |
return { notice: null }; |
| 200 |
}; |
| 201 |
|
| 202 |
const setBusyState = ( isBusy ) => updateScreenData( { previewIsLoading: isBusy } ); |
| 203 |
|
| 204 |
processAjaxRequest( { requestData, onSuccessfulRequest, setBusyState, noticeOperations } ); |
| 205 |
}; |
| 206 |
|
| 207 |
/** |
| 208 |
* Returns the Confirm Delete Modal component's JSX markup. |
| 209 |
* |
| 210 |
* @param {Object} props Component props. |
| 211 |
* @param {string} props.title Title of the modal. |
| 212 |
* @param {string} props.deleteUrl URL to delete the table. |
| 213 |
* @param {Function} props.closeConfirmDeleteModal Callback to close the Confirm Delete modal. |
| 214 |
* @return {Object} Confirm Delete Modal component. |
| 215 |
*/ |
| 216 |
const ConfirmDeleteModal = ( { title, deleteUrl, closeConfirmDeleteModal } ) => { |
| 217 |
const cancelButtonRef = useRef(); |
| 218 |
const deleteButtonRef = useRef(); |
| 219 |
|
| 220 |
const handleEnter = useCallback( |
| 221 |
( event ) => { |
| 222 |
// Avoid triggering the action when a button is focused, as this can cause a double submission. |
| 223 |
const isCancelOrDeleteButton = |
| 224 |
event.target === cancelButtonRef.current || |
| 225 |
event.target === deleteButtonRef.current; |
| 226 |
|
| 227 |
if ( ! isCancelOrDeleteButton && 'Enter' === event.key ) { |
| 228 |
deleteButtonRef.current.click(); |
| 229 |
} |
| 230 |
}, |
| 231 |
[], |
| 232 |
); |
| 233 |
|
| 234 |
return ( |
| 235 |
<Modal |
| 236 |
size="medium" |
| 237 |
icon={ <Icon icon={ TablePressIcon } size="36" style={ { display: 'flex', marginRight: '1rem' } } /> } |
| 238 |
title={ title } |
| 239 |
isDismissible={ false } |
| 240 |
onKeyDown={ handleEnter } |
| 241 |
onRequestClose={ closeConfirmDeleteModal } |
| 242 |
> |
| 243 |
<VStack spacing={ 8 }> |
| 244 |
<span> |
| 245 |
{ _n( 'Do you really want to delete this table?', 'Do you really want to delete these tables?', 1, 'tablepress' ) } |
| 246 |
<br /> |
| 247 |
{ __( 'Deleting a table is permanent and can not be undone!', 'tablepress' ) } |
| 248 |
</span> |
| 249 |
<HStack alignment="right"> |
| 250 |
<Button |
| 251 |
ref={ cancelButtonRef } |
| 252 |
variant="tertiary" |
| 253 |
text={ __( 'Cancel', 'tablepress' ) } |
| 254 |
onClick={ closeConfirmDeleteModal } |
| 255 |
/> |
| 256 |
<Button |
| 257 |
ref={ deleteButtonRef } |
| 258 |
variant="primary" |
| 259 |
isDestructive={ true } |
| 260 |
href={ deleteUrl } |
| 261 |
text={ __( 'Delete', 'tablepress' ) } |
| 262 |
onClick={ () => { |
| 263 |
// Prevent onunload warning, by calling the unset method. |
| 264 |
tp.helpers.unsaved_changes.unset(); |
| 265 |
closeConfirmDeleteModal(); |
| 266 |
} } |
| 267 |
/> |
| 268 |
</HStack> |
| 269 |
</VStack> |
| 270 |
</Modal> |
| 271 |
); |
| 272 |
}; |
| 273 |
|
| 274 |
const HeaderBar = ( { noticeOperations, noticeUI, screenData, updateScreenData, tableOptions, tableMeta, updateTableMeta, features } ) => { |
| 275 |
const noticesStoreDispatch = useDispatch( noticesStore ); |
| 276 |
const { createSuccessNotice } = noticesStoreDispatch; |
| 277 |
const [ confirmDeleteModalIsOpen, setConfirmDeleteModalIsOpen ] = useState( false ); |
| 278 |
|
| 279 |
// Copy shortcode to clipboard. |
| 280 |
const shortcodeText = `[${ tp.table.shortcode } id=${ tableMeta.id } /]`; |
| 281 |
const copyShortcodeButtonRef = useCopyToClipboard( shortcodeText, () => { |
| 282 |
createSuccessNotice( |
| 283 |
__( 'Copied Shortcode to clipboard.', 'tablepress' ), |
| 284 |
{ |
| 285 |
type: 'snackbar', |
| 286 |
icon: <Icon icon={ TablePressIconSimple } />, |
| 287 |
} |
| 288 |
); |
| 289 |
} ); |
| 290 |
|
| 291 |
const scrollToTop = () => window.scrollTo( { top: 0, behavior: 'smooth' } ); |
| 292 |
|
| 293 |
const featureModules = Object.keys( tp?.modules ?? {} ) |
| 294 |
.filter( ( module ) => features.includes( module ) ) |
| 295 |
.map( ( module ) => ( { |
| 296 |
module, |
| 297 |
name: tp.modules[ module ].name, |
| 298 |
} ) ) |
| 299 |
.sort( ( a, b ) => a.name.localeCompare( b.name ) ); |
| 300 |
|
| 301 |
return ( |
| 302 |
<VStack> |
| 303 |
<HStack style={ { maxWidth: '1000px' } }> |
| 304 |
<HStack alignment="left" spacing={ 3 } expanded={ false }> |
| 305 |
{ /* Left side: Quick Navigation, Table ID, Table name */ } |
| 306 |
<DropdownMenu |
| 307 |
icon={ TablePressIconSimple } |
| 308 |
label={ __( 'Quick navigation', 'tablepress' ) } |
| 309 |
open={ screenData.quickNavigationDropdownIsOpen } |
| 310 |
onToggle={ ( newQuickNavigationDropdownIsOpen ) => updateScreenData( { quickNavigationDropdownIsOpen: newQuickNavigationDropdownIsOpen } ) } |
| 311 |
toggleProps={ { |
| 312 |
onDoubleClick: scrollToTop, /* Scroll to Top on Double-click. (Double-click will also close the dropdown.) */ |
| 313 |
shortcut: { |
| 314 |
ariaLabel: shortcutAriaLabel.primary( 'j' ), |
| 315 |
display: displayShortcut.primary( 'j' ), |
| 316 |
}, |
| 317 |
} } |
| 318 |
> |
| 319 |
{ ( { onClose: closeMenu } ) => ( |
| 320 |
<> |
| 321 |
<MenuGroup> |
| 322 |
<MenuItem |
| 323 |
icon={ arrowUp } |
| 324 |
onClick={ () => { |
| 325 |
scrollToTop(); |
| 326 |
closeMenu(); |
| 327 |
} } |
| 328 |
> |
| 329 |
{ __( 'Scroll to Top', 'tablepress' ) } |
| 330 |
</MenuItem> |
| 331 |
</MenuGroup> |
| 332 |
<MenuGroup label={ featureModules.length > 0 && __( 'Common', 'tablepress' ) }> |
| 333 |
{ |
| 334 |
[ |
| 335 |
{ section: 'table-information', label: __( 'Table Information', 'tablepress' ), icon: info }, |
| 336 |
{ section: 'table-data', label: __( 'Table Content', 'tablepress' ), icon: blockTable }, |
| 337 |
{ section: 'table-manipulation', label: __( 'Table Manipulation', 'tablepress' ), icon: pencil }, |
| 338 |
{ section: 'table-options', label: __( 'Table Options', 'tablepress' ), icon: settings }, |
| 339 |
{ section: 'datatables-features', label: __( 'Table Features for Site Visitors', 'tablepress' ), icon: siteLogo }, |
| 340 |
].map( ( { section, label, icon } ) => ( |
| 341 |
<MenuItem |
| 342 |
key={ section } |
| 343 |
icon={ icon } |
| 344 |
onClick={ () => { |
| 345 |
document.getElementById( `tablepress_edit-${section}` ).scrollIntoView( { behavior: 'smooth', block: 'start' } ); |
| 346 |
closeMenu(); |
| 347 |
} } |
| 348 |
> |
| 349 |
{ label } |
| 350 |
</MenuItem> |
| 351 |
) ) |
| 352 |
} |
| 353 |
</MenuGroup> |
| 354 |
{ featureModules.length > 0 && ( |
| 355 |
<MenuGroup label={ __( 'Feature Modules', 'tablepress' ) }> |
| 356 |
{ featureModules.map( ( { module } ) => ( |
| 357 |
<MenuItem |
| 358 |
key={ module } |
| 359 |
onClick={ () => { |
| 360 |
document.getElementById( `tablepress_edit-${module}` ).scrollIntoView( { behavior: 'smooth', block: 'start' } ); |
| 361 |
closeMenu(); |
| 362 |
} } |
| 363 |
> |
| 364 |
{ tp.modules[ module ].name } |
| 365 |
</MenuItem> |
| 366 |
) ) } |
| 367 |
</MenuGroup> |
| 368 |
) } |
| 369 |
</> |
| 370 |
) } |
| 371 |
</DropdownMenu> |
| 372 |
<span |
| 373 |
className="tablepress-header-table-id" |
| 374 |
style={ { flexShrink: 0 } }> |
| 375 |
{ sprintf( __( 'ID: %s', 'tablepress' ), tableMeta.id ) } |
| 376 |
</span> |
| 377 |
<Tooltip text={ sprintf( __( 'Table Name: %s', 'tablepress' ), tableMeta.name ) }> |
| 378 |
<h2 className="tablepress-header-table-name"> |
| 379 |
{ tableMeta.name } |
| 380 |
</h2> |
| 381 |
</Tooltip> |
| 382 |
</HStack> |
| 383 |
<HStack alignment="right" spacing={ 3 } expanded={ false } style={ { flexShrink: 0 } }> |
| 384 |
{ /* Right side: Spinner, Preview, Save Changes, Dropdown */ } |
| 385 |
{ |
| 386 |
( screenData.isSaving || screenData.previewIsLoading ) && ( |
| 387 |
<Spinner style={ { margin: 0 } } /> |
| 388 |
) |
| 389 |
} |
| 390 |
{ |
| 391 |
tp.screenOptions.currentUserCanPreviewTable && ( |
| 392 |
<Button |
| 393 |
variant="secondary" |
| 394 |
href={ screenData.previewUrl } |
| 395 |
text={ __( 'Preview', 'tablepress' ) } |
| 396 |
shortcut={ screenData.previewIsLoading ? undefined : |
| 397 |
{ |
| 398 |
ariaLabel: shortcutAriaLabel.primary( 'p' ), |
| 399 |
display: displayShortcut.primary( 'p' ), |
| 400 |
} |
| 401 |
} |
| 402 |
isBusy={ screenData.previewIsLoading } |
| 403 |
disabled={ screenData.previewIsLoading } |
| 404 |
accessibleWhenDisabled={ true } |
| 405 |
onClick={ ( event ) => { |
| 406 |
// Open the Preview Modal if the button is clicked, except if an unmodified preview in a new tab is requested. |
| 407 |
if ( tp.made_changes || ! ( event.ctrlKey || event.metaKey || event.shiftKey ) ) { |
| 408 |
event.preventDefault(); |
| 409 |
showPreview( { updateScreenData, tableOptions, tableMeta, noticeOperations } ); |
| 410 |
} |
| 411 |
} } |
| 412 |
/> |
| 413 |
) |
| 414 |
} |
| 415 |
<Button |
| 416 |
variant="primary" |
| 417 |
text={ __( 'Save Changes', 'tablepress' ) } |
| 418 |
shortcut={ screenData.isSaving ? undefined : |
| 419 |
{ |
| 420 |
ariaLabel: shortcutAriaLabel.primary( 's' ), |
| 421 |
display: displayShortcut.primary( 's' ), |
| 422 |
} |
| 423 |
} |
| 424 |
isBusy={ screenData.isSaving } |
| 425 |
disabled={ screenData.isSaving } |
| 426 |
accessibleWhenDisabled={ true } |
| 427 |
onClick={ () => saveTableChanges( { screenData, updateScreenData, tableOptions, tableMeta, updateTableMeta, noticeOperations, noticesStoreDispatch } ) } |
| 428 |
/> |
| 429 |
<DropdownMenu |
| 430 |
icon={ moreVertical } |
| 431 |
label={ __( 'More actions', 'tablepress' ) } |
| 432 |
> |
| 433 |
{ ( { onClose: closeMenu } ) => ( |
| 434 |
<> |
| 435 |
<MenuGroup> |
| 436 |
<MenuItem |
| 437 |
ref={ copyShortcodeButtonRef } |
| 438 |
icon={ shortcode } |
| 439 |
info={ shortcodeText } |
| 440 |
onClick={ () => closeMenu() } |
| 441 |
> |
| 442 |
{ __( 'Copy Shortcode', 'tablepress' ) } |
| 443 |
</MenuItem> |
| 444 |
</MenuGroup> |
| 445 |
{ ( tp.screenOptions.currentUserCanCopyTable || tp.screenOptions.currentUserCanExportTable ) && ( |
| 446 |
<MenuGroup> |
| 447 |
{ tp.screenOptions.currentUserCanCopyTable && ( |
| 448 |
<MenuItem |
| 449 |
icon={ copy } |
| 450 |
href={ screenData.copyUrl } |
| 451 |
> |
| 452 |
{ __( 'Copy Table', 'tablepress' ) } |
| 453 |
</MenuItem> |
| 454 |
) } |
| 455 |
{ tp.screenOptions.currentUserCanExportTable && ( |
| 456 |
<MenuItem |
| 457 |
icon={ download } |
| 458 |
href={ screenData.exportUrl } |
| 459 |
> |
| 460 |
{ __( 'Export Table', 'tablepress' ) } |
| 461 |
</MenuItem> |
| 462 |
) } |
| 463 |
</MenuGroup> |
| 464 |
) } |
| 465 |
{ tp.screenOptions.currentUserCanDeleteTable && ( |
| 466 |
<MenuGroup> |
| 467 |
<MenuItem |
| 468 |
icon={ trash } |
| 469 |
isDestructive={ true } |
| 470 |
href={ screenData.deleteUrl } |
| 471 |
onClick={ ( event ) => { |
| 472 |
setConfirmDeleteModalIsOpen( true ); |
| 473 |
event.preventDefault(); |
| 474 |
closeMenu(); |
| 475 |
} } |
| 476 |
> |
| 477 |
{ __( 'Delete Table', 'tablepress' ) } |
| 478 |
</MenuItem> |
| 479 |
</MenuGroup> |
| 480 |
) } |
| 481 |
</> |
| 482 |
) } |
| 483 |
</DropdownMenu> |
| 484 |
</HStack> |
| 485 |
</HStack> |
| 486 |
{ noticeUI } |
| 487 |
{ confirmDeleteModalIsOpen && ( |
| 488 |
<ConfirmDeleteModal |
| 489 |
title={ sprintf( __( 'Delete “%1$s” (ID %2$s)', 'tablepress' ), tableMeta.name, tableMeta.id ) } |
| 490 |
deleteUrl={ screenData.deleteUrl } |
| 491 |
closeConfirmDeleteModal={ () => setConfirmDeleteModalIsOpen( false ) } |
| 492 |
/> |
| 493 |
) } |
| 494 |
</VStack> |
| 495 |
); |
| 496 |
}; |
| 497 |
|
| 498 |
// A copy of the section to be used for the bottom buttons, with keyboard shortcuts. |
| 499 |
const HeaderBarWithKeyboardShortcutsWithNotices = withNotices( ( props ) => { |
| 500 |
const noticesStoreDispatch = useDispatch( noticesStore ); |
| 501 |
|
| 502 |
const saveChangesCallback = ( event ) => { |
| 503 |
event.preventDefault(); |
| 504 |
// Blur the focussed element to make sure that all `change` and `blur` events were triggered. |
| 505 |
document.activeElement.blur(); // eslint-disable-line @wordpress/no-global-active-element |
| 506 |
// Don't save changes directly, but trigger a save on the next render. |
| 507 |
props.updateScreenData( { triggerSaveChanges: true } ); |
| 508 |
}; |
| 509 |
|
| 510 |
/* |
| 511 |
* Save changes when the `triggerSaveChanges` screen data option is set. |
| 512 |
* This ensures that the save function receives the latest data, after all `change` and `blur` events were triggered. |
| 513 |
*/ |
| 514 |
useEffect( () => { |
| 515 |
if ( props.screenData.triggerSaveChanges ) { |
| 516 |
props.updateScreenData( { triggerSaveChanges: false } ); |
| 517 |
saveTableChanges( { ...props, noticesStoreDispatch } ); |
| 518 |
} |
| 519 |
}, [ props, noticesStoreDispatch ] ); |
| 520 |
|
| 521 |
const openNavigationDropdownCallback = ( event ) => { |
| 522 |
event.preventDefault(); |
| 523 |
props.updateScreenData( { quickNavigationDropdownIsOpen: ! props.screenData.quickNavigationDropdownIsOpen } ); |
| 524 |
}; |
| 525 |
|
| 526 |
const shortcuts = { |
| 527 |
'mod+s': saveChangesCallback, |
| 528 |
'mod+j': openNavigationDropdownCallback, |
| 529 |
}; |
| 530 |
|
| 531 |
if ( tp.screenOptions.currentUserCanPreviewTable ) { |
| 532 |
const showPreviewCallback = ( event ) => { |
| 533 |
event.preventDefault(); |
| 534 |
// Blur the focussed element to make sure that all `change` and `blur` events were triggered. |
| 535 |
document.activeElement.blur(); // eslint-disable-line @wordpress/no-global-active-element |
| 536 |
// Don't load the preview directly, but trigger it on the next render. |
| 537 |
props.updateScreenData( { triggerPreview: true } ); |
| 538 |
}; |
| 539 |
|
| 540 |
/* |
| 541 |
* Trigger the preview when the `triggerPreview` screen data option is set. |
| 542 |
* This ensures that the save function receives the latest data, after all `change` and `blur` events were triggered. |
| 543 |
*/ |
| 544 |
useEffect( () => { |
| 545 |
if ( props.screenData.triggerPreview ) { |
| 546 |
props.updateScreenData( { triggerPreview: false } ); |
| 547 |
showPreview( props ); |
| 548 |
} |
| 549 |
}, [ props ] ); |
| 550 |
|
| 551 |
shortcuts[ 'mod+p' ] = showPreviewCallback; |
| 552 |
} |
| 553 |
|
| 554 |
return ( |
| 555 |
<> |
| 556 |
<HeaderBar { ...props } /> |
| 557 |
<KeyboardShortcuts |
| 558 |
bindGlobal={ true } |
| 559 |
shortcuts={ shortcuts } |
| 560 |
/> |
| 561 |
<Notifications /> |
| 562 |
</> |
| 563 |
); |
| 564 |
} ); |
| 565 |
|
| 566 |
initializeReactComponentInPortal( |
| 567 |
'header-bar', |
| 568 |
'edit', |
| 569 |
HeaderBarWithKeyboardShortcutsWithNotices, |
| 570 |
); |
| 571 |
|