Skip to content

Commit 1755b42

Browse files
authored
Merge pull request #426 from alleyinteractive/feature/GH-399/scheduled-posts
Allow pinning scheduled posts
2 parents fbc0cd3 + 8418afe commit 1755b42

File tree

9 files changed

+223
-14
lines changed

9 files changed

+223
-14
lines changed

CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@
22

33
All notable changes to `WP Curate` will be documented in this file.
44

5-
## 3.0.0
5+
## Unreleased
6+
7+
- Enhancement: Enable pinning scheduled posts via `wp_curate_include_future_posts` filter.
8+
9+
## 3.0.0 - 2025-10-31
610

711
- Support for previews of Query block and Patterns.
812
- Pattern/Variation picker when inserting a Query block.

blocks/post/edit.tsx

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
import { useState } from 'react';
22
import classnames from 'classnames';
3+
import type { WP_REST_API_Post as WpRestApiPost } from 'wp-types'; // eslint-disable-line camelcase
4+
35
// @ts-expect-error BlockContextProvider not available in types yet.
46
import { InnerBlocks, useBlockProps, BlockContextProvider } from '@wordpress/block-editor';
5-
import { PostPicker } from '@alleyinteractive/block-editor-tools';
7+
import { PostPicker, usePostById } from '@alleyinteractive/block-editor-tools';
68
import { dispatch, select, useSelect } from '@wordpress/data';
79
import { __ } from '@wordpress/i18n';
8-
import { Button } from '@wordpress/components';
10+
import { Button, Notice } from '@wordpress/components';
911
import { useCallback } from '@wordpress/element';
1012

1113
import type { Block } from '../../types/block';
1214
import NoRender from './norender';
1315
import SearchFilters from '../../components/SearchFilters';
1416
import recursivelyFindBlocksByName from '../../services/recursivelyFindBlocksByName';
17+
import { postTypeWithFuture } from '../../services/utils';
1518

1619
import type {
1720
Term,
@@ -47,6 +50,7 @@ interface PostTypeOrTerm {
4750
interface Window {
4851
wpCurateQueryBlock: {
4952
allowedPostTypes: PostTypeOrTerm[];
53+
includeFuturePosts: boolean;
5054
};
5155
}
5256

@@ -73,6 +77,7 @@ export default function Edit({
7377
const {
7478
wpCurateQueryBlock: {
7579
allowedPostTypes = [],
80+
includeFuturePosts,
7681
} = {},
7782
} = (window as any as Window);
7883

@@ -282,6 +287,13 @@ export default function Edit({
282287

283288
const shouldShowFilter = displayTypes.length !== postTypes.length
284289
|| Object.values(terms).some((termList) => Array.isArray(termList) && termList.length > 0);
290+
291+
const postObj = usePostById(
292+
postId,
293+
// @ts-ignore This function does work with this argument.
294+
includeFuturePosts ? postTypeWithFuture : null,
295+
) as WpRestApiPost | null;
296+
285297
return (
286298
<div
287299
{...useBlockProps(
@@ -296,6 +308,15 @@ export default function Edit({
296308
},
297309
)}
298310
>
311+
{typeof postObj === 'object' && postObj !== null && 'status' in postObj && postObj.status === 'future' ? (
312+
<Notice
313+
status="warning"
314+
isDismissible={false}
315+
>
316+
{__('Scheduled', 'wp-curate')}
317+
</Notice>
318+
) : null}
319+
299320
<BlockContextProvider value={{ postId }}>
300321
<InnerBlocks />
301322
</BlockContextProvider>
@@ -314,6 +335,8 @@ export default function Edit({
314335
onUpdate={updatePost}
315336
onReset={resetPost}
316337
value={selected ?? 0}
338+
// @ts-ignore This function does work with this prop.
339+
getPostType={includeFuturePosts ? postTypeWithFuture : null}
317340
previewRender={(NoRender)}
318341
className="wp-curate-post-block__post-picker"
319342
selectText={__('Pin a Post', 'wp-curate')}
@@ -326,7 +349,10 @@ export default function Edit({
326349
setFiltered={setFiltered}
327350
/>
328351
)}
329-
params={params}
352+
params={{
353+
...params,
354+
wp_curate_include_future: includeFuturePosts,
355+
}}
330356
/>
331357
{
332358
// If this post isn't already in the posts list, show a button to pin it.

blocks/query/edit.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ interface Window {
4040
allowedTaxonomies: PostTypeOrTerm[];
4141
parselyAvailable: string,
4242
maxPosts: string,
43+
includeFuturePosts: boolean,
4344
};
4445
}
4546

@@ -82,6 +83,7 @@ export default function Edit({
8283
allowedTaxonomies = [],
8384
parselyAvailable = 'false',
8485
maxPosts = '10',
86+
includeFuturePosts,
8587
} = {},
8688
} = (window as any as Window);
8789

@@ -228,6 +230,7 @@ export default function Edit({
228230
per_page: postsToInclude.length,
229231
type: postTypeString,
230232
include: postsToInclude,
233+
status: includeFuturePosts ? ['publish', 'future'] : 'publish',
231234
_locale: 'user',
232235
context: 'edit',
233236
},
@@ -244,6 +247,7 @@ export default function Edit({
244247
manualPosts,
245248
setAttributes,
246249
postTypeString,
250+
includeFuturePosts,
247251
]);
248252

249253
// When numberOfPosts changes, update manualPosts array.

blocks/query/index.php

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ function wp_curate_query_block_init(): void {
6060
'wp-curate-query-editor-script',
6161
'wpCurateQueryBlock',
6262
[
63-
'allowedPostTypes' => array_filter(
63+
'allowedPostTypes' => array_filter(
6464
array_map(
6565
function ( $slug ) {
6666
$post_type_object = get_post_type_object( $slug );
@@ -77,7 +77,7 @@ function ( $slug ) {
7777
$allowed_post_types
7878
)
7979
),
80-
'allowedTaxonomies' => array_filter(
80+
'allowedTaxonomies' => array_filter(
8181
array_map(
8282
function ( $slug ) {
8383
$taxonomy = get_taxonomy( $slug );
@@ -95,16 +95,16 @@ function ( $slug ) {
9595
$allowed_taxonomies,
9696
),
9797
),
98-
'parselyAvailable' => $parsely_available ? 'true' : 'false',
99-
'maxPosts' => $max_posts,
98+
'parselyAvailable' => $parsely_available ? 'true' : 'false',
99+
'maxPosts' => $max_posts,
100100
/**
101101
* Filters the order by options shown in the sidebar of the query block.
102102
*
103103
* @param array<string, string> $options The order by options as value => label pairs.
104104
*
105105
* @since 2.6.4
106106
*/
107-
'rawOrderByOptions' => apply_filters( 'wp_curate_order_by_options', [
107+
'rawOrderByOptions' => apply_filters( 'wp_curate_order_by_options', [
108108
'date' => __( 'Date', 'wp-curate' ),
109109
'title' => __( 'Title', 'wp-curate' ),
110110
] ),
@@ -115,8 +115,16 @@ function ( $slug ) {
115115
*
116116
* @since 2.6.4
117117
*/
118-
'orderByMetaKeys' => apply_filters( 'wp_curate_order_by_meta_keys', [] ),
119-
]
118+
'orderByMetaKeys' => apply_filters( 'wp_curate_order_by_meta_keys', [] ),
119+
/**
120+
* Filters whether to allow scheduled posts to be selected in the post picker.
121+
*
122+
* @since 3.1.0
123+
*
124+
* @param bool $include_future_posts Whether to include scheduled posts.
125+
*/
126+
'includeFuturePosts' => apply_filters( 'wp_curate_include_future_posts', false ),
127+
],
120128
);
121129
}
122130
add_action( 'init', 'wp_curate_query_block_init', 900 );

blocks/subquery/edit.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ interface Window {
3737
allowedTaxonomies: PostTypeOrTerm[];
3838
parselyAvailable: string,
3939
maxPosts: number,
40+
includeFuturePosts: boolean,
4041
};
4142
}
4243

@@ -86,6 +87,7 @@ export default function Edit({
8687
allowedTaxonomies = [],
8788
parselyAvailable = 'false',
8889
maxPosts = 10,
90+
includeFuturePosts,
8991
} = {},
9092
} = (window as any as Window);
9193

@@ -225,6 +227,7 @@ export default function Edit({
225227
per_page: postsToInclude.length,
226228
type: postTypeString,
227229
include: postsToInclude,
230+
status: includeFuturePosts ? ['publish', 'future'] : 'publish',
228231
_locale: 'user',
229232
context: 'edit',
230233
},
@@ -248,7 +251,7 @@ export default function Edit({
248251
};
249252

250253
updateValidPosts();
251-
}, [isFirstPost, manualPosts, postTypeString, setAttributes]);
254+
}, [includeFuturePosts, isFirstPost, manualPosts, postTypeString, setAttributes]);
252255

253256
/**
254257
* Check if deduplication is needed when validPosts are available.

components/QueryControls/index.tsx

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Fragment, useState } from 'react';
2-
import { PostPicker, TermSelector, Checkboxes } from '@alleyinteractive/block-editor-tools';
32
import classnames from 'classnames';
3+
4+
import { PostPicker, TermSelector, Checkboxes } from '@alleyinteractive/block-editor-tools';
45
import {
56
PanelBody,
67
PanelRow,
@@ -15,7 +16,9 @@ import { createInterpolateElement } from '@wordpress/element';
1516
import { __, sprintf } from '@wordpress/i18n';
1617
import { useDispatch } from '@wordpress/data';
1718
import { store as noticesStore } from '@wordpress/notices';
19+
1820
import SearchFilters from '../SearchFilters';
21+
import { postTypeWithFuture } from '../../services/utils';
1922

2023
import type {
2124
Option,
@@ -31,6 +34,7 @@ interface Window {
3134
wpCurateQueryBlock: {
3235
rawOrderByOptions: Record<string, string>;
3336
orderByMetaKeys: string[];
37+
includeFuturePosts: boolean;
3438
};
3539
}
3640

@@ -100,6 +104,7 @@ export default function QueryControls({
100104
date: __('Date', 'wp-curate'),
101105
},
102106
orderByMetaKeys = [],
107+
includeFuturePosts,
103108
} = {},
104109
} = (window as any as Window);
105110

@@ -204,6 +209,7 @@ export default function QueryControls({
204209

205210
const shouldShowFilter = displayTypes.length !== postTypes.length
206211
|| Object.values(terms).some((termList) => Array.isArray(termList) && termList.length > 0);
212+
207213
return (
208214
<>
209215
<InspectorControls>
@@ -254,14 +260,19 @@ export default function QueryControls({
254260
onUpdate={(id: number) => { setManualPost(id, index); }}
255261
value={manualPosts[index] || 0}
256262
className="manual-posts__picker"
263+
// @ts-ignore This function does work with this prop.
264+
getPostType={includeFuturePosts ? postTypeWithFuture : null}
257265
filters={(
258266
<SearchFilters
259267
shouldShowFilter={shouldShowFilter}
260268
filtered={filtered}
261269
setFiltered={setFiltered}
262270
/>
263271
)}
264-
params={params}
272+
params={{
273+
...params,
274+
wp_curate_include_future: Number(includeFuturePosts),
275+
}}
265276
/>
266277
</PanelRow>
267278
))}

services/deduplicate/index.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@ import { store as blockEditorStore } from '@wordpress/block-editor';
33
import type { Block } from '../../types/block';
44
import recursivelyFindBlocksByName from '../recursivelyFindBlocksByName';
55

6+
interface Window {
7+
wpCurateQueryBlock: {
8+
includeFuturePosts: boolean;
9+
};
10+
}
11+
612
const usedIds = new Map();
713
const curatedIds = new Map();
814

@@ -83,6 +89,12 @@ export function mainDedupe() {
8389
return;
8490
}
8591

92+
const {
93+
wpCurateQueryBlock: {
94+
includeFuturePosts,
95+
} = {},
96+
} = (window as any as Window);
97+
8698
running = true;
8799
// Clear the flag for another run.
88100
redo = false;
@@ -202,6 +214,7 @@ export function mainDedupe() {
202214
type: postTypeString,
203215
include: templateIds.join(','),
204216
orderby: 'include',
217+
wp_curate_include_future: includeFuturePosts,
205218
},
206219
queryId: 0,
207220
},

services/utils.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@
55
* @link https://github.com/WordPress/gutenberg/blob/trunk/packages/block-library/src/query/utils.js
66
*/
77

8+
import apiFetch from '@wordpress/api-fetch';
89
import { useSelect } from '@wordpress/data';
910
import { useMemo } from '@wordpress/element';
1011
import { store as blockEditorStore } from '@wordpress/block-editor';
1112
import {
1213
cloneBlock,
1314
store as blocksStore,
1415
} from '@wordpress/blocks';
16+
import { addQueryArgs } from '@wordpress/url';
1517

1618
import type { BlockInstance, BlockVariation } from '@wordpress/blocks';
1719
import type { BlockPattern } from '../blocks/query/types';
@@ -170,3 +172,32 @@ export const usePatterns = (clientId: string, name: string): BlockPattern[] => u
170172
},
171173
[name, clientId],
172174
);
175+
176+
/**
177+
* Custom function for use with usePostById to get the post type that includes scheduled posts.
178+
*
179+
* @param {number} postId The post ID.
180+
* @return {Promise<string|null>} The post type or null if not found.
181+
*/
182+
export const postTypeWithFuture = async (postId: number) => {
183+
let type = null;
184+
185+
const path = addQueryArgs('/wp/v2/search', {
186+
include: postId,
187+
wp_curate_include_future: 1,
188+
});
189+
190+
const results = await apiFetch({ path });
191+
192+
if (
193+
Array.isArray(results)
194+
&& results.length > 0
195+
&& typeof results[0] === 'object'
196+
&& results[0] !== null
197+
&& 'subtype' in results[0]
198+
) {
199+
type = results[0].subtype;
200+
}
201+
202+
return type;
203+
};

0 commit comments

Comments
 (0)