ajax-request.js 0000644 00000013114 15021264441 0007512 0 ustar 00 /**
* JavaScript code for AJAX requests with Notices functionality on admin screens.
*
* @package TablePress
* @subpackage Views JavaScript
* @author Tobias Bäthge
* @since 3.1.0
*/
/**
* WordPress dependencies.
*/
import {
Icon,
} from '@wordpress/components';
import { RawHTML } from '@wordpress/element';
import { __, sprintf } from '@wordpress/i18n';
import { buildQueryString } from '@wordpress/url';
import { TablePressIconSimple } from '../../img/tablepress-icon';
/**
* Default callback for handling specifics of a successful request.
*
* @param {Object} data
*/
const onSuccessfulRequestDefault = ( data ) => {
const actionMessages = {
success_save: __( 'The changes were saved successfully.', 'tablepress' ),
};
const notice = {
status: ( data.message.includes( 'error' ) ) ? 'error' : 'success',
content: actionMessages[ data.message ],
type: ( data.message.includes( 'error' ) ) ? 'notice' : 'snackbar',
};
return { notice };
};
/**
* Processes an AJAX request with Notices functionality.
*
* @param {Object} props Function parameters.
* @param {Object} props.requestData Request data.
* @param {Function} props.onSuccessfulRequest Callback for handling a successful save.
* @param {Function} props.setBusyState Callback for setting the busy state.
* @param {Function} props.noticeOperations Callbacks for working with notices.
* @param {Function} props.noticesStoreDispatch Dispatch function for notices store. (Optional, only for "success" Snackbar notices.)
*/
const processAjaxRequest = ( { requestData, onSuccessfulRequest = onSuccessfulRequestDefault, setBusyState, noticeOperations, noticesStoreDispatch } ) => {
/**
* Shows a notice to the user.
*
* @param {Object} notice Notice data.
* @param {string} notice.status Status of the notice (error, success, warning, info).
* @param {string} notice.content Content of the notice.
*/
const showNotice = ( { status, content } ) => {
const id = `notice-${ Date.now() }`;
content = <>
{ /* Notices don't have a DOM ID, so add a custom span which has one, for the fade-out and removal. */ }
{ content }
>;
noticeOperations.createNotice( { id, status, content, isDismissible: ( 'error' === status ) } );
if ( 'error' !== status ) {
// Fade out non-error notices after 5 seconds and then remove them.
setTimeout( () => {
const notice = document.getElementById( id ).closest( '.components-notice' );
notice.addEventListener( 'transitionend', () => noticeOperations.removeNotice( id ) );
notice.style.opacity = 0;
}, 5000 );
} else {
// Scroll error notices into view.
setTimeout( () => {
const notice = document.getElementById( id ).closest( '.components-notice' );
if ( notice.getBoundingClientRect().bottom > ( window.innerHeight || document.documentElement.clientHeight ) ) {
notice.scrollIntoView( { behavior: 'smooth', block: 'end', inline: 'nearest' } );
}
}, 1 );
}
};
// Put the screen into "is busy" mode.
setBusyState( true );
document.body.classList.add( 'wait' );
// Save the table data to the server via an AJAX request.
fetch( ajaxurl, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Accept: 'application/json',
},
body: buildQueryString( requestData ),
} )
// Check for HTTP connection problems.
.then( ( response ) => {
if ( ! response.ok ) {
throw new Error( sprintf( __( 'There was a problem with the server, HTTP response code %1$d (%2$s).', 'tablepress' ), response.status, response.statusText ) );
}
return response.json();
} )
// Check for problems with the transmitted data.
.then( ( data ) => {
if ( 'undefined' === typeof data || null === data || '-1' === data || 'undefined' === typeof data.success ) {
throw new Error( __( 'The JSON data returned from the server is unclear or incomplete.', 'tablepress' ) );
}
if ( true !== data.success ) {
const debugHtml = data.error_details ? `
${ __( 'These errors were encountered:', 'tablepress' ) }
${ data.error_details }
` : '';
throw new Error( `${ __( 'There was a problem with the request.', 'tablepress' ) }
${ debugHtml }` );
}
handleRequestSuccess( data );
} )
// Handle errors.
.catch( ( error ) => {
handleRequestError( error.message );
} )
// Clean up.
.finally( () => {
// Reset the screen from "is busy" mode.
setBusyState( false );
document.body.classList.remove( 'wait' );
} );
/**
* Handles a successful AJAX request.
*
* @param {Object} data Request response data.
*/
const handleRequestSuccess = ( data ) => {
const { notice } = onSuccessfulRequest( data );
if ( notice ) {
if ( 'snackbar' === notice.type && 'undefined' !== typeof noticesStoreDispatch ) {
// Dispatch a Snackbar notice.
noticesStoreDispatch.createSuccessNotice(
notice.content,
{
type: 'snackbar',
icon: ,
}
);
} else {
// Show a normal notice.
showNotice( notice );
}
}
};
/**
* Handles an error during the AJAX request.
*
* @param {string} message Error message.
*/
const handleRequestError = ( message ) => {
message = __( 'Attention: Unfortunately, an error occurred.', 'tablepress' ) + ' ' + message + '
' + sprintf( __( 'Please see the TablePress FAQ page for suggestions.', 'tablepress' ), 'https://tablepress.org/faq/common-errors/' );
const notice = {
status: 'error',
content: message,
};
showNotice( notice );
};
};
export default processAjaxRequest;
react-loader.js 0000644 00000003152 15021264441 0007444 0 ustar 00 /**
* Common functions for loading React components in TablePress JS.
*
* @package TablePress
* @subpackage Views JavaScript
* @author Tobias Bäthge
* @since 2.2.0
*/
/**
* WordPress dependencies.
*/
import { StrictMode } from 'react';
import { createPortal, createRoot } from 'react-dom';
import { addFilter } from '@wordpress/hooks';
/**
* Initializes a React component on the page.
*
* @param {string} rootId HTML ID of the root element for the component.
* @param {Component} Component JSX component.
*/
export const initializeReactComponent = ( rootId, Component ) => {
if ( process.env.DEVELOP ) {
Component = { Component };
}
const root = document.getElementById( rootId );
if ( root ) {
createRoot( root ).render( Component );
}
};
/**
* Initializes a React component on the page, in a React Portal, and registers its meta box.
*
* @param {string} slug Slug of the component/feature module.
* @param {string} screen Slug/action of the screen.
* @param {Component} Component JSX component.
*/
export const initializeReactComponentInPortal = ( slug, screen, Component ) => {
addFilter(
`tablepress.${screen}ScreenFeatures`,
`tp/${slug}/${screen}-screen-feature`,
( features ) => ( [ ...features, slug ] ),
);
addFilter(
`tablepress.${screen}ScreenPortals`,
`tp/${slug}/${screen}-screen-portal`,
( Portals ) => {
return ( props ) => (
<>
{
createPortal(
,
document.getElementById( `tablepress-${slug}-section` ),
)
}
>
);
},
);
};
functions.js 0000644 00000001256 15021264441 0007115 0 ustar 00 /**
* Common functions that are used in TablePress JS.
*
* @package TablePress
* @subpackage Views JavaScript
* @author Tobias Bäthge
* @since 2.0.0
*/
/**
* Alias for document.getElementById and document.querySelectorAll, depending on the first character of the passed selector string. Resembles jQuery.
*
* @param {string} selector Selector string. If it starts with #, a single ID is selected, all matching selectors otherwise.
* @return {Element|NodeList} A single DOM Element or a DOM NodeList matching the selector.
*/
export const $ = ( selector ) => ( '#' === selector[0] ? document.getElementById( selector.slice( 1 ) ) : document.querySelectorAll( selector ) );
help.jsx 0000644 00000005440 15021264441 0006224 0 ustar 00 /**
* JavaScript code for the HelpBox and Help components.
*
* @package TablePress
* @subpackage Edit Screen
* @author Tobias Bäthge
* @since 3.1.0
*/
/**
* WordPress dependencies.
*/
import { useRef, useState } from 'react';
import { createPortal } from 'react-dom';
import {
Button,
Icon,
Modal,
} from '@wordpress/components';
import { help } from '@wordpress/icons';
import { __ } from '@wordpress/i18n';
/**
* Returns the HelpBox component's JSX markup.
*
* @param {Object} props Function parameters.
* @param {string} props.title The title of the HelpBox.
* @param {Object} props.buttonProps Additional props for the Button.
* @param {Object} props.modalProps Additional props for the Modal.
* @param {Object} props.children The Help content.
* @return {Object} HelpBox component.
*/
export const HelpBox = ( { title, buttonProps= {}, modalProps = {}, children } ) => {
const [ modalOpen, setModalOpen ] = useState( false );
const openModal = () => setModalOpen( true );
const closeModal = () => setModalOpen( false );
return (
<>
{ modalOpen && (
}
title={ title }
onRequestClose={ closeModal }
{ ...modalProps }
>
{ children }
) }
>
);
};
/**
* Returns the Help component's JSX markup.
*
* @param {Object} props Function parameters.
* @param {string} props.section The section on the "Edit" screen.
* @param {string} props.title The title of the module.
* @param {Object} props.buttonProps Additional props for the Button.
* @param {Object} props.modalProps Additional props for the Modal.
* @param {Object} props.children The Help content.
* @return {Object} Help component.
*/
export const Help = ( { section, title, buttonProps = {}, modalProps = {}, children } ) => {
// Store a reference to the Help Box container, which is moved in the DOM, to hold the Portal.
const helpContainer = useRef( null );
// Create a container for the Help Box and move it to the desired position in the DOM.
if ( ! helpContainer.current ) {
helpContainer.current = document.createElement( 'div' );
helpContainer.current.className = 'help-container';
document.getElementById( `tablepress-${section}-section` ).closest( '.postbox' ).querySelector( '.handle-actions' ).prepend( helpContainer.current );
}
return createPortal(
{ children }
,
helpContainer.current,
);
};
notifications.jsx 0000644 00000001477 15021264441 0010153 0 ustar 00 /**
* JavaScript code for the "Edit" section integration of Snackbar Notices.
*
* @package TablePress
* @subpackage Views JavaScript
* @author Tobias Bäthge
* @since 3.1.0
*/
/**
* WordPress dependencies.
*/
import { SnackbarList } from '@wordpress/components';
import { useDispatch, useSelect } from '@wordpress/data';
import { store as noticesStore } from '@wordpress/notices';
const Notifications = () => {
const { removeNotice } = useDispatch( noticesStore );
const notices = useSelect(
( select ) => select( noticesStore ).getNotices(),
[]
);
const snackbarNotices = notices.filter( ( { type } ) => type === 'snackbar' );
if ( snackbarNotices.length === 0 ) {
return null;
}
return (
);
};
export { Notifications };
index.php 0000644 00000000034 15021264441 0006360 0 ustar 00 {
// Add keyboard shortcut as title attribute to the "Save Changes" button, with correct modifier key for Mac/non-Mac.
const modifierKey = ( window?.navigator?.platform?.includes( 'Mac' ) ) ?
_x( '⌘', 'keyboard shortcut modifier key on a Mac keyboard', 'tablepress' ) :
_x( 'Ctrl+', 'keyboard shortcut modifier key on a non-Mac keyboard', 'tablepress' );
$button.title = sprintf( $button.dataset.shortcut, modifierKey ); // eslint-disable-line @wordpress/valid-sprintf
/**
* Registers keyboard events and triggers corresponding actions by emulating button clicks.
*
* @since 2.2.0
*
* @param {Event} event Keyboard event.
*/
const keyboardShortcuts = ( event ) => {
let action = '';
if ( event.ctrlKey || event.metaKey ) {
if ( 83 === event.keyCode ) {
// Save Changes: Ctrl/Cmd + S.
action = 'save-changes';
}
}
if ( 'save-changes' === action ) {
// Blur the focussed element to make sure that all change events were triggered.
document.activeElement.blur(); // eslint-disable-line @wordpress/no-global-active-element
// Emulate a click on the button corresponding to the action.
$button.click();
// Prevent the browser's native handling of the shortcut, i.e. showing the Save or Print dialogs.
event.preventDefault();
}
};
// Register keyboard shortcut handler.
window.addEventListener( 'keydown', keyboardShortcuts, true );
};
alert.jsx 0000644 00000004264 15021264441 0006406 0 ustar 00 /**
* JavaScript code for the "Alert" component.
*
* @package TablePress
* @subpackage Views JavaScript
* @author Tobias Bäthge
* @since 3.1.0
*/
/**
* WordPress dependencies.
*/
import { useCallback, useRef } from 'react';
import {
Button,
__experimentalHStack as HStack, // eslint-disable-line @wordpress/no-unsafe-wp-apis
Icon,
Modal,
__experimentalVStack as VStack, // eslint-disable-line @wordpress/no-unsafe-wp-apis
} from '@wordpress/components';
import { __ } from '@wordpress/i18n';
/**
* Internal dependencies.
*/
import { TablePressIcon } from '../../img/tablepress-icon';
/**
* Returns the Alert component's JSX markup.
*
* @param {Object} props Component props.
* @param {string} props.title Title of the alert. No header will be shown if this is not set.
* @param {string} props.text Text of the alert.
* @param {Function} props.onConfirm Callback to confirm the alert.
* @param {Object} props.modalProps Additional props for the Modal.
* @return {Object} Alert component.
*/
const Alert = ( { title, text, onConfirm, modalProps } ) => {
const confirmButtonRef = useRef();
const handleEnter = useCallback(
( event ) => {
// Avoid triggering the action when a button is focused, as this can cause a double submission.
const isConfirmButton = event.target === confirmButtonRef.current;
if ( ! isConfirmButton && 'Enter' === event.key ) {
onConfirm();
event.preventDefault(); // This prevents that the triggering button is "clicked" (via "Enter") again.
}
},
[ onConfirm ],
);
return (
}
title={ title }
__experimentalHideHeader={ undefined === title }
isDismissible={ false }
shouldCloseOnEsc={ false }
shouldCloseOnClickOutside={ false }
onKeyDown={ handleEnter }
{ ...modalProps }
>
{ text }
);
};
export { Alert };