From e600dcf5500b04c2073c7539703f4e5f3406e858 Mon Sep 17 00:00:00 2001 From: Ian Svoboda Date: Sun, 22 Dec 2024 21:36:08 -0500 Subject: [PATCH 1/3] WIP - Client side caching --- src/editor/style-html-attribute.js | 47 ++++++++++++++++++++++++++++-- src/hoc/withDynamicTag.js | 47 +++++++++++++++++++++++++++++- 2 files changed, 91 insertions(+), 3 deletions(-) diff --git a/src/editor/style-html-attribute.js b/src/editor/style-html-attribute.js index 3c010bdb3..1c7df9fe1 100644 --- a/src/editor/style-html-attribute.js +++ b/src/editor/style-html-attribute.js @@ -1,23 +1,66 @@ import { addFilter } from '@wordpress/hooks'; import { replaceTags } from '../dynamic-tags/utils'; +const cache = {}; + +function getCacheKey( clientId, context ) { + const { + 'generateblocks/loopIndex': loopIndex, + 'generateblocks/loopPreviewId': previewId, + postId, + } = context; + + let key = ''; + + if ( loopIndex ) { + key += `${ loopIndex }_`; + } + + if ( previewId ) { + key += `${ previewId }_`; + } else if ( postId ) { + key += `${ postId }_`; + } + + key += clientId; + + return key; +} + addFilter( 'generateblocks.editor.htmlAttributes.style', 'generateblocks/styleWithReplacements', async( style, props ) => { - const { context } = props; + const { context, clientId } = props; // Check if any replacements need to be made - if ( ! style.includes( '{{' ) ) { + if ( ! style.includes( '{{' ) || ! style ) { return style; } + const blockCacheKey = getCacheKey( clientId, context ); + + // Prime the cache for this block. + if ( ! cache[ blockCacheKey ] ) { + cache[ blockCacheKey ] = {}; + } + + // Get the cached result if available. + if ( cache[ clientId ][ style ] ) { + console.log( 'Using cached data', cache[ clientId ][ style ] ); + return cache[ style ]; + } + const replacements = await replaceTags( style, context ); if ( ! replacements.length ) { return style; } + // Cache the result. + cache[ clientId ][ style ] = replacements; + console.log( 'Cache miss, setting data', cache[ clientId ][ style ] ); + const withReplacements = replacements.reduce( ( acc, { original, replacement, fallback } ) => { if ( ! replacement ) { return acc.replaceAll( original, fallback ); diff --git a/src/hoc/withDynamicTag.js b/src/hoc/withDynamicTag.js index 4db2623f4..7e39ad2fc 100644 --- a/src/hoc/withDynamicTag.js +++ b/src/hoc/withDynamicTag.js @@ -2,11 +2,38 @@ import { useState, useEffect } from '@wordpress/element'; import { useSelect } from '@wordpress/data'; import { replaceTags } from '../dynamic-tags/utils'; +const cache = {}; + +function getCacheKey( clientId, context ) { + const { + 'generateblocks/loopIndex': loopIndex, + 'generateblocks/loopPreviewId': previewId, + postId, + } = context; + + let key = ''; + + if ( loopIndex ) { + key += `${ loopIndex }_`; + } + + if ( previewId ) { + key += `${ previewId }_`; + } else if ( postId ) { + key += `${ postId }_`; + } + + key += clientId; + + return key; +} + export function withDynamicTag( WrappedComponent ) { return ( ( props ) => { const { context, attributes, + clientId, } = props; const { @@ -18,6 +45,11 @@ export function withDynamicTag( WrappedComponent ) { const [ dynamicTagValue, setDynamicTagValue ] = useState( '' ); const [ contentMode, setContentMode ] = useState( 'edit' ); const isSavingPost = useSelect( ( select ) => select( 'core/editor' ).isSavingPost() ); + const blockCacheKey = getCacheKey( clientId, context ); + + if ( ! cache[ blockCacheKey ] ) { + cache[ blockCacheKey ] = {}; + } const getContentValue = () => { if ( 'img' === tagName ) { @@ -43,14 +75,27 @@ export function withDynamicTag( WrappedComponent ) { return; } + console.log( contentMode ); + + if ( cache[ blockCacheKey ][ contentValue ] ) { + console.log( 'Using cached data', cache[ blockCacheKey ][ contentValue ] ); + setDynamicTagValue( cache[ blockCacheKey ][ contentValue ] ); + return; + } + async function fetchData() { const response = await replaceTags( contentValue, context ); + console.log( 'cache miss, setting cache', cache[ blockCacheKey ][ contentValue ] ); + setDynamicTagValue( response ); + + // Cache the response. + cache[ blockCacheKey ][ contentValue ] = response; } fetchData(); - }, [ contentValue, contentMode, context, tagName, isSavingPost ] ); + }, [ contentValue, contentMode, context, tagName, isSavingPost, blockCacheKey ] ); return ( Date: Sun, 22 Dec 2024 23:10:02 -0500 Subject: [PATCH 2/3] Implement object cache --- includes/dynamic-tags/class-dynamic-tags.php | 31 ++++++++++++++++-- .../components/DynamicTagBlockToolbar.jsx | 10 +++--- src/dynamic-tags/utils.js | 5 +-- src/editor/style-html-attribute.js | 9 ++---- src/hoc/withDynamicTag.js | 32 +++++++++---------- 5 files changed, 55 insertions(+), 32 deletions(-) diff --git a/includes/dynamic-tags/class-dynamic-tags.php b/includes/dynamic-tags/class-dynamic-tags.php index 82b2c79e9..09a57f99c 100644 --- a/includes/dynamic-tags/class-dynamic-tags.php +++ b/includes/dynamic-tags/class-dynamic-tags.php @@ -499,15 +499,32 @@ public function register_rest_routes() { * @return WP_REST_Response */ public function get_dynamic_tag_replacements( $request ) { - $content = urldecode( $request->get_param( 'content' ) ); + $content = $request->get_param( 'content' ); $context = $request->get_param( 'context' ); - $fallback_id = $context['postId'] ?? 0; + $client_id = $request->get_param( 'clientId' ); + $post_id = $context['postId'] ?? 0; + $fallback_id = $post_id; $instance = new stdClass(); $replacements = []; // Set up an instance object with a context key. $instance->context = $context; + // Create a unique cache key. + $cache_key = sprintf( + 'replacements_%s_%s_%s', + md5( $content ), + $client_id, + $post_id + ); + + $replacements = wp_cache_get( $cache_key, 'generate_blocks_dynamic_tags' ); + + // Return the cache here if present. + if ( false !== $replacements ) { + return rest_ensure_response( $replacements ); + } + $all_tags = GenerateBlocks_Register_Dynamic_Tag::get_tags(); $tags_list = []; @@ -567,6 +584,16 @@ public function get_dynamic_tag_replacements( $request ) { } } + // Set the cache with filterable duration. + /** + * Set the duration of the cache for dynamic tag replacements. + * + * @since 2.0.0 + */ + $cache_duration = apply_filters( 'generateblocks_dynamic_tags_replacement_cache_duration', 3600, $content, $context, $request ); + + wp_cache_set( $cache_key, $replacements, 'generateblocks_dynamic_tags', $cache_duration ); + return rest_ensure_response( $replacements ); } diff --git a/src/dynamic-tags/components/DynamicTagBlockToolbar.jsx b/src/dynamic-tags/components/DynamicTagBlockToolbar.jsx index 16b510b7d..2ddea315a 100644 --- a/src/dynamic-tags/components/DynamicTagBlockToolbar.jsx +++ b/src/dynamic-tags/components/DynamicTagBlockToolbar.jsx @@ -74,11 +74,11 @@ export function DynamicTagBlockToolbar( { return contentValue.substring( selectionStart.offset, selectionEnd.offset ); }, [ selectionStart, selectionEnd, value ] ); - useEffect( () => { - if ( foundTags.length && ! isSelected ) { - setContentMode( 'preview' ); - } - }, [ foundTags.length, isSelected ] ); + // useEffect( () => { + // if ( foundTags.length && ! isSelected ) { + // setContentMode( 'preview' ); + // } + // }, [ foundTags.length, isSelected ] ); return ( diff --git a/src/dynamic-tags/utils.js b/src/dynamic-tags/utils.js index 1e5af18d5..f9df2c396 100644 --- a/src/dynamic-tags/utils.js +++ b/src/dynamic-tags/utils.js @@ -1,7 +1,7 @@ import apiFetch from '@wordpress/api-fetch'; import { applyFilters } from '@wordpress/hooks'; -export async function replaceTags( content, context = {} ) { +export async function replaceTags( { content, context = {}, clientId } ) { // Define an async function to fetch data try { const response = await apiFetch( { @@ -9,7 +9,8 @@ export async function replaceTags( content, context = {} ) { method: 'POST', data: { content, - context: applyFilters( 'generateblocks.editor.preview.context', context, { content } ), + context: applyFilters( 'generateblocks.editor.preview.context', context, { content, clientId } ), + clientId, }, } ); diff --git a/src/editor/style-html-attribute.js b/src/editor/style-html-attribute.js index 1c7df9fe1..36a798344 100644 --- a/src/editor/style-html-attribute.js +++ b/src/editor/style-html-attribute.js @@ -6,7 +6,6 @@ const cache = {}; function getCacheKey( clientId, context ) { const { 'generateblocks/loopIndex': loopIndex, - 'generateblocks/loopPreviewId': previewId, postId, } = context; @@ -16,9 +15,7 @@ function getCacheKey( clientId, context ) { key += `${ loopIndex }_`; } - if ( previewId ) { - key += `${ previewId }_`; - } else if ( postId ) { + if ( postId ) { key += `${ postId }_`; } @@ -47,11 +44,10 @@ addFilter( // Get the cached result if available. if ( cache[ clientId ][ style ] ) { - console.log( 'Using cached data', cache[ clientId ][ style ] ); return cache[ style ]; } - const replacements = await replaceTags( style, context ); + const replacements = await replaceTags( { content: style, context, clientId } ); if ( ! replacements.length ) { return style; @@ -59,7 +55,6 @@ addFilter( // Cache the result. cache[ clientId ][ style ] = replacements; - console.log( 'Cache miss, setting data', cache[ clientId ][ style ] ); const withReplacements = replacements.reduce( ( acc, { original, replacement, fallback } ) => { if ( ! replacement ) { diff --git a/src/hoc/withDynamicTag.js b/src/hoc/withDynamicTag.js index 7e39ad2fc..15a355867 100644 --- a/src/hoc/withDynamicTag.js +++ b/src/hoc/withDynamicTag.js @@ -1,4 +1,4 @@ -import { useState, useEffect } from '@wordpress/element'; +import { useState, useEffect, useMemo } from '@wordpress/element'; import { useSelect } from '@wordpress/data'; import { replaceTags } from '../dynamic-tags/utils'; @@ -7,7 +7,6 @@ const cache = {}; function getCacheKey( clientId, context ) { const { 'generateblocks/loopIndex': loopIndex, - 'generateblocks/loopPreviewId': previewId, postId, } = context; @@ -17,9 +16,7 @@ function getCacheKey( clientId, context ) { key += `${ loopIndex }_`; } - if ( previewId ) { - key += `${ previewId }_`; - } else if ( postId ) { + if ( postId ) { key += `${ postId }_`; } @@ -34,6 +31,7 @@ export function withDynamicTag( WrappedComponent ) { context, attributes, clientId, + isSelected, } = props; const { @@ -43,7 +41,7 @@ export function withDynamicTag( WrappedComponent ) { } = attributes; const [ dynamicTagValue, setDynamicTagValue ] = useState( '' ); - const [ contentMode, setContentMode ] = useState( 'edit' ); + const [ contentMode, setContentMode ] = useState( 'preview' ); const isSavingPost = useSelect( ( select ) => select( 'core/editor' ).isSavingPost() ); const blockCacheKey = getCacheKey( clientId, context ); @@ -51,7 +49,7 @@ export function withDynamicTag( WrappedComponent ) { cache[ blockCacheKey ] = {}; } - const getContentValue = () => { + const contentValue = useMemo( () => { if ( 'img' === tagName ) { return htmlAttributes?.src; } @@ -61,8 +59,7 @@ export function withDynamicTag( WrappedComponent ) { } return content?.text ?? content; - }; - const contentValue = getContentValue(); + }, [ tagName, htmlAttributes?.src, content ] ); useEffect( () => { if ( ! contentValue || ! contentValue.includes( '{{' ) ) { @@ -75,18 +72,13 @@ export function withDynamicTag( WrappedComponent ) { return; } - console.log( contentMode ); - if ( cache[ blockCacheKey ][ contentValue ] ) { - console.log( 'Using cached data', cache[ blockCacheKey ][ contentValue ] ); setDynamicTagValue( cache[ blockCacheKey ][ contentValue ] ); return; } async function fetchData() { - const response = await replaceTags( contentValue, context ); - - console.log( 'cache miss, setting cache', cache[ blockCacheKey ][ contentValue ] ); + const response = await replaceTags( { content: contentValue, context, clientId } ); setDynamicTagValue( response ); @@ -95,7 +87,15 @@ export function withDynamicTag( WrappedComponent ) { } fetchData(); - }, [ contentValue, contentMode, context, tagName, isSavingPost, blockCacheKey ] ); + }, [ + contentValue, + contentMode, + context, + tagName, + isSavingPost, + blockCacheKey, + isSelected, + ] ); return ( Date: Sun, 22 Dec 2024 23:20:42 -0500 Subject: [PATCH 3/3] lint fixes --- src/dynamic-tags/components/DynamicTagBlockToolbar.jsx | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/dynamic-tags/components/DynamicTagBlockToolbar.jsx b/src/dynamic-tags/components/DynamicTagBlockToolbar.jsx index 2ddea315a..ae13159ec 100644 --- a/src/dynamic-tags/components/DynamicTagBlockToolbar.jsx +++ b/src/dynamic-tags/components/DynamicTagBlockToolbar.jsx @@ -1,6 +1,6 @@ import { ToolbarGroup, ToolbarButton } from '@wordpress/components'; import { BlockControls, store as blockEditorStore } from '@wordpress/block-editor'; -import { useEffect, useMemo } from '@wordpress/element'; +import { useMemo } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { useSelect } from '@wordpress/data'; import { create, insert, replace, RichTextData } from '@wordpress/rich-text'; @@ -47,7 +47,6 @@ export function DynamicTagBlockToolbar( { value, contentMode, setContentMode, - isSelected, onChange, context, } ) { @@ -74,12 +73,6 @@ export function DynamicTagBlockToolbar( { return contentValue.substring( selectionStart.offset, selectionEnd.offset ); }, [ selectionStart, selectionEnd, value ] ); - // useEffect( () => { - // if ( foundTags.length && ! isSelected ) { - // setContentMode( 'preview' ); - // } - // }, [ foundTags.length, isSelected ] ); - return (