screen.jsx000064400000006231150212675370006563 0ustar00/** * JavaScript code for the "Edit Screen" component. * * @package TablePress * @subpackage Edit Screen * @author Tobias Bäthge * @since 3.0.0 */ /** * WordPress dependencies. */ import { useEffect, useState } from 'react'; import { withFilters, } from '@wordpress/components'; import { applyFilters } from '@wordpress/hooks'; /* * Allow other scripts to register their UI components to be rendered on the Edit Screen. * Portals allow to render components outside of the normal React tree, in separate DOM nodes. */ const features = applyFilters( 'tablepress.editScreenFeatures', [] ); const Portals = withFilters( 'tablepress.editScreenPortals' )( () => <> ); /** * Returns the "Edit Screen" component's JSX markup. * * @return {Object} Edit Screen component. */ const Screen = () => { const [ screenData, setScreenData ] = useState( { copyUrl: tp.screenOptions.copyUrl, deleteUrl: tp.screenOptions.deleteUrl, exportUrl: tp.screenOptions.exportUrl, isSaving: false, previewIsLoading: false, previewIsOpen: false, previewSrcDoc: '', previewUrl: tp.screenOptions.previewUrl, triggerPreview: false, triggerSaveChanges: false, } ); const [ tableOptions, setTableOptions ] = useState( () => ( { ...tp.table.options } ) ); const [ tableMeta, setTableMeta ] = useState( () => ( { ...tp.table.meta } ) ); // Turn off "Enable Visitor Features" if the table has merged cells. useEffect( () => { if ( tableOptions.use_datatables && tp.helpers.editor.has_merged_body_cells() ) { updateTableOptions( { use_datatables: false } ); } }, [] ); // eslint-disable-line react-hooks/exhaustive-deps -- This should only run on the initial render, so no dependencies are needed. const updateScreenData = ( updatedScreenData ) => { // Use an updater function to ensure that the current state is used when updating screen data. setScreenData( ( currentScreenData ) => ( { ...currentScreenData, ...updatedScreenData, } ) ); }; const updateTableOptions = ( updatedTableOptions ) => { // Use an updater function to ensure that the current state is used when updating table options. setTableOptions( ( currentTableOptions ) => ( { ...currentTableOptions, ...updatedTableOptions, } ) ); tp.table.options = { ...tp.table.options, ...updatedTableOptions }; tp.helpers.unsaved_changes.set(); // Redraw the table when certain options are changed. if ( [ 'table_head', 'table_foot' ].some( ( optionName ) => ( Object.keys( updatedTableOptions ).includes( optionName ) ) ) ) { tp.editor.updateTable(); } }; const updateTableMeta = ( updatedTableMeta ) => { // Use an updater function to ensure that the current state is used when updating table meta. setTableMeta( ( currentTableMeta ) => ( { ...currentTableMeta, ...updatedTableMeta, } ) ); tp.table.meta = { ...tp.table.meta, ...updatedTableMeta }; tp.helpers.unsaved_changes.set(); }; return ( ); }; export default Screen; table-manipulation.js000064400000034405150212675370010705 0ustar00/** * JavaScript code for the "Edit" section integration of the "Table Manipulation" buttons. * * @package TablePress * @subpackage Views JavaScript * @author Tobias Bäthge * @since 3.0.0 */ /** * WordPress dependencies. */ import { useState } from 'react'; import { Button, __experimentalHStack as HStack, // eslint-disable-line @wordpress/no-unsafe-wp-apis __experimentalNumberControl as NumberControl, // eslint-disable-line @wordpress/no-unsafe-wp-apis __experimentalVStack as VStack, // eslint-disable-line @wordpress/no-unsafe-wp-apis } from '@wordpress/components'; import { createInterpolateElement, RawHTML } from '@wordpress/element'; import { __, sprintf } from '@wordpress/i18n'; import { displayShortcut, shortcutAriaLabel } from '@wordpress/keycodes'; /** * Internal dependencies. */ import { initializeReactComponentInPortal } from '../common/react-loader'; import { Alert } from '../common/alert'; import { HelpBox } from '../common/help'; const Section = () => { const [ columnsAppendNumber, setColumnsAppendNumber ] = useState( 1 ); const [ rowsAppendNumber, setRowsAppendNumber ] = useState( 1 ); const [ alertMoveInvalidIsShown, setAlertMoveInvalidIsShown ] = useState( false ); const [ alertDeleteRowsInvalidIsShown, setAlertDeleteRowsInvalidIsShown ] = useState( false ); const [ alertDeleteColumnsInvalidIsShown, setAlertDeleteColumnsInvalidIsShown ] = useState( false ); const move = ( direction, type ) => { if ( ! tp.helpers.move_allowed( type, direction ) ) { setAlertMoveInvalidIsShown( true ); return; } tp.callbacks.move( direction, type ); }; const remove = ( type ) => { const handlingRows = ( 'rows' === type ); const numRoCs = handlingRows ? tp.editor.options.data.length : tp.editor.options.columns.length; if ( numRoCs === tp.helpers.selection[ type ].length ) { if ( handlingRows ) { setAlertDeleteRowsInvalidIsShown( true ); } else { setAlertDeleteColumnsInvalidIsShown( true ); } return; } tp.callbacks.remove( type ); }; return (
{ __( 'Selected cells', 'tablepress' ) }: { __( 'Selected cells', 'tablepress' ) }:
{ __( 'Selected rows', 'tablepress' ) }: { __( 'Selected columns', 'tablepress' ) }:
{ __( 'Selected rows', 'tablepress' ) }: { __( 'Selected columns', 'tablepress' ) }:
{ __( 'Selected rows', 'tablepress' ) }: { __( 'Selected columns', 'tablepress' ) }:
{ alertMoveInvalidIsShown && ( setAlertMoveInvalidIsShown( false ) } modalProps={ { className: 'has-size-small', // Using size: 'small' is only possible in WP 6.5+. } } /> ) } { alertDeleteRowsInvalidIsShown && ( setAlertDeleteRowsInvalidIsShown( false ) } /> ) } { alertDeleteColumnsInvalidIsShown && ( setAlertDeleteColumnsInvalidIsShown( false ) } /> ) }
); }; initializeReactComponentInPortal( 'table-manipulation', 'edit', Section, ); screen-options.js000064400000006430150212675370010065 0ustar00/** * JavaScript code for the "Screen Options" tab integration on the "Edit" screen. * * @package TablePress * @subpackage Views JavaScript * @author Tobias Bäthge * @since 3.0.0 */ /* globals tp, ajaxurl */ /** * WordPress dependencies. */ import { __ } from '@wordpress/i18n'; import { buildQueryString } from '@wordpress/url'; /** * Internal dependencies. */ import { $ } from '../common/functions'; /** * Updates table editor layout with new screen option values. * * @param {Event} event `input` event of the screen options fields. */ const update = ( event ) => { if ( ! event.target ) { return; } if ( 'table_editor_line_clamp' === event.target.id ) { tp.editor.el.style.setProperty( '--table-editor-line-clamp', parseInt( event.target.value, 10 ) ); tp.editor.updateCornerPosition(); return; } if ( 'table_editor_column_width' === event.target.id ) { tp.screenOptions.table_editor_column_width = parseInt( event.target.value, 10 ); tp.screenOptions.table_editor_column_width = Math.max( tp.screenOptions.table_editor_column_width, 30 ); // Ensure a minimum column width of 30 pixesl. tp.screenOptions.table_editor_column_width = Math.min( tp.screenOptions.table_editor_column_width, 9999 ); // Ensure a maximum column width of 9999 pixesl. tp.editor.colgroup.forEach( ( col ) => col.setAttribute( 'width', tp.screenOptions.table_editor_column_width ) ); tp.editor.updateCornerPosition(); return; } }; /** * Designates a screen option field to have been changed, so that the value is sent to the server when it is blurred. * * @param {Event} event `change` event of the screen options fields. */ const setWasChanged = ( event ) => { if ( event.target ) { event.target.was_changed = true; } }; /** * Saves screen options to the server after they have been changed and the field is blurred. * * @param {Event} event `blur` event of the screen options fields. */ const save = ( event ) => { if ( ! event.target ) { return; } if ( ! event.target.was_changed ) { return; } event.target.was_changed = false; // Prepare the data for the AJAX request. const request_data = { action: 'tablepress_save_screen_options', _ajax_nonce: tp.nonces.screen_options, tablepress: { [ event.target.id ]: parseInt( event.target.value, 10 ), }, }; // Add spinner and change cursor. event.target.parentNode.insertAdjacentHTML( 'beforeend', `` ); 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( request_data ), } ) .finally( () => { $( '#spinner-save-changes' ).remove(); document.body.classList.remove( 'wait' ); } ); }; // Register callbacks for the screen options. const screenOptions = $( '#tablepress-screen-options' ); if ( screenOptions ) { screenOptions.addEventListener( 'input', update ); screenOptions.addEventListener( 'change', setWasChanged ); screenOptions.addEventListener( 'focusout', save ); // Use the `focusout` event instead of `blur` as that does not bubble. } data/index.php000064400000000034150212675370007302 0ustar00 { const numRows = tp.editor.options.data.length; const numColumns = tp.editor.options.columns.length; const numSelectedRows = tp.helpers.selection.rows.length; const numSelectedColumns = tp.helpers.selection.columns.length; const isMac = window?.navigator?.platform?.includes( 'Mac' ); const metaKey = isMac ? _x( '⌘', 'keyboard shortcut modifier key on a Mac keyboard', 'tablepress' ) : _x( 'Ctrl+', 'keyboard shortcut modifier key on a non-Mac keyboard', 'tablepress' ); const optionKey = isMac ? _x( '⌥', 'keyboard shortcut option key on a Mac keyboard', 'tablepress' ) : _x( 'Alt+', 'keyboard shortcut Alt key on a non-Mac keyboard', 'tablepress' ); // Call-by-reference object for the cell_merge_allowed() call. const errorMessage = { text: '', }; tp.helpers.visibility.update(); const items = [ // Undo/Redo. { title: __( 'Undo', 'tablepress' ), shortcut: sprintf( _x( '%1$sZ', 'keyboard shortcut for Undo', 'tablepress' ), metaKey ), onclick: obj.undo, disabled: ( -1 === obj.historyIndex ), }, { title: __( 'Redo', 'tablepress' ), shortcut: sprintf( _x( '%1$sY', 'keyboard shortcut for Redo', 'tablepress' ), metaKey ), onclick: obj.redo, disabled: ( obj.historyIndex === obj.history.length - 1 ), }, // Cut/Copy/Paste. { type: 'divisor', }, { title: __( 'Cut', 'tablepress' ), shortcut: sprintf( _x( '%1$sX', 'keyboard shortcut for Cut', 'tablepress' ), metaKey ), onclick() { /* eslint-disable @wordpress/no-global-active-element */ if ( 'TEXTAREA' === document.activeElement.tagName && document.activeElement.selectionStart !== document.activeElement.selectionEnd ) { document.execCommand( 'copy' ); // If text is selected in the actively edited cell, only copy that. const cursorPosition = document.activeElement.selectionStart; document.activeElement.value = document.activeElement.value.slice( 0, document.activeElement.selectionStart ) + document.activeElement.value.slice( document.activeElement.selectionEnd ); // Cut the selected content. document.activeElement.selectionEnd = cursorPosition; } else { obj.copy( true ); // Otherwise, copy highlighted cells. obj.setValue( obj.highlighted, '' ); // Make cell content empty. } /* eslint-enable @wordpress/no-global-active-element */ }, }, { title: __( 'Copy', 'tablepress' ), shortcut: sprintf( _x( '%1$sC', 'keyboard shortcut for Copy', 'tablepress' ), metaKey ), onclick() { if ( 'TEXTAREA' === document.activeElement.tagName && document.activeElement.selectionStart !== document.activeElement.selectionEnd ) { // eslint-disable-line @wordpress/no-global-active-element document.execCommand( 'copy' ); // If text is selected in the actively edited cell, only copy that. } else { obj.copy( true ); // Otherwise, copy highlighted cells. } }, }, { title: __( 'Paste', 'tablepress' ), shortcut: sprintf( _x( '%1$sV', 'keyboard shortcut for Paste', 'tablepress' ), metaKey ), onclick() { /* eslint-disable @wordpress/no-global-active-element */ if ( 'TEXTAREA' === document.activeElement.tagName ) { window.navigator.clipboard.readText().then( ( text ) => { if ( text ) { const cursorPosition = document.activeElement.selectionStart + text.length; document.activeElement.value = document.activeElement.value.slice( 0, document.activeElement.selectionStart ) + text + document.activeElement.value.slice( document.activeElement.selectionEnd ); // Paste at the selection. document.activeElement.selectionEnd = cursorPosition; } } ); } else if ( obj.selectedCell ) { window.navigator.clipboard.readText().then( ( text ) => { if ( text ) { obj.paste( obj.selectedCell[0], obj.selectedCell[1], text ); } } ); } /* eslint-enable @wordpress/no-global-active-element */ }, // Firefox does not offer the readText() method, so "Paste" needs to be disabled. disabled: ! window?.navigator?.clipboard?.readText, tooltip: ! window?.navigator?.clipboard?.readText ? __( 'Your browser does not allow pasting via the context menu. Use the keyboard shortcut instead.', 'tablepress' ) : '', }, // Insert Link, Insert Image, Open Advanced Editor. { type: 'divisor', }, { title: __( 'Insert Link', 'tablepress' ), shortcut: sprintf( _x( '%1$sL', 'keyboard shortcut for Insert Link', 'tablepress' ), metaKey ), onclick: tp.callbacks.insert_link.open_dialog.bind( null, ( 'TEXTAREA' === document.activeElement.tagName ) ? document.activeElement : null ), // eslint-disable-line @wordpress/no-global-active-element }, { title: __( 'Insert Image', 'tablepress' ), shortcut: sprintf( _x( '%1$sI', 'keyboard shortcut for Insert Image', 'tablepress' ), metaKey ), onclick: tp.callbacks.insert_image.open_dialog.bind( null, ( 'TEXTAREA' === document.activeElement.tagName ) ? document.activeElement : null ), // eslint-disable-line @wordpress/no-global-active-element }, { title: __( 'Advanced Editor', 'tablepress' ), shortcut: sprintf( _x( '%1$sE', 'keyboard shortcut for Advanced Editor', 'tablepress' ), metaKey ), onclick: tp.callbacks.advanced_editor.open_dialog.bind( null, ( 'TEXTAREA' === document.activeElement.tagName ) ? document.activeElement : null ), // eslint-disable-line @wordpress/no-global-active-element }, // Duplicate/Insert/Append/Delete. { type: 'divisor', }, { title: __( 'Duplicate …', 'tablepress' ), submenu: [ { title: _n( 'Duplicate row', 'Duplicate rows', numSelectedRows, 'tablepress' ), onclick: tp.callbacks.insert_duplicate.bind( null, 'duplicate', 'rows' ), }, { title: _n( 'Duplicate column', 'Duplicate columns', numSelectedColumns, 'tablepress' ), onclick: tp.callbacks.insert_duplicate.bind( null, 'duplicate', 'columns' ), }, ], }, { title: __( 'Insert …', 'tablepress' ), submenu: [ { title: _n( 'Insert row above', 'Insert rows above', numSelectedRows, 'tablepress' ), onclick: tp.callbacks.insert_duplicate.bind( null, 'insert', 'rows', 'before' ), }, { title: _n( 'Insert row below', 'Insert rows below', numSelectedRows, 'tablepress' ), onclick: tp.callbacks.insert_duplicate.bind( null, 'insert', 'rows', 'after' ), }, { title: _n( 'Insert column on the left', 'Insert columns on the left', numSelectedColumns, 'tablepress' ), onclick: tp.callbacks.insert_duplicate.bind( null, 'insert', 'columns', 'before' ), }, { title: _n( 'Insert column on the right', 'Insert columns on the right', numSelectedColumns, 'tablepress' ), onclick: tp.callbacks.insert_duplicate.bind( null, 'insert', 'columns', 'after' ), }, ], }, { title: __( 'Append …', 'tablepress' ), submenu: [ { title: __( 'Append row', 'tablepress' ), onclick: tp.callbacks.append.bind( null, 'rows', 1 ), }, { title: __( 'Append column', 'tablepress' ), onclick: tp.callbacks.append.bind( null, 'columns', 1 ), }, ], }, { title: __( 'Delete …', 'tablepress' ), submenu: [ { title: _n( 'Delete row', 'Delete rows', numSelectedRows, 'tablepress' ), onclick: tp.callbacks.remove.bind( null, 'rows' ), disabled: numRows === numSelectedRows, tooltip: numRows === numSelectedRows ? __( 'This option is disabled.', 'tablepress' ) + ' ' + __( 'You can not delete all table rows!', 'tablepress' ) : '', }, { title: _n( 'Delete column', 'Delete columns', numSelectedColumns, 'tablepress' ), onclick: tp.callbacks.remove.bind( null, 'columns' ), disabled: numColumns === numSelectedColumns, tooltip: numColumns === numSelectedColumns ? __( 'This option is disabled.', 'tablepress' ) + ' ' + __( 'You can not delete all table columns!', 'tablepress' ) : '', }, ], }, // Move rows/columns, Sort by column. { type: 'divisor', }, { title: __( 'Move …', 'tablepress' ), submenu: [ { title: _n( 'Move row up', 'Move rows up', numSelectedRows, 'tablepress' ), shortcut: sprintf( _x( '%1$s⇧↑', 'keyboard shortcut for Move up', 'tablepress' ), metaKey ), onclick: tp.callbacks.move.bind( null, 'up', 'rows' ), disabled: ! tp.helpers.move_allowed( 'rows', 'up' ), }, { title: _n( 'Move row down', 'Move rows down', numSelectedRows, 'tablepress' ), shortcut: sprintf( _x( '%1$s⇧↓', 'keyboard shortcut for Move down', 'tablepress' ), metaKey ), onclick: tp.callbacks.move.bind( null, 'down', 'rows' ), disabled: ! tp.helpers.move_allowed( 'rows', 'down' ), }, { title: _n( 'Move column left', 'Move columns left', numSelectedColumns, 'tablepress' ), shortcut: sprintf( _x( '%1$s⇧←', 'keyboard shortcut for Move left', 'tablepress' ), metaKey ), onclick: tp.callbacks.move.bind( null, 'left', 'columns' ), disabled: ! tp.helpers.move_allowed( 'columns', 'left' ), }, { title: _n( 'Move column right', 'Move columns right', numSelectedColumns, 'tablepress' ), shortcut: sprintf( _x( '%1$s⇧→', 'keyboard shortcut for Move right', 'tablepress' ), metaKey ), onclick: tp.callbacks.move.bind( null, 'right', 'columns' ), disabled: ! tp.helpers.move_allowed( 'columns', 'right' ), }, { type: 'divisor', }, { title: _n( 'Move row to the top', 'Move rows to the top', numSelectedRows, 'tablepress' ), shortcut: sprintf( _x( '%1$s%2$s⇧↑', 'keyboard shortcut for Move to the top', 'tablepress' ), metaKey, optionKey ), onclick: tp.callbacks.move.bind( null, 'top', 'rows' ), disabled: ! tp.helpers.move_allowed( 'rows', 'top' ), }, { title: _n( 'Move row to the bottom', 'Move rows to the bottom', numSelectedRows, 'tablepress' ), shortcut: sprintf( _x( '%1$s%2$s⇧↓', 'keyboard shortcut for Move to the bottom', 'tablepress' ), metaKey, optionKey ), onclick: tp.callbacks.move.bind( null, 'bottom', 'rows' ), disabled: ! tp.helpers.move_allowed( 'rows', 'bottom' ), }, { title: _n( 'Move column to first', 'Move columns to first', numSelectedColumns, 'tablepress' ), shortcut: sprintf( _x( '%1$s%2$s⇧←', 'keyboard shortcut for Move to first', 'tablepress' ), metaKey, optionKey ), onclick: tp.callbacks.move.bind( null, 'first', 'columns' ), disabled: ! tp.helpers.move_allowed( 'columns', 'first' ), }, { title: _n( 'Move column to last', 'Move columns to last', numSelectedColumns, 'tablepress' ), shortcut: sprintf( _x( '%1$s%2$s⇧→', 'keyboard shortcut for Move to last', 'tablepress' ), metaKey, optionKey ), onclick: tp.callbacks.move.bind( null, 'last', 'columns' ), disabled: ! tp.helpers.move_allowed( 'columns', 'last' ), }, ], }, { title: __( 'Sort by column …', 'tablepress' ), submenu: [ { title: __( 'Sort by column ascending', 'tablepress' ), onclick: tp.callbacks.sort.bind( null, 'asc' ), disabled: 1 !== numSelectedColumns, tooltip: 1 !== numSelectedColumns ? __( 'This option is disabled because more than one column was selected.', 'tablepress' ) : '', }, { title: __( 'Sort by column descending', 'tablepress' ), onclick: tp.callbacks.sort.bind( null, 'desc' ), disabled: 1 !== numSelectedColumns, tooltip: 1 !== numSelectedColumns ? __( 'This option is disabled because more than one column was selected.', 'tablepress' ) : '', }, ], }, // Hide/Show rows/columns. { type: 'divisor', }, { title: __( 'Hide/Show …', 'tablepress' ), submenu: [ { title: _n( 'Hide row', 'Hide rows', numSelectedRows, 'tablepress' ), onclick: tp.callbacks.hide_unhide.bind( null, 'hide', 'rows' ), disabled: ! tp.helpers.visibility.selection_contains( 'rows', 1 ), tooltip: ! tp.helpers.visibility.selection_contains( 'rows', 1 ) ? __( 'This option is disabled because no visible rows were selected.', 'tablepress' ) : '', }, { title: _n( 'Hide column', 'Hide columns', numSelectedColumns, 'tablepress' ), onclick: tp.callbacks.hide_unhide.bind( null, 'hide', 'columns' ), disabled: ! tp.helpers.visibility.selection_contains( 'columns', 1 ), tooltip: ! tp.helpers.visibility.selection_contains( 'columns', 1 ) ? __( 'This option is disabled because no visible columns were selected.', 'tablepress' ) : '', }, { title: _n( 'Show row', 'Show rows', numSelectedRows, 'tablepress' ), onclick: tp.callbacks.hide_unhide.bind( null, 'unhide', 'rows' ), disabled: ! tp.helpers.visibility.selection_contains( 'rows', 0 ), tooltip: ! tp.helpers.visibility.selection_contains( 'rows', 0 ) ? __( 'This option is disabled because no hidden rows were selected.', 'tablepress' ) : '', }, { title: _n( 'Show column', 'Show columns', numSelectedColumns, 'tablepress' ), onclick: tp.callbacks.hide_unhide.bind( null, 'unhide', 'columns' ), disabled: ! tp.helpers.visibility.selection_contains( 'columns', 0 ), tooltip: ! tp.helpers.visibility.selection_contains( 'columns', 0 ) ? __( 'This option is disabled because no hidden columns were selected.', 'tablepress' ) : '', }, ], }, // Merging/Unmerging cells. { type: 'divisor', }, { title: __( 'Combine/Merge cells', 'tablepress' ), onclick: tp.callbacks.merge_cells, disabled: ( 1 === numSelectedRows && 1 === numSelectedColumns ) || ! tp.helpers.cell_merge_allowed( 'no-alert' ), tooltip: ( 1 === numSelectedRows && 1 === numSelectedColumns ) || ! tp.helpers.cell_merge_allowed( 'no-alert', errorMessage ) ? __( 'This option is disabled.', 'tablepress' ) + ' ' + errorMessage.text : '', }, ]; // Allow other scripts to modify the context menu. return applyFilters( 'tablepress.editScreenContextMenuItems', items, obj ); }; export default contextMenu; table-information.js000064400000014132150212675370010525 0ustar00/** * JavaScript code for the "Edit" section integration of the "Table Information". * * @package TablePress * @subpackage Views JavaScript * @author Tobias Bäthge * @since 3.0.0 */ /** * WordPress dependencies. */ import { useEffect, useState } from 'react'; import { Button, __experimentalHStack as HStack, // eslint-disable-line @wordpress/no-unsafe-wp-apis Icon, __experimentalInputControl as InputControl, // eslint-disable-line @wordpress/no-unsafe-wp-apis __experimentalInputControlSuffixWrapper as InputControlSuffixWrapper, // eslint-disable-line @wordpress/no-unsafe-wp-apis TextareaControl, TextControl, __experimentalVStack as VStack, // eslint-disable-line @wordpress/no-unsafe-wp-apis } from '@wordpress/components'; import { useCopyToClipboard } from '@wordpress/compose'; import { useDispatch } from '@wordpress/data'; import { createInterpolateElement } from '@wordpress/element'; import { copySmall } from '@wordpress/icons'; import { __, sprintf } from '@wordpress/i18n'; import { store as noticesStore } from '@wordpress/notices'; /** * Internal dependencies. */ import { initializeReactComponentInPortal } from '../common/react-loader'; import { TablePressIconSimple } from '../../img/tablepress-icon'; const Section = ( { tableMeta, updateTableMeta } ) => { const { createSuccessNotice } = useDispatch( noticesStore ); const [ tableId, setTableId ] = useState( tableMeta.id ); const copyShortcodeButtonRef = useCopyToClipboard( `[${ tp.table.shortcode } id=${ tableMeta.id } /]`, () => { createSuccessNotice( __( 'Copied Shortcode to clipboard.', 'tablepress' ), { type: 'snackbar', icon: , } ); } ); useEffect( () => { setTableId( tableMeta.id ); }, [ tableMeta.id ] ); return (
setTableId( newTableId.replace( /[^0-9a-zA-Z_-]/g, '' ) ) } onBlur={ ( event ) => { if ( tableMeta.id === tableId ) { return; } // The table IDs "" and "0" are not allowed, or in other words, the table ID has to fulfill /[A-Za-z1-9-_]|[A-Za-z0-9-_]{2,}/. if ( '' === tableId || '0' === tableId ) { // This alert can not be replaced by the `Alert` component, as that does not stop the cmd+S keyboard shortcut from running. window.alert( __( 'This table ID is invalid. Please enter a different table ID.', 'tablepress' ) ); setTableId( tableMeta.id ); event.target.focus(); return; } // This alert can not be replaced by a `Modal` component, as that does not stop the cmd+S keyboard shortcut from running. if ( ! window.confirm( __( 'Do you really want to change the Table ID? All blocks and Shortcodes for this table in your posts and pages will have to be adjusted!', 'tablepress' ) ) ) { setTableId( tableMeta.id ); return; } // Set the new table ID. updateTableMeta( { id: tableId } ); document.getElementById( 'table-information-shortcode' ).focus(); } } required={ true } readOnly={ ! tp.screenOptions.currentUserCanEditTableId } />
updateTableMeta( { name } ) } />
updateTableMeta( { description } ) } rows="4" />
{ __( 'Last Modified', 'tablepress' ) }: { sprintf( __( '%1$s by %2$s', 'tablepress' ), tableMeta.lastModified, tableMeta.lastEditor ) }
); }; initializeReactComponentInPortal( 'table-information', 'edit', Section, ); datatables-features.js000064400000023533150212675370011040 0ustar00/** * JavaScript code for the "Edit" section integration of the "Table Features for Site Visitors". * * @package TablePress * @subpackage Views JavaScript * @author Tobias Bäthge * @since 3.0.0 */ /** * WordPress dependencies. */ import { useState } from 'react'; import { CheckboxControl, __experimentalHStack as HStack, // eslint-disable-line @wordpress/no-unsafe-wp-apis __experimentalNumberControl as NumberControl, // eslint-disable-line @wordpress/no-unsafe-wp-apis TextareaControl, __experimentalVStack as VStack, // eslint-disable-line @wordpress/no-unsafe-wp-apis } from '@wordpress/components'; import { createInterpolateElement } from '@wordpress/element'; import { __, sprintf } from '@wordpress/i18n'; /** * Internal dependencies. */ import { initializeReactComponentInPortal } from '../common/react-loader'; import { Alert } from '../common/alert'; // Wrap the "Custom Commands" in a separate component, to keep internal states at a lower level. const CustomCommandsControl = ( { tableOptionCustomCommands, dataTablesEnabled, updateTableOptions } ) => { const [ customCommands, setCustomCommands ] = useState( tableOptionCustomCommands ); const [ customCommandsExpanded, setCustomCommandsExpanded ] = useState( false ); /* // Updating the state variable when the table option changes is not necessary, as it's never modified externally. useEffect( () => { setCustomCommands( tableOptionCustomCommands ); }, [ tableOptionCustomCommands ] ); */ return ( setCustomCommands( newCustomCommands ) } onBlur={ () => { if ( customCommands !== tableOptionCustomCommands ) { updateTableOptions( { datatables_custom_commands: customCommands } ); } } } onFocus={ () => setCustomCommandsExpanded( true ) } help={ createInterpolateElement( __( 'Additional parameters from the DataTables documentation to be added to the JS call.', 'tablepress' ) + ' ' + __( 'For advanced use only.', 'tablepress' ), { a: , // eslint-disable-line jsx-a11y/anchor-has-content }, ) } /> ); }; const Section = ( { tableOptions, updateTableOptions } ) => { const [ alertEnableDataTablesMergedCellsIsShown, setAlertEnableDataTablesMergedCellsIsShown ] = useState( false ); const tableHeadEnabled = ( tableOptions.table_head > 0 ); const dataTablesEnabled = ( tableHeadEnabled && tableOptions.use_datatables ); return ( { ( ! tableHeadEnabled ) && ( { sprintf( __( 'These features and options are only available when the “%1$s” setting in the “%2$s” section is used.', 'tablepress' ), __( 'Table Header', 'tablepress' ), __( 'Table Options', 'tablepress' ), ) } ) } { ( tp.screenOptions.showCustomCommands ) && ( ) }
{ __( 'Enable Visitor Features', 'tablepress' ) }: { // Don't turn on "Enable Visitor Features" if the table has merged cells. if ( use_datatables ) { tp.helpers.visibility.update(); // Update information about hidden rows and columns. if ( tp.helpers.editor.has_merged_body_cells() ) { setAlertEnableDataTablesMergedCellsIsShown( true ); return; } } updateTableOptions( { use_datatables } ); } } /> { alertEnableDataTablesMergedCellsIsShown && ( setAlertEnableDataTablesMergedCellsIsShown( false ) } modalProps={ { className: 'has-size-medium', // Using size: 'medium' is only possible in WP 6.5+. } } /> ) }
{ __( 'Sorting', 'tablepress' ) }: updateTableOptions( { datatables_sort } ) } />
{ __( 'Search/Filtering', 'tablepress' ) }: updateTableOptions( { datatables_filter } ) } />
{ __( 'Pagination', 'tablepress' ) }: updateTableOptions( { datatables_paginate } ) } />
{ __( 'Pagination Length Change', 'tablepress' ) }: updateTableOptions( { datatables_lengthchange } ) } />
{ __( 'Info', 'tablepress' ) }: updateTableOptions( { datatables_info } ) } />
{ __( 'Horizontal Scrolling', 'tablepress' ) }: updateTableOptions( { datatables_scrollx } ) } />
); }; initializeReactComponentInPortal( 'datatables-features', 'edit', Section, ); index.php000064400000000034150212675370006371 0ustar00 ( tp.table.visibility[ type ][ roc_idx ] === visibility ) ); }; /** * For the context menu and button, determine whether moving the rows/columns of the current selection is allowed. * * @param {[type]} type [description] * @param {[type]} direction [description] * @return {boolean} Whether the move is allowed or not. */ tp.helpers.move_allowed = function ( type, direction ) { // When moving up or left, or to top or first, test the first row/column of the selected range. let roc_to_test = tp.helpers.selection[ type ][0]; let min_max_roc = 0; // First row/column. // When moving down or right, or bottom or last, test the last row/column of the selected range. if ( 'down' === direction || 'right' === direction || 'bottom' === direction || 'last' === direction ) { roc_to_test = tp.helpers.selection[ type ][ tp.helpers.selection[ type ].length - 1 ]; min_max_roc = ( 'rows' === type ) ? tp.editor.options.data.length - 1 : tp.editor.options.columns.length - 1; } // Moving is disallowed if the first/last row/column is already at the target edge. if ( min_max_roc === roc_to_test ) { return false; } // Otherwise allow the move. return true; }; /** * Determines whether merging the current selection is allowed. * * Note that this does currently not take into account hidden rows! * * This is e.g. used to give feedback in the context menu and "Combine/Merge" button. * * @param {string} errors Whether errors should also be alert()ed. * @param {Object} error_message Call-by-reference object for the error message. * @return {boolean} Whether the merge is allowed or not. */ tp.helpers.cell_merge_allowed = function ( errors, error_message = {} ) { const alertOnError = ( 'alert' === errors ); const first_selected_row_idx = tp.helpers.selection.rows[0]; const last_selected_row_idx = tp.helpers.selection.rows[ tp.helpers.selection.rows.length - 1 ]; const first_body_row_idx = tp.table.options.table_head; const last_body_row_idx = tp.editor.options.data.length - 1 - tp.table.options.table_foot; // If table header rows are used and the "Enable Visitor Features" option is active, cell merging is only allowed in the table header and footer rows. if ( tp.table.options.table_head > 0 && tp.table.options.use_datatables && ! ( first_selected_row_idx < first_body_row_idx && last_selected_row_idx < first_body_row_idx ) && ! ( first_selected_row_idx > last_body_row_idx && last_selected_row_idx > last_body_row_idx ) ) { error_message.text = sprintf( __( 'You can not combine these cells, because the “%1$s” checkbox in the “%2$s” section is checked.', 'tablepress' ), __( 'Enable Visitor Features', 'tablepress' ), __( 'Table Features for Site Visitors', 'tablepress' ) ) + ' ' + __( 'When the Table Features for Site Visitors are used, merging is only allowed in the table header and footer rows.', 'tablepress' ); if ( alertOnError ) { // This alert can not be replaced by the `Alert` component, as that does not pause the code execution. window.alert( error_message.text ); } return false; } // If table header rows are used, and a header row and at least one adjacent body row are selected, disable merging cells. if ( first_selected_row_idx < first_body_row_idx && last_selected_row_idx >= first_body_row_idx ) { error_message.text = sprintf( __( 'You can not combine these cells, because the “%1$s” setting in the “%2$s” section is active.', 'tablepress' ), __( 'Table Header', 'tablepress' ), __( 'Table Options', 'tablepress' ) ); if ( alertOnError ) { // This alert can not be replaced by the `Alert` component, as that does not pause the code execution. window.alert( error_message.text ); } return false; } // If table footer rows are used, and a footer row and at least one adjacent body row are selected, disable merging cells. if ( first_selected_row_idx <= last_body_row_idx && last_selected_row_idx > last_body_row_idx ) { error_message.text = sprintf( __( 'You can not combine these cells, because the “%1$s” setting in the “%2$s” section is active.', 'tablepress' ), __( 'Table Footer', 'tablepress' ), __( 'Table Options', 'tablepress' ) ); if ( alertOnError ) { // This alert can not be replaced by the `Alert` component, as that does not pause the code execution. window.alert( error_message.text ); } return false; } // Otherwise allow the merge. return true; }; tp.helpers.editor = tp.helpers.editor || {}; /** * [editor_reselect description] * * @param {[type]} el [description] * @param {[type]} obj Jspreadsheet instance, passed e.g. by onblur. If not present, we use tp.editor. */ tp.helpers.editor.reselect = function ( el, obj ) { if ( 'undefined' === typeof obj ) { obj = tp.editor; } obj.updateSelectionFromCoords( tp.helpers.selection.columns[0], tp.helpers.selection.rows[0], tp.helpers.selection.columns[ tp.helpers.selection.columns.length - 1 ], tp.helpers.selection.rows[ tp.helpers.selection.rows.length - 1 ] ); }; /** * Checks if the table has merged cells in the visible body rows and columns. * * @return {boolean} True if the table has merged cells in the body, false otherwise. */ tp.helpers.editor.has_merged_body_cells = function () { const first_body_row_idx = tp.table.options.table_head; const first_footer_row_idx = tp.editor.options.data.length - tp.table.options.table_foot; const num_columns = tp.editor.options.columns.length; // Loop through all cells after the table header rows and before the table footer rows. for ( let row_idx = first_body_row_idx; row_idx < first_footer_row_idx; row_idx++ ) { for ( let col_idx = 0; col_idx < num_columns; col_idx++ ) { if ( ( '#rowspan#' === tp.editor.options.data[ row_idx ][ col_idx ] || '#colspan#' === tp.editor.options.data[ row_idx ][ col_idx ] ) && 1 === tp.table.visibility.rows[ row_idx ] && 1 === tp.table.visibility.columns[ col_idx ] ) { return true; } } } return false; }; /** * Creates the sorting function that is used when sorting the table by a column. * * @param {number} direction Sorting direction. 0 for ascending, 1 for descending. * @return {Function} Sorting function. */ tp.helpers.editor.sorting = function ( direction ) { direction = direction ? -1 : 1; return function ( a, b ) { // The actual value is stored in the second array element, the first contains the row index. const sortResult = a[1].localeCompare( b[1], undefined, { numeric: true, sensitivity: 'base', } ); return direction * sortResult; }; }; tp.callbacks = tp.callbacks || {}; tp.callbacks.editor = tp.callbacks.editor || {}; /** * [editor_onselection description] * * @param {[type]} instance [description] * @param {[type]} x1 [description] * @param {[type]} y1 [description] * @param {[type]} x2 [description] * @param {[type]} y2 [description] * @param {[type]} origin [description] */ tp.callbacks.editor.onselection = function ( instance, x1, y1, x2, y2 /*, origin */ ) { tp.helpers.selection = { rows: [], columns: [], }; for ( let row_idx = y1; row_idx <= y2; row_idx++ ) { tp.helpers.selection.rows.push( row_idx ); } for ( let col_idx = x1; col_idx <= x2; col_idx++ ) { tp.helpers.selection.columns.push( col_idx ); } }; /** * [editor_onupdatetable description] * * @param {[type]} instance [description] * @param {[type]} cell [description] * @param {[type]} col_idx [description] * @param {[type]} row_idx [description] * @param {[type]} value [description] * @param {[type]} label [description] * @param {[type]} cell_name [description] */ tp.callbacks.editor.onupdatetable = function ( instance, cell, col_idx, row_idx, value, label, cell_name ) { const meta = instance.jspreadsheet.options.meta[ cell_name ]; // Add class to cells (td) of hidden columns. cell.classList.toggle( 'column-hidden', Boolean( meta?.column_hidden ) ); // Add classes to row (tr) for hidden rows and head/foot row. Only needs to be done once per row, thus when processing the first column. if ( 0 === col_idx ) { cell.parentNode.classList.toggle( 'row-hidden', Boolean( meta?.row_hidden ) ); cell.parentNode.classList.remove( 'head-row', 'foot-row' ); // After processing the last row, potentially add classes to the head and foot rows. if ( row_idx === instance.jspreadsheet.rows.length - 1 ) { const visible_rows = instance.jspreadsheet.content.querySelectorAll( ':scope tbody tr:not(.row-hidden)' ); for ( let idx = 0; idx < tp.table.options.table_head; idx++ ) { visible_rows[ idx ]?.classList.add( 'head-row' ); } // Designating footer rows only makes sense for tables that have enough rows to show them. if ( visible_rows.length >= tp.table.options.table_head + tp.table.options.table_foot ) { const last_row_idx = visible_rows.length - 1; for ( let idx = 0; idx < tp.table.options.table_foot; idx++ ) { visible_rows[ last_row_idx - idx ]?.classList.add( 'foot-row' ); } } } } }; /** * [editor_oninsertroc description] * * Abbreviations: * roc: row or column * cor: column or row * * @param {[type]} type [description] * @param {[type]} action [description] * @param {[type]} el [description] * @param {[type]} roc_idx [description] * @param {[type]} num_rocs [description] * @param {[type]} roc_records [description] * @param {[type]} insertBefore [description] */ tp.callbacks.editor.oninsertroc = function ( type, action, el, roc_idx, num_rocs, roc_records, insertBefore ) { const handling_rows = ( 'rows' === type ); const property = handling_rows ? 'column_hidden' : 'row_hidden'; const duplicating = ( 'duplicate' === action ); const from_roc_idx = roc_idx + ( insertBefore ? num_rocs : 0 ); const num_cors = handling_rows ? tp.editor.options.columns.length : tp.editor.options.data.length; // Get data of row/column that is copied. const from_meta = {}; for ( let cor_idx = 0; cor_idx < num_cors; cor_idx++ ) { const cell_idx = handling_rows ? [ cor_idx, from_roc_idx ] : [ from_roc_idx, cor_idx ]; const meta = tp.editor.options.meta[ jspreadsheet.getColumnNameFromId( cell_idx ) ]; if ( ! meta ) { continue; } // When duplicating, copy full cell meta, otherwise only the necessary property (row visibility for columns, column visibility for rows). if ( duplicating ) { from_meta[ cor_idx ] = meta; } else if ( meta[ property ] ) { from_meta[ cor_idx ] = from_meta[ cor_idx ] || {}; from_meta[ cor_idx ][ property ] = true; } } const from_meta_keys = Object.keys( from_meta ); // Bail early if there's nothing to copy. if ( ! from_meta_keys.length ) { return; } // Construct meta data for target rows/columns. const to_meta = {}; if ( ! insertBefore ) { roc_idx++; // When appending (i.e. insert after), we start after the current row or column. } for ( let new_roc = 0; new_roc < num_rocs; new_roc++ ) { const to_roc_idx = roc_idx + new_roc; from_meta_keys.forEach( function ( cor_idx ) { const cell_idx = handling_rows ? [ cor_idx, to_roc_idx ] : [ to_roc_idx, cor_idx ]; to_meta[ jspreadsheet.getColumnNameFromId( cell_idx ) ] = from_meta[ cor_idx ]; } ); } tp.editor.setMeta( to_meta ); tp.editor.updateTable(); // Redraw table. }; /** * [editor_onmove description] * * @param {[type]} el [description] * @param {[type]} old_roc_idx [description] * @param {[type]} new_roc_idx [description] */ tp.callbacks.editor.onmove = function ( /* el, old_roc_idx, new_roc_idx */ ) { tp.helpers.editor.reselect(); tp.helpers.unsaved_changes.set(); }; /** * [editor_onsort description] * * @param {[type]} el [description] * @param {[type]} column [description] * @param {[type]} order [description] */ tp.callbacks.editor.onsort = function ( /* el, column, order */ ) { tp.editor.updateTable(); // Redraw table. tp.helpers.unsaved_changes.set(); }; /** * Copy the generated link or image HTML code from the helper textarea to the first selected table cell. */ tp.helpers.editor.insert_from_helper_textarea = function () { tp.editor.setValueFromCoords( tp.helpers.selection.columns[0], tp.helpers.selection.rows[0], this.value ); }; tp.callbacks.insert_link = {}; /** * Open the wpLink dialog for inserting links. * * @param {HTMLElement|null} $active_textarea Active textarea of the table editor or null. */ tp.callbacks.insert_link.open_dialog = function ( $active_textarea = null ) { const $helper_textarea = $( '#textarea-insert-helper' ); $helper_textarea.value = tp.editor.options.data[ tp.helpers.selection.rows[0] ][ tp.helpers.selection.columns[0] ]; if ( $active_textarea ) { $helper_textarea.selectionStart = $active_textarea.selectionStart; $helper_textarea.selectionEnd = $active_textarea.selectionEnd; } else { $helper_textarea.selectionStart = $helper_textarea.value.length; $helper_textarea.selectionEnd = $helper_textarea.value.length; } const cell_name = jexcel.getColumnNameFromId( [ tp.helpers.selection.columns[0], tp.helpers.selection.rows[0] ] ); $( '#link-modal-title' ).textContent = sprintf( __( 'Insert Link into cell %1$s', 'tablepress' ), cell_name ); wpLink.open( 'textarea-insert-helper' ); jexcel.current = null; // This is necessary to prevent problems with the focus when the "Insert Link" dialog is called from the context menu. }; tp.callbacks.insert_image = {}; /** * Open the WP Media library for inserting images. * * @param {HTMLElement|null} $active_textarea Active textarea of the table editor or null. */ tp.callbacks.insert_image.open_dialog = function ( $active_textarea = null ) { const $helper_textarea = $( '#textarea-insert-helper' ); $helper_textarea.value = tp.editor.options.data[ tp.helpers.selection.rows[0] ][ tp.helpers.selection.columns[0] ]; if ( $active_textarea ) { $helper_textarea.selectionStart = $active_textarea.selectionStart; $helper_textarea.selectionEnd = $active_textarea.selectionEnd; } else { $helper_textarea.selectionStart = $helper_textarea.value.length; $helper_textarea.selectionEnd = $helper_textarea.value.length; } wp.media.editor.open( 'textarea-insert-helper', { frame: 'post', state: 'insert', title: wp.media.view.l10n.addMedia, multiple: true, } ); const cell_name = jexcel.getColumnNameFromId( [ tp.helpers.selection.columns[0], tp.helpers.selection.rows[0] ] ); document.querySelector( '#media-frame-title h1' ).textContent = sprintf( __( 'Add media to cell %1$s', 'tablepress' ), cell_name ); jexcel.current = null; // This is necessary to prevent problems with the focus when the "Insert Link" dialog is called from the context menu. }; tp.callbacks.advanced_editor = {}; tp.callbacks.advanced_editor.$textarea = $( '#advanced-editor-content' ); /** * Open the wpdialog for the Advanced Editor. * * @param {HTMLElement|null} $active_textarea Active textarea of the table editor or null. */ tp.callbacks.advanced_editor.open_dialog = function ( $active_textarea = null ) { tp.callbacks.advanced_editor.$textarea.value = tp.editor.options.data[ tp.helpers.selection.rows[0] ][ tp.helpers.selection.columns[0] ]; const cell_name = jexcel.getColumnNameFromId( [ tp.helpers.selection.columns[0], tp.helpers.selection.rows[0] ] ); const title = sprintf( __( 'Advanced Editor for cell %1$s', 'tablepress' ), cell_name ); $( '#advanced-editor-label' ).textContent = title; // Screen reader label for the "Advanced Editor" textarea. $( '#link-modal-title' ).textContent = sprintf( __( 'Insert Link into cell %1$s', 'tablepress' ), cell_name ); jQuery( '#advanced-editor' ).wpdialog( { width: 600, modal: true, title, resizable: false, // Height of textarea does not increase when resizing editor height. closeOnEscape: true, buttons: [ { text: __( 'Cancel', 'tablepress' ), class: 'button button-cancel', click() { jQuery( this ).wpdialog( 'close' ); }, }, { text: __( 'OK', 'tablepress' ), class: 'button button-primary button-ok', click: tp.callbacks.advanced_editor.confirm_save, }, ], } ); jexcel.current = null; // This is necessary to prevent problems with the focus and cells being emptied when the Advanced Editor is called from the context menu. if ( $active_textarea ) { tp.callbacks.advanced_editor.$textarea.selectionStart = $active_textarea.selectionStart; tp.callbacks.advanced_editor.$textarea.selectionEnd = $active_textarea.selectionEnd; } else { tp.callbacks.advanced_editor.$textarea.selectionStart = tp.callbacks.advanced_editor.$textarea.value.length; tp.callbacks.advanced_editor.$textarea.selectionEnd = tp.callbacks.advanced_editor.$textarea.value.length; } tp.callbacks.advanced_editor.$textarea.focus(); }; /** * Confirm and save changes of the Advanced Editor. */ tp.callbacks.advanced_editor.confirm_save = function () { const current_value = tp.editor.options.data[ tp.helpers.selection.rows[0] ][ tp.helpers.selection.columns[0] ]; // Only set the cell content if changes were made to not wrongly call tp.helpers.unsaved_changes.set(). if ( tp.callbacks.advanced_editor.$textarea.value !== current_value ) { tp.editor.setValueFromCoords( tp.helpers.selection.columns[0], tp.helpers.selection.rows[0], tp.callbacks.advanced_editor.$textarea.value ); } jQuery( this ).wpdialog( 'close' ); }; /** * Inserts or duplicates rows or columns before each currently selected row/column. * * @param {string} action The action to perform on the selected rows/columns ("insert" or "duplicate"). * @param {string} type What to insert or duplicate ("rows" or "columns"). * @param {string} position Where to insert or duplicate ("before" or "after"). Default "before". */ tp.callbacks.insert_duplicate = function ( action, type, position = 'before' ) { const handling_rows = ( 'rows' === type ); const insert_function = handling_rows ? tp.editor.insertRow : tp.editor.insertColumn; const getData_function = handling_rows ? tp.editor.getRowData : tp.editor.getColumnData; const duplicating = ( 'duplicate' === action ); // Dynamically set the event handler, so that we have the action available in it. tp.editor.options[ handling_rows ? 'oninsertrow' : 'oninsertcolumn' ] = tp.callbacks.editor.oninsertroc.bind( null, type, action ); tp.helpers.selection[ type ].forEach( function ( roc_idx, array_idx ) { const shifted_roc_idx = roc_idx + array_idx; // Not having to deal with shifted indices is possible by looping through the reversed array, but that's likely slower. const data = duplicating ? getData_function( shifted_roc_idx ) : 1; const position_bool = 'before' === position; // true means "before". insert_function( data, shifted_roc_idx, position_bool ); } ); tp.helpers.unsaved_changes.set(); // Select both inserted/duplicated rows/columns if more than one were selected. const num_selected_rocs = tp.helpers.selection[ type ].length; if ( num_selected_rocs > 1 ) { tp.editor.updateSelectionFromCoords( tp.helpers.selection.columns[0], tp.helpers.selection.rows[0], handling_rows ? tp.helpers.selection.columns[ tp.helpers.selection.columns.length - 1 ] : tp.helpers.selection.columns[ tp.helpers.selection.columns.length - 1 ] + num_selected_rocs, handling_rows ? tp.helpers.selection.rows[ tp.helpers.selection.rows.length - 1 ] + num_selected_rocs : tp.helpers.selection.rows[ tp.helpers.selection.rows.length - 1 ] ); } }; /** * Removes currently selected rows or columns. * * @param {string} type What to remove ("rows" or "columns"). */ tp.callbacks.remove = function ( type ) { const handling_rows = 'rows' === type; const num_cors = handling_rows ? tp.editor.options.columns.length : tp.editor.options.data.length; const last_roc_idx = handling_rows ? tp.editor.options.data.length - 1 : tp.editor.options.columns.length - 1; // Visibility meta information has to be deleted manually, as otherwise the Jspreadsheet meta information can get out of sync. if ( tp.editor.options.meta ) { tp.helpers.selection[ type ].forEach( function ( roc_idx ) { for ( let cor_idx = 0; cor_idx < num_cors; cor_idx++ ) { const cell_idx = handling_rows ? [ cor_idx, roc_idx ] : [ roc_idx, cor_idx ]; delete tp.editor.options.meta[ jspreadsheet.getColumnNameFromId( cell_idx ) ]; } } ); } const delete_function = handling_rows ? tp.editor.deleteRow : tp.editor.deleteColumn; delete_function( tp.helpers.selection[ type ][0], tp.helpers.selection[ type ].length ); tp.helpers.unsaved_changes.set(); // Reselect last visible row/column, if last rows/columns were deleted. if ( last_roc_idx === tp.helpers.selection[ type ][ tp.helpers.selection[ type ].length - 1 ] ) { const col_idx = handling_rows ? tp.helpers.selection.columns[0] : tp.helpers.selection.columns[0] - 1; const row_idx = handling_rows ? tp.helpers.selection.rows[0] - 1 : tp.helpers.selection.rows[0]; tp.editor.updateSelectionFromCoords( col_idx, row_idx, col_idx, row_idx ); } }; /** * Appends rows or columns at the bottom or right end of the table. * * @param {string} type What to append ("rows" or "columns"). * @param {number} num_rocs Number of rows or columns to append. */ tp.callbacks.append = function ( type, num_rocs ) { const handling_rows = ( 'rows' === type ); const insert_function = handling_rows ? tp.editor.insertRow : tp.editor.insertColumn; // Dynamically set the event handler, so that we have the action available in it. tp.editor.options[ handling_rows ? 'oninsertrow' : 'oninsertcolumn' ] = tp.callbacks.editor.oninsertroc.bind( null, type, 'append' ); insert_function( num_rocs ); tp.helpers.unsaved_changes.set(); }; /** * Moves currently selected rows or columns. * * @param {string} direction Where to move the selected rows or columns (for rows: "up"/"down"/"top"/"bottom", for columns: "left"/right"/"first"/"last"). * @param {string} type What to move ("rows" or "columns"). */ tp.callbacks.move = function ( direction, type ) { const handling_rows = ( 'rows' === type ); // Default case: up/left let rocs = tp.helpers.selection[ type ]; // When moving up or left, start with the first row/column of the selected range. let position_difference = -1; // New row/column number is one smaller than current row/column number. // Alternate case: down/right if ( 'down' === direction || 'right' === direction ) { rocs = rocs.slice().reverse(); // When moving down or right, reverse the order, to start with the last row/column of the selected range. slice() is needed here to create an array copy. position_difference = 1; // New row/column number is one higher than current row/column number. } else if ( 'top' === direction || 'first' === direction ) { position_difference = -rocs[0]; } else if ( 'bottom' === direction || 'last' === direction ) { rocs = rocs.slice().reverse(); // When moving down or right, reverse the order, to start with the last row/column of the selected range. slice() is needed here to create an array copy. const min_max_roc = ( 'rows' === type ) ? tp.editor.options.data.length - 1 : tp.editor.options.columns.length - 1; position_difference = min_max_roc - rocs[0]; } // Bail early if there is nothing to do (e.g. when the selected range is already at the target edge). if ( 0 === position_difference ) { return; } // Move the selected rows/columns individually. const move_function = handling_rows ? tp.editor.moveRow : tp.editor.moveColumn; rocs.forEach( ( roc_idx ) => move_function( roc_idx, roc_idx + position_difference ) ); tp.helpers.unsaved_changes.set(); // Reselect moved selection. tp.editor.updateSelectionFromCoords( handling_rows ? tp.helpers.selection.columns[0] : tp.helpers.selection.columns[0] + position_difference, handling_rows ? tp.helpers.selection.rows[0] + position_difference : tp.helpers.selection.rows[0], handling_rows ? tp.helpers.selection.columns[ tp.helpers.selection.columns.length - 1 ] : tp.helpers.selection.columns[ tp.helpers.selection.columns.length - 1 ] + position_difference, handling_rows ? tp.helpers.selection.rows[ tp.helpers.selection.rows.length - 1 ] + position_difference : tp.helpers.selection.rows[ tp.helpers.selection.rows.length - 1 ] ); }; /** * Sorts the table data by the first currently selected column. * * @param {string} direction Sort order/direction ("asc" for ascending, "desc" for descending). */ tp.callbacks.sort = function ( direction ) { tp.editor.orderBy( tp.helpers.selection.columns[0], ( 'desc' === direction ) ); }; /** * Hides or unhides selected rows or columns. * * @param {string} action The action to perform on the rows/columns ("hide" or "unhide"). * @param {string} type What to hide or unhide ("rows" or "columns"). */ tp.callbacks.hide_unhide = function ( action, type ) { const handling_rows = ( 'rows' === type ); const property = handling_rows ? 'row_hidden' : 'column_hidden'; const num_cors = handling_rows ? tp.editor.options.columns.length : tp.editor.options.data.length; const cell_hidden = ( 'hide' === action ); const meta = {}; tp.helpers.selection[ type ].forEach( function ( roc_idx ) { for ( let cor_idx = 0; cor_idx < num_cors; cor_idx++ ) { const cell_idx = handling_rows ? [ cor_idx, roc_idx ] : [ roc_idx, cor_idx ]; const cell_name = jspreadsheet.getColumnNameFromId( cell_idx ); meta[ cell_name ] = {}; meta[ cell_name ][ property ] = cell_hidden; } } ); tp.editor.setMeta( meta ); tp.helpers.unsaved_changes.set(); tp.editor.updateTable(); // Redraw table. }; /** * Combines/merges the currently selected cells. */ tp.callbacks.merge_cells = function () { const current_col_idx = tp.helpers.selection.columns[0]; const current_row_idx = tp.helpers.selection.rows[0]; const colspan = tp.helpers.selection.columns.length; const rowspan = tp.helpers.selection.rows.length; for ( let row_idx = 1; row_idx < rowspan; row_idx++ ) { tp.editor.setValueFromCoords( current_col_idx, current_row_idx + row_idx, '#rowspan#' ); } for ( let col_idx = 1; col_idx < colspan; col_idx++ ) { tp.editor.setValueFromCoords( current_col_idx + col_idx, current_row_idx, '#colspan#' ); } for ( let row_idx = 1; row_idx < rowspan; row_idx++ ) { for ( let col_idx = 1; col_idx < colspan; col_idx++ ) { tp.editor.setValueFromCoords( current_col_idx + col_idx, current_row_idx + row_idx, '#span#' ); } } tp.helpers.unsaved_changes.set(); }; /* * Initialize Jspreadsheet. */ tp.editor = jspreadsheet( $( '#table-editor' ), { data: tp.table.data, meta: tp.helpers.visibility.load(), wordWrap: true, rowDrag: true, rowResize: true, columnSorting: true, columnDrag: true, columnResize: true, defaultColWidth: tp.screenOptions.table_editor_column_width, defaultColAlign: 'left', parseFormulas: false, allowExport: false, allowComments: false, allowManualInsertRow: false, // To prevent addition of new row when Enter is pressed in last row. allowManualInsertColumn: false, // To prevent addition of new column when Tab is pressed in last column. about: false, secureFormulas: false, detachForUpdates: true, onselection: tp.callbacks.editor.onselection, updateTable: tp.callbacks.editor.onupdatetable, contextMenu, sorting: tp.helpers.editor.sorting, // Keep the selection when certain events occur and the table loses focus. onmoverow: tp.callbacks.editor.onmove, onmovecolumn: tp.callbacks.editor.onmove, onblur: tp.helpers.editor.reselect, onload: tp.helpers.editor.reselect, // When the table is loaded, select the top-left cell A1. onchange: tp.helpers.unsaved_changes.set, onsort: tp.callbacks.editor.onsort, } ); // Register callback for inserting a link into a cell after it has been constructed in the wpLink dialog. jQuery( '#textarea-insert-helper' ).on( 'change', tp.helpers.editor.insert_from_helper_textarea ); // This must use jQuery, as wpLink triggers jQuery events, which can not be observed by native JS listeners. // This code requires jQuery, and it must run when the DOM is ready. jQuery( () => { // Fix issue with wpLink input fields not being usable, when called through the "Advanced Editor". They are immediately losing focus without this. jQuery( '#wp-link' ).on( 'focus', 'input', ( event ) => ( event.stopPropagation() ) ); // Fix issue with Media Library input fields in the sidebar not being usable, when called through the "Advanced Editor". They are immediately losing focus without this. jQuery( 'body' ).on( 'focus', '.media-modal .media-frame-content input, .media-modal .media-frame-content textarea', ( event ) => ( event.stopPropagation() ) ); } ); table-preview.js000064400000003251150212675370007661 0ustar00/** * JavaScript code for the "Edit" section integration of the "Table Preview". * * @package TablePress * @subpackage Views JavaScript * @author Tobias Bäthge * @since 3.1.0 */ /** * WordPress dependencies. */ import { Icon, Modal, } from '@wordpress/components'; import { TablePressIcon } from '../../img/tablepress-icon'; import { __, sprintf } from '@wordpress/i18n'; /** * Internal dependencies. */ import { initializeReactComponentInPortal } from '../common/react-loader'; const Section = ( { screenData, updateScreenData, tableMeta } ) => { // Bail early if the current user can't preview the table or the preview is not open. if ( ! tp.screenOptions.currentUserCanPreviewTable || ! screenData.previewIsOpen ) { return <>; } // Get the table name, and use "(no name)" if the table has no name. let tableName = tableMeta.name; if ( '' === tableName.trim() ) { tableName = __( '(no name)', 'tablepress' ); } const title = sprintf( __( 'Preview of table “%1$s” (ID %2$s)', 'tablepress' ), tableName, tableMeta.id ); return ( } title={ title } className="table-preview-modal" onRequestClose={ () => updateScreenData( { previewIsOpen: false, previewSrcDoc: '' } ) } isFullScreen={ true } // Using size="full" is only possible in WP 6.5+. >