{
+ setAttributes( {
+ query: {
+ ...attributes.query,
+ perPage: newCount,
+ offset,
+ },
+ } );
+ } }
+ value={ perPage }
+ />
+ );
+};
diff --git a/src/variations/event-query/components/event-exclude-controls.js b/src/variations/event-query/components/event-exclude-controls.js
new file mode 100644
index 000000000..9e507b768
--- /dev/null
+++ b/src/variations/event-query/components/event-exclude-controls.js
@@ -0,0 +1,40 @@
+/**
+ * WordPress dependencies
+ */
+import { ToggleControl } from '@wordpress/components';
+import { useSelect } from '@wordpress/data';
+import { __ } from '@wordpress/i18n';
+
+/**
+ * A component that lets you exclude the current event from the query
+ *
+ * @return {Element} EventExcludeControls
+ */
+export const EventExcludeControls = ( { attributes, setAttributes } ) => {
+ const { query: { exclude_current: excludeCurrent } = {} } = attributes;
+
+ const currentPost = useSelect( ( select ) => {
+ return select( 'core/editor' ).getCurrentPost();
+ }, [] );
+
+ if ( ! currentPost ) {
+ return { __( 'Loading…', 'gatherpress' ) }
;
+ }
+
+ return (
+ <>
+ {
+ setAttributes( {
+ query: {
+ ...attributes.query,
+ exclude_current: value ? currentPost.id : 0,
+ },
+ } );
+ } }
+ />
+ >
+ );
+};
diff --git a/src/variations/event-query/components/event-include-unfinished-controls.js b/src/variations/event-query/components/event-include-unfinished-controls.js
new file mode 100644
index 000000000..45e68d4d8
--- /dev/null
+++ b/src/variations/event-query/components/event-include-unfinished-controls.js
@@ -0,0 +1,36 @@
+/**
+ * WordPress dependencies
+ */
+import { ToggleControl } from '@wordpress/components';
+import { __, _x, sprintf } from '@wordpress/i18n';
+
+/**
+ * A component that lets you include the current event from the query
+ *
+ * @return {Element} EventIncludeUnfinishedControls
+ */
+export const EventIncludeUnfinishedControls = ( { attributes, setAttributes } ) => {
+ const { query: { include_unfinished: includeUnfinished } = {} } = attributes;
+
+ return (
+ <>
+ {
+ setAttributes( {
+ query: {
+ ...attributes.query,
+ include_unfinished: value ? 1 : 0,
+ },
+ } );
+ } }
+ />
+ >
+ );
+};
diff --git a/src/variations/event-query/components/event-list-type-controls.js b/src/variations/event-query/components/event-list-type-controls.js
new file mode 100644
index 000000000..523418ad4
--- /dev/null
+++ b/src/variations/event-query/components/event-list-type-controls.js
@@ -0,0 +1,46 @@
+/**
+ * WordPress dependencies
+ */
+import { ToggleControl } from '@wordpress/components';
+import { useSelect } from '@wordpress/data';
+import { __, _x, sprintf } from '@wordpress/i18n';
+
+/**
+ * A component that lets you pick posts to be excluded from the query
+ *
+ * @return {Element} EventListTypeControls
+ */
+export const EventListTypeControls = ( { attributes, setAttributes } ) => {
+ const { query: { gatherpress_events_query: eventListType = 'upcoming' } = {} } = attributes;
+
+ const currentPost = useSelect( ( select ) => {
+ return select( 'core/editor' ).getCurrentPost();
+ }, [] );
+
+ if ( ! currentPost ) {
+ return { __( 'Loading…', 'gatherpress' ) }
;
+ }
+
+ return (
+ <>
+ {/* { __( 'Type of event list', 'gatherpress' ) }
*/}
+ {
+ setAttributes( {
+ query: {
+ ...attributes.query,
+ gatherpress_events_query: value ? 'upcoming' : 'past',
+ },
+ } );
+ } }
+ />
+ >
+ );
+};
diff --git a/src/variations/event-query/components/event-offset-controls.js b/src/variations/event-query/components/event-offset-controls.js
new file mode 100644
index 000000000..4ca8d7f46
--- /dev/null
+++ b/src/variations/event-query/components/event-offset-controls.js
@@ -0,0 +1,26 @@
+/**
+ * WordPress dependencies
+ */
+
+import { RangeControl } from '@wordpress/components';
+import { __ } from '@wordpress/i18n';
+
+export const EventOffsetControls = ( { attributes, setAttributes } ) => {
+ const { query: { offset = 0 } = {} } = attributes;
+ return (
+ {
+ setAttributes( {
+ query: {
+ ...attributes.query,
+ offset: newOffset,
+ },
+ } );
+ } }
+ />
+ );
+};
diff --git a/src/variations/event-query/components/event-order-controls.js b/src/variations/event-query/components/event-order-controls.js
new file mode 100644
index 000000000..606f9d319
--- /dev/null
+++ b/src/variations/event-query/components/event-order-controls.js
@@ -0,0 +1,95 @@
+/**
+ * WordPress dependencies
+ */
+import { SelectControl, ToggleControl } from '@wordpress/components';
+import { __ } from '@wordpress/i18n';
+
+/**
+ * EventOrderControls component
+ *
+ * @param {*} param0
+ * @return {Element} EventCountControls
+ */
+export const EventOrderControls = ( { attributes, setAttributes } ) => {
+ const { query: { order, orderBy } = {} } = attributes;
+ const label = order === 'asc' ? __( 'Ascending Order', 'gatherpress' ) : __( 'Descending Order', 'gatherpress' );
+ return (
+ <>
+ {
+ setAttributes( {
+ query: {
+ ...attributes.query,
+ orderBy: newOrderBy,
+ },
+ } );
+ } }
+ />
+ {
+ setAttributes( {
+ query: {
+ ...attributes.query,
+ order: order === 'asc' ? 'desc' : 'asc',
+ },
+ } );
+ } }
+ />
+ >
+ );
+};
diff --git a/src/variations/event-query/components/post-date-query-controls.js b/src/variations/event-query/components/post-date-query-controls.js
new file mode 100644
index 000000000..27dba0c93
--- /dev/null
+++ b/src/variations/event-query/components/post-date-query-controls.js
@@ -0,0 +1,117 @@
+/**
+ * WordPress dependencies
+ */
+import {
+ DatePicker,
+ SelectControl,
+ CheckboxControl,
+} from '@wordpress/components';
+import { __ } from '@wordpress/i18n';
+
+export const PostDateQueryControls = ( { attributes, setAttributes } ) => {
+ const {
+ query: {
+ date_query: {
+ relation: relationFromQuery = '',
+ date_primary: datePrimary = new Date(),
+ date_secondary: dateSecondary = new Date(),
+ inclusive: isInclusive = false,
+ } = {},
+ } = {},
+ } = attributes;
+
+ return (
+ <>
+ { __( 'Post Date Query', 'gatherpress-query-loop' ) }
+ {
+ setAttributes( {
+ query: {
+ ...attributes.query,
+ date_query:
+ relation !== ''
+ ? {
+ ...attributes.query.date_query,
+ relation,
+ }
+ : '',
+ },
+ } );
+ } }
+ />
+ { relationFromQuery !== '' && (
+ <>
+ { relationFromQuery === 'between' && (
+ { __( 'Start date', 'gatherpress-query-loop' ) }
+ ) }
+ {
+ setAttributes( {
+ query: {
+ ...attributes.query,
+ date_query: {
+ ...attributes.query.date_query,
+ date_primary: newDate,
+ },
+ },
+ } );
+ } }
+ />
+
+ { relationFromQuery === 'between' && (
+ <>
+ { __( 'End date', 'gatherpress-query-loop' ) }
+ {
+ setAttributes( {
+ query: {
+ ...attributes.query,
+ date_query: {
+ ...attributes.query.date_query,
+ date_secondary: newDate,
+ },
+ },
+ } );
+ } }
+ />
+ >
+ ) }
+
+
+ {
+ setAttributes( {
+ query: {
+ ...attributes.query,
+ date_query: {
+ ...attributes.query.date_query,
+ inclusive: newIsInclusive,
+ },
+ },
+ } );
+ } }
+ />
+ >
+ ) }
+ >
+ );
+};
diff --git a/src/variations/event-query/controls.js b/src/variations/event-query/controls.js
new file mode 100644
index 000000000..aa07e43ba
--- /dev/null
+++ b/src/variations/event-query/controls.js
@@ -0,0 +1,156 @@
+/**
+ * WordPress dependencies
+ */
+import { addFilter } from '@wordpress/hooks';
+import { InspectorControls } from '@wordpress/block-editor';
+import { PanelBody } from '@wordpress/components';
+import { __ } from '@wordpress/i18n';
+
+
+import { useEffect } from '@wordpress/element';
+/**
+ * Internal dependencies
+ */
+import { NAME } from '.';
+import GPQLControls from './slots/gpql-controls';
+import GPQLControlsInheritedQuery from './slots/gpql-controls-inherited-query';
+import { EventCountControls } from './components/event-count-controls';
+import { EventExcludeControls } from './components/event-exclude-controls';
+import { EventListTypeControls } from './components/event-list-type-controls';
+import { EventOffsetControls } from './components/event-offset-controls';
+import { EventOrderControls } from './components/event-order-controls';
+import { EventIncludeUnfinishedControls } from './components/event-include-unfinished-controls';
+
+// import { PostDateQueryControls } from './components/post-date-query-controls';
+
+
+
+import { isEventPostType } from '../../helpers/event';
+
+/**
+ * Determines if the active variation is this one
+ *
+ * @param {*} props
+ * @return {boolean} Is this the correct variation?
+ */
+const isGatherPressQueryLoop = ( props ) => {
+ const {
+ attributes: { namespace },
+ } = props;
+ return namespace && namespace === NAME;
+};
+
+/**
+ * UX helper for when using a regular query block
+ * and "Event" gets selected as post type,
+ * the UI changes to everything necessary for events.
+ *
+ * By adding the relevant attributes,
+ * the block is transformed into the "Event Query" block variation.
+ *
+ * @param {*} props
+ * @returns
+ */
+const QueryPosttypeObserver = ( props ) => {
+ const { postType } = props.attributes.query;
+ useEffect(() => {
+ if ('gatherpress_event' === postType ) {
+ const newAttributes = {
+ ...props.attributes,
+ namespace: NAME,
+ query: {
+ ...props.attributes.query,
+ gatherpress_events_query: 'upcoming',
+ include_unfinished: 1,
+ order: 'asc',
+ orderBy: 'datetime',
+ inherit: false
+ }
+ };
+ props.setAttributes(newAttributes);
+ }
+ // Dependency array, every time the postType is changed,
+ // the useEffect callback will be called.
+ }, [ postType ]);
+ return;
+
+}
+
+
+/**
+ * Custom controls
+ *
+ * @param {*} BlockEdit
+ * @return {Element} BlockEdit instance
+ */
+const withGatherPressQueryControls = ( BlockEdit ) => ( props ) => {
+ // If this is something totally different, return early.
+ if ( ! isGatherPressQueryLoop( props ) && 'core/query' !== props.name ) {
+ return ;
+ }
+ // Regular core/query blocks should become this addition.
+ if ( ! isGatherPressQueryLoop( props ) ) {
+ return (
+ <>
+
+ ;
+ >
+ );
+ }
+ // If the is the correct variation, add the custom controls.
+ const isEventContext = isEventPostType();
+ // If the inherit prop is false, add all the controls.
+ const { attributes } = props;
+ if ( attributes.query.inherit === false ) {
+ return (
+ <>
+
+
+
+
+ {/* Toggle between 'upcoming' & 'past' events. */}
+
+
+
+ { isEventContext && (
+
+ )}
+
+
+
+
+ {/* */}
+
+
+
+ >
+ );
+ }
+ // Add some controls if the inherit prop is true.
+ return (
+ <>
+
+
+
+
+
+
+
+ >
+ );
+
+};
+
+addFilter( 'editor.BlockEdit', 'core/query', withGatherPressQueryControls );
diff --git a/src/variations/event-query/index.js b/src/variations/event-query/index.js
new file mode 100644
index 000000000..d44bc0d30
--- /dev/null
+++ b/src/variations/event-query/index.js
@@ -0,0 +1,234 @@
+/**
+ * WordPress dependencies
+ */
+import { registerBlockVariation } from '@wordpress/blocks';
+import { __ } from '@wordpress/i18n';
+// import { list-view } from '@wordpress/icons';
+
+/**
+ * Internal dependencies
+ */
+// Load block-variations for pagination-blocks.
+import './pagination'; // @TODO: add as separate variations !
+import './controls';
+
+import GPQLControls from './slots/gpql-controls';
+import GPQLControlsInheritedQuery from './slots/gpql-controls-inherited-query';
+
+import {
+ // GPV_BLOCK,
+ NO_RESULTS_BLOCK,
+ QUERY_PAGINATION_BLOCK
+} from './templates';
+
+// import GPQLIcon from '../components/icon';
+
+
+const NAME = 'gatherpress-event-query';
+
+const QUERY_ATTRIBUTES = {
+ namespace: NAME,
+ query: {
+ perPage: 3,
+ pages: 0,
+ offset: 0,
+ postType: 'gatherpress_event',
+ gatherpress_events_query: 'upcoming',
+ include_unfinished: 1,
+ order: 'asc',
+ orderBy: 'datetime',
+ inherit: false
+ }
+};
+
+const VARIATION_ATTRIBUTES = {
+ category: 'gatherpress',
+ keywords: [
+ __('Events', 'gatherpress'),
+ __('Dates', 'gatherpress'),
+ ],
+ // icon: list-view,
+ // isActive: ['namespace', 'scope'],
+ // isActive: ['query.postType'], // Idea based on @patriciabt|s feedback in slack.
+ isActive: ['namespace', 'query.postType'],
+ attributes: {
+ ...QUERY_ATTRIBUTES
+ },
+ allowedControls: ['inherit', 'taxQuery'],
+ scope: ['block'],
+}
+
+
+/**
+ * Docs about the Query block.
+ *
+ * General information on how to modify the query loop block, that's worth reading and learning:
+ *
+ * @see https://developer.wordpress.org/block-editor/how-to-guides/block-tutorial/extending-the-query-loop-block/#extending-the-query
+ * @see https://wpfieldwork.com/modify-query-loop-block-to-filter-by-custom-field/
+ * @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-variations/
+ * @see https://jeffreycarandang.com/restrict-wordpress-gutenberg-block-settings-based-on-post-type-user-roles-or-block-context/
+ */
+
+/**
+ * This is the main query-block variation to list events exclusively.
+ * A user can pick the block directly from the inserter or the left sidebar.
+ *
+ * @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-variations/
+ */
+registerBlockVariation('core/query', {
+ ...VARIATION_ATTRIBUTES,
+ name: NAME,
+ title: __('Event Query', 'gatherpress'),
+ description: __('Create event queries', 'gatherpress'),
+ scope: ['inserter', 'transform'],
+ /*
+ * Having innerBlocks in THIS (visible) variation, essentially
+ * skips the setup phase of the Query Loop block with suggested patterns
+ * and the block is inserted with these inner blocks as its starting content.
+ *
+ * This is not what I wanted, so I disabled it.
+ *
+ * @see https://developer.wordpress.org/block-editor/how-to-guides/block-tutorial/extending-the-query-loop-block/#customize-your-variation-layout
+
+ innerBlocks: [
+ [
+ 'core/post-template',
+ {},
+ [
+ [ 'gatherpress/event-date' ],
+ [ 'core/post-title' ],
+ [ 'core/post-excerpt' ]
+ ],
+ ],
+ [ 'core/query-pagination' ],
+ [ 'core/query-no-results' ],
+ ], */
+
+ example: {
+ attributes: {
+ ...QUERY_ATTRIBUTES
+ },
+ innerBlocks: [
+ {
+ name: 'core/post-template',
+ attributes: {},
+ innerBlocks: [
+ {
+ name: 'gatherpress/event-date',
+ },
+ {
+ name: 'core/post-title',
+ },
+ // {
+ // ...GPV_BLOCK,
+ // }
+ ],
+ },
+ ],
+ },
+});
+
+
+/**
+ * One of the 'Start blank' patterns for the gatherpress query loop variation.
+ */
+registerBlockVariation('core/query', {
+ ...VARIATION_ATTRIBUTES,
+ name: 'gatherpress-event-query-map-date',
+ title: __('Map & Event-Date', 'gatherpress'),
+ description: __('Create gatherpress queries with Map & Date', 'gatherpress'),
+ innerBlocks: [
+ [
+ 'core/post-template',
+ {
+ metadata:{
+ name:__(
+ 'Events Template',
+ 'gatherpress'
+ )
+ }
+ },
+ [
+ ['gatherpress/venue'],
+ ['gatherpress/event-date'],
+ ],
+ ],
+ QUERY_PAGINATION_BLOCK,
+ NO_RESULTS_BLOCK
+ ],
+});
+
+/**
+ * One of the 'Start blank' patterns for the gatherpress query loop variation.
+ */
+registerBlockVariation('core/query', {
+ ...VARIATION_ATTRIBUTES,
+ name: 'gatherpress-event-query-date-title',
+ title: __('Event-Date, Title & Venue details', 'gatherpress'),
+ description: __('Create gatherpress queries with Event-Date & Title', 'gatherpress'),
+ innerBlocks: [
+ [
+ 'core/post-template',
+ {
+ metadata:{
+ name:__(
+ 'Events Template',
+ 'gatherpress'
+ )
+ }
+ },
+ [
+ {
+ name: 'gatherpress/event-date',
+ },
+ {
+ name: 'core/post-title',
+ },
+ // {
+ // ...GPV_BLOCK,
+ // },
+ ],
+ ],
+ QUERY_PAGINATION_BLOCK,
+ NO_RESULTS_BLOCK
+ ],
+});
+
+/**
+ * One of the 'Start blank' patterns for the gatherpress query loop variation.
+ */
+registerBlockVariation('core/query', {
+ ...VARIATION_ATTRIBUTES,
+ name: 'gatherpress-event-query-date-address',
+ title: __('Event-Date & Venue Details', 'gatherpress'),
+ description: __('Create gatherpress queries with Event-Date & Venue Details', 'gatherpress'),
+ innerBlocks: [
+ [
+ 'core/post-template',
+ {
+ metadata:{
+ name:__(
+ 'Events Template',
+ 'gatherpress'
+ )
+ }
+ },
+ [
+ {
+ name:'gatherpress/event-date'
+ },
+ // {
+ // ...GPV_BLOCK,
+ // },
+ ],
+ ],
+ QUERY_PAGINATION_BLOCK,
+ NO_RESULTS_BLOCK
+ ],
+});
+
+
+
+
+export { NAME, GPQLControls, GPQLControlsInheritedQuery };
diff --git a/src/variations/event-query/pagination.js b/src/variations/event-query/pagination.js
new file mode 100644
index 000000000..941992c37
--- /dev/null
+++ b/src/variations/event-query/pagination.js
@@ -0,0 +1,49 @@
+/**
+ * WordPress dependencies
+ */
+import { registerBlockVariation } from '@wordpress/blocks';
+import { __ } from '@wordpress/i18n';
+// import { queryPaginationNext, queryPaginationPrevious } from '@wordpress/icons';
+
+/**
+ * Internal dependencies
+ */
+// import GPQLIcon from '../components/icon';
+
+/**
+ * Update UI for pagination blocks to speak 'events', not 'posts'.
+ */
+registerBlockVariation('core/query-pagination-previous', {
+ category: 'gatherpress',
+ keywords: [
+ __('Page numbers', 'default'),
+ __('Pagination', 'default'),
+ ],
+ // icon: GPQLIcon( queryPaginationPrevious ),
+ isActive: ['className'],
+ attributes: {
+ className: 'gatherpress-query-pagination-previous',
+ label: __('Previous Events', 'gatherpress'),
+ },
+ // scope: ['block'],
+ name: 'gatherpress-query-pagination-previous',
+ title: __('Previous Events', 'gatherpress'),
+ description: __('Displays the previous events link.', 'gatherpress'),
+});
+registerBlockVariation('core/query-pagination-next', {
+ category: 'gatherpress',
+ keywords: [
+ __('Page numbers', 'default'),
+ __('Pagination', 'default'),
+ ],
+ // icon: GPQLIcon( queryPaginationNext ),
+ isActive: ['className'],
+ attributes: {
+ className: 'gatherpress-query-pagination-next',
+ label: __('Next Events', 'gatherpress'),
+ },
+ // scope: ['block'],
+ name: 'gatherpress-query-pagination-next',
+ title: __('Next Events', 'gatherpress'),
+ description: __('Displays the next events link.', 'gatherpress'),
+});
diff --git a/src/variations/event-query/slots/gpql-controls-inherited-query.js b/src/variations/event-query/slots/gpql-controls-inherited-query.js
new file mode 100644
index 000000000..3ecf4d18c
--- /dev/null
+++ b/src/variations/event-query/slots/gpql-controls-inherited-query.js
@@ -0,0 +1,15 @@
+/**
+ * WordPress dependencies
+ */
+import { createSlotFill } from '@wordpress/components';
+
+/**
+ * Create our Slot and Fill components
+ */
+const { Fill, Slot } = createSlotFill( 'GPQLControlsInheritedQuery' );
+
+const GPQLControlsInheritedQuery = ( { children } ) => { children };
+
+GPQLControlsInheritedQuery.Slot = Slot;
+
+export default GPQLControlsInheritedQuery;
diff --git a/src/variations/event-query/slots/gpql-controls.js b/src/variations/event-query/slots/gpql-controls.js
new file mode 100644
index 000000000..46e83dfbf
--- /dev/null
+++ b/src/variations/event-query/slots/gpql-controls.js
@@ -0,0 +1,21 @@
+/**
+ * WordPress dependencies
+ */
+import { createSlotFill } from '@wordpress/components';
+
+/**
+ * Create our Slot and Fill components
+ */
+const { Fill, Slot } = createSlotFill( 'GPQLControls' );
+
+const GPQLControls = ( { children } ) => { children };
+
+GPQLControls.Slot = ( { fillProps } ) => (
+
+ { ( fills ) => {
+ return fills.length ? fills : null;
+ } }
+
+);
+
+export default GPQLControls;
diff --git a/src/variations/event-query/templates.js b/src/variations/event-query/templates.js
new file mode 100644
index 000000000..1f768d9d0
--- /dev/null
+++ b/src/variations/event-query/templates.js
@@ -0,0 +1,65 @@
+
+/**
+ * WordPress dependencies
+ */
+import { __ } from '@wordpress/i18n';
+/**
+ * Internal dependencies
+ */
+// import { GPV_CLASS_NAME } from '../helpers/namespace';
+
+// export const GPV_BLOCK = {
+// name: 'core/group',
+// attributes: {
+// className: GPV_CLASS_NAME,
+// // is neccessary to make isActive work !!
+// // @see https://github.com/WordPress/gutenberg/issues/41303#issuecomment-1526193087
+// layout: { type: 'flex', orientation: 'nonsense' }, // works
+// },
+// innerBlocks: [
+// [
+// 'core/pattern',
+// {
+// slug: 'gatherpress/venue-details',
+// },
+// ],
+// ],
+// }
+
+const NO_RESULTS_TEMPLATE = [
+ [
+ 'core/paragraph',
+ {
+ placeholder: __(
+ 'Add text or blocks that will display when a query returns no events.',
+ 'gatherpress'
+ ),
+ },
+ ],
+];
+
+export const NO_RESULTS_BLOCK = [
+ 'core/query-no-results',
+ {
+ metadata:{
+ name:__(
+ 'No events',
+ 'gatherpress'
+ )
+ }
+ },
+ NO_RESULTS_TEMPLATE
+];
+
+
+
+const QUERY_PAGINATION_TEMPLATE = [
+ [ 'core/query-pagination-previous', { label:__('Previous Events', 'gatherpress'), className: 'gatherpress-query-pagination-previous' } ],
+ [ 'core/query-pagination-numbers' ],
+ [ 'core/query-pagination-next', { label:__('Next Events', 'gatherpress'), className: 'gatherpress-query-pagination-next' } ],
+];
+export const QUERY_PAGINATION_BLOCK = [
+ 'core/query-pagination',
+ {},
+ QUERY_PAGINATION_TEMPLATE
+];
\ No newline at end of file