From 8b5533992eb36fef47e6972e5928432f09f4cc7a Mon Sep 17 00:00:00 2001 From: "Zachary A. Martz" Date: Fri, 21 Nov 2025 14:49:59 -0500 Subject: [PATCH 1/8] =?UTF-8?q?Add=20=E2=80=9CShow=20Terms=20in=20Current?= =?UTF-8?q?=20Results=E2=80=9D=20option=20to=20taxonomy=20filter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- build/taxonomy/block.json | 4 +++ build/taxonomy/index.asset.php | 2 +- build/taxonomy/index.js | 2 +- build/taxonomy/render.php | 46 +++++++++++++++++++------ inc/namespace.php | 62 ++++++++++++++++++++++++++++++++++ 6 files changed, 104 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index f543ef9..3050fa5 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Easy to use and lightweight, built using the WordPress Interactivity API. * Add a query block. This can anyhere that the query block is supported e.g. page, template, or pattern. * Add one of the filter blocks and configure as required: - * Taxonomy filter. Select which taxonomy to to use, customise the label (and whether it's shown), and customise the text used when none is selected. +* Taxonomy filter. Select which taxonomy to to use, customise the label (and whether it's shown), and customise the text used when none is selected. Optionally enable **Show Terms in Current Results** to limit the dropdown to terms attached to the posts returned by the surrounding query. * Post type filter. Customise the label (and whether it's shown), as well as the text used when no filter is applied. * Search block. No extra options. diff --git a/build/taxonomy/block.json b/build/taxonomy/block.json index 6cdb93d..ea84455 100644 --- a/build/taxonomy/block.json +++ b/build/taxonomy/block.json @@ -49,6 +49,10 @@ "taxonomy": { "type": "string" }, + "limitToCurrentResults": { + "type": "boolean", + "default": false + }, "emptyLabel": { "type": "string", "default": "" diff --git a/build/taxonomy/index.asset.php b/build/taxonomy/index.asset.php index 4b3ef34..c2f86f4 100644 --- a/build/taxonomy/index.asset.php +++ b/build/taxonomy/index.asset.php @@ -1 +1 @@ - array('react-jsx-runtime', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-data', 'wp-i18n'), 'version' => 'f1456d24ac8e3da497aa'); + array('react-jsx-runtime', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-data', 'wp-i18n'), 'version' => '0784db9d40d6fdb17335'); diff --git a/build/taxonomy/index.js b/build/taxonomy/index.js index 5868836..6eb0731 100644 --- a/build/taxonomy/index.js +++ b/build/taxonomy/index.js @@ -1 +1 @@ -(()=>{"use strict";const e=window.wp.blocks,l=window.wp.i18n,t=window.wp.blockEditor,o=window.wp.components,n=window.wp.data,r=window.ReactJSXRuntime,a=JSON.parse('{"UU":"query-filter/taxonomy"}');(0,e.registerBlockType)(a.UU,{edit:function({attributes:e,setAttributes:a}){const{taxonomy:i,emptyLabel:s,label:c,showLabel:u}=e,b=(0,n.useSelect)((e=>{const l=(e("core").getTaxonomies({per_page:100})||[]).filter((e=>e.visibility.publicly_queryable));return l&&l.length>0&&!i&&a({taxonomy:l[0].slug,label:l[0].name}),l}),[i]),y=(0,n.useSelect)((e=>e("core").getEntityRecords("taxonomy",i,{number:50})||[]),[i]);return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(t.InspectorControls,{children:(0,r.jsxs)(o.PanelBody,{title:(0,l.__)("Taxonomy Settings","query-filter"),children:[(0,r.jsx)(o.SelectControl,{label:(0,l.__)("Select Taxonomy","query-filter"),value:i,options:(b||[]).map((e=>({label:e.name,value:e.slug}))),onChange:e=>a({taxonomy:e,label:b.find((l=>l.slug===e)).name})}),(0,r.jsx)(o.TextControl,{label:(0,l.__)("Label","query-filter"),value:c,help:(0,l.__)("If empty then no label will be shown","query-filter"),onChange:e=>a({label:e})}),(0,r.jsx)(o.ToggleControl,{label:(0,l.__)("Show Label","query-filter"),checked:u,onChange:e=>a({showLabel:e})}),(0,r.jsx)(o.TextControl,{label:(0,l.__)("Empty Choice Label","query-filter"),value:s,placeholder:(0,l.__)("All","query-filter"),onChange:e=>a({emptyLabel:e})})]})}),(0,r.jsxs)("div",{...(0,t.useBlockProps)({className:"wp-block-query-filter"}),children:[u&&(0,r.jsx)("label",{className:"wp-block-query-filter-taxonomy__label wp-block-query-filter__label",children:c}),(0,r.jsxs)("select",{className:"wp-block-query-filter-taxonomy__select wp-block-query-filter__select",inert:!0,children:[(0,r.jsx)("option",{children:s||(0,l.__)("All","query-filter")}),y.map((e=>(0,r.jsx)("option",{children:e.name},e.slug)))]})]})]})}})})(); \ No newline at end of file +(()=>{"use strict";const e=window.wp.blocks,l=window.wp.i18n,t=window.wp.blockEditor,o=window.wp.components,r=window.wp.data,n=window.ReactJSXRuntime,s=JSON.parse('{"UU":"query-filter/taxonomy"}');(0,e.registerBlockType)(s.UU,{edit:function({attributes:e,setAttributes:s}){const{taxonomy:a,emptyLabel:i,label:c,showLabel:u,limitToCurrentResults:y}=e,b=(0,r.useSelect)((e=>{const l=(e("core").getTaxonomies({per_page:100})||[]).filter((e=>e.visibility.publicly_queryable));return l&&l.length>0&&!a&&s({taxonomy:l[0].slug,label:l[0].name}),l}),[a]),m=(0,r.useSelect)((e=>e("core").getEntityRecords("taxonomy",a,{number:50})||[]),[a]);return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(t.InspectorControls,{children:(0,n.jsxs)(o.PanelBody,{title:(0,l.__)("Taxonomy Settings","query-filter"),children:[(0,n.jsx)(o.SelectControl,{label:(0,l.__)("Select Taxonomy","query-filter"),value:a,options:(b||[]).map((e=>({label:e.name,value:e.slug}))),onChange:e=>s({taxonomy:e,label:b.find((l=>l.slug===e)).name})}),(0,n.jsx)(o.TextControl,{label:(0,l.__)("Label","query-filter"),value:c,help:(0,l.__)("If empty then no label will be shown","query-filter"),onChange:e=>s({label:e})}),(0,n.jsx)(o.ToggleControl,{label:(0,l.__)("Show Label","query-filter"),checked:u,onChange:e=>s({showLabel:e})}),(0,n.jsx)(o.TextControl,{label:(0,l.__)("Empty Choice Label","query-filter"),value:i,placeholder:(0,l.__)("All","query-filter"),onChange:e=>s({emptyLabel:e})}),(0,n.jsx)(o.ToggleControl,{label:(0,l.__)("Show Terms in Current Results","query-filter"),help:(0,l.__)("Only include terms that are attached to posts returned by this query.","query-filter"),checked:y,onChange:e=>s({limitToCurrentResults:e})})]})}),(0,n.jsxs)("div",{...(0,t.useBlockProps)({className:"wp-block-query-filter"}),children:[u&&(0,n.jsx)("label",{className:"wp-block-query-filter-taxonomy__label wp-block-query-filter__label",children:c}),(0,n.jsxs)("select",{className:"wp-block-query-filter-taxonomy__select wp-block-query-filter__select",inert:!0,children:[(0,n.jsx)("option",{children:i||(0,l.__)("All","query-filter")}),m.map((e=>(0,n.jsx)("option",{children:e.name},e.slug)))]})]})]})}})})(); \ No newline at end of file diff --git a/build/taxonomy/render.php b/build/taxonomy/render.php index 01d9800..6b2a483 100644 --- a/build/taxonomy/render.php +++ b/build/taxonomy/render.php @@ -7,24 +7,48 @@ $taxonomy = get_taxonomy( $attributes['taxonomy'] ); -if ( $block->context['query']['inherit'] ) { - $query_var = sprintf( 'query-%s', $attributes['taxonomy'] ); - $page_var = 'page'; - $base_url = str_replace( '/page/' . get_query_var( 'paged' ), '', remove_query_arg( [ $query_var, $page_var ] ) ); -} else { +if ( empty( $block->context['query']['inherit'] ) ) { $query_id = $block->context['queryId'] ?? 0; $query_var = sprintf( 'query-%d-%s', $query_id, $attributes['taxonomy'] ); $page_var = isset( $block->context['queryId'] ) ? 'query-' . $block->context['queryId'] . '-page' : 'query-page'; $base_url = remove_query_arg( [ $query_var, $page_var ] ); +} else { + $query_var = sprintf( 'query-%s', $attributes['taxonomy'] ); + $page_var = 'page'; + $base_url = str_replace( '/page/' . get_query_var( 'paged' ), '', remove_query_arg( [ $query_var, $page_var ] ) ); } -$terms = get_terms( [ - 'hide_empty' => true, - 'taxonomy' => $attributes['taxonomy'], - 'number' => 100, -] ); +$terms = []; + +if ( ! empty( $attributes['limitToCurrentResults'] ) ) { + $post_ids = HM\Query_Loop_Filter\get_query_loop_post_ids_for_block( $block ); + + if ( ! empty( $post_ids ) ) { + $terms = wp_get_object_terms( + $post_ids, + $attributes['taxonomy'], + [ + 'orderby' => 'name', + 'order' => 'ASC', + 'number' => 100, + ] + ); + } + + if ( is_wp_error( $terms ) ) { + return; + } +} + +if ( empty( $terms ) ) { + $terms = get_terms( [ + 'hide_empty' => true, + 'taxonomy' => $attributes['taxonomy'], + 'number' => 100, + ] ); +} -if ( is_wp_error( $terms ) || empty( $terms ) ) { +if ( is_wp_error( $terms ) || ( empty( $terms ) && empty( $attributes['limitToCurrentResults'] ) ) ) { return; } ?> diff --git a/inc/namespace.php b/inc/namespace.php index 0bd4afa..7843a90 100644 --- a/inc/namespace.php +++ b/inc/namespace.php @@ -19,6 +19,7 @@ function bootstrap() : void { // General hooks. add_filter( 'query_loop_block_query_vars', __NAMESPACE__ . '\\filter_query_loop_block_query_vars', 10, 3 ); add_action( 'pre_get_posts', __NAMESPACE__ . '\\pre_get_posts_transpose_query_vars' ); + add_filter( 'the_posts', __NAMESPACE__ . '\\store_query_loop_posts', 10, 2 ); add_filter( 'block_type_metadata', __NAMESPACE__ . '\\filter_block_type_metadata', 10 ); add_action( 'init', __NAMESPACE__ . '\\register_blocks' ); add_action( 'enqueue_block_assets', __NAMESPACE__ . '\\action_wp_enqueue_scripts' ); @@ -221,3 +222,64 @@ function render_block_query( $block_content, $block ) { return (string) $block_content; } + +/** + * Cache the post IDs for each rendered query loop. + * + * @param array $posts Array of post objects. + * @param WP_Query $query Current WP_Query object. + * @return array + */ +function store_query_loop_posts( array $posts, WP_Query $query ) : array { + $query_id = $query->get( 'query_id', null ); + + if ( is_null( $query_id ) ) { + return $posts; + } + + $cache = &query_loop_posts_cache(); + $cache[ $query_id ] = array_map( 'intval', wp_list_pluck( $posts, 'ID' ) ); + + return $posts; +} + +/** + * Retrieve the post IDs associated with the provided block's query. + * + * @param \WP_Block $block Block instance. + * @return array + */ +function get_query_loop_post_ids_for_block( \WP_Block $block ) : array { + if ( empty( $block->context['query'] ) ) { + return []; + } + + if ( ! empty( $block->context['query']['inherit'] ) ) { + global $wp_query; + + return array_map( 'intval', wp_list_pluck( $wp_query->posts ?? [], 'ID' ) ); + } + + $query_id = $block->context['queryId'] ?? null; + + if ( is_null( $query_id ) ) { + return []; + } + + $cache = &query_loop_posts_cache(); + + return $cache[ $query_id ] ?? []; +} + +/** + * Helper to store post IDs for each query loop. + * + * @return array> + */ +function &query_loop_posts_cache() : array { + if ( ! isset( $GLOBALS['hm_query_loop_filter_query_posts'] ) || ! is_array( $GLOBALS['hm_query_loop_filter_query_posts'] ) ) { + $GLOBALS['hm_query_loop_filter_query_posts'] = []; + } + + return $GLOBALS['hm_query_loop_filter_query_posts']; +} From dfdc7d525d1b4c7674cc16f8bb4a87a814ee4610 Mon Sep 17 00:00:00 2001 From: "Zachary A. Martz" Date: Fri, 21 Nov 2025 16:13:47 -0500 Subject: [PATCH 2/8] Add sort filter block and unify query parameter handling --- README.md | 1 + build/.DS_Store | Bin 0 -> 6148 bytes build/post-type/render.php | 44 +++++++-- build/sort/block.json | 73 +++++++++++++++ build/sort/index.asset.php | 12 +++ build/sort/index.js | 155 +++++++++++++++++++++++++++++++ build/sort/render.php | 108 ++++++++++++++++++++++ build/taxonomy/render.php | 46 ++++++--- inc/namespace.php | 185 +++++++++++++++++++++++++++++++++---- 9 files changed, 583 insertions(+), 41 deletions(-) create mode 100644 build/.DS_Store create mode 100644 build/sort/block.json create mode 100644 build/sort/index.asset.php create mode 100644 build/sort/index.js create mode 100644 build/sort/render.php diff --git a/README.md b/README.md index 3050fa5..e48daca 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ Easy to use and lightweight, built using the WordPress Interactivity API. * Add one of the filter blocks and configure as required: * Taxonomy filter. Select which taxonomy to to use, customise the label (and whether it's shown), and customise the text used when none is selected. Optionally enable **Show Terms in Current Results** to limit the dropdown to terms attached to the posts returned by the surrounding query. * Post type filter. Customise the label (and whether it's shown), as well as the text used when no filter is applied. +* Sort filter. Presents the same ordering options available in the Query Loop block (“Order By”) so visitors can toggle between newest/oldest, alphabetical, menu order, etc. Pick which sort choices appear via checkboxes in the block settings. * Search block. No extra options. ![image](https://github.com/user-attachments/assets/e2f9b62d-91f7-4c22-87ac-078b4d031a60) diff --git a/build/.DS_Store b/build/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..3899286d1381667b82937ff8363fe668917f0313 GIT binary patch literal 6148 zcmeHK%}N6?5Kh`^vlL+u3VK`cTCi2A7B5S!FW`zERO&7*y0}eByLFXP*t@=vFXHn! zlcZv)f+rC>1CwtuKO6F8$q!?Ux5nK%V=iM%fFc$gXuc4Pqb^9ndJwt(hGdHM*;AB_ z1`FBb_=^nS+6k7jkUhfMeOkXLQ4z#mqd^jn(pv3}7mB5o)v_pyintE$R2HORIv%yc z;U)IYluDwR9Y*JIFYCDL$4aJQEPF#;5O;f!a&-~QuF6_!EW0CJ8<>D_gwt`iCX@QX zVZG|@H=5I`H)-zHz}{<2r;gaz-Z?t$+$Vjho(-EEempH}7H9AR#=^uNf}u>5yoK1T zZkA6-3=jjvz!EWFw?I)@qB+x4i2-8ZXAI!};DI9g7ITAo>wpHYj~Fi@qJWKW2}IAL zZ!tFr5fE-l0Zl2lPYiC#!SC5T-(qgilryeJhVR&sxqYE;717<`Y486Jc>6# awSeD41JJja8w3vsT?8}@)DQ!|%D_7!$W5~V literal 0 HcmV?d00001 diff --git a/build/post-type/render.php b/build/post-type/render.php index 70d43f2..c01e445 100644 --- a/build/post-type/render.php +++ b/build/post-type/render.php @@ -3,17 +3,30 @@ $id = 'query-filter-' . wp_generate_uuid4(); +$target_query_id = empty( $block->context['query']['inherit'] ) + ? (string) ( $block->context['queryId'] ?? 0 ) + : 'main'; + +$query_var = 'query-post_type'; +$page_var = 'query-page'; +$base_url = remove_query_arg( [ $query_var, $page_var, 'query-post_id', 'query-id' ] ); + if ( $block->context['query']['inherit'] ) { - $query_var = 'query-post_type'; - $page_var = 'page'; - $base_url = str_replace( '/page/' . get_query_var( 'paged' ), '', remove_query_arg( [ $query_var, $page_var ] ) ); -} else { - $query_id = $block->context['queryId'] ?? 0; - $query_var = sprintf( 'query-%d-post_type', $query_id ); - $page_var = isset( $block->context['queryId'] ) ? 'query-' . $block->context['queryId'] . '-page' : 'query-page'; - $base_url = remove_query_arg( [ $query_var, $page_var ] ); + $current_paged = (int) get_query_var( 'paged' ); + if ( $current_paged > 1 ) { + $base_url = str_replace( '/page/' . $current_paged, '', $base_url ); + } + $base_url = remove_query_arg( [ 'page' ], $base_url ); } +$base_url = add_query_arg( + [ + 'query-post_id' => $target_query_id, + ], + $base_url +); +$base_url = HM\Query_Loop_Filter\normalize_query_filter_url( $base_url ); + $post_types = array_map( 'trim', explode( ',', $block->context['query']['postType'] ?? 'post' ) ); // Support for enhanced query block. @@ -47,8 +60,19 @@ diff --git a/build/sort/block.json b/build/sort/block.json new file mode 100644 index 0000000..a3b1990 --- /dev/null +++ b/build/sort/block.json @@ -0,0 +1,73 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 3, + "name": "query-filter/sort", + "version": "0.1.0", + "title": "Sort Filter", + "category": "theme", + "icon": "sort", + "description": "Allows visitors to change the order of results within a query loop block.", + "ancestor": [ + "core/query" + ], + "usesContext": [ + "queryId", + "query" + ], + "supports": { + "html": false, + "className": true, + "customClassName": true, + "color": { + "background": true, + "text": true + }, + "typography": { + "fontSize": true, + "textAlign": true, + "lineHeight": true, + "__experimentalFontFamily": true, + "__experimentalFontWeight": true, + "__experimentalFontStyle": true, + "__experimentalTextTransform": true, + "__experimentalTextDecoration": true, + "__experimentalLetterSpacing": true, + "__experimentalDefaultControls": { + "fontSize": true + } + }, + "spacing": { + "margin": true, + "padding": true, + "blockGap": true + }, + "interactivity": { + "clientNavigation": true + } + }, + "attributes": { + "label": { + "type": "string" + }, + "showLabel": { + "type": "boolean", + "default": true + }, + "options": { + "type": "object", + "default": { + "date_desc": true, + "date_asc": true, + "title_asc": true, + "title_desc": true, + "comment_desc": true, + "menu_order": true + } + } + }, + "textdomain": "query-filter", + "editorScript": "file:./index.js", + "style": "query-filter-view", + "render": "file:./render.php" +} + diff --git a/build/sort/index.asset.php b/build/sort/index.asset.php new file mode 100644 index 0000000..ce0507c --- /dev/null +++ b/build/sort/index.asset.php @@ -0,0 +1,12 @@ + [ + 'wp-block-editor', + 'wp-blocks', + 'wp-components', + 'wp-element', + 'wp-i18n', + ], + 'version' => '1.0.0', +]; + diff --git a/build/sort/index.js b/build/sort/index.js new file mode 100644 index 0000000..555281a --- /dev/null +++ b/build/sort/index.js @@ -0,0 +1,155 @@ +( function ( blocks, blockEditor, components, element, i18n ) { + const { registerBlockType } = blocks; + const { InspectorControls, useBlockProps } = blockEditor; + const { + PanelBody, + TextControl, + ToggleControl, + CheckboxControl, + } = components; + const { Fragment, createElement: el } = element; + const { __ } = i18n; + + const SORT_OPTIONS = [ + { + key: 'date_desc', + label: __( 'Newest to Oldest', 'query-filter' ), + orderby: 'date', + order: 'DESC', + }, + { + key: 'date_asc', + label: __( 'Oldest to Newest', 'query-filter' ), + orderby: 'date', + order: 'ASC', + }, + { + key: 'title_asc', + label: __( 'A → Z', 'query-filter' ), + orderby: 'title', + order: 'ASC', + }, + { + key: 'title_desc', + label: __( 'Z → A', 'query-filter' ), + orderby: 'title', + order: 'DESC', + }, + { + key: 'comment_desc', + label: __( 'Most Commented', 'query-filter' ), + orderby: 'comment_count', + order: 'DESC', + }, + { + key: 'menu_order', + label: __( 'Menu Order', 'query-filter' ), + orderby: 'menu_order', + order: 'ASC', + }, + ]; + + const DEFAULT_OPTIONS = SORT_OPTIONS.reduce( ( acc, option ) => { + acc[ option.key ] = true; + return acc; + }, {} ); + + registerBlockType( 'query-filter/sort', { + edit( { attributes, setAttributes } ) { + const { label, showLabel = true, options = {} } = attributes; + + const resolvedOptions = { + ...DEFAULT_OPTIONS, + ...options, + }; + + const enabledOptions = SORT_OPTIONS.filter( + ( option ) => resolvedOptions[ option.key ] + ); + + return el( + Fragment, + null, + el( + InspectorControls, + null, + el( + PanelBody, + { title: __( 'Sort Settings', 'query-filter' ) }, + el( TextControl, { + label: __( 'Label', 'query-filter' ), + value: label, + help: __( + 'If empty then no label will be shown', + 'query-filter' + ), + onChange: ( next ) => + setAttributes( { label: next } ), + } ), + el( ToggleControl, { + label: __( 'Show Label', 'query-filter' ), + checked: showLabel, + onChange: ( next ) => + setAttributes( { showLabel: next } ), + } ), + el( + 'div', + { className: 'wp-block-query-filter-sort__options' }, + SORT_OPTIONS.map( ( option ) => + el( CheckboxControl, { + key: option.key, + label: option.label, + checked: resolvedOptions[ option.key ], + onChange: ( next ) => + setAttributes( { + options: { + ...resolvedOptions, + [ option.key ]: next, + }, + } ), + } ) + ) + ) + ) + ), + el( + 'div', + useBlockProps( { className: 'wp-block-query-filter' } ), + showLabel && + el( + 'label', + { + className: + 'wp-block-query-filter-sort__label wp-block-query-filter__label', + }, + label || __( 'Order Results', 'query-filter' ) + ), + el( + 'select', + { + className: + 'wp-block-query-filter-sort__select wp-block-query-filter__select', + disabled: true, + }, + enabledOptions.map( ( option ) => + el( + 'option', + { + key: option.key, + }, + option.label + ) + ) + ) + ) + ); + }, + } ); +} )( + window.wp.blocks, + window.wp.blockEditor, + window.wp.components, + window.wp.element, + window.wp.i18n +); + diff --git a/build/sort/render.php b/build/sort/render.php new file mode 100644 index 0000000..a044d8b --- /dev/null +++ b/build/sort/render.php @@ -0,0 +1,108 @@ +context['query'] ) ) { + return; +} + +wp_enqueue_script_module( 'query-filter-taxonomy-view-script-module' ); + +$id = 'query-filter-' . wp_generate_uuid4(); +$label = $attributes['label'] ?? __( 'Order Results', 'query-filter' ); +$show_label = $attributes['showLabel'] ?? true; + +$sort_options = [ + 'date_desc' => [ + 'label' => __( 'Newest to Oldest', 'query-filter' ), + 'orderby' => 'date', + 'order' => 'DESC', + ], + 'date_asc' => [ + 'label' => __( 'Oldest to Newest', 'query-filter' ), + 'orderby' => 'date', + 'order' => 'ASC', + ], + 'title_asc' => [ + 'label' => __( 'A → Z', 'query-filter' ), + 'orderby' => 'title', + 'order' => 'ASC', + ], + 'title_desc' => [ + 'label' => __( 'Z → A', 'query-filter' ), + 'orderby' => 'title', + 'order' => 'DESC', + ], + 'comment_desc' => [ + 'label' => __( 'Most Commented', 'query-filter' ), + 'orderby' => 'comment_count', + 'order' => 'DESC', + ], + 'menu_order' => [ + 'label' => __( 'Menu Order', 'query-filter' ), + 'orderby' => 'menu_order', + 'order' => 'ASC', + ], +]; + +$enabled = array_merge( + array_fill_keys( array_keys( $sort_options ), true ), + is_array( $attributes['options'] ?? null ) ? $attributes['options'] : [] +); + +$target_query_id = empty( $block->context['query']['inherit'] ) + ? (string) ( $block->context['queryId'] ?? 0 ) + : 'main'; + +$orderby_var = 'query-post_orderby'; +$page_var = 'query-page'; +$base_url = remove_query_arg( [ $orderby_var, $page_var, 'query-post_id', 'query-id' ] ); + +if ( ! empty( $block->context['query']['inherit'] ) ) { + $current_paged = (int) get_query_var( 'paged' ); + if ( $current_paged > 1 ) { + $base_url = str_replace( '/page/' . $current_paged, '', $base_url ); + } + $base_url = remove_query_arg( [ 'page' ], $base_url ); +} + +$base_url = add_query_arg( + [ + 'query-post_id' => $target_query_id, + ], + $base_url +); +$base_url = HM\Query_Loop_Filter\normalize_query_filter_url( $base_url ); + +if ( empty( array_filter( $enabled ) ) ) { + return; +} + +$current_selection = sanitize_text_field( wp_unslash( $_GET[ $orderby_var ] ?? '' ) ); + +?> +
'wp-block-query-filter' ] ); ?> data-wp-interactive="query-filter" data-wp-context="{}"> + + +
+ diff --git a/build/taxonomy/render.php b/build/taxonomy/render.php index 6b2a483..66d16f8 100644 --- a/build/taxonomy/render.php +++ b/build/taxonomy/render.php @@ -7,17 +7,30 @@ $taxonomy = get_taxonomy( $attributes['taxonomy'] ); -if ( empty( $block->context['query']['inherit'] ) ) { - $query_id = $block->context['queryId'] ?? 0; - $query_var = sprintf( 'query-%d-%s', $query_id, $attributes['taxonomy'] ); - $page_var = isset( $block->context['queryId'] ) ? 'query-' . $block->context['queryId'] . '-page' : 'query-page'; - $base_url = remove_query_arg( [ $query_var, $page_var ] ); -} else { - $query_var = sprintf( 'query-%s', $attributes['taxonomy'] ); - $page_var = 'page'; - $base_url = str_replace( '/page/' . get_query_var( 'paged' ), '', remove_query_arg( [ $query_var, $page_var ] ) ); +$target_query_id = empty( $block->context['query']['inherit'] ) + ? (string) ( $block->context['queryId'] ?? 0 ) + : 'main'; + +$query_var = sprintf( 'query-%s', $attributes['taxonomy'] ); +$page_var = 'query-page'; +$base_url = remove_query_arg( [ $query_var, $page_var, 'query-post_id', 'query-id' ] ); + +if ( ! empty( $block->context['query']['inherit'] ) ) { + $current_paged = (int) get_query_var( 'paged' ); + if ( $current_paged > 1 ) { + $base_url = str_replace( '/page/' . $current_paged, '', $base_url ); + } + $base_url = remove_query_arg( [ 'page' ], $base_url ); } +$base_url = add_query_arg( + [ + 'query-post_id' => $target_query_id, + ], + $base_url +); +$base_url = HM\Query_Loop_Filter\normalize_query_filter_url( $base_url ); + $terms = []; if ( ! empty( $attributes['limitToCurrentResults'] ) ) { @@ -59,8 +72,19 @@ diff --git a/inc/namespace.php b/inc/namespace.php index 7843a90..985418c 100644 --- a/inc/namespace.php +++ b/inc/namespace.php @@ -53,6 +53,7 @@ function action_wp_enqueue_scripts() : void { function register_blocks() : void { register_block_type( ROOT_DIR . '/build/taxonomy' ); register_block_type( ROOT_DIR . '/build/post-type' ); + register_block_type( ROOT_DIR . '/build/sort' ); } /** @@ -83,11 +84,41 @@ function pre_get_posts_transpose_query_vars( WP_Query $query ) : void { return; } - $prefix = $query->is_main_query() ? 'query-' : "query-{$query_id}-"; + $current_query_identifier = $query->is_main_query() ? 'main' : (string) $query_id; + $requested_query_id = sanitize_text_field( + wp_unslash( + $_GET['query-post_id'] + ?? ( $_GET['query-id'] ?? '' ) + ) + ); + $legacy_prefix = $query->is_main_query() ? 'query-' : "query-{$query_id}-"; + $use_legacy_params = false; + + if ( 'main' !== $current_query_identifier && empty( $requested_query_id ) ) { + foreach ( array_keys( $_GET ) as $key ) { + if ( strpos( $key, $legacy_prefix ) === 0 ) { + $use_legacy_params = true; + break; + } + } + } + + if ( ! $use_legacy_params ) { + if ( 'main' !== $current_query_identifier && $requested_query_id !== $current_query_identifier ) { + return; + } + + if ( 'main' === $current_query_identifier && $requested_query_id && 'main' !== $requested_query_id ) { + return; + } + } + $tax_query = []; $valid_keys = [ 'post_type' => $query->is_search() ? 'any' : 'post', 's' => '', + 'orderby' => '', + 'order' => '', ]; // Preserve valid params for later retrieval. @@ -99,32 +130,110 @@ function pre_get_posts_transpose_query_vars( WP_Query $query ) : void { } // Map get params to this query. - foreach ( $_GET as $key => $value ) { - if ( strpos( $key, $prefix ) === 0 ) { - $key = str_replace( $prefix, '', $key ); + if ( $use_legacy_params ) { + foreach ( $_GET as $key => $value ) { + if ( strpos( $key, $legacy_prefix ) !== 0 ) { + continue; + } + + $param = str_replace( $legacy_prefix, '', $key ); $value = sanitize_text_field( urldecode( wp_unslash( $value ) ) ); - // Handle taxonomies specifically. - if ( get_taxonomy( $key ) ) { + if ( 'page' === $param ) { + $paged = max( 1, absint( $value ) ); + $query->set( 'paged', $paged ); + continue; + } + + if ( 'post_orderby' === $param ) { + $parts = explode( ':', $value ); + $orderby = sanitize_key( $parts[0] ?? '' ); + $order = strtoupper( sanitize_text_field( $parts[1] ?? '' ) ); + + if ( ! empty( $orderby ) ) { + $query->set( 'orderby', $orderby ); + } + + if ( in_array( $order, [ 'ASC', 'DESC' ], true ) ) { + $query->set( 'order', $order ); + } + + continue; + } + + if ( get_taxonomy( $param ) ) { $tax_query['relation'] = 'AND'; $tax_query[] = [ - 'taxonomy' => $key, + 'taxonomy' => $param, 'terms' => [ $value ], 'field' => 'slug', ]; - } else { - // Other options should map directly to query vars. - $key = sanitize_key( $key ); + continue; + } + + $param = sanitize_key( $param ); + + if ( ! array_key_exists( $param, $valid_keys ) ) { + continue; + } + + $query->set( + $param, + $value + ); + } + } else { + foreach ( $_GET as $key => $value ) { + if ( strpos( $key, 'query-' ) !== 0 || in_array( $key, [ 'query-post_id', 'query-id' ], true ) ) { + continue; + } + + $value = sanitize_text_field( urldecode( wp_unslash( $value ) ) ); + $param = substr( $key, 6 ); - if ( ! in_array( $key, array_keys( $valid_keys ), true ) ) { - continue; + if ( 'page' === $param ) { + $paged = max( 1, absint( $value ) ); + $query->set( 'paged', $paged ); + continue; + } + + if ( get_taxonomy( $param ) ) { + $tax_query['relation'] = 'AND'; + $tax_query[] = [ + 'taxonomy' => $param, + 'terms' => [ $value ], + 'field' => 'slug', + ]; + continue; + } + + if ( 'post_orderby' === $param ) { + $parts = explode( ':', $value ); + $orderby = sanitize_key( $parts[0] ?? '' ); + $order = strtoupper( sanitize_text_field( $parts[1] ?? '' ) ); + + if ( ! empty( $orderby ) ) { + $query->set( 'orderby', $orderby ); + } + + if ( in_array( $order, [ 'ASC', 'DESC' ], true ) ) { + $query->set( 'order', $order ); } - $query->set( - $key, - $value - ); + continue; + } + + // Other options should map directly to query vars. + $param = sanitize_key( $param ); + + if ( ! array_key_exists( $param, $valid_keys ) ) { + continue; } + + $query->set( + $param, + $value + ); } } @@ -173,11 +282,27 @@ function render_block_search( string $block_content, array $block, \WP_Block $in wp_enqueue_script_module( 'query-filter-taxonomy-view-script-module' ); - $query_var = empty( $instance->context['query']['inherit'] ) - ? sprintf( 'query-%d-s', $instance->context['queryId'] ?? 0 ) - : 's'; + $target_query_id = empty( $instance->context['query']['inherit'] ) + ? (string) ( $instance->context['queryId'] ?? 0 ) + : 'main'; + $query_var = 'query-s'; - $action = str_replace( '/page/'. get_query_var( 'paged', 1 ), '', add_query_arg( [ $query_var => '' ] ) ); + $action = remove_query_arg( [ $query_var, 'query-page', 'query-post_id', 'query-id' ] ); + if ( ! empty( $instance->context['query']['inherit'] ) ) { + $current_paged = (int) get_query_var( 'paged', 1 ); + if ( $current_paged > 1 ) { + $action = str_replace( '/page/' . $current_paged, '', $action ); + } + $action = remove_query_arg( [ 'page' ], $action ); + } + + $action = add_query_arg( + [ + 'query-post_id' => $target_query_id, + $query_var => '', + ], + $action + ); // Note sanitize_text_field trims whitespace from start/end of string causing unexpected behaviour. $value = wp_unslash( $_GET[ $query_var ] ?? '' ); @@ -283,3 +408,23 @@ function &query_loop_posts_cache() : array { return $GLOBALS['hm_query_loop_filter_query_posts']; } + +/** + * Ensure certain query parameters remain readable in URLs. + * + * @param string $url URL to normalize. + * @return string + */ +function normalize_query_filter_url( string $url ) : string { + if ( false === strpos( $url, 'query-post_orderby=' ) ) { + return $url; + } + + return preg_replace_callback( + '/(query-post_orderby=)([^&#]+)/', + static function ( $matches ) { + return $matches[1] . rawurldecode( $matches[2] ); + }, + $url + ); +} From ba32ac4dc02d9544c2b7e0c101235ad1e8ea6686 Mon Sep 17 00:00:00 2001 From: "Zachary A. Martz" Date: Tue, 25 Nov 2025 10:15:22 -0500 Subject: [PATCH 3/8] Add taxonomy text block + rich text format with canonical pagination handling --- build/taxonomy-text/block.json | 65 ++++++ build/taxonomy-text/index.asset.php | 1 + build/taxonomy-text/index.js | 240 +++++++++++++++++++ build/taxonomy-text/render.php | 33 +++ inc/namespace.php | 346 ++++++++++++++++++++++++++++ 5 files changed, 685 insertions(+) create mode 100644 build/taxonomy-text/block.json create mode 100644 build/taxonomy-text/index.asset.php create mode 100644 build/taxonomy-text/index.js create mode 100644 build/taxonomy-text/render.php diff --git a/build/taxonomy-text/block.json b/build/taxonomy-text/block.json new file mode 100644 index 0000000..87dc887 --- /dev/null +++ b/build/taxonomy-text/block.json @@ -0,0 +1,65 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 3, + "name": "query-filter/taxonomy-text", + "version": "0.1.0", + "title": "Taxonomy Text", + "category": "theme", + "icon": "feedback", + "description": "Outputs the currently selected taxonomy term or sort option for the surrounding query loop.", + "keywords": [ + "query", + "filter", + "taxonomy", + "seo" + ], + "ancestor": [ + "core/query" + ], + "usesContext": [ + "queryId", + "query" + ], + "supports": { + "html": false, + "className": true, + "customClassName": true, + "color": { + "text": true, + "background": true + }, + "typography": { + "fontSize": true, + "lineHeight": true, + "__experimentalFontWeight": true, + "__experimentalTextTransform": true, + "__experimentalLetterSpacing": true + }, + "spacing": { + "margin": true, + "padding": true + } + }, + "attributes": { + "filterType": { + "type": "string", + "enum": [ + "tag", + "category", + "sort" + ], + "default": "tag" + }, + "prefix": { + "type": "string", + "default": "" + }, + "suffix": { + "type": "string", + "default": "" + } + }, + "textdomain": "query-filter", + "editorScript": "file:./index.js", + "render": "file:./render.php" +} \ No newline at end of file diff --git a/build/taxonomy-text/index.asset.php b/build/taxonomy-text/index.asset.php new file mode 100644 index 0000000..19e857c --- /dev/null +++ b/build/taxonomy-text/index.asset.php @@ -0,0 +1 @@ + array('wp-block-editor', 'wp-blocks', 'wp-components', 'wp-element', 'wp-i18n', 'wp-primitives', 'wp-rich-text'), 'version' => '20251125'); diff --git a/build/taxonomy-text/index.js b/build/taxonomy-text/index.js new file mode 100644 index 0000000..698f2a8 --- /dev/null +++ b/build/taxonomy-text/index.js @@ -0,0 +1,240 @@ +( function ( wp ) { + const { __ } = wp.i18n; + const { registerBlockType } = wp.blocks; + const { registerFormatType, toggleFormat, insert } = wp.richText; + const { + InspectorControls, + RichTextToolbarButton, + useBlockProps, + } = wp.blockEditor; + const { + Button, + Flex, + FlexItem, + Modal, + PanelBody, + SelectControl, + TextControl, + } = wp.components; + const { Fragment, useEffect, useMemo, useState, createElement: el } = + wp.element; + const { SVG, Path } = wp.primitives; + + const FILTER_OPTIONS = [ + { label: __( 'Tag', 'query-filter' ), value: 'tag' }, + { label: __( 'Category', 'query-filter' ), value: 'category' }, + { label: __( 'Sort', 'query-filter' ), value: 'sort' }, + ]; + + const BLOCK_NAME = 'query-filter/taxonomy-text'; + const FORMAT_NAME = 'query-filter/taxonomy-inline-text'; + const ATTRIBUTE_KEY = 'data-query-filter-text'; + const PLACEHOLDER_TEXT = __( 'taxonomy', 'query-filter' ); + + const ICON = el( + SVG, + { viewBox: '0 0 24 24', xmlns: 'http://www.w3.org/2000/svg' }, + el( Path, { + d: 'M17.5 4v5a2 2 0 0 1-2 2h-7a2 2 0 0 1-2-2V4H8v5a.5.5 0 0 0 .5.5h7A.5.5 0 0 0 16 9V4h1.5Zm0 16v-5a2 2 0 0 0-2-2h-7a2 2 0 0 0-2 2v5H8v-5a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 .5.5v5h1.5Z', + } ) + ); + + const parseSettings = ( value ) => { + if ( ! value ) { + return { filterType: 'tag', prefix: '', suffix: '' }; + } + + try { + const parsed = JSON.parse( value ); + + return { + filterType: parsed.filterType || 'tag', + prefix: parsed.prefix || '', + suffix: parsed.suffix || '', + }; + } catch ( error ) { + return { filterType: 'tag', prefix: '', suffix: '' }; + } + }; + + const FormatEdit = ( { + value, + onChange, + isActive, + activeAttributes, + } ) => { + const currentSettings = useMemo( + () => parseSettings( activeAttributes?.[ ATTRIBUTE_KEY ] ), + [ activeAttributes?.[ ATTRIBUTE_KEY ] ] + ); + const [ isOpen, setIsOpen ] = useState( false ); + const [ settings, setSettings ] = useState( currentSettings ); + + useEffect( () => { + setSettings( currentSettings ); + }, [ currentSettings ] ); + + const applyFormat = () => { + const attributes = { + [ ATTRIBUTE_KEY ]: JSON.stringify( settings ), + }; + const selectionCollapsed = value.start === value.end; + let nextValue = value; + + if ( selectionCollapsed ) { + nextValue = insert( nextValue, PLACEHOLDER_TEXT ); + const end = nextValue.start; + const start = end - PLACEHOLDER_TEXT.length; + nextValue = { ...nextValue, start, end }; + } + + onChange( + toggleFormat( nextValue, { + type: FORMAT_NAME, + attributes, + } ) + ); + setIsOpen( false ); + }; + + const removeFormat = () => { + onChange( toggleFormat( value, { type: FORMAT_NAME } ) ); + setIsOpen( false ); + }; + + return el( + Fragment, + null, + el( RichTextToolbarButton, { + icon: ICON, + title: __( 'Taxonomy Text', 'query-filter' ), + onClick: () => setIsOpen( true ), + isActive, + } ), + isOpen && + el( + Modal, + { + title: __( 'Dynamic Taxonomy Text', 'query-filter' ), + onRequestClose: () => setIsOpen( false ), + }, + el( SelectControl, { + label: __( 'Filter Source', 'query-filter' ), + value: settings.filterType, + options: FILTER_OPTIONS, + onChange: ( filterType ) => + setSettings( ( prev ) => ( { + ...prev, + filterType, + } ) ), + } ), + el( TextControl, { + label: __( 'Prefix Text', 'query-filter' ), + value: settings.prefix, + onChange: ( prefix ) => + setSettings( ( prev ) => ( { + ...prev, + prefix, + } ) ), + } ), + el( TextControl, { + label: __( 'Suffix Text', 'query-filter' ), + value: settings.suffix, + onChange: ( suffix ) => + setSettings( ( prev ) => ( { + ...prev, + suffix, + } ) ), + } ), + el( + Flex, + { justify: 'flex-start' }, + el( + FlexItem, + null, + el( + Button, + { variant: 'primary', onClick: applyFormat }, + __( 'Apply', 'query-filter' ) + ) + ), + isActive && + el( + FlexItem, + null, + el( + Button, + { variant: 'tertiary', onClick: removeFormat }, + __( 'Remove', 'query-filter' ) + ) + ) + ) + ) + ); + }; + + const BlockEdit = ( { attributes, setAttributes } ) => { + const { filterType = 'tag', prefix = '', suffix = '' } = attributes; + const blockProps = useBlockProps( { + className: `taxonomy-text taxonomy-text--${ filterType }`, + } ); + const previewValue = + filterType === 'sort' + ? __( 'Selected Sort', 'query-filter' ) + : __( 'Selected Term', 'query-filter' ); + + return el( + Fragment, + null, + el( + InspectorControls, + null, + el( + PanelBody, + { title: __( 'Display Settings', 'query-filter' ) }, + el( SelectControl, { + label: __( 'Filter Source', 'query-filter' ), + value: filterType, + options: FILTER_OPTIONS, + onChange: ( nextFilter ) => + setAttributes( { filterType: nextFilter } ), + } ), + el( TextControl, { + label: __( 'Prefix Text', 'query-filter' ), + value: prefix, + onChange: ( value ) => + setAttributes( { prefix: value } ), + placeholder: __( 'e.g. Showing results for ', 'query-filter' ), + } ), + el( TextControl, { + label: __( 'Suffix Text', 'query-filter' ), + value: suffix, + onChange: ( value ) => + setAttributes( { suffix: value } ), + placeholder: __( 'e.g. only', 'query-filter' ), + } ) + ) + ), + el( + 'span', + { ...blockProps }, + `${ prefix || '' }${ previewValue }${ suffix || '' }` + ) + ); + }; + + registerFormatType( FORMAT_NAME, { + title: __( 'Taxonomy Text', 'query-filter' ), + tagName: 'span', + className: 'taxonomy-text-inline', + attributes: { + [ ATTRIBUTE_KEY ]: ATTRIBUTE_KEY, + }, + edit: FormatEdit, + } ); + + registerBlockType( BLOCK_NAME, { + edit: BlockEdit, + } ); +} )( window.wp ); + diff --git a/build/taxonomy-text/render.php b/build/taxonomy-text/render.php new file mode 100644 index 0000000..5afbfa0 --- /dev/null +++ b/build/taxonomy-text/render.php @@ -0,0 +1,33 @@ + $block->context['query'] ?? [], + 'queryId' => $block->context['queryId'] ?? null, + ] +); + +if ( is_null( $result ) ) { + return ''; +} + +$wrapper_attributes = get_block_wrapper_attributes( + [ + 'class' => sprintf( + 'taxonomy-text taxonomy-text--%s', + sanitize_html_class( $result['filter_type'] ) + ), + ] +); + +?> +> + + + diff --git a/inc/namespace.php b/inc/namespace.php index 985418c..f6613df 100644 --- a/inc/namespace.php +++ b/inc/namespace.php @@ -19,6 +19,10 @@ function bootstrap() : void { // General hooks. add_filter( 'query_loop_block_query_vars', __NAMESPACE__ . '\\filter_query_loop_block_query_vars', 10, 3 ); add_action( 'pre_get_posts', __NAMESPACE__ . '\\pre_get_posts_transpose_query_vars' ); + add_action( 'template_redirect', __NAMESPACE__ . '\\maybe_redirect_taxonomy_query_page' ); + add_action( 'enqueue_block_editor_assets', __NAMESPACE__ . '\\enqueue_block_editor_assets' ); + add_filter( 'render_block', __NAMESPACE__ . '\\filter_inline_taxonomy_text_in_block', 10, 2 ); + add_filter( 'the_content', __NAMESPACE__ . '\\filter_inline_taxonomy_text_in_content', 12 ); add_filter( 'the_posts', __NAMESPACE__ . '\\store_query_loop_posts', 10, 2 ); add_filter( 'block_type_metadata', __NAMESPACE__ . '\\filter_block_type_metadata', 10 ); add_action( 'init', __NAMESPACE__ . '\\register_blocks' ); @@ -46,6 +50,29 @@ function action_wp_enqueue_scripts() : void { ); } +/** + * Enqueue editor-only assets for block/format features. + * + * @return void + */ +function enqueue_block_editor_assets() : void { + $asset_path = ROOT_DIR . '/build/taxonomy-text/index.asset.php'; + + if ( ! file_exists( $asset_path ) ) { + return; + } + + $asset = include $asset_path; + + wp_enqueue_script( + 'query-filter-taxonomy-text', + plugins_url( '/build/taxonomy-text/index.js', PLUGIN_FILE ), + $asset['dependencies'], + $asset['version'], + true + ); +} + /** * Fires after WordPress has finished loading but before any headers are sent. * @@ -54,6 +81,7 @@ function register_blocks() : void { register_block_type( ROOT_DIR . '/build/taxonomy' ); register_block_type( ROOT_DIR . '/build/post-type' ); register_block_type( ROOT_DIR . '/build/sort' ); + register_block_type( ROOT_DIR . '/build/taxonomy-text' ); } /** @@ -114,6 +142,7 @@ function pre_get_posts_transpose_query_vars( WP_Query $query ) : void { } $tax_query = []; + $page_param_handled = false; $valid_keys = [ 'post_type' => $query->is_search() ? 'any' : 'post', 's' => '', @@ -142,6 +171,7 @@ function pre_get_posts_transpose_query_vars( WP_Query $query ) : void { if ( 'page' === $param ) { $paged = max( 1, absint( $value ) ); $query->set( 'paged', $paged ); + $page_param_handled = true; continue; } @@ -194,6 +224,7 @@ function pre_get_posts_transpose_query_vars( WP_Query $query ) : void { if ( 'page' === $param ) { $paged = max( 1, absint( $value ) ); $query->set( 'paged', $paged ); + $page_param_handled = true; continue; } @@ -237,6 +268,22 @@ function pre_get_posts_transpose_query_vars( WP_Query $query ) : void { } } + if ( ! $page_param_handled ) { + $path_paged = (int) get_query_var( 'paged', 0 ); + + if ( $path_paged > 1 ) { + $should_use_path_paged = ( 'main' === $current_query_identifier ); + + if ( ! $should_use_path_paged && ! $use_legacy_params ) { + $should_use_path_paged = (string) $current_query_identifier === $requested_query_id; + } + + if ( $should_use_path_paged && ! (int) $query->get( 'paged' ) ) { + $query->set( 'paged', $path_paged ); + } + } + } + if ( ! empty( $tax_query ) ) { $existing_query = $query->get( 'tax_query', [] ); @@ -428,3 +475,302 @@ static function ( $matches ) { $url ); } + +/** + * Redirect taxonomy pagination requests to pretty permalinks. + * + * @return void + */ +function maybe_redirect_taxonomy_query_page() : void { + if ( is_admin() || wp_doing_ajax() ) { + return; + } + + if ( ! ( is_category() || is_tag() || is_tax() ) ) { + return; + } + + $page_param = null; + + if ( isset( $_GET['query-page'] ) ) { + $page_param = 'query-page'; + } else { + foreach ( array_keys( $_GET ) as $key ) { + if ( preg_match( '/^query-\d+-page$/', $key ) ) { + $page_param = $key; + break; + } + } + } + + if ( is_null( $page_param ) ) { + return; + } + + $page = max( 1, absint( wp_unslash( $_GET[ $page_param ] ) ) ); + + if ( $page <= 1 ) { + return; + } + + $destination = get_pagenum_link( $page ); + + if ( empty( $destination ) ) { + return; + } + + $params = []; + + foreach ( $_GET as $key => $value ) { + if ( $key === $page_param || strpos( $key, 'query-' ) !== 0 ) { + continue; + } + + $params[ $key ] = sanitize_text_field( wp_unslash( $value ) ); + } + + if ( ! empty( $params ) ) { + $destination = add_query_arg( $params, $destination ); + } + + wp_safe_redirect( $destination, 301 ); + exit; +} + +/** + * Build the rendered taxonomy text result for the block/format. + * + * @param array $attributes Attributes. + * @param array $context Block context. + * @return array{ text: string, filter_type: string }|null + */ +function get_taxonomy_text_result( array $attributes, array $context = [] ) : ?array { + $allowed_filters = [ 'tag', 'category', 'sort' ]; + $filter_type = $attributes['filterType'] ?? 'tag'; + + if ( ! in_array( $filter_type, $allowed_filters, true ) ) { + $filter_type = 'tag'; + } + + $prefix = (string) ( $attributes['prefix'] ?? '' ); + $suffix = (string) ( $attributes['suffix'] ?? '' ); + $query_context = $context['query'] ?? []; + $inherits_main = empty( $context ) || ! empty( $query_context['inherit'] ); + $target_query_id = $inherits_main ? 'main' : (string) ( $context['queryId'] ?? 0 ); + + $requested_query_id = sanitize_text_field( + wp_unslash( + $_GET['query-post_id'] + ?? ( $_GET['query-id'] ?? '' ) + ) + ); + + $legacy_prefix = 'main' === $target_query_id ? 'query-' : "query-{$target_query_id}-"; + $use_legacy_params = false; + + if ( 'main' !== $target_query_id && empty( $requested_query_id ) ) { + foreach ( array_keys( $_GET ) as $key ) { + if ( strpos( $key, $legacy_prefix ) === 0 ) { + $use_legacy_params = true; + break; + } + } + } + + if ( ! $use_legacy_params ) { + if ( 'main' !== $target_query_id && $requested_query_id !== $target_query_id ) { + return null; + } + + if ( 'main' === $target_query_id && $requested_query_id && 'main' !== $requested_query_id ) { + return null; + } + } + + $param_suffix_map = [ + 'tag' => 'post_tag', + 'category' => 'category', + 'sort' => 'post_orderby', + ]; + + $param_candidates = array_unique( [ + 'query-' . $param_suffix_map[ $filter_type ], + $legacy_prefix . $param_suffix_map[ $filter_type ], + ] ); + + $raw_value = ''; + + foreach ( $param_candidates as $param_name ) { + if ( isset( $_GET[ $param_name ] ) && '' !== $_GET[ $param_name ] ) { + $raw_value = sanitize_text_field( urldecode( wp_unslash( $_GET[ $param_name ] ) ) ); + break; + } + } + + if ( '' === $raw_value ) { + return null; + } + + $display_value = ''; + + if ( 'sort' === $filter_type ) { + $sort_labels = [ + 'date:DESC' => __( 'Newest to Oldest', 'query-filter' ), + 'date:ASC' => __( 'Oldest to Newest', 'query-filter' ), + 'title:ASC' => __( 'A → Z', 'query-filter' ), + 'title:DESC' => __( 'Z → A', 'query-filter' ), + 'comment_count:DESC' => __( 'Most Commented', 'query-filter' ), + 'menu_order:ASC' => __( 'Menu Order', 'query-filter' ), + ]; + + $parts = explode( ':', $raw_value ); + $orderby = sanitize_key( $parts[0] ?? '' ); + $order = strtoupper( sanitize_text_field( $parts[1] ?? '' ) ); + $normalized = $orderby . ':' . $order; + + if ( isset( $sort_labels[ $normalized ] ) ) { + $display_value = $sort_labels[ $normalized ]; + } + } else { + $taxonomy = 'tag' === $filter_type ? 'post_tag' : 'category'; + $raw_slugs = array_filter( + array_map( + 'trim', + explode( ',', $raw_value ) + ) + ); + $slugs = array_values( + array_filter( + array_map( + static function ( $slug ) { + $sanitized = sanitize_title( $slug ); + + return '' === $sanitized ? null : $sanitized; + }, + $raw_slugs + ) + ) + ); + + if ( empty( $slugs ) ) { + return null; + } + + $terms = get_terms( + [ + 'taxonomy' => $taxonomy, + 'slug' => $slugs, + 'hide_empty' => false, + ] + ); + + if ( is_wp_error( $terms ) || empty( $terms ) ) { + return null; + } + + $term_lookup = []; + + foreach ( $terms as $term ) { + $term_lookup[ $term->slug ] = $term->name; + } + + $ordered_names = []; + + foreach ( $slugs as $slug ) { + if ( isset( $term_lookup[ $slug ] ) ) { + $ordered_names[] = $term_lookup[ $slug ]; + } + } + + if ( empty( $ordered_names ) ) { + return null; + } + + $display_value = implode( ', ', $ordered_names ); + } + + if ( '' === $display_value ) { + return null; + } + + $text = $prefix . $display_value . $suffix; + + if ( '' === trim( $text ) ) { + return null; + } + + return [ + 'text' => $text, + 'filter_type' => $filter_type, + ]; +} + +/** + * Replace inline taxonomy spans in rendered content. + * + * @param string $content Content string. + * @param array $context Block context. + * @return string + */ +function replace_inline_taxonomy_text_spans( string $content, array $context = [] ) : string { + if ( false === strpos( $content, 'data-query-filter-text=' ) ) { + return $content; + } + + $pattern = '/]*)data-query-filter-text="([^"]+)"([^>]*)>(.*?)<\/span>/si'; + + return preg_replace_callback( + $pattern, + static function ( $matches ) use ( $context ) { + $decoded = html_entity_decode( $matches[2], ENT_QUOTES, 'UTF-8' ); + $attributes = json_decode( $decoded, true ); + + if ( ! is_array( $attributes ) ) { + return $matches[0]; + } + + $result = get_taxonomy_text_result( $attributes, $context ); + + if ( is_null( $result ) ) { + return ''; + } + + $class = sprintf( + 'taxonomy-text taxonomy-text--%s', + sanitize_html_class( $result['filter_type'] ) + ); + + return sprintf( + '%s', + esc_attr( $class ), + esc_html( $result['text'] ) + ); + }, + $content + ); +} + +/** + * Filter the rendered block content to inject inline taxonomy text. + * + * @param string $block_content Block HTML. + * @param array $block Parsed block data. + * @return string + */ +function filter_inline_taxonomy_text_in_block( string $block_content, array $block ) : string { + if ( false === strpos( $block_content, 'data-query-filter-text=' ) ) { + return $block_content; + } + + return replace_inline_taxonomy_text_spans( $block_content, $block['context'] ?? [] ); +} + +/** + * Fallback for legacy content where render_block is not invoked. + * + * @param string $content Post content. + * @return string + */ +function filter_inline_taxonomy_text_in_content( string $content ) : string { + return replace_inline_taxonomy_text_spans( $content ); +} From 70e4a1b2c188a5b76cf92dc85256bc621bf35e5c Mon Sep 17 00:00:00 2001 From: "Zachary A. Martz" Date: Tue, 25 Nov 2025 11:12:41 -0500 Subject: [PATCH 4/8] Add taxonomy text title and description options MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - add a valueType attribute plus inspector control so the block can output either the selected term’s title or its description (sort filters always use title), and update the PHP helper to return the correct sanitized value - expand the RichText format modal with the same value-type dropdown, auto- insert a descriptive placeholder like “taxonomy tag description”, and update existing spans instead of inserting duplicates when re-editing - rebuild the taxonomy-text editor script/assets and enqueue them so both the standalone block and inline format always reflect the new options --- build/post-type/render.php | 44 +---- build/taxonomy-text/block.json | 8 + build/taxonomy-text/index.asset.php | 2 +- build/taxonomy-text/index.js | 241 +---------------------- build/taxonomy-text/render.php | 2 +- build/taxonomy/block.json | 4 - build/taxonomy/index.asset.php | 2 +- build/taxonomy/index.js | 2 +- build/taxonomy/render.php | 82 ++------ inc/namespace.php | 22 ++- src/taxonomy-text/block.json | 57 ++++++ src/taxonomy-text/edit.js | 84 ++++++++ src/taxonomy-text/format.js | 200 +++++++++++++++++++ src/{taxonomy => taxonomy-text}/index.js | 8 +- src/taxonomy-text/render.php | 33 ++++ src/taxonomy/block.json | 64 ------ src/taxonomy/edit.js | 109 ---------- src/taxonomy/render.php | 42 ---- src/taxonomy/style-index.css | 9 - src/taxonomy/view.js | 46 ----- 20 files changed, 433 insertions(+), 628 deletions(-) create mode 100644 src/taxonomy-text/block.json create mode 100644 src/taxonomy-text/edit.js create mode 100644 src/taxonomy-text/format.js rename src/{taxonomy => taxonomy-text}/index.js (75%) create mode 100644 src/taxonomy-text/render.php delete mode 100644 src/taxonomy/block.json delete mode 100644 src/taxonomy/edit.js delete mode 100644 src/taxonomy/render.php delete mode 100644 src/taxonomy/style-index.css delete mode 100644 src/taxonomy/view.js diff --git a/build/post-type/render.php b/build/post-type/render.php index c01e445..70d43f2 100644 --- a/build/post-type/render.php +++ b/build/post-type/render.php @@ -3,30 +3,17 @@ $id = 'query-filter-' . wp_generate_uuid4(); -$target_query_id = empty( $block->context['query']['inherit'] ) - ? (string) ( $block->context['queryId'] ?? 0 ) - : 'main'; - -$query_var = 'query-post_type'; -$page_var = 'query-page'; -$base_url = remove_query_arg( [ $query_var, $page_var, 'query-post_id', 'query-id' ] ); - if ( $block->context['query']['inherit'] ) { - $current_paged = (int) get_query_var( 'paged' ); - if ( $current_paged > 1 ) { - $base_url = str_replace( '/page/' . $current_paged, '', $base_url ); - } - $base_url = remove_query_arg( [ 'page' ], $base_url ); + $query_var = 'query-post_type'; + $page_var = 'page'; + $base_url = str_replace( '/page/' . get_query_var( 'paged' ), '', remove_query_arg( [ $query_var, $page_var ] ) ); +} else { + $query_id = $block->context['queryId'] ?? 0; + $query_var = sprintf( 'query-%d-post_type', $query_id ); + $page_var = isset( $block->context['queryId'] ) ? 'query-' . $block->context['queryId'] . '-page' : 'query-page'; + $base_url = remove_query_arg( [ $query_var, $page_var ] ); } -$base_url = add_query_arg( - [ - 'query-post_id' => $target_query_id, - ], - $base_url -); -$base_url = HM\Query_Loop_Filter\normalize_query_filter_url( $base_url ); - $post_types = array_map( 'trim', explode( ',', $block->context['query']['postType'] ?? 'post' ) ); // Support for enhanced query block. @@ -60,19 +47,8 @@ diff --git a/build/taxonomy-text/block.json b/build/taxonomy-text/block.json index 87dc887..536ac62 100644 --- a/build/taxonomy-text/block.json +++ b/build/taxonomy-text/block.json @@ -50,6 +50,14 @@ ], "default": "tag" }, + "valueType": { + "type": "string", + "enum": [ + "title", + "description" + ], + "default": "title" + }, "prefix": { "type": "string", "default": "" diff --git a/build/taxonomy-text/index.asset.php b/build/taxonomy-text/index.asset.php index 19e857c..563e42a 100644 --- a/build/taxonomy-text/index.asset.php +++ b/build/taxonomy-text/index.asset.php @@ -1 +1 @@ - array('wp-block-editor', 'wp-blocks', 'wp-components', 'wp-element', 'wp-i18n', 'wp-primitives', 'wp-rich-text'), 'version' => '20251125'); + array('react-jsx-runtime', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-element', 'wp-i18n', 'wp-primitives', 'wp-rich-text'), 'version' => '39c5f52a952637099d57'); diff --git a/build/taxonomy-text/index.js b/build/taxonomy-text/index.js index 698f2a8..9fa8ee6 100644 --- a/build/taxonomy-text/index.js +++ b/build/taxonomy-text/index.js @@ -1,240 +1 @@ -( function ( wp ) { - const { __ } = wp.i18n; - const { registerBlockType } = wp.blocks; - const { registerFormatType, toggleFormat, insert } = wp.richText; - const { - InspectorControls, - RichTextToolbarButton, - useBlockProps, - } = wp.blockEditor; - const { - Button, - Flex, - FlexItem, - Modal, - PanelBody, - SelectControl, - TextControl, - } = wp.components; - const { Fragment, useEffect, useMemo, useState, createElement: el } = - wp.element; - const { SVG, Path } = wp.primitives; - - const FILTER_OPTIONS = [ - { label: __( 'Tag', 'query-filter' ), value: 'tag' }, - { label: __( 'Category', 'query-filter' ), value: 'category' }, - { label: __( 'Sort', 'query-filter' ), value: 'sort' }, - ]; - - const BLOCK_NAME = 'query-filter/taxonomy-text'; - const FORMAT_NAME = 'query-filter/taxonomy-inline-text'; - const ATTRIBUTE_KEY = 'data-query-filter-text'; - const PLACEHOLDER_TEXT = __( 'taxonomy', 'query-filter' ); - - const ICON = el( - SVG, - { viewBox: '0 0 24 24', xmlns: 'http://www.w3.org/2000/svg' }, - el( Path, { - d: 'M17.5 4v5a2 2 0 0 1-2 2h-7a2 2 0 0 1-2-2V4H8v5a.5.5 0 0 0 .5.5h7A.5.5 0 0 0 16 9V4h1.5Zm0 16v-5a2 2 0 0 0-2-2h-7a2 2 0 0 0-2 2v5H8v-5a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 .5.5v5h1.5Z', - } ) - ); - - const parseSettings = ( value ) => { - if ( ! value ) { - return { filterType: 'tag', prefix: '', suffix: '' }; - } - - try { - const parsed = JSON.parse( value ); - - return { - filterType: parsed.filterType || 'tag', - prefix: parsed.prefix || '', - suffix: parsed.suffix || '', - }; - } catch ( error ) { - return { filterType: 'tag', prefix: '', suffix: '' }; - } - }; - - const FormatEdit = ( { - value, - onChange, - isActive, - activeAttributes, - } ) => { - const currentSettings = useMemo( - () => parseSettings( activeAttributes?.[ ATTRIBUTE_KEY ] ), - [ activeAttributes?.[ ATTRIBUTE_KEY ] ] - ); - const [ isOpen, setIsOpen ] = useState( false ); - const [ settings, setSettings ] = useState( currentSettings ); - - useEffect( () => { - setSettings( currentSettings ); - }, [ currentSettings ] ); - - const applyFormat = () => { - const attributes = { - [ ATTRIBUTE_KEY ]: JSON.stringify( settings ), - }; - const selectionCollapsed = value.start === value.end; - let nextValue = value; - - if ( selectionCollapsed ) { - nextValue = insert( nextValue, PLACEHOLDER_TEXT ); - const end = nextValue.start; - const start = end - PLACEHOLDER_TEXT.length; - nextValue = { ...nextValue, start, end }; - } - - onChange( - toggleFormat( nextValue, { - type: FORMAT_NAME, - attributes, - } ) - ); - setIsOpen( false ); - }; - - const removeFormat = () => { - onChange( toggleFormat( value, { type: FORMAT_NAME } ) ); - setIsOpen( false ); - }; - - return el( - Fragment, - null, - el( RichTextToolbarButton, { - icon: ICON, - title: __( 'Taxonomy Text', 'query-filter' ), - onClick: () => setIsOpen( true ), - isActive, - } ), - isOpen && - el( - Modal, - { - title: __( 'Dynamic Taxonomy Text', 'query-filter' ), - onRequestClose: () => setIsOpen( false ), - }, - el( SelectControl, { - label: __( 'Filter Source', 'query-filter' ), - value: settings.filterType, - options: FILTER_OPTIONS, - onChange: ( filterType ) => - setSettings( ( prev ) => ( { - ...prev, - filterType, - } ) ), - } ), - el( TextControl, { - label: __( 'Prefix Text', 'query-filter' ), - value: settings.prefix, - onChange: ( prefix ) => - setSettings( ( prev ) => ( { - ...prev, - prefix, - } ) ), - } ), - el( TextControl, { - label: __( 'Suffix Text', 'query-filter' ), - value: settings.suffix, - onChange: ( suffix ) => - setSettings( ( prev ) => ( { - ...prev, - suffix, - } ) ), - } ), - el( - Flex, - { justify: 'flex-start' }, - el( - FlexItem, - null, - el( - Button, - { variant: 'primary', onClick: applyFormat }, - __( 'Apply', 'query-filter' ) - ) - ), - isActive && - el( - FlexItem, - null, - el( - Button, - { variant: 'tertiary', onClick: removeFormat }, - __( 'Remove', 'query-filter' ) - ) - ) - ) - ) - ); - }; - - const BlockEdit = ( { attributes, setAttributes } ) => { - const { filterType = 'tag', prefix = '', suffix = '' } = attributes; - const blockProps = useBlockProps( { - className: `taxonomy-text taxonomy-text--${ filterType }`, - } ); - const previewValue = - filterType === 'sort' - ? __( 'Selected Sort', 'query-filter' ) - : __( 'Selected Term', 'query-filter' ); - - return el( - Fragment, - null, - el( - InspectorControls, - null, - el( - PanelBody, - { title: __( 'Display Settings', 'query-filter' ) }, - el( SelectControl, { - label: __( 'Filter Source', 'query-filter' ), - value: filterType, - options: FILTER_OPTIONS, - onChange: ( nextFilter ) => - setAttributes( { filterType: nextFilter } ), - } ), - el( TextControl, { - label: __( 'Prefix Text', 'query-filter' ), - value: prefix, - onChange: ( value ) => - setAttributes( { prefix: value } ), - placeholder: __( 'e.g. Showing results for ', 'query-filter' ), - } ), - el( TextControl, { - label: __( 'Suffix Text', 'query-filter' ), - value: suffix, - onChange: ( value ) => - setAttributes( { suffix: value } ), - placeholder: __( 'e.g. only', 'query-filter' ), - } ) - ) - ), - el( - 'span', - { ...blockProps }, - `${ prefix || '' }${ previewValue }${ suffix || '' }` - ) - ); - }; - - registerFormatType( FORMAT_NAME, { - title: __( 'Taxonomy Text', 'query-filter' ), - tagName: 'span', - className: 'taxonomy-text-inline', - attributes: { - [ ATTRIBUTE_KEY ]: ATTRIBUTE_KEY, - }, - edit: FormatEdit, - } ); - - registerBlockType( BLOCK_NAME, { - edit: BlockEdit, - } ); -} )( window.wp ); - +(()=>{"use strict";const e=window.wp.blocks,t=JSON.parse('{"UU":"query-filter/taxonomy-text"}'),l=window.wp.i18n,r=window.wp.blockEditor,i=window.wp.components,a=window.ReactJSXRuntime,o=[{label:(0,l.__)("Tag","query-filter"),value:"tag"},{label:(0,l.__)("Category","query-filter"),value:"category"},{label:(0,l.__)("Sort","query-filter"),value:"sort"}],n=[{label:(0,l.__)("Title","query-filter"),value:"title"},{label:(0,l.__)("Description","query-filter"),value:"description"}],s=window.wp.richText,u=window.wp.primitives;var y=(0,a.jsx)(u.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",children:(0,a.jsx)(u.Path,{d:"M17.5 4v5a2 2 0 0 1-2 2h-7a2 2 0 0 1-2-2V4H8v5a.5.5 0 0 0 .5.5h7A.5.5 0 0 0 16 9V4h1.5Zm0 16v-5a2 2 0 0 0-2-2h-7a2 2 0 0 0-2 2v5H8v-5a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 .5.5v5h1.5Z"})});const f=window.wp.element,p="query-filter/taxonomy-inline-text",x="data-query-filter-text",c={filterType:"tag",valueType:"title",prefix:"",suffix:""},_=[{label:(0,l.__)("Tag","query-filter"),value:"tag"},{label:(0,l.__)("Category","query-filter"),value:"category"},{label:(0,l.__)("Sort","query-filter"),value:"sort"}],T=[{label:(0,l.__)("Title","query-filter"),value:"title"},{label:(0,l.__)("Description","query-filter"),value:"description"}],v=(e,t)=>e.find((e=>e.value===t))?.label||t;(0,s.registerFormatType)(p,{title:(0,l.__)("Taxonomy Text","query-filter"),tagName:"span",className:"taxonomy-text-inline",attributes:{[x]:x},edit:({value:e,onChange:t,isActive:o,activeAttributes:n})=>{const u=(0,f.useMemo)((()=>(e=>{if(!e)return c;try{const t=JSON.parse(e);return{filterType:t.filterType||c.filterType,valueType:t.valueType||c.valueType,prefix:t.prefix||"",suffix:t.suffix||""}}catch(e){return c}})(n?.[x])),[n?.[x]]),[d,h]=(0,f.useState)(!1),[q,g]=(0,f.useState)(u);return(0,f.useEffect)((()=>{g(u)}),[u]),(0,f.useEffect)((()=>{"sort"===q.filterType&&"title"!==q.valueType&&g((e=>({...e,valueType:"title"})))}),[q.filterType,q.valueType]),(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)(r.RichTextToolbarButton,{icon:y,title:(0,l.__)("Taxonomy Text","query-filter"),onClick:()=>h(!0),isActive:o}),d&&(0,a.jsxs)(i.Modal,{title:(0,l.__)("Dynamic Taxonomy Text","query-filter"),onRequestClose:()=>h(!1),children:[(0,a.jsx)(i.SelectControl,{label:(0,l.__)("Filter Source","query-filter"),value:q.filterType,options:_,onChange:e=>{g((t=>({...t,filterType:e,valueType:"sort"===e?"title":t.valueType})))}}),(0,a.jsx)(i.SelectControl,{label:(0,l.__)("Value Type","query-filter"),value:q.valueType,options:T,disabled:"sort"===q.filterType,onChange:e=>g((t=>({...t,valueType:e})))}),(0,a.jsx)(i.TextControl,{label:(0,l.__)("Prefix Text","query-filter"),value:q.prefix,onChange:e=>g((t=>({...t,prefix:e})))}),(0,a.jsx)(i.TextControl,{label:(0,l.__)("Suffix Text","query-filter"),value:q.suffix,onChange:e=>g((t=>({...t,suffix:e})))}),(0,a.jsx)(i.Flex,{justify:"flex-start",children:(0,a.jsx)(i.FlexItem,{children:(0,a.jsx)(i.Button,{variant:"primary",onClick:()=>{const r={[x]:JSON.stringify(q)},i=e.start===e.end;let a=e;if(i&&!o){const e=(e=>{const t=[(0,l.__)("taxonomy","query-filter"),v(_,e.filterType).toLowerCase()],r="sort"===e.filterType?(0,l.__)("title","query-filter"):v(T,e.valueType);return t.push(r.toLowerCase()),t.join(" ")})(q);a=(0,s.insert)(a,e);const t=a.start,r=t-e.length;a={...a,start:r,end:t}}const n=(0,s.applyFormat)(a,{type:p,attributes:r});t(n),h(!1)},children:(0,l.__)("Apply","query-filter")})})})]})]})}}),(0,e.registerBlockType)(t.UU,{edit:function({attributes:e,setAttributes:t}){const{filterType:s="tag",valueType:u="title",prefix:y="",suffix:f=""}=e,p=(0,r.useBlockProps)({className:`taxonomy-text taxonomy-text--${s}`}),x="sort"===s?(0,l.__)("Selected Sort","query-filter"):"description"===u?(0,l.__)("Selected Term Description","query-filter"):(0,l.__)("Selected Term","query-filter");return(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)(r.InspectorControls,{children:(0,a.jsxs)(i.PanelBody,{title:(0,l.__)("Display Settings","query-filter"),children:[(0,a.jsx)(i.SelectControl,{label:(0,l.__)("Filter Source","query-filter"),value:s,options:o,onChange:e=>{t({filterType:e,valueType:"sort"===e?"title":u})}}),(0,a.jsx)(i.SelectControl,{label:(0,l.__)("Value Type","query-filter"),value:u,options:n,disabled:"sort"===s,onChange:e=>t({valueType:e})}),(0,a.jsx)(i.TextControl,{label:(0,l.__)("Prefix Text","query-filter"),value:y,onChange:e=>t({prefix:e}),placeholder:(0,l.__)("e.g. Showing results for ","query-filter")}),(0,a.jsx)(i.TextControl,{label:(0,l.__)("Suffix Text","query-filter"),value:f,onChange:e=>t({suffix:e}),placeholder:(0,l.__)("e.g. only","query-filter")})]})}),(0,a.jsx)("span",{...p,children:`${y||""}${x}${f||""}`})]})}})})(); \ No newline at end of file diff --git a/build/taxonomy-text/render.php b/build/taxonomy-text/render.php index 5afbfa0..6a63c44 100644 --- a/build/taxonomy-text/render.php +++ b/build/taxonomy-text/render.php @@ -1,6 +1,6 @@ array('react-jsx-runtime', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-data', 'wp-i18n'), 'version' => '0784db9d40d6fdb17335'); + array('react-jsx-runtime', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-data', 'wp-i18n'), 'version' => 'f1456d24ac8e3da497aa'); diff --git a/build/taxonomy/index.js b/build/taxonomy/index.js index 6eb0731..5868836 100644 --- a/build/taxonomy/index.js +++ b/build/taxonomy/index.js @@ -1 +1 @@ -(()=>{"use strict";const e=window.wp.blocks,l=window.wp.i18n,t=window.wp.blockEditor,o=window.wp.components,r=window.wp.data,n=window.ReactJSXRuntime,s=JSON.parse('{"UU":"query-filter/taxonomy"}');(0,e.registerBlockType)(s.UU,{edit:function({attributes:e,setAttributes:s}){const{taxonomy:a,emptyLabel:i,label:c,showLabel:u,limitToCurrentResults:y}=e,b=(0,r.useSelect)((e=>{const l=(e("core").getTaxonomies({per_page:100})||[]).filter((e=>e.visibility.publicly_queryable));return l&&l.length>0&&!a&&s({taxonomy:l[0].slug,label:l[0].name}),l}),[a]),m=(0,r.useSelect)((e=>e("core").getEntityRecords("taxonomy",a,{number:50})||[]),[a]);return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(t.InspectorControls,{children:(0,n.jsxs)(o.PanelBody,{title:(0,l.__)("Taxonomy Settings","query-filter"),children:[(0,n.jsx)(o.SelectControl,{label:(0,l.__)("Select Taxonomy","query-filter"),value:a,options:(b||[]).map((e=>({label:e.name,value:e.slug}))),onChange:e=>s({taxonomy:e,label:b.find((l=>l.slug===e)).name})}),(0,n.jsx)(o.TextControl,{label:(0,l.__)("Label","query-filter"),value:c,help:(0,l.__)("If empty then no label will be shown","query-filter"),onChange:e=>s({label:e})}),(0,n.jsx)(o.ToggleControl,{label:(0,l.__)("Show Label","query-filter"),checked:u,onChange:e=>s({showLabel:e})}),(0,n.jsx)(o.TextControl,{label:(0,l.__)("Empty Choice Label","query-filter"),value:i,placeholder:(0,l.__)("All","query-filter"),onChange:e=>s({emptyLabel:e})}),(0,n.jsx)(o.ToggleControl,{label:(0,l.__)("Show Terms in Current Results","query-filter"),help:(0,l.__)("Only include terms that are attached to posts returned by this query.","query-filter"),checked:y,onChange:e=>s({limitToCurrentResults:e})})]})}),(0,n.jsxs)("div",{...(0,t.useBlockProps)({className:"wp-block-query-filter"}),children:[u&&(0,n.jsx)("label",{className:"wp-block-query-filter-taxonomy__label wp-block-query-filter__label",children:c}),(0,n.jsxs)("select",{className:"wp-block-query-filter-taxonomy__select wp-block-query-filter__select",inert:!0,children:[(0,n.jsx)("option",{children:i||(0,l.__)("All","query-filter")}),m.map((e=>(0,n.jsx)("option",{children:e.name},e.slug)))]})]})]})}})})(); \ No newline at end of file +(()=>{"use strict";const e=window.wp.blocks,l=window.wp.i18n,t=window.wp.blockEditor,o=window.wp.components,n=window.wp.data,r=window.ReactJSXRuntime,a=JSON.parse('{"UU":"query-filter/taxonomy"}');(0,e.registerBlockType)(a.UU,{edit:function({attributes:e,setAttributes:a}){const{taxonomy:i,emptyLabel:s,label:c,showLabel:u}=e,b=(0,n.useSelect)((e=>{const l=(e("core").getTaxonomies({per_page:100})||[]).filter((e=>e.visibility.publicly_queryable));return l&&l.length>0&&!i&&a({taxonomy:l[0].slug,label:l[0].name}),l}),[i]),y=(0,n.useSelect)((e=>e("core").getEntityRecords("taxonomy",i,{number:50})||[]),[i]);return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(t.InspectorControls,{children:(0,r.jsxs)(o.PanelBody,{title:(0,l.__)("Taxonomy Settings","query-filter"),children:[(0,r.jsx)(o.SelectControl,{label:(0,l.__)("Select Taxonomy","query-filter"),value:i,options:(b||[]).map((e=>({label:e.name,value:e.slug}))),onChange:e=>a({taxonomy:e,label:b.find((l=>l.slug===e)).name})}),(0,r.jsx)(o.TextControl,{label:(0,l.__)("Label","query-filter"),value:c,help:(0,l.__)("If empty then no label will be shown","query-filter"),onChange:e=>a({label:e})}),(0,r.jsx)(o.ToggleControl,{label:(0,l.__)("Show Label","query-filter"),checked:u,onChange:e=>a({showLabel:e})}),(0,r.jsx)(o.TextControl,{label:(0,l.__)("Empty Choice Label","query-filter"),value:s,placeholder:(0,l.__)("All","query-filter"),onChange:e=>a({emptyLabel:e})})]})}),(0,r.jsxs)("div",{...(0,t.useBlockProps)({className:"wp-block-query-filter"}),children:[u&&(0,r.jsx)("label",{className:"wp-block-query-filter-taxonomy__label wp-block-query-filter__label",children:c}),(0,r.jsxs)("select",{className:"wp-block-query-filter-taxonomy__select wp-block-query-filter__select",inert:!0,children:[(0,r.jsx)("option",{children:s||(0,l.__)("All","query-filter")}),y.map((e=>(0,r.jsx)("option",{children:e.name},e.slug)))]})]})]})}})})(); \ No newline at end of file diff --git a/build/taxonomy/render.php b/build/taxonomy/render.php index 66d16f8..97258b4 100644 --- a/build/taxonomy/render.php +++ b/build/taxonomy/render.php @@ -7,61 +7,24 @@ $taxonomy = get_taxonomy( $attributes['taxonomy'] ); -$target_query_id = empty( $block->context['query']['inherit'] ) - ? (string) ( $block->context['queryId'] ?? 0 ) - : 'main'; - -$query_var = sprintf( 'query-%s', $attributes['taxonomy'] ); -$page_var = 'query-page'; -$base_url = remove_query_arg( [ $query_var, $page_var, 'query-post_id', 'query-id' ] ); - -if ( ! empty( $block->context['query']['inherit'] ) ) { - $current_paged = (int) get_query_var( 'paged' ); - if ( $current_paged > 1 ) { - $base_url = str_replace( '/page/' . $current_paged, '', $base_url ); - } - $base_url = remove_query_arg( [ 'page' ], $base_url ); +if ( empty( $block->context['query']['inherit'] ) ) { + $query_id = $block->context['queryId'] ?? 0; + $query_var = sprintf( 'query-%d-%s', $query_id, $attributes['taxonomy'] ); + $page_var = isset( $block->context['queryId'] ) ? 'query-' . $block->context['queryId'] . '-page' : 'query-page'; + $base_url = remove_query_arg( [ $query_var, $page_var ] ); +} else { + $query_var = sprintf( 'query-%s', $attributes['taxonomy'] ); + $page_var = 'page'; + $base_url = str_replace( '/page/' . get_query_var( 'paged' ), '', remove_query_arg( [ $query_var, $page_var ] ) ); } -$base_url = add_query_arg( - [ - 'query-post_id' => $target_query_id, - ], - $base_url -); -$base_url = HM\Query_Loop_Filter\normalize_query_filter_url( $base_url ); - -$terms = []; - -if ( ! empty( $attributes['limitToCurrentResults'] ) ) { - $post_ids = HM\Query_Loop_Filter\get_query_loop_post_ids_for_block( $block ); - - if ( ! empty( $post_ids ) ) { - $terms = wp_get_object_terms( - $post_ids, - $attributes['taxonomy'], - [ - 'orderby' => 'name', - 'order' => 'ASC', - 'number' => 100, - ] - ); - } - - if ( is_wp_error( $terms ) ) { - return; - } -} - -if ( empty( $terms ) ) { - $terms = get_terms( [ - 'hide_empty' => true, - 'taxonomy' => $attributes['taxonomy'], - 'number' => 100, - ] ); -} +$terms = get_terms( [ + 'hide_empty' => true, + 'taxonomy' => $attributes['taxonomy'], + 'number' => 100, +] ); -if ( is_wp_error( $terms ) || ( empty( $terms ) && empty( $attributes['limitToCurrentResults'] ) ) ) { +if ( is_wp_error( $terms ) || empty( $terms ) ) { return; } ?> @@ -72,19 +35,8 @@ diff --git a/inc/namespace.php b/inc/namespace.php index f6613df..bdf54ca 100644 --- a/inc/namespace.php +++ b/inc/namespace.php @@ -547,11 +547,16 @@ function maybe_redirect_taxonomy_query_page() : void { function get_taxonomy_text_result( array $attributes, array $context = [] ) : ?array { $allowed_filters = [ 'tag', 'category', 'sort' ]; $filter_type = $attributes['filterType'] ?? 'tag'; + $value_type = $attributes['valueType'] ?? 'title'; if ( ! in_array( $filter_type, $allowed_filters, true ) ) { $filter_type = 'tag'; } + if ( ! in_array( $value_type, [ 'title', 'description' ], true ) ) { + $value_type = 'title'; + } + $prefix = (string) ( $attributes['prefix'] ?? '' ); $suffix = (string) ( $attributes['suffix'] ?? '' ); $query_context = $context['query'] ?? []; @@ -631,6 +636,7 @@ function get_taxonomy_text_result( array $attributes, array $context = [] ) : ?a if ( isset( $sort_labels[ $normalized ] ) ) { $display_value = $sort_labels[ $normalized ]; } + $value_type = 'title'; } else { $taxonomy = 'tag' === $filter_type ? 'post_tag' : 'category'; $raw_slugs = array_filter( @@ -671,22 +677,26 @@ static function ( $slug ) { $term_lookup = []; foreach ( $terms as $term ) { - $term_lookup[ $term->slug ] = $term->name; + $term_lookup[ $term->slug ] = [ + 'title' => $term->name, + 'description' => trim( wp_strip_all_tags( $term->description ?? '' ) ), + ]; } - $ordered_names = []; + $ordered_values = []; + $value_key = 'description' === $value_type ? 'description' : 'title'; foreach ( $slugs as $slug ) { - if ( isset( $term_lookup[ $slug ] ) ) { - $ordered_names[] = $term_lookup[ $slug ]; + if ( isset( $term_lookup[ $slug ][ $value_key ] ) && '' !== $term_lookup[ $slug ][ $value_key ] ) { + $ordered_values[] = $term_lookup[ $slug ][ $value_key ]; } } - if ( empty( $ordered_names ) ) { + if ( empty( $ordered_values ) ) { return null; } - $display_value = implode( ', ', $ordered_names ); + $display_value = implode( ', ', $ordered_values ); } if ( '' === $display_value ) { diff --git a/src/taxonomy-text/block.json b/src/taxonomy-text/block.json new file mode 100644 index 0000000..4ee88e5 --- /dev/null +++ b/src/taxonomy-text/block.json @@ -0,0 +1,57 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 3, + "name": "query-filter/taxonomy-text", + "version": "0.1.0", + "title": "Taxonomy Text", + "category": "theme", + "icon": "feedback", + "description": "Outputs the currently selected taxonomy term or sort option for the surrounding query loop.", + "keywords": [ "query", "filter", "taxonomy", "seo" ], + "ancestor": [ "core/query" ], + "usesContext": [ "queryId", "query" ], + "supports": { + "html": false, + "className": true, + "customClassName": true, + "color": { + "text": true, + "background": true + }, + "typography": { + "fontSize": true, + "lineHeight": true, + "__experimentalFontWeight": true, + "__experimentalTextTransform": true, + "__experimentalLetterSpacing": true + }, + "spacing": { + "margin": true, + "padding": true + } + }, + "attributes": { + "filterType": { + "type": "string", + "enum": [ "tag", "category", "sort" ], + "default": "tag" + }, + "valueType": { + "type": "string", + "enum": [ "title", "description" ], + "default": "title" + }, + "prefix": { + "type": "string", + "default": "" + }, + "suffix": { + "type": "string", + "default": "" + } + }, + "textdomain": "query-filter", + "editorScript": "file:./index.js", + "render": "file:./render.php" +} + diff --git a/src/taxonomy-text/edit.js b/src/taxonomy-text/edit.js new file mode 100644 index 0000000..2367861 --- /dev/null +++ b/src/taxonomy-text/edit.js @@ -0,0 +1,84 @@ +import { __ } from '@wordpress/i18n'; +import { + InspectorControls, + useBlockProps, +} from '@wordpress/block-editor'; +import { PanelBody, SelectControl, TextControl } from '@wordpress/components'; + +const FILTER_OPTIONS = [ + { label: __( 'Tag', 'query-filter' ), value: 'tag' }, + { label: __( 'Category', 'query-filter' ), value: 'category' }, + { label: __( 'Sort', 'query-filter' ), value: 'sort' }, +]; + +const VALUE_TYPE_OPTIONS = [ + { label: __( 'Title', 'query-filter' ), value: 'title' }, + { label: __( 'Description', 'query-filter' ), value: 'description' }, +]; + +export default function Edit( { attributes, setAttributes } ) { + const { + filterType = 'tag', + valueType = 'title', + prefix = '', + suffix = '', + } = attributes; + + const blockProps = useBlockProps( { + className: `taxonomy-text taxonomy-text--${ filterType }`, + } ); + + const previewValue = + filterType === 'sort' + ? __( 'Selected Sort', 'query-filter' ) + : valueType === 'description' + ? __( 'Selected Term Description', 'query-filter' ) + : __( 'Selected Term', 'query-filter' ); + + const handleFilterChange = ( nextFilter ) => { + setAttributes( { + filterType: nextFilter, + valueType: nextFilter === 'sort' ? 'title' : valueType, + } ); + }; + + return ( + <> + + + + + setAttributes( { valueType: nextValue } ) + } + /> + setAttributes( { prefix: value } ) } + placeholder={ __( 'e.g. Showing results for ', 'query-filter' ) } + /> + setAttributes( { suffix: value } ) } + placeholder={ __( 'e.g. only', 'query-filter' ) } + /> + + + + { `${ prefix || '' }${ previewValue }${ suffix || '' }` } + + + ); +} + diff --git a/src/taxonomy-text/format.js b/src/taxonomy-text/format.js new file mode 100644 index 0000000..4725489 --- /dev/null +++ b/src/taxonomy-text/format.js @@ -0,0 +1,200 @@ +import { __ } from '@wordpress/i18n'; +import { + registerFormatType, + insert, + applyFormat, +} from '@wordpress/rich-text'; +import { RichTextToolbarButton } from '@wordpress/block-editor'; +import { + Button, + Flex, + FlexItem, + Modal, + SelectControl, + TextControl, +} from '@wordpress/components'; +import { stack } from '@wordpress/icons'; +import { useEffect, useMemo, useState } from '@wordpress/element'; + +const FORMAT_NAME = 'query-filter/taxonomy-inline-text'; +const ATTRIBUTE_KEY = 'data-query-filter-text'; + +const DEFAULT_SETTINGS = { + filterType: 'tag', + valueType: 'title', + prefix: '', + suffix: '', +}; + +const FILTER_OPTIONS = [ + { label: __( 'Tag', 'query-filter' ), value: 'tag' }, + { label: __( 'Category', 'query-filter' ), value: 'category' }, + { label: __( 'Sort', 'query-filter' ), value: 'sort' }, +]; + +const VALUE_TYPE_OPTIONS = [ + { label: __( 'Title', 'query-filter' ), value: 'title' }, + { label: __( 'Description', 'query-filter' ), value: 'description' }, +]; + +const getOptionLabel = ( options, value ) => + options.find( ( option ) => option.value === value )?.label || value; + +const getPlaceholderText = ( settings ) => { + const parts = [ + __( 'taxonomy', 'query-filter' ), + getOptionLabel( FILTER_OPTIONS, settings.filterType ).toLowerCase(), + ]; + + const valueLabel = + settings.filterType === 'sort' + ? __( 'title', 'query-filter' ) + : getOptionLabel( VALUE_TYPE_OPTIONS, settings.valueType ); + + parts.push( valueLabel.toLowerCase() ); + + return parts.join( ' ' ); +}; + +const parseSettings = ( attributeValue ) => { + if ( ! attributeValue ) { + return DEFAULT_SETTINGS; + } + + try { + const parsed = JSON.parse( attributeValue ); + + return { + filterType: parsed.filterType || DEFAULT_SETTINGS.filterType, + valueType: parsed.valueType || DEFAULT_SETTINGS.valueType, + prefix: parsed.prefix || '', + suffix: parsed.suffix || '', + }; + } catch ( error ) { + return DEFAULT_SETTINGS; + } +}; + +const FormatEdit = ( { + value, + onChange, + isActive, + activeAttributes, +} ) => { + const currentSettings = useMemo( + () => parseSettings( activeAttributes?.[ ATTRIBUTE_KEY ] ), + [ activeAttributes?.[ ATTRIBUTE_KEY ] ] + ); + const [ isOpen, setIsOpen ] = useState( false ); + const [ settings, setSettings ] = useState( currentSettings ); + + useEffect( () => { + setSettings( currentSettings ); + }, [ currentSettings ] ); + + useEffect( () => { + if ( settings.filterType === 'sort' && settings.valueType !== 'title' ) { + setSettings( ( prev ) => ( { ...prev, valueType: 'title' } ) ); + } + }, [ settings.filterType, settings.valueType ] ); + + const applyFormatToSelection = () => { + const attributes = { + [ ATTRIBUTE_KEY ]: JSON.stringify( settings ), + }; + const selectionCollapsed = value.start === value.end; + let nextValue = value; + + if ( selectionCollapsed && ! isActive ) { + const placeholder = getPlaceholderText( settings ); + nextValue = insert( nextValue, placeholder ); + const end = nextValue.start; + const start = end - placeholder.length; + nextValue = { ...nextValue, start, end }; + } + + const newValue = applyFormat( nextValue, { + type: FORMAT_NAME, + attributes, + } ); + + onChange( newValue ); + setIsOpen( false ); + }; + + const handleFilterChange = ( filterType ) => { + setSettings( ( prev ) => ( { + ...prev, + filterType, + valueType: filterType === 'sort' ? 'title' : prev.valueType, + } ) ); + }; + + return ( + <> + setIsOpen( true ) } + isActive={ isActive } + /> + { isOpen && ( + setIsOpen( false ) } + > + + + setSettings( ( prev ) => ( { + ...prev, + valueType, + } ) ) + } + /> + + setSettings( ( prev ) => ( { ...prev, prefix } ) ) + } + /> + + setSettings( ( prev ) => ( { ...prev, suffix } ) ) + } + /> + + + + + + + ) } + + ); +}; + +registerFormatType( FORMAT_NAME, { + title: __( 'Taxonomy Text', 'query-filter' ), + tagName: 'span', + className: 'taxonomy-text-inline', + attributes: { + [ ATTRIBUTE_KEY ]: ATTRIBUTE_KEY, + }, + edit: FormatEdit, +} ); + diff --git a/src/taxonomy/index.js b/src/taxonomy-text/index.js similarity index 75% rename from src/taxonomy/index.js rename to src/taxonomy-text/index.js index 30d8967..1945ce8 100644 --- a/src/taxonomy/index.js +++ b/src/taxonomy-text/index.js @@ -1,11 +1,9 @@ import { registerBlockType } from '@wordpress/blocks'; -import Edit from './edit'; import metadata from './block.json'; -import './style-index.css'; +import Edit from './edit'; +import './format'; registerBlockType( metadata.name, { - /** - * @see ./edit.js - */ edit: Edit, } ); + diff --git a/src/taxonomy-text/render.php b/src/taxonomy-text/render.php new file mode 100644 index 0000000..6a63c44 --- /dev/null +++ b/src/taxonomy-text/render.php @@ -0,0 +1,33 @@ + $block->context['query'] ?? [], + 'queryId' => $block->context['queryId'] ?? null, + ] +); + +if ( is_null( $result ) ) { + return ''; +} + +$wrapper_attributes = get_block_wrapper_attributes( + [ + 'class' => sprintf( + 'taxonomy-text taxonomy-text--%s', + sanitize_html_class( $result['filter_type'] ) + ), + ] +); + +?> +> + + + diff --git a/src/taxonomy/block.json b/src/taxonomy/block.json deleted file mode 100644 index 1f1a331..0000000 --- a/src/taxonomy/block.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - "$schema": "https://schemas.wp.org/trunk/block.json", - "apiVersion": 3, - "name": "query-filter/taxonomy", - "version": "0.1.0", - "title": "Taxonomy Filter", - "category": "theme", - "icon": "filter", - "description": "Allows users to filter by taxonomy terms when placed wihin a query loop block", - "ancestor": [ "core/query" ], - "usesContext": [ "queryId", "query" ], - "supports": { - "html": false, - "className": true, - "customClassName": true, - "color": { - "background": true, - "text": true - }, - "typography": { - "fontSize": true, - "textAlign": true, - "lineHeight": true, - "__experimentalFontFamily": true, - "__experimentalFontWeight": true, - "__experimentalFontStyle": true, - "__experimentalTextTransform": true, - "__experimentalTextDecoration": true, - "__experimentalLetterSpacing": true, - "__experimentalDefaultControls": { - "fontSize": true - } - }, - "spacing": { - "margin": true, - "padding": true, - "blockGap": true - }, - "interactivity": { - "clientNavigation": true - } - }, - "attributes": { - "taxonomy": { - "type": "string" - }, - "emptyLabel": { - "type": "string", - "default": "" - }, - "label": { - "type": "string" - }, - "showLabel": { - "type": "boolean", - "default": true - } - }, - "textdomain": "query-filter", - "editorScript": "file:./index.js", - "style": "query-filter-view", - "viewScriptModule": "file:./view.js", - "render": "file:./render.php" -} diff --git a/src/taxonomy/edit.js b/src/taxonomy/edit.js deleted file mode 100644 index 5562901..0000000 --- a/src/taxonomy/edit.js +++ /dev/null @@ -1,109 +0,0 @@ -import { __ } from '@wordpress/i18n'; -import { useBlockProps, InspectorControls } from '@wordpress/block-editor'; -import { - PanelBody, - SelectControl, - TextControl, - ToggleControl, -} from '@wordpress/components'; -import { useSelect } from '@wordpress/data'; - -export default function Edit( { attributes, setAttributes } ) { - const { taxonomy, emptyLabel, label, showLabel } = attributes; - - const taxonomies = useSelect( - ( select ) => { - const results = ( - select( 'core' ).getTaxonomies( { per_page: 100 } ) || [] - ).filter( ( taxonomy ) => taxonomy.visibility.publicly_queryable ); - - if ( results && results.length > 0 && ! taxonomy ) { - setAttributes( { - taxonomy: results[ 0 ].slug, - label: results[ 0 ].name, - } ); - } - - return results; - }, - [ taxonomy ] - ); - - const terms = useSelect( - ( select ) => { - return ( - select( 'core' ).getEntityRecords( 'taxonomy', taxonomy, { - number: 50, - } ) || [] - ); - }, - [ taxonomy ] - ); - - return ( - <> - - - ( { - label: taxonomy.name, - value: taxonomy.slug, - } ) ) } - onChange={ ( taxonomy ) => - setAttributes( { - taxonomy, - label: taxonomies.find( - ( tax ) => tax.slug === taxonomy - ).name, - } ) - } - /> - setAttributes( { label } ) } - /> - - setAttributes( { showLabel } ) - } - /> - - setAttributes( { emptyLabel } ) - } - /> - - -
- { showLabel && ( - - ) } - -
- - ); -} diff --git a/src/taxonomy/render.php b/src/taxonomy/render.php deleted file mode 100644 index 97258b4..0000000 --- a/src/taxonomy/render.php +++ /dev/null @@ -1,42 +0,0 @@ -context['query']['inherit'] ) ) { - $query_id = $block->context['queryId'] ?? 0; - $query_var = sprintf( 'query-%d-%s', $query_id, $attributes['taxonomy'] ); - $page_var = isset( $block->context['queryId'] ) ? 'query-' . $block->context['queryId'] . '-page' : 'query-page'; - $base_url = remove_query_arg( [ $query_var, $page_var ] ); -} else { - $query_var = sprintf( 'query-%s', $attributes['taxonomy'] ); - $page_var = 'page'; - $base_url = str_replace( '/page/' . get_query_var( 'paged' ), '', remove_query_arg( [ $query_var, $page_var ] ) ); -} - -$terms = get_terms( [ - 'hide_empty' => true, - 'taxonomy' => $attributes['taxonomy'], - 'number' => 100, -] ); - -if ( is_wp_error( $terms ) || empty( $terms ) ) { - return; -} -?> - -
'wp-block-query-filter' ] ); ?> data-wp-interactive="query-filter" data-wp-context="{}"> - - -
diff --git a/src/taxonomy/style-index.css b/src/taxonomy/style-index.css deleted file mode 100644 index 621fee1..0000000 --- a/src/taxonomy/style-index.css +++ /dev/null @@ -1,9 +0,0 @@ -@view-transition { - navigation: auto; -} - -.wp-block-query-filter { - display: flex; - flex-direction: column; - justify-content: stretch; -} diff --git a/src/taxonomy/view.js b/src/taxonomy/view.js deleted file mode 100644 index 2438d11..0000000 --- a/src/taxonomy/view.js +++ /dev/null @@ -1,46 +0,0 @@ -import { store, getElement } from '@wordpress/interactivity'; - -const updateURL = async ( action, value, name ) => { - const url = new URL( action ); - if ( value || name === 's' ) { - url.searchParams.set( name, value ); - } else { - url.searchParams.delete( name ); - } - const { actions } = await import( '@wordpress/interactivity-router' ); - await actions.navigate( url.toString() ); -}; - -const { state } = store( 'query-filter', { - actions: { - *navigate( e ) { - e.preventDefault(); - const { actions } = yield import( - '@wordpress/interactivity-router' - ); - yield actions.navigate( e.target.value ); - }, - *search( e ) { - e.preventDefault(); - const { ref } = getElement(); - let action, name, value; - if ( ref.tagName === 'FORM' ) { - const input = ref.querySelector( 'input[type="search"]' ); - action = ref.action; - name = input.name; - value = input.value; - } else { - action = ref.closest( 'form' ).action; - name = ref.name; - value = ref.value; - } - - // Don't navigate if the search didn't really change. - if ( value === state.searchValue ) return; - - state.searchValue = value; - - yield updateURL( action, value, name ); - }, - }, -} ); From 5b05a644f691be551952c866d68aaca7bf527986 Mon Sep 17 00:00:00 2001 From: "Zachary A. Martz" Date: Thu, 11 Dec 2025 15:29:14 -0500 Subject: [PATCH 5/8] add back in original src and build for taxonomy --- build/taxonomy/block.json | 6 +- build/taxonomy/index-rtl.css | 2 +- build/taxonomy/index.asset.php | 2 +- build/taxonomy/index.css | 2 +- build/taxonomy/index.js | 2 +- build/taxonomy/render.php | 84 +++++++++++++++++----- src/taxonomy/block.json | 69 ++++++++++++++++++ src/taxonomy/edit.js | 124 +++++++++++++++++++++++++++++++++ src/taxonomy/index.js | 8 +++ src/taxonomy/render.php | 90 ++++++++++++++++++++++++ src/taxonomy/style-index.css | 4 ++ src/taxonomy/view.js | 46 ++++++++++++ 12 files changed, 416 insertions(+), 23 deletions(-) create mode 100644 src/taxonomy/block.json create mode 100644 src/taxonomy/edit.js create mode 100644 src/taxonomy/index.js create mode 100644 src/taxonomy/render.php create mode 100644 src/taxonomy/style-index.css create mode 100644 src/taxonomy/view.js diff --git a/build/taxonomy/block.json b/build/taxonomy/block.json index 6cdb93d..275ef8b 100644 --- a/build/taxonomy/block.json +++ b/build/taxonomy/block.json @@ -6,7 +6,7 @@ "title": "Taxonomy Filter", "category": "theme", "icon": "filter", - "description": "Allows users to filter by taxonomy terms when placed wihin a query loop block", + "description": "Allows users to filter by taxonomy terms when placed within a query loop block", "ancestor": [ "core/query" ], @@ -59,6 +59,10 @@ "showLabel": { "type": "boolean", "default": true + }, + "limitToCurrentResults": { + "type": "boolean", + "default": false } }, "textdomain": "query-filter", diff --git a/build/taxonomy/index-rtl.css b/build/taxonomy/index-rtl.css index 39bc4f5..8b13789 100644 --- a/build/taxonomy/index-rtl.css +++ b/build/taxonomy/index-rtl.css @@ -1 +1 @@ -@view-transition{navigation:auto}.wp-block-query-filter{display:flex;flex-direction:column;justify-content:stretch} + diff --git a/build/taxonomy/index.asset.php b/build/taxonomy/index.asset.php index 4b3ef34..398e51c 100644 --- a/build/taxonomy/index.asset.php +++ b/build/taxonomy/index.asset.php @@ -1 +1 @@ - array('react-jsx-runtime', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-data', 'wp-i18n'), 'version' => 'f1456d24ac8e3da497aa'); + array('react-jsx-runtime', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-data', 'wp-i18n'), 'version' => '579d7a7dcf9fe19ab7b2'); diff --git a/build/taxonomy/index.css b/build/taxonomy/index.css index 39bc4f5..8b13789 100644 --- a/build/taxonomy/index.css +++ b/build/taxonomy/index.css @@ -1 +1 @@ -@view-transition{navigation:auto}.wp-block-query-filter{display:flex;flex-direction:column;justify-content:stretch} + diff --git a/build/taxonomy/index.js b/build/taxonomy/index.js index 5868836..2fb0d5a 100644 --- a/build/taxonomy/index.js +++ b/build/taxonomy/index.js @@ -1 +1 @@ -(()=>{"use strict";const e=window.wp.blocks,l=window.wp.i18n,t=window.wp.blockEditor,o=window.wp.components,n=window.wp.data,r=window.ReactJSXRuntime,a=JSON.parse('{"UU":"query-filter/taxonomy"}');(0,e.registerBlockType)(a.UU,{edit:function({attributes:e,setAttributes:a}){const{taxonomy:i,emptyLabel:s,label:c,showLabel:u}=e,b=(0,n.useSelect)((e=>{const l=(e("core").getTaxonomies({per_page:100})||[]).filter((e=>e.visibility.publicly_queryable));return l&&l.length>0&&!i&&a({taxonomy:l[0].slug,label:l[0].name}),l}),[i]),y=(0,n.useSelect)((e=>e("core").getEntityRecords("taxonomy",i,{number:50})||[]),[i]);return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(t.InspectorControls,{children:(0,r.jsxs)(o.PanelBody,{title:(0,l.__)("Taxonomy Settings","query-filter"),children:[(0,r.jsx)(o.SelectControl,{label:(0,l.__)("Select Taxonomy","query-filter"),value:i,options:(b||[]).map((e=>({label:e.name,value:e.slug}))),onChange:e=>a({taxonomy:e,label:b.find((l=>l.slug===e)).name})}),(0,r.jsx)(o.TextControl,{label:(0,l.__)("Label","query-filter"),value:c,help:(0,l.__)("If empty then no label will be shown","query-filter"),onChange:e=>a({label:e})}),(0,r.jsx)(o.ToggleControl,{label:(0,l.__)("Show Label","query-filter"),checked:u,onChange:e=>a({showLabel:e})}),(0,r.jsx)(o.TextControl,{label:(0,l.__)("Empty Choice Label","query-filter"),value:s,placeholder:(0,l.__)("All","query-filter"),onChange:e=>a({emptyLabel:e})})]})}),(0,r.jsxs)("div",{...(0,t.useBlockProps)({className:"wp-block-query-filter"}),children:[u&&(0,r.jsx)("label",{className:"wp-block-query-filter-taxonomy__label wp-block-query-filter__label",children:c}),(0,r.jsxs)("select",{className:"wp-block-query-filter-taxonomy__select wp-block-query-filter__select",inert:!0,children:[(0,r.jsx)("option",{children:s||(0,l.__)("All","query-filter")}),y.map((e=>(0,r.jsx)("option",{children:e.name},e.slug)))]})]})]})}})})(); \ No newline at end of file +(()=>{"use strict";const e=window.wp.blocks,l=JSON.parse('{"UU":"query-filter/taxonomy"}'),t=window.wp.i18n,o=window.wp.blockEditor,n=window.wp.components,r=window.wp.data,s=window.ReactJSXRuntime;(0,e.registerBlockType)(l.UU,{edit:function({attributes:e,setAttributes:l}){const{taxonomy:a,emptyLabel:i,label:c,showLabel:u,limitToCurrentResults:y}=e,b=(0,r.useSelect)((e=>{const t=(e("core").getTaxonomies({per_page:100})||[]).filter((e=>e.visibility?.publicly_queryable));return t&&t.length>0&&!a&&l({taxonomy:t[0].slug,label:t[0].name}),t}),[a]),m=(0,r.useSelect)((e=>e("core").getEntityRecords("taxonomy",a,{number:50,hide_empty:!0})||[]),[a]),_=(0,o.useBlockProps)({className:"wp-block-query-filter"});return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(o.InspectorControls,{children:(0,s.jsxs)(n.PanelBody,{title:(0,t.__)("Taxonomy Settings","query-filter"),children:[(0,s.jsx)(n.SelectControl,{label:(0,t.__)("Select Taxonomy","query-filter"),value:a,options:(b||[]).map((e=>({label:e.name,value:e.slug}))),onChange:e=>{const t=b.find((l=>l.slug===e));l({taxonomy:e,label:t?.name||c})}}),(0,s.jsx)(n.TextControl,{label:(0,t.__)("Label","query-filter"),value:c,help:(0,t.__)("If empty then no label will be shown","query-filter"),onChange:e=>l({label:e})}),(0,s.jsx)(n.ToggleControl,{label:(0,t.__)("Show Label","query-filter"),checked:u,onChange:e=>l({showLabel:e})}),(0,s.jsx)(n.TextControl,{label:(0,t.__)("Empty Choice Label","query-filter"),value:i,placeholder:(0,t.__)("All","query-filter"),onChange:e=>l({emptyLabel:e})}),(0,s.jsx)(n.ToggleControl,{label:(0,t.__)("Only show terms in current results","query-filter"),checked:!!y,onChange:e=>l({limitToCurrentResults:e})})]})}),(0,s.jsxs)("div",{..._,children:[u&&(0,s.jsx)("label",{className:"wp-block-query-filter-taxonomy__label wp-block-query-filter__label",children:c||(0,t.__)("Taxonomy","query-filter")}),(0,s.jsxs)("select",{className:"wp-block-query-filter-taxonomy__select wp-block-query-filter__select",inert:!0,children:[(0,s.jsx)("option",{children:i||(0,t.__)("All","query-filter")}),m.map((e=>(0,s.jsx)("option",{children:e.name},e.slug)))]})]})]})}})})(); \ No newline at end of file diff --git a/build/taxonomy/render.php b/build/taxonomy/render.php index 97258b4..e4e349e 100644 --- a/build/taxonomy/render.php +++ b/build/taxonomy/render.php @@ -4,27 +4,63 @@ } $id = 'query-filter-' . wp_generate_uuid4(); - $taxonomy = get_taxonomy( $attributes['taxonomy'] ); -if ( empty( $block->context['query']['inherit'] ) ) { - $query_id = $block->context['queryId'] ?? 0; - $query_var = sprintf( 'query-%d-%s', $query_id, $attributes['taxonomy'] ); - $page_var = isset( $block->context['queryId'] ) ? 'query-' . $block->context['queryId'] . '-page' : 'query-page'; - $base_url = remove_query_arg( [ $query_var, $page_var ] ); -} else { - $query_var = sprintf( 'query-%s', $attributes['taxonomy'] ); - $page_var = 'page'; - $base_url = str_replace( '/page/' . get_query_var( 'paged' ), '', remove_query_arg( [ $query_var, $page_var ] ) ); +$target_query_id = empty( $block->context['query']['inherit'] ) + ? (string) ( $block->context['queryId'] ?? 0 ) + : 'main'; + +$query_var = sprintf( 'query-%s', $attributes['taxonomy'] ); +$page_var = 'query-page'; +$base_url = remove_query_arg( [ $query_var, $page_var, 'query-post_id', 'query-id' ] ); + +if ( ! empty( $block->context['query']['inherit'] ) ) { + $current_paged = (int) get_query_var( 'paged' ); + if ( $current_paged > 1 ) { + $base_url = str_replace( '/page/' . $current_paged, '', $base_url ); + } + $base_url = remove_query_arg( [ 'page' ], $base_url ); } -$terms = get_terms( [ - 'hide_empty' => true, - 'taxonomy' => $attributes['taxonomy'], - 'number' => 100, -] ); +$base_url = add_query_arg( + [ + 'query-post_id' => $target_query_id, + ], + $base_url +); +$base_url = HM\Query_Loop_Filter\normalize_query_filter_url( $base_url ); + +$terms = []; + +if ( ! empty( $attributes['limitToCurrentResults'] ) ) { + $post_ids = HM\Query_Loop_Filter\get_query_loop_post_ids_for_block( $block ); + + if ( ! empty( $post_ids ) ) { + $terms = wp_get_object_terms( + $post_ids, + $attributes['taxonomy'], + [ + 'orderby' => 'name', + 'order' => 'ASC', + 'number' => 100, + ] + ); + } -if ( is_wp_error( $terms ) || empty( $terms ) ) { + if ( is_wp_error( $terms ) ) { + return; + } +} + +if ( empty( $terms ) ) { + $terms = get_terms( [ + 'hide_empty' => true, + 'taxonomy' => $attributes['taxonomy'], + 'number' => 100, + ] ); +} + +if ( is_wp_error( $terms ) || ( empty( $terms ) && empty( $attributes['limitToCurrentResults'] ) ) ) { return; } ?> @@ -35,8 +71,20 @@ + diff --git a/src/taxonomy/block.json b/src/taxonomy/block.json new file mode 100644 index 0000000..6d1c1ea --- /dev/null +++ b/src/taxonomy/block.json @@ -0,0 +1,69 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 3, + "name": "query-filter/taxonomy", + "version": "0.1.0", + "title": "Taxonomy Filter", + "category": "theme", + "icon": "filter", + "description": "Allows users to filter by taxonomy terms when placed within a query loop block", + "ancestor": [ "core/query" ], + "usesContext": [ "queryId", "query" ], + "supports": { + "html": false, + "className": true, + "customClassName": true, + "color": { + "background": true, + "text": true + }, + "typography": { + "fontSize": true, + "textAlign": true, + "lineHeight": true, + "__experimentalFontFamily": true, + "__experimentalFontWeight": true, + "__experimentalFontStyle": true, + "__experimentalTextTransform": true, + "__experimentalTextDecoration": true, + "__experimentalLetterSpacing": true, + "__experimentalDefaultControls": { + "fontSize": true + } + }, + "spacing": { + "margin": true, + "padding": true, + "blockGap": true + }, + "interactivity": { + "clientNavigation": true + } + }, + "attributes": { + "taxonomy": { + "type": "string" + }, + "emptyLabel": { + "type": "string", + "default": "" + }, + "label": { + "type": "string" + }, + "showLabel": { + "type": "boolean", + "default": true + }, + "limitToCurrentResults": { + "type": "boolean", + "default": false + } + }, + "textdomain": "query-filter", + "editorScript": "file:./index.js", + "style": "query-filter-view", + "viewScriptModule": "file:./view.js", + "render": "file:./render.php" +} + diff --git a/src/taxonomy/edit.js b/src/taxonomy/edit.js new file mode 100644 index 0000000..573dddb --- /dev/null +++ b/src/taxonomy/edit.js @@ -0,0 +1,124 @@ +import { __ } from '@wordpress/i18n'; +import { InspectorControls, useBlockProps } from '@wordpress/block-editor'; +import { + PanelBody, + SelectControl, + TextControl, + ToggleControl, +} from '@wordpress/components'; +import { useSelect } from '@wordpress/data'; + +export default function Edit( { attributes, setAttributes } ) { + const { + taxonomy, + emptyLabel, + label, + showLabel, + limitToCurrentResults, + } = attributes; + + const taxonomies = useSelect( + ( select ) => { + const results = ( + select( 'core' ).getTaxonomies( { per_page: 100 } ) || [] + ).filter( ( tax ) => tax.visibility?.publicly_queryable ); + + if ( results && results.length > 0 && ! taxonomy ) { + setAttributes( { + taxonomy: results[ 0 ].slug, + label: results[ 0 ].name, + } ); + } + + return results; + }, + [ taxonomy ] + ); + + const terms = useSelect( + ( select ) => + select( 'core' ).getEntityRecords( 'taxonomy', taxonomy, { + number: 50, + hide_empty: true, + } ) || [], + [ taxonomy ] + ); + + const blockProps = useBlockProps( { + className: 'wp-block-query-filter', + } ); + + return ( + <> + + + ( { + label: tax.name, + value: tax.slug, + } ) ) } + onChange={ ( value ) => { + const selected = taxonomies.find( + ( tax ) => tax.slug === value + ); + setAttributes( { + taxonomy: value, + label: selected?.name || label, + } ); + } } + /> + setAttributes( { label: value } ) } + /> + setAttributes( { showLabel: value } ) } + /> + + setAttributes( { emptyLabel: value } ) + } + /> + + setAttributes( { limitToCurrentResults: value } ) + } + /> + + +
+ { showLabel && ( + + ) } + +
+ + ); +} + diff --git a/src/taxonomy/index.js b/src/taxonomy/index.js new file mode 100644 index 0000000..20dadbc --- /dev/null +++ b/src/taxonomy/index.js @@ -0,0 +1,8 @@ +import { registerBlockType } from '@wordpress/blocks'; +import metadata from './block.json'; +import Edit from './edit'; +import './style-index.css'; + +registerBlockType( metadata.name, { + edit: Edit, +} ); diff --git a/src/taxonomy/render.php b/src/taxonomy/render.php new file mode 100644 index 0000000..e4e349e --- /dev/null +++ b/src/taxonomy/render.php @@ -0,0 +1,90 @@ +context['query']['inherit'] ) + ? (string) ( $block->context['queryId'] ?? 0 ) + : 'main'; + +$query_var = sprintf( 'query-%s', $attributes['taxonomy'] ); +$page_var = 'query-page'; +$base_url = remove_query_arg( [ $query_var, $page_var, 'query-post_id', 'query-id' ] ); + +if ( ! empty( $block->context['query']['inherit'] ) ) { + $current_paged = (int) get_query_var( 'paged' ); + if ( $current_paged > 1 ) { + $base_url = str_replace( '/page/' . $current_paged, '', $base_url ); + } + $base_url = remove_query_arg( [ 'page' ], $base_url ); +} + +$base_url = add_query_arg( + [ + 'query-post_id' => $target_query_id, + ], + $base_url +); +$base_url = HM\Query_Loop_Filter\normalize_query_filter_url( $base_url ); + +$terms = []; + +if ( ! empty( $attributes['limitToCurrentResults'] ) ) { + $post_ids = HM\Query_Loop_Filter\get_query_loop_post_ids_for_block( $block ); + + if ( ! empty( $post_ids ) ) { + $terms = wp_get_object_terms( + $post_ids, + $attributes['taxonomy'], + [ + 'orderby' => 'name', + 'order' => 'ASC', + 'number' => 100, + ] + ); + } + + if ( is_wp_error( $terms ) ) { + return; + } +} + +if ( empty( $terms ) ) { + $terms = get_terms( [ + 'hide_empty' => true, + 'taxonomy' => $attributes['taxonomy'], + 'number' => 100, + ] ); +} + +if ( is_wp_error( $terms ) || ( empty( $terms ) && empty( $attributes['limitToCurrentResults'] ) ) ) { + return; +} +?> + +
'wp-block-query-filter' ] ); ?> data-wp-interactive="query-filter" data-wp-context="{}"> + + +
+ diff --git a/src/taxonomy/style-index.css b/src/taxonomy/style-index.css new file mode 100644 index 0000000..f3c507c --- /dev/null +++ b/src/taxonomy/style-index.css @@ -0,0 +1,4 @@ +.wp-block-query-filter { + /* Add your editor styles here if needed. */ +} + diff --git a/src/taxonomy/view.js b/src/taxonomy/view.js new file mode 100644 index 0000000..27e3e0a --- /dev/null +++ b/src/taxonomy/view.js @@ -0,0 +1,46 @@ +import { store, getElement } from '@wordpress/interactivity'; + +const updateURL = async ( action, value, name ) => { + const url = new URL( action ); + if ( value || name === 's' ) { + url.searchParams.set( name, value ); + } else { + url.searchParams.delete( name ); + } + const { actions } = await import( '@wordpress/interactivity-router' ); + await actions.navigate( url.toString() ); +}; + +const { state } = store( 'query-filter', { + actions: { + *navigate( e ) { + e.preventDefault(); + const { actions } = yield import( + '@wordpress/interactivity-router' + ); + yield actions.navigate( e.target.value ); + }, + *search( e ) { + e.preventDefault(); + const { ref } = getElement(); + let action, name, value; + if ( ref.tagName === 'FORM' ) { + const input = ref.querySelector( 'input[type="search"]' ); + action = ref.action; + name = input.name; + value = input.value; + } else { + action = ref.closest( 'form' ).action; + name = ref.name; + value = ref.value; + } + + if ( value === state.searchValue ) return; + + state.searchValue = value; + + yield updateURL( action, value, name ); + }, + }, +} ); + From 677c8146edb942677d2545465fb4a6a312de0cd2 Mon Sep 17 00:00:00 2001 From: "Zachary A. Martz" Date: Thu, 11 Dec 2025 16:29:56 -0500 Subject: [PATCH 6/8] added page number as a dynamic filter-text option --- build/taxonomy-text/block.json | 10 ++- build/taxonomy-text/index.asset.php | 2 +- build/taxonomy-text/index.js | 2 +- inc/namespace.php | 67 +++++++++++++----- package-lock.json | 105 +++++++++++++++++++++++++++- package.json | 1 + src/taxonomy-text/block.json | 8 ++- src/taxonomy-text/edit.js | 26 ++++++- src/taxonomy-text/format.js | 39 +++++++++-- 9 files changed, 229 insertions(+), 31 deletions(-) diff --git a/build/taxonomy-text/block.json b/build/taxonomy-text/block.json index 536ac62..262e752 100644 --- a/build/taxonomy-text/block.json +++ b/build/taxonomy-text/block.json @@ -46,7 +46,8 @@ "enum": [ "tag", "category", - "sort" + "sort", + "page" ], "default": "tag" }, @@ -54,10 +55,15 @@ "type": "string", "enum": [ "title", - "description" + "description", + "page" ], "default": "title" }, + "showAfterFirstPage": { + "type": "boolean", + "default": true + }, "prefix": { "type": "string", "default": "" diff --git a/build/taxonomy-text/index.asset.php b/build/taxonomy-text/index.asset.php index 563e42a..121bc20 100644 --- a/build/taxonomy-text/index.asset.php +++ b/build/taxonomy-text/index.asset.php @@ -1 +1 @@ - array('react-jsx-runtime', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-element', 'wp-i18n', 'wp-primitives', 'wp-rich-text'), 'version' => '39c5f52a952637099d57'); + array('react-jsx-runtime', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-element', 'wp-i18n', 'wp-primitives', 'wp-rich-text'), 'version' => 'd3347a56786c7099a4d5'); diff --git a/build/taxonomy-text/index.js b/build/taxonomy-text/index.js index 9fa8ee6..53030a9 100644 --- a/build/taxonomy-text/index.js +++ b/build/taxonomy-text/index.js @@ -1 +1 @@ -(()=>{"use strict";const e=window.wp.blocks,t=JSON.parse('{"UU":"query-filter/taxonomy-text"}'),l=window.wp.i18n,r=window.wp.blockEditor,i=window.wp.components,a=window.ReactJSXRuntime,o=[{label:(0,l.__)("Tag","query-filter"),value:"tag"},{label:(0,l.__)("Category","query-filter"),value:"category"},{label:(0,l.__)("Sort","query-filter"),value:"sort"}],n=[{label:(0,l.__)("Title","query-filter"),value:"title"},{label:(0,l.__)("Description","query-filter"),value:"description"}],s=window.wp.richText,u=window.wp.primitives;var y=(0,a.jsx)(u.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",children:(0,a.jsx)(u.Path,{d:"M17.5 4v5a2 2 0 0 1-2 2h-7a2 2 0 0 1-2-2V4H8v5a.5.5 0 0 0 .5.5h7A.5.5 0 0 0 16 9V4h1.5Zm0 16v-5a2 2 0 0 0-2-2h-7a2 2 0 0 0-2 2v5H8v-5a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 .5.5v5h1.5Z"})});const f=window.wp.element,p="query-filter/taxonomy-inline-text",x="data-query-filter-text",c={filterType:"tag",valueType:"title",prefix:"",suffix:""},_=[{label:(0,l.__)("Tag","query-filter"),value:"tag"},{label:(0,l.__)("Category","query-filter"),value:"category"},{label:(0,l.__)("Sort","query-filter"),value:"sort"}],T=[{label:(0,l.__)("Title","query-filter"),value:"title"},{label:(0,l.__)("Description","query-filter"),value:"description"}],v=(e,t)=>e.find((e=>e.value===t))?.label||t;(0,s.registerFormatType)(p,{title:(0,l.__)("Taxonomy Text","query-filter"),tagName:"span",className:"taxonomy-text-inline",attributes:{[x]:x},edit:({value:e,onChange:t,isActive:o,activeAttributes:n})=>{const u=(0,f.useMemo)((()=>(e=>{if(!e)return c;try{const t=JSON.parse(e);return{filterType:t.filterType||c.filterType,valueType:t.valueType||c.valueType,prefix:t.prefix||"",suffix:t.suffix||""}}catch(e){return c}})(n?.[x])),[n?.[x]]),[d,h]=(0,f.useState)(!1),[q,g]=(0,f.useState)(u);return(0,f.useEffect)((()=>{g(u)}),[u]),(0,f.useEffect)((()=>{"sort"===q.filterType&&"title"!==q.valueType&&g((e=>({...e,valueType:"title"})))}),[q.filterType,q.valueType]),(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)(r.RichTextToolbarButton,{icon:y,title:(0,l.__)("Taxonomy Text","query-filter"),onClick:()=>h(!0),isActive:o}),d&&(0,a.jsxs)(i.Modal,{title:(0,l.__)("Dynamic Taxonomy Text","query-filter"),onRequestClose:()=>h(!1),children:[(0,a.jsx)(i.SelectControl,{label:(0,l.__)("Filter Source","query-filter"),value:q.filterType,options:_,onChange:e=>{g((t=>({...t,filterType:e,valueType:"sort"===e?"title":t.valueType})))}}),(0,a.jsx)(i.SelectControl,{label:(0,l.__)("Value Type","query-filter"),value:q.valueType,options:T,disabled:"sort"===q.filterType,onChange:e=>g((t=>({...t,valueType:e})))}),(0,a.jsx)(i.TextControl,{label:(0,l.__)("Prefix Text","query-filter"),value:q.prefix,onChange:e=>g((t=>({...t,prefix:e})))}),(0,a.jsx)(i.TextControl,{label:(0,l.__)("Suffix Text","query-filter"),value:q.suffix,onChange:e=>g((t=>({...t,suffix:e})))}),(0,a.jsx)(i.Flex,{justify:"flex-start",children:(0,a.jsx)(i.FlexItem,{children:(0,a.jsx)(i.Button,{variant:"primary",onClick:()=>{const r={[x]:JSON.stringify(q)},i=e.start===e.end;let a=e;if(i&&!o){const e=(e=>{const t=[(0,l.__)("taxonomy","query-filter"),v(_,e.filterType).toLowerCase()],r="sort"===e.filterType?(0,l.__)("title","query-filter"):v(T,e.valueType);return t.push(r.toLowerCase()),t.join(" ")})(q);a=(0,s.insert)(a,e);const t=a.start,r=t-e.length;a={...a,start:r,end:t}}const n=(0,s.applyFormat)(a,{type:p,attributes:r});t(n),h(!1)},children:(0,l.__)("Apply","query-filter")})})})]})]})}}),(0,e.registerBlockType)(t.UU,{edit:function({attributes:e,setAttributes:t}){const{filterType:s="tag",valueType:u="title",prefix:y="",suffix:f=""}=e,p=(0,r.useBlockProps)({className:`taxonomy-text taxonomy-text--${s}`}),x="sort"===s?(0,l.__)("Selected Sort","query-filter"):"description"===u?(0,l.__)("Selected Term Description","query-filter"):(0,l.__)("Selected Term","query-filter");return(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)(r.InspectorControls,{children:(0,a.jsxs)(i.PanelBody,{title:(0,l.__)("Display Settings","query-filter"),children:[(0,a.jsx)(i.SelectControl,{label:(0,l.__)("Filter Source","query-filter"),value:s,options:o,onChange:e=>{t({filterType:e,valueType:"sort"===e?"title":u})}}),(0,a.jsx)(i.SelectControl,{label:(0,l.__)("Value Type","query-filter"),value:u,options:n,disabled:"sort"===s,onChange:e=>t({valueType:e})}),(0,a.jsx)(i.TextControl,{label:(0,l.__)("Prefix Text","query-filter"),value:y,onChange:e=>t({prefix:e}),placeholder:(0,l.__)("e.g. Showing results for ","query-filter")}),(0,a.jsx)(i.TextControl,{label:(0,l.__)("Suffix Text","query-filter"),value:f,onChange:e=>t({suffix:e}),placeholder:(0,l.__)("e.g. only","query-filter")})]})}),(0,a.jsx)("span",{...p,children:`${y||""}${x}${f||""}`})]})}})})(); \ No newline at end of file +(()=>{"use strict";const e=window.wp.blocks,t=JSON.parse('{"UU":"query-filter/taxonomy-text"}'),l=window.wp.i18n,r=window.wp.blockEditor,i=window.wp.components,a=window.ReactJSXRuntime,o=[{label:(0,l.__)("Tag","query-filter"),value:"tag"},{label:(0,l.__)("Category","query-filter"),value:"category"},{label:(0,l.__)("Sort","query-filter"),value:"sort"},{label:(0,l.__)("Page Number","query-filter"),value:"page"}],s=[{label:(0,l.__)("Title","query-filter"),value:"title"},{label:(0,l.__)("Description","query-filter"),value:"description"},{label:(0,l.__)("Page Number","query-filter"),value:"page"}],n=window.wp.richText,u=window.wp.primitives;var f=(0,a.jsx)(u.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",children:(0,a.jsx)(u.Path,{d:"M17.5 4v5a2 2 0 0 1-2 2h-7a2 2 0 0 1-2-2V4H8v5a.5.5 0 0 0 .5.5h7A.5.5 0 0 0 16 9V4h1.5Zm0 16v-5a2 2 0 0 0-2-2h-7a2 2 0 0 0-2 2v5H8v-5a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 .5.5v5h1.5Z"})});const y=window.wp.element,p="query-filter/taxonomy-inline-text",c="data-query-filter-text",x={filterType:"tag",valueType:"title",prefix:"",suffix:"",showAfterFirstPage:!0},_=[{label:(0,l.__)("Tag","query-filter"),value:"tag"},{label:(0,l.__)("Category","query-filter"),value:"category"},{label:(0,l.__)("Sort","query-filter"),value:"sort"},{label:(0,l.__)("Page Number","query-filter"),value:"page"}],g=[{label:(0,l.__)("Title","query-filter"),value:"title"},{label:(0,l.__)("Description","query-filter"),value:"description"},{label:(0,l.__)("Page Number","query-filter"),value:"page"}],T=(e,t)=>e.find((e=>e.value===t))?.label||t;(0,n.registerFormatType)(p,{title:(0,l.__)("Taxonomy Text","query-filter"),tagName:"span",className:"taxonomy-text-inline",attributes:{[c]:c},edit:({value:e,onChange:t,isActive:o,activeAttributes:s})=>{const u=(0,y.useMemo)((()=>(e=>{if(!e)return x;try{var t;const l=JSON.parse(e);return{filterType:l.filterType||x.filterType,valueType:l.valueType||x.valueType,prefix:l.prefix||"",suffix:l.suffix||"",showAfterFirstPage:null!==(t=l.showAfterFirstPage)&&void 0!==t?t:x.showAfterFirstPage}}catch(e){return x}})(s?.[c])),[s?.[c]]),[v,d]=(0,y.useState)(!1),[h,w]=(0,y.useState)(u);return(0,y.useEffect)((()=>{w(u)}),[u]),(0,y.useEffect)((()=>{["sort","page"].includes(h.filterType)&&"description"===h.valueType&&w((e=>({...e,valueType:"title"})))}),[h.filterType,h.valueType]),(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)(r.RichTextToolbarButton,{icon:f,title:(0,l.__)("Taxonomy Text","query-filter"),onClick:()=>d(!0),isActive:o}),v&&(0,a.jsxs)(i.Modal,{title:(0,l.__)("Dynamic Taxonomy Text","query-filter"),onRequestClose:()=>d(!1),children:[(0,a.jsx)(i.SelectControl,{label:(0,l.__)("Filter Source","query-filter"),value:h.filterType,options:_,onChange:e=>{w((t=>({...t,filterType:e,valueType:"page"===e?"page":["sort","page"].includes(e)&&"description"===t.valueType?"title":t.valueType})))}}),(0,a.jsx)(i.SelectControl,{label:(0,l.__)("Value Type","query-filter"),value:h.valueType,options:g,disabled:("sort"===h.filterType||"page"===h.filterType)&&"description"===h.valueType,onChange:e=>w((t=>({...t,valueType:e})))}),(0,a.jsx)(i.TextControl,{label:(0,l.__)("Prefix Text","query-filter"),value:h.prefix,onChange:e=>w((t=>({...t,prefix:e})))}),(0,a.jsx)(i.TextControl,{label:(0,l.__)("Suffix Text","query-filter"),value:h.suffix,onChange:e=>w((t=>({...t,suffix:e})))}),"page"===h.filterType&&(0,a.jsx)(i.ToggleControl,{label:(0,l.__)("Only show after page 1","query-filter"),checked:!!h.showAfterFirstPage,onChange:e=>w((t=>({...t,showAfterFirstPage:e})))}),(0,a.jsx)(i.Flex,{justify:"flex-start",children:(0,a.jsx)(i.FlexItem,{children:(0,a.jsx)(i.Button,{variant:"primary",onClick:()=>{const r={[c]:JSON.stringify(h)},i=e.start===e.end;let a=e;if(i&&!o){const e=(e=>{const t=[(0,l.__)("taxonomy","query-filter"),T(_,e.filterType).toLowerCase()],r="sort"===e.filterType&&"page"!==e.valueType?(0,l.__)("title","query-filter"):T(g,e.valueType);return t.push(r.toLowerCase()),t.join(" ")})(h);a=(0,n.insert)(a,e);const t=a.start,r=t-e.length;a={...a,start:r,end:t}}const s=(0,n.applyFormat)(a,{type:p,attributes:r});t(s),d(!1)},children:(0,l.__)("Apply","query-filter")})})})]})]})}}),(0,e.registerBlockType)(t.UU,{edit:function({attributes:e,setAttributes:t}){const{filterType:n="tag",valueType:u="title",prefix:f="",suffix:y="",showAfterFirstPage:p=!0}=e,c=(0,r.useBlockProps)({className:`taxonomy-text taxonomy-text--${n}`}),x="sort"===n?(0,l.__)("Selected Sort","query-filter"):"description"===u?(0,l.__)("Selected Term Description","query-filter"):(0,l.__)("Selected Term","query-filter");return(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)(r.InspectorControls,{children:(0,a.jsxs)(i.PanelBody,{title:(0,l.__)("Display Settings","query-filter"),children:[(0,a.jsx)(i.SelectControl,{label:(0,l.__)("Filter Source","query-filter"),value:n,options:o,onChange:e=>{t({filterType:e,valueType:["sort","page"].includes(e)&&"description"===u?"title":u})}}),(0,a.jsx)(i.SelectControl,{label:(0,l.__)("Value Type","query-filter"),value:u,options:s,disabled:("sort"===n||"page"===n)&&"description"===u,onChange:e=>t({valueType:e})}),"page"===n&&(0,a.jsx)(ToggleControl,{label:(0,l.__)("Only show after page 1","query-filter"),checked:!!p,onChange:e=>t({showAfterFirstPage:e})}),(0,a.jsx)(i.TextControl,{label:(0,l.__)("Prefix Text","query-filter"),value:f,onChange:e=>t({prefix:e}),placeholder:(0,l.__)("e.g. Showing results for ","query-filter")}),(0,a.jsx)(i.TextControl,{label:(0,l.__)("Suffix Text","query-filter"),value:y,onChange:e=>t({suffix:e}),placeholder:(0,l.__)("e.g. only","query-filter")})]})}),(0,a.jsx)("span",{...c,children:`${f||""}${x}${y||""}`})]})}})})(); \ No newline at end of file diff --git a/inc/namespace.php b/inc/namespace.php index bdf54ca..790d340 100644 --- a/inc/namespace.php +++ b/inc/namespace.php @@ -553,7 +553,7 @@ function get_taxonomy_text_result( array $attributes, array $context = [] ) : ?a $filter_type = 'tag'; } - if ( ! in_array( $value_type, [ 'title', 'description' ], true ) ) { + if ( ! in_array( $value_type, [ 'title', 'description', 'page' ], true ) ) { $value_type = 'title'; } @@ -598,27 +598,40 @@ function get_taxonomy_text_result( array $attributes, array $context = [] ) : ?a 'sort' => 'post_orderby', ]; - $param_candidates = array_unique( [ - 'query-' . $param_suffix_map[ $filter_type ], - $legacy_prefix . $param_suffix_map[ $filter_type ], - ] ); + $show_after_first_page = isset( $attributes['showAfterFirstPage'] ) + ? (bool) $attributes['showAfterFirstPage'] + : true; + + if ( 'page' === $value_type ) { + $paged = 1; - $raw_value = ''; + // Prefer explicit query-* page params for the targeted query. + foreach ( array_keys( $_GET ) as $key ) { + if ( 'main' === $target_query_id && 'query-page' === $key ) { + $paged = max( 1, absint( wp_unslash( $_GET[ $key ] ) ) ); + break; + } - foreach ( $param_candidates as $param_name ) { - if ( isset( $_GET[ $param_name ] ) && '' !== $_GET[ $param_name ] ) { - $raw_value = sanitize_text_field( urldecode( wp_unslash( $_GET[ $param_name ] ) ) ); - break; + if ( + 'main' !== $target_query_id + && preg_match( '/^query-(\d+)-page$/', $key, $matches ) + && (string) $matches[1] === (string) $target_query_id + ) { + $paged = max( 1, absint( wp_unslash( $_GET[ $key ] ) ) ); + break; + } } - } - if ( '' === $raw_value ) { - return null; - } + if ( $paged <= 1 ) { + $paged = (int) get_query_var( 'paged', 1 ); + } - $display_value = ''; + if ( $paged <= 1 && $show_after_first_page ) { + return null; + } - if ( 'sort' === $filter_type ) { + $display_value = (string) max( 1, $paged ); + } elseif ( 'sort' === $filter_type ) { $sort_labels = [ 'date:DESC' => __( 'Newest to Oldest', 'query-filter' ), 'date:ASC' => __( 'Oldest to Newest', 'query-filter' ), @@ -636,8 +649,28 @@ function get_taxonomy_text_result( array $attributes, array $context = [] ) : ?a if ( isset( $sort_labels[ $normalized ] ) ) { $display_value = $sort_labels[ $normalized ]; } - $value_type = 'title'; + if ( 'description' === $value_type ) { + $value_type = 'title'; + } } else { + $param_candidates = array_unique( [ + 'query-' . $param_suffix_map[ $filter_type ], + $legacy_prefix . $param_suffix_map[ $filter_type ], + ] ); + + $raw_value = ''; + + foreach ( $param_candidates as $param_name ) { + if ( isset( $_GET[ $param_name ] ) && '' !== $_GET[ $param_name ] ) { + $raw_value = sanitize_text_field( urldecode( wp_unslash( $_GET[ $param_name ] ) ) ); + break; + } + } + + if ( '' === $raw_value ) { + return null; + } + $taxonomy = 'tag' === $filter_type ? 'post_tag' : 'category'; $raw_slugs = array_filter( array_map( diff --git a/package-lock.json b/package-lock.json index 6e6203b..e9db18e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,6 +7,7 @@ "name": "query-loop-filter", "license": "GPL-2.0-or-later", "dependencies": { + "@wordpress/icons": "^11.3.0", "@wordpress/scripts": "^28.6.0" } }, @@ -3276,6 +3277,11 @@ "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==" }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==" + }, "node_modules/@types/qs": { "version": "6.9.15", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.15.tgz", @@ -3286,6 +3292,23 @@ "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==" }, + "node_modules/@types/react": { + "version": "18.3.27", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz", + "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, "node_modules/@types/retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", @@ -3906,6 +3929,41 @@ "@playwright/test": ">=1" } }, + "node_modules/@wordpress/element": { + "version": "6.36.0", + "resolved": "https://registry.npmjs.org/@wordpress/element/-/element-6.36.0.tgz", + "integrity": "sha512-6Ym/Ucik49skz1XJ2GRXENoMjJx7EYnY+fbfor9KtChiCd9/3H4/rI4sZgewVPIO//fCKEk7G30HoR+xB7GZMQ==", + "dependencies": { + "@types/react": "^18.2.79", + "@types/react-dom": "^18.2.25", + "@wordpress/escape-html": "^3.36.0", + "change-case": "^4.1.2", + "is-plain-object": "^5.0.0", + "react": "^18.3.0", + "react-dom": "^18.3.0" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + } + }, + "node_modules/@wordpress/element/node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@wordpress/escape-html": { + "version": "3.36.0", + "resolved": "https://registry.npmjs.org/@wordpress/escape-html/-/escape-html-3.36.0.tgz", + "integrity": "sha512-0FvvlVPv+7X8lX5ExcTh6ib/xckGIuVXdnHglR3rZC1MJI682cx4JRUR0Igk6nKyPS8UiSQCKtN3U1aSPtZaCg==", + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + } + }, "node_modules/@wordpress/eslint-plugin": { "version": "20.3.0", "resolved": "https://registry.npmjs.org/@wordpress/eslint-plugin/-/eslint-plugin-20.3.0.tgz", @@ -3988,6 +4046,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@wordpress/icons": { + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/@wordpress/icons/-/icons-11.3.0.tgz", + "integrity": "sha512-ortBmmzg0855houBuJyR1v1WgKjUiPzBTGUbzOAEeNlOoisXaQO0hLIL7L8vFcd2L46i8PQHPvwpL5ifFTSC+g==", + "dependencies": { + "@wordpress/element": "^6.36.0", + "@wordpress/primitives": "^4.36.0" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + }, + "peerDependencies": { + "react": "^18.0.0" + } + }, "node_modules/@wordpress/jest-console": { "version": "8.7.0", "resolved": "https://registry.npmjs.org/@wordpress/jest-console/-/jest-console-8.7.0.tgz", @@ -4061,6 +4135,22 @@ "prettier": ">=3" } }, + "node_modules/@wordpress/primitives": { + "version": "4.36.0", + "resolved": "https://registry.npmjs.org/@wordpress/primitives/-/primitives-4.36.0.tgz", + "integrity": "sha512-Y/5Vkd/oAFNVe/dJpnpfnJ/ar4uWorCD0DAWglpFktNSAJfD3cqVbchJ6zNzqwXG9lR+7yqeh7ZHsmp2juoy3Q==", + "dependencies": { + "@wordpress/element": "^6.36.0", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=18.12.0", + "npm": ">=8.19.2" + }, + "peerDependencies": { + "react": "^18.0.0" + } + }, "node_modules/@wordpress/scripts": { "version": "28.6.0", "resolved": "https://registry.npmjs.org/@wordpress/scripts/-/scripts-28.6.0.tgz", @@ -5460,6 +5550,14 @@ "node": ">=0.10.0" } }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "engines": { + "node": ">=6" + } + }, "node_modules/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -6109,6 +6207,11 @@ "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==" }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==" + }, "node_modules/cwd": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/cwd/-/cwd-0.10.0.tgz", @@ -13504,7 +13607,6 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", - "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -14116,7 +14218,6 @@ "version": "0.23.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", - "peer": true, "dependencies": { "loose-envify": "^1.1.0" } diff --git a/package.json b/package.json index 97bda5d..8cc369a 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "author": "Human Made Limited", "license": "GPL-2.0-or-later", "dependencies": { + "@wordpress/icons": "^11.3.0", "@wordpress/scripts": "^28.6.0" } } diff --git a/src/taxonomy-text/block.json b/src/taxonomy-text/block.json index 4ee88e5..c2c952b 100644 --- a/src/taxonomy-text/block.json +++ b/src/taxonomy-text/block.json @@ -33,14 +33,18 @@ "attributes": { "filterType": { "type": "string", - "enum": [ "tag", "category", "sort" ], + "enum": [ "tag", "category", "sort", "page" ], "default": "tag" }, "valueType": { "type": "string", - "enum": [ "title", "description" ], + "enum": [ "title", "description", "page" ], "default": "title" }, + "showAfterFirstPage": { + "type": "boolean", + "default": true + }, "prefix": { "type": "string", "default": "" diff --git a/src/taxonomy-text/edit.js b/src/taxonomy-text/edit.js index 2367861..2def90d 100644 --- a/src/taxonomy-text/edit.js +++ b/src/taxonomy-text/edit.js @@ -9,11 +9,13 @@ const FILTER_OPTIONS = [ { label: __( 'Tag', 'query-filter' ), value: 'tag' }, { label: __( 'Category', 'query-filter' ), value: 'category' }, { label: __( 'Sort', 'query-filter' ), value: 'sort' }, + { label: __( 'Page Number', 'query-filter' ), value: 'page' }, ]; const VALUE_TYPE_OPTIONS = [ { label: __( 'Title', 'query-filter' ), value: 'title' }, { label: __( 'Description', 'query-filter' ), value: 'description' }, + { label: __( 'Page Number', 'query-filter' ), value: 'page' }, ]; export default function Edit( { attributes, setAttributes } ) { @@ -22,6 +24,7 @@ export default function Edit( { attributes, setAttributes } ) { valueType = 'title', prefix = '', suffix = '', + showAfterFirstPage = true, } = attributes; const blockProps = useBlockProps( { @@ -38,7 +41,11 @@ export default function Edit( { attributes, setAttributes } ) { const handleFilterChange = ( nextFilter ) => { setAttributes( { filterType: nextFilter, - valueType: nextFilter === 'sort' ? 'title' : valueType, + valueType: + [ 'sort', 'page' ].includes( nextFilter ) && + valueType === 'description' + ? 'title' + : valueType, } ); }; @@ -56,11 +63,26 @@ export default function Edit( { attributes, setAttributes } ) { label={ __( 'Value Type', 'query-filter' ) } value={ valueType } options={ VALUE_TYPE_OPTIONS } - disabled={ filterType === 'sort' } + disabled={ + ( filterType === 'sort' || filterType === 'page' ) && + valueType === 'description' + } onChange={ ( nextValue ) => setAttributes( { valueType: nextValue } ) } /> + { filterType === 'page' && ( + + setAttributes( { showAfterFirstPage: value } ) + } + /> + ) } @@ -47,7 +51,7 @@ const getPlaceholderText = ( settings ) => { ]; const valueLabel = - settings.filterType === 'sort' + settings.filterType === 'sort' && settings.valueType !== 'page' ? __( 'title', 'query-filter' ) : getOptionLabel( VALUE_TYPE_OPTIONS, settings.valueType ); @@ -69,6 +73,8 @@ const parseSettings = ( attributeValue ) => { valueType: parsed.valueType || DEFAULT_SETTINGS.valueType, prefix: parsed.prefix || '', suffix: parsed.suffix || '', + showAfterFirstPage: + parsed.showAfterFirstPage ?? DEFAULT_SETTINGS.showAfterFirstPage, }; } catch ( error ) { return DEFAULT_SETTINGS; @@ -93,7 +99,10 @@ const FormatEdit = ( { }, [ currentSettings ] ); useEffect( () => { - if ( settings.filterType === 'sort' && settings.valueType !== 'title' ) { + if ( + [ 'sort', 'page' ].includes( settings.filterType ) && + settings.valueType === 'description' + ) { setSettings( ( prev ) => ( { ...prev, valueType: 'title' } ) ); } }, [ settings.filterType, settings.valueType ] ); @@ -126,7 +135,13 @@ const FormatEdit = ( { setSettings( ( prev ) => ( { ...prev, filterType, - valueType: filterType === 'sort' ? 'title' : prev.valueType, + valueType: + filterType === 'page' + ? 'page' + : [ 'sort', 'page' ].includes( filterType ) && + prev.valueType === 'description' + ? 'title' + : prev.valueType, } ) ); }; @@ -153,7 +168,11 @@ const FormatEdit = ( { label={ __( 'Value Type', 'query-filter' ) } value={ settings.valueType } options={ VALUE_TYPE_OPTIONS } - disabled={ settings.filterType === 'sort' } + disabled={ + ( settings.filterType === 'sort' || + settings.filterType === 'page' ) && + settings.valueType === 'description' + } onChange={ ( valueType ) => setSettings( ( prev ) => ( { ...prev, @@ -175,6 +194,18 @@ const FormatEdit = ( { setSettings( ( prev ) => ( { ...prev, suffix } ) ) } /> + { settings.filterType === 'page' && ( + + setSettings( ( prev ) => ( { + ...prev, + showAfterFirstPage: next, + } ) ) + } + /> + ) }