Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions class-wp-press-this-plugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -1550,6 +1550,8 @@ public function html() {
'postId' => $post_ID,
'title' => $post_title,
'content' => $post_content,
'postStatus' => $post->post_status,
'postDate' => mysql_to_rfc3339( $post->post_date ),
'nonce' => wp_create_nonce( 'update-post_' . $post_ID ),
'categoryNonce' => wp_create_nonce( 'add-category' ),

Expand Down Expand Up @@ -1613,6 +1615,7 @@ public function html() {
// Config.
'redirInParent' => $site_settings['redirInParent'],
'isRTL' => is_rtl(),
'timezone' => wp_timezone_string(),

// Allowed blocks.
'allowedBlocks' => $this->get_allowed_blocks(),
Expand Down
1 change: 1 addition & 0 deletions includes/class-press-this-assets.php
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ public function get_script_dependencies() {
'wp-format-library',
'wp-html-entities',
'wp-i18n',
'wp-keyboard-shortcuts',
'wp-primitives',
);
}
Expand Down
45 changes: 44 additions & 1 deletion press-this-plugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,25 @@ function press_this_register_rest_routes() {
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field',
'default' => 'draft',
'enum' => array( 'draft', 'publish' ),
'enum' => array( 'draft', 'publish', 'future' ),
),
'date' => array(
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field',
'validate_callback' => function ( $value ) {
if ( empty( $value ) ) {
return true;
}
if ( ! preg_match( '/^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}(:\d{2})?$/', $value ) ) {
return new WP_Error(
'press_this_invalid_date_format',
__( 'Date must be in ISO 8601 format.', 'press-this' ),
array( 'status' => 400 )
);
}
return true;
},
'default' => '',
),
'format' => array(
'type' => 'string',
Expand Down Expand Up @@ -342,6 +360,31 @@ function press_this_rest_save_post( $request ) {
}
}

// Handle future (scheduled) status.
if ( 'future' === $status ) {
if ( current_user_can( 'publish_posts' ) ) {
$date = $request->get_param( 'date' );
if ( empty( $date ) || false === strtotime( $date ) ) {
return new WP_Error(
'press_this_invalid_date',
__( 'A valid date is required to schedule a post.', 'press-this' ),
array( 'status' => 400 )
);
}
// The frontend sends a naive datetime in the site's local timezone (no TZ qualifier).
// WordPress sets PHP's timezone to UTC, so strtotime() interprets the string as UTC
// and gmdate() formats it back as UTC -- the round-trip preserves the original value.
// The result is the site-local time string we need for post_date.
$post_data['post_date'] = gmdate( 'Y-m-d H:i:s', strtotime( $date ) );
$post_data['post_date_gmt'] = get_gmt_from_date( $post_data['post_date'] );
$post_data['post_status'] = 'future';
// Required: wp_update_post ignores post_date changes unless edit_date is true.
$post_data['edit_date'] = true;
} else {
$post_data['post_status'] = 'pending';
}
}

// Side-load images from content.
// Require admin includes needed by media_sideload_image() (not loaded in REST context).
require_once ABSPATH . 'wp-admin/includes/file.php';
Expand Down
25 changes: 25 additions & 0 deletions src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
/**
* Main App component.
*
* @return {JSX.Element} The App component.

Check failure on line 35 in src/App.js

View workflow job for this annotation

GitHub Actions / Code Linting

The type 'JSX' is undefined
*/
export default function App() {
const data = useMemo( () => getInitialData(), [] );
Expand All @@ -47,6 +47,10 @@
publishLabel: __( 'Publish', 'press-this' ),
} );

// Post status and date as React state so they update after scheduling.
const [ postStatus, setPostStatus ] = useState( () => data.postStatus || '' );

Check failure on line 51 in src/App.js

View workflow job for this annotation

GitHub Actions / Code Linting

Replace `·()·=>·data.postStatus·||·''·` with `⏎↹↹()·=>·data.postStatus·||·''⏎↹`
const [ postDate, setPostDate ] = useState( () => data.postDate || '' );

// Build initial post object for editor.
const post = useMemo(
() => ( {
Expand Down Expand Up @@ -316,6 +320,21 @@
setSaveState( state );
}, [] );

/**

Check failure on line 323 in src/App.js

View workflow job for this annotation

GitHub Actions / Code Linting

Expected JSDoc block lines to be aligned
* Handle post status changes from PressThisEditor after a successful save.
* Updates local state so the Header reflects the new status (e.g., "Reschedule").
*
* @param {Object} change Status change details.
* @param {string} change.status New post status.
* @param {string} change.date New post date (ISO 8601).
*/
const handlePostStatusChange = useCallback( ( change ) => {
setPostStatus( change.status );
if ( change.date ) {
setPostDate( change.date );
}
}, [] );

// State for undo/redo from editor.
// Split into primitive values so React can skip re-renders when values
// haven't changed (Object.is comparison). The handlers are stable refs
Expand Down Expand Up @@ -360,6 +379,10 @@
onRedo={ redoHandler }
hasUndo={ hasUndo }
hasRedo={ hasRedo }
capabilities={ capabilities }
timezone={ data.timezone }
postStatus={ postStatus }
postDate={ postDate }
/>

<div className="press-this-app__body">
Expand All @@ -377,6 +400,8 @@
onScrapeProcessed={ handleScrapeProcessed }
onSaveReady={ handleSaveReady }
onUndoReady={ handleUndoReady }
timezone={ data.timezone }
onPostStatusChange={ handlePostStatusChange }
categoryNonce={ data.categoryNonce || '' }
ajaxUrl={ data.ajaxUrl || '' }
/>
Expand Down
Loading
Loading