From 7ac5e6a081a0d56c44e005a648e847b81e8dc0b1 Mon Sep 17 00:00:00 2001 From: Timothy Mugo Date: Fri, 11 Sep 2026 09:57:24 +0300 Subject: [PATCH 01/13] chore: Update WordPress runtime version and improve safe decoding for special symbols in tooltip components --- Dockerfile | 2 +- .../blocks/format-bar/infotooltip.js | 14 +++++++++++--- .../blocks/format-bar/references.js | 14 +++++++++++--- 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/Dockerfile b/Dockerfile index 10fdac703..628223c24 100644 --- a/Dockerfile +++ b/Dockerfile @@ -43,7 +43,7 @@ RUN mkdir -p wp-content/plugins \ RUN chown -R 82:82 wp-content \ && tar -caf /wp-content.tgz --exclude="**/node_modules" wp-content -FROM wordpress:7.0.2-fpm-alpine AS runtime +FROM wordpress:7.1.0-fpm-alpine AS runtime LABEL org.opencontainers.image.description="WordPress image for Data Viz" LABEL org.opencontainers.image.authors="Development Gateway " LABEL org.opencontainers.image.url="https://github.com/devgateway/data-viz-wordpress" diff --git a/plugins/wp-react-blocks-plugin/blocks/format-bar/infotooltip.js b/plugins/wp-react-blocks-plugin/blocks/format-bar/infotooltip.js index 72d39a549..566fa8bb9 100644 --- a/plugins/wp-react-blocks-plugin/blocks/format-bar/infotooltip.js +++ b/plugins/wp-react-blocks-plugin/blocks/format-bar/infotooltip.js @@ -14,6 +14,14 @@ import {info as linkIcon} from '@wordpress/icons'; import {Button, Modal, Panel, PanelBody, PanelRow, Popover, TextareaControl} from '@wordpress/components'; import { BLOCKS_NS } from '@devgateway/dvz-wp-commons'; +function safeDecodeURIComponent(text) { + try { + return decodeURIComponent(text); + } catch (e) { + return text; + } +} + const name = BLOCKS_NS + '/info-tooltip'; const reference = { name, @@ -35,7 +43,7 @@ const PopUI = ({onClose, onCancel}) => { return onCancel()}> setText(encodeURIComponent(value))} /> @@ -91,8 +99,8 @@ function InlineUI({value, onChange, activeObjectAttributes, contentRef, onClose} setContent(content)} + value={safeDecodeURIComponent(content)} + onChange={(newValue) => setContent(encodeURIComponent(newValue))} /> diff --git a/plugins/wp-react-blocks-plugin/blocks/format-bar/references.js b/plugins/wp-react-blocks-plugin/blocks/format-bar/references.js index 74923fcf4..f5789b13d 100644 --- a/plugins/wp-react-blocks-plugin/blocks/format-bar/references.js +++ b/plugins/wp-react-blocks-plugin/blocks/format-bar/references.js @@ -28,6 +28,14 @@ import { SelectControl } from '@wordpress/components'; +function safeDecodeURIComponent(text) { + try { + return decodeURIComponent(text); + } catch (e) { + return text; + } +} + const name = BLOCKS_NS + '/reference'; const reference = { name, @@ -81,7 +89,7 @@ const PopUI = ({onClose, onCancel, value}) => { setText(encodeURIComponent(value))} /> setContent(content)} + value={safeDecodeURIComponent(content)} + onChange={(newValue) => setContent(encodeURIComponent(newValue))} /> From 36f91844552f61b5fa173f7b7f71a50aa7925f31 Mon Sep 17 00:00:00 2001 From: Timothy Mugo Date: Mon, 21 Sep 2026 13:51:20 +0300 Subject: [PATCH 02/13] feat: scaffold tailwind gutenberg --- Dockerfile | 3 +- plugins/tailwind-gutenberg/composer.json | 14 + plugins/tailwind-gutenberg/composer.lock | 20 + plugins/tailwind-gutenberg/index.php | 2 + plugins/tailwind-gutenberg/js/editor.js | 67 ++ plugins/tailwind-gutenberg/package.json | 26 + plugins/tailwind-gutenberg/src/Plugin.php | 32 + .../tailwind-gutenberg/tailwind-gutenberg.php | 23 + .../tailwind-gutenberg/vendor/autoload.php | 25 + .../vendor/composer/ClassLoader.php | 579 ++++++++++++++++++ .../vendor/composer/InstalledVersions.php | 359 +++++++++++ .../vendor/composer/LICENSE | 21 + .../vendor/composer/autoload_classmap.php | 11 + .../vendor/composer/autoload_namespaces.php | 9 + .../vendor/composer/autoload_psr4.php | 10 + .../vendor/composer/autoload_real.php | 38 ++ .../vendor/composer/autoload_static.php | 37 ++ .../vendor/composer/installed.json | 5 + .../vendor/composer/installed.php | 23 + .../vendor/composer/platform_check.php | 26 + pnpm-lock.yaml | 198 +++--- 21 files changed, 1407 insertions(+), 121 deletions(-) create mode 100644 plugins/tailwind-gutenberg/composer.json create mode 100644 plugins/tailwind-gutenberg/composer.lock create mode 100644 plugins/tailwind-gutenberg/index.php create mode 100644 plugins/tailwind-gutenberg/js/editor.js create mode 100644 plugins/tailwind-gutenberg/package.json create mode 100644 plugins/tailwind-gutenberg/src/Plugin.php create mode 100644 plugins/tailwind-gutenberg/tailwind-gutenberg.php create mode 100644 plugins/tailwind-gutenberg/vendor/autoload.php create mode 100644 plugins/tailwind-gutenberg/vendor/composer/ClassLoader.php create mode 100644 plugins/tailwind-gutenberg/vendor/composer/InstalledVersions.php create mode 100644 plugins/tailwind-gutenberg/vendor/composer/LICENSE create mode 100644 plugins/tailwind-gutenberg/vendor/composer/autoload_classmap.php create mode 100644 plugins/tailwind-gutenberg/vendor/composer/autoload_namespaces.php create mode 100644 plugins/tailwind-gutenberg/vendor/composer/autoload_psr4.php create mode 100644 plugins/tailwind-gutenberg/vendor/composer/autoload_real.php create mode 100644 plugins/tailwind-gutenberg/vendor/composer/autoload_static.php create mode 100644 plugins/tailwind-gutenberg/vendor/composer/installed.json create mode 100644 plugins/tailwind-gutenberg/vendor/composer/installed.php create mode 100644 plugins/tailwind-gutenberg/vendor/composer/platform_check.php diff --git a/Dockerfile b/Dockerfile index 628223c24..ebc2c143c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,6 +20,7 @@ COPY package.json /app/package.json #Packages COPY packages/commons/package.json /app/packages/commons/package.json COPY plugins/wp-react-blocks-plugin/blocks/package.json /app/plugins/wp-react-blocks-plugin/blocks/package.json +COPY plugins/tailwind-gutenberg/package.json /app/plugins/tailwind-gutenberg/package.json RUN --mount=type=cache,id=pnpm,target=/pnpm/store \ pnpm install --frozen-lockfile --shamefully-hoist @@ -30,7 +31,7 @@ COPY . /app # Build the plugins RUN BLOCKS_CATEGORY=wp-react-lib-blocks BLOCKS_NS=viz \ - pnpm -r --filter="@devgateway/dvz-wp-commons" --filter="dg-react-blocks" build + pnpm -r --filter="@devgateway/dvz-wp-commons" --filter="dg-react-blocks" --filter="tailwind-gutenberg" build # Organize WordPress files to the container COPY wp-theme wp-content/themes/dg-semantic diff --git a/plugins/tailwind-gutenberg/composer.json b/plugins/tailwind-gutenberg/composer.json new file mode 100644 index 000000000..f3064bcb9 --- /dev/null +++ b/plugins/tailwind-gutenberg/composer.json @@ -0,0 +1,14 @@ +{ + "name": "devgateway/tailwind-gutenberg", + "description": "Adds Tailwind CSS utility classes to any Gutenberg block, with autocomplete and live preview.", + "type": "wordpress-plugin", + "license": "GPL-2.0-or-later", + "require": { + "php": ">=8.0" + }, + "autoload": { + "psr-4": { + "Twg\\": "src/" + } + } +} diff --git a/plugins/tailwind-gutenberg/composer.lock b/plugins/tailwind-gutenberg/composer.lock new file mode 100644 index 000000000..254c80370 --- /dev/null +++ b/plugins/tailwind-gutenberg/composer.lock @@ -0,0 +1,20 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "ec010574960aec756ba2bd6df8a9d53b", + "packages": [], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=8.0" + }, + "platform-dev": [], + "plugin-api-version": "2.6.0" +} diff --git a/plugins/tailwind-gutenberg/index.php b/plugins/tailwind-gutenberg/index.php new file mode 100644 index 000000000..62200328f --- /dev/null +++ b/plugins/tailwind-gutenberg/index.php @@ -0,0 +1,2 @@ + { + return ( props ) => { + const { attributes, setAttributes, isSelected } = props; + + if ( ! ( ATTRIBUTE_NAME in attributes ) ) { + return ; + } + + return ( + + + { isSelected && ( + + + + setAttributes( { [ ATTRIBUTE_NAME ]: value } ) + } + /> + + + ) } + + ); + }; +}, 'withTwgInspectorControl' ); + +function applyTwgExtraProps( extraProps, _blockType, attributes ) { + const classes = attributes && attributes[ ATTRIBUTE_NAME ]; + + if ( classes ) { + extraProps.className = classnames( extraProps.className, classes ); + } + + return extraProps; +} + +addFilter( 'blocks.registerBlockType', 'twg/attributes', addTwgAttribute ); +addFilter( 'editor.BlockEdit', 'twg/inspector', withTwgInspectorControl ); +addFilter( 'blocks.getSaveContent.extraProps', 'twg/extra-props', applyTwgExtraProps ); diff --git a/plugins/tailwind-gutenberg/package.json b/plugins/tailwind-gutenberg/package.json new file mode 100644 index 000000000..b3b18a4b1 --- /dev/null +++ b/plugins/tailwind-gutenberg/package.json @@ -0,0 +1,26 @@ +{ + "name": "tailwind-gutenberg", + "version": "0.1.0", + "private": true, + "license": "GPL-2.0-or-later", + "description": "Editor integration for the Tailwind Classes for Gutenberg plugin.", + "author": "Development Gateway", + "scripts": { + "build": "wp-scripts build js/editor.js", + "start": "wp-scripts start js/editor.js", + "lint:js": "wp-scripts lint-js" + }, + "devDependencies": { + "@wordpress/scripts": "^30.25.0" + }, + "dependencies": { + "@wordpress/block-editor": "^15.5.0", + "@wordpress/blocks": "^15.5.0", + "@wordpress/components": "^30.5.0", + "@wordpress/compose": "^7.32.0", + "@wordpress/element": "^6.32.0", + "@wordpress/hooks": "^4.32.0", + "@wordpress/i18n": "^6.5.0", + "classnames": "^2.5.1" + } +} diff --git a/plugins/tailwind-gutenberg/src/Plugin.php b/plugins/tailwind-gutenberg/src/Plugin.php new file mode 100644 index 000000000..875d2325b --- /dev/null +++ b/plugins/tailwind-gutenberg/src/Plugin.php @@ -0,0 +1,32 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier + * @author Jordi Boggiano + * @see https://www.php-fig.org/psr/psr-0/ + * @see https://www.php-fig.org/psr/psr-4/ + */ +class ClassLoader +{ + /** @var \Closure(string):void */ + private static $includeFile; + + /** @var string|null */ + private $vendorDir; + + // PSR-4 + /** + * @var array> + */ + private $prefixLengthsPsr4 = array(); + /** + * @var array> + */ + private $prefixDirsPsr4 = array(); + /** + * @var list + */ + private $fallbackDirsPsr4 = array(); + + // PSR-0 + /** + * List of PSR-0 prefixes + * + * Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2'))) + * + * @var array>> + */ + private $prefixesPsr0 = array(); + /** + * @var list + */ + private $fallbackDirsPsr0 = array(); + + /** @var bool */ + private $useIncludePath = false; + + /** + * @var array + */ + private $classMap = array(); + + /** @var bool */ + private $classMapAuthoritative = false; + + /** + * @var array + */ + private $missingClasses = array(); + + /** @var string|null */ + private $apcuPrefix; + + /** + * @var array + */ + private static $registeredLoaders = array(); + + /** + * @param string|null $vendorDir + */ + public function __construct($vendorDir = null) + { + $this->vendorDir = $vendorDir; + self::initializeIncludeClosure(); + } + + /** + * @return array> + */ + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); + } + + return array(); + } + + /** + * @return array> + */ + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + /** + * @return list + */ + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + /** + * @return list + */ + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + /** + * @return array Array of classname => path + */ + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array $classMap Class to filename map + * + * @return void + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param list|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + * + * @return void + */ + public function add($prefix, $paths, $prepend = false) + { + $paths = (array) $paths; + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param list|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + * + * @return void + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + $paths = (array) $paths; + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param list|string $paths The PSR-0 base directories + * + * @return void + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param list|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + * + * @return void + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + * + * @return void + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + * + * @return void + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * APCu prefix to use to cache found/not-found classes, if the extension is enabled. + * + * @param string|null $apcuPrefix + * + * @return void + */ + public function setApcuPrefix($apcuPrefix) + { + $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; + } + + /** + * The APCu prefix in use, or null if APCu caching is not enabled. + * + * @return string|null + */ + public function getApcuPrefix() + { + return $this->apcuPrefix; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + * + * @return void + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + + if (null === $this->vendorDir) { + return; + } + + if ($prepend) { + self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; + } else { + unset(self::$registeredLoaders[$this->vendorDir]); + self::$registeredLoaders[$this->vendorDir] = $this; + } + } + + /** + * Unregisters this instance as an autoloader. + * + * @return void + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + + if (null !== $this->vendorDir) { + unset(self::$registeredLoaders[$this->vendorDir]); + } + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return true|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + $includeFile = self::$includeFile; + $includeFile($file); + + return true; + } + + return null; + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { + return false; + } + if (null !== $this->apcuPrefix) { + $file = apcu_fetch($this->apcuPrefix.$class, $hit); + if ($hit) { + return $file; + } + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if (false === $file && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if (null !== $this->apcuPrefix) { + apcu_add($this->apcuPrefix.$class, $file); + } + + if (false === $file) { + // Remember that this class does not exist. + $this->missingClasses[$class] = true; + } + + return $file; + } + + /** + * Returns the currently registered loaders keyed by their corresponding vendor directories. + * + * @return array + */ + public static function getRegisteredLoaders() + { + return self::$registeredLoaders; + } + + /** + * @param string $class + * @param string $ext + * @return string|false + */ + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + $subPath = $class; + while (false !== $lastPos = strrpos($subPath, '\\')) { + $subPath = substr($subPath, 0, $lastPos); + $search = $subPath . '\\'; + if (isset($this->prefixDirsPsr4[$search])) { + $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); + foreach ($this->prefixDirsPsr4[$search] as $dir) { + if (file_exists($file = $dir . $pathEnd)) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + + return false; + } + + /** + * @return void + */ + private static function initializeIncludeClosure() + { + if (self::$includeFile !== null) { + return; + } + + /** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + * + * @param string $file + * @return void + */ + self::$includeFile = \Closure::bind(static function($file) { + include $file; + }, null, null); + } +} diff --git a/plugins/tailwind-gutenberg/vendor/composer/InstalledVersions.php b/plugins/tailwind-gutenberg/vendor/composer/InstalledVersions.php new file mode 100644 index 000000000..51e734a77 --- /dev/null +++ b/plugins/tailwind-gutenberg/vendor/composer/InstalledVersions.php @@ -0,0 +1,359 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer; + +use Composer\Autoload\ClassLoader; +use Composer\Semver\VersionParser; + +/** + * This class is copied in every Composer installed project and available to all + * + * See also https://getcomposer.org/doc/07-runtime.md#installed-versions + * + * To require its presence, you can require `composer-runtime-api ^2.0` + * + * @final + */ +class InstalledVersions +{ + /** + * @var mixed[]|null + * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}|array{}|null + */ + private static $installed; + + /** + * @var bool|null + */ + private static $canGetVendors; + + /** + * @var array[] + * @psalm-var array}> + */ + private static $installedByVendor = array(); + + /** + * Returns a list of all package names which are present, either by being installed, replaced or provided + * + * @return string[] + * @psalm-return list + */ + public static function getInstalledPackages() + { + $packages = array(); + foreach (self::getInstalled() as $installed) { + $packages[] = array_keys($installed['versions']); + } + + if (1 === \count($packages)) { + return $packages[0]; + } + + return array_keys(array_flip(\call_user_func_array('array_merge', $packages))); + } + + /** + * Returns a list of all package names with a specific type e.g. 'library' + * + * @param string $type + * @return string[] + * @psalm-return list + */ + public static function getInstalledPackagesByType($type) + { + $packagesByType = array(); + + foreach (self::getInstalled() as $installed) { + foreach ($installed['versions'] as $name => $package) { + if (isset($package['type']) && $package['type'] === $type) { + $packagesByType[] = $name; + } + } + } + + return $packagesByType; + } + + /** + * Checks whether the given package is installed + * + * This also returns true if the package name is provided or replaced by another package + * + * @param string $packageName + * @param bool $includeDevRequirements + * @return bool + */ + public static function isInstalled($packageName, $includeDevRequirements = true) + { + foreach (self::getInstalled() as $installed) { + if (isset($installed['versions'][$packageName])) { + return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false; + } + } + + return false; + } + + /** + * Checks whether the given package satisfies a version constraint + * + * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call: + * + * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3') + * + * @param VersionParser $parser Install composer/semver to have access to this class and functionality + * @param string $packageName + * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package + * @return bool + */ + public static function satisfies(VersionParser $parser, $packageName, $constraint) + { + $constraint = $parser->parseConstraints((string) $constraint); + $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); + + return $provided->matches($constraint); + } + + /** + * Returns a version constraint representing all the range(s) which are installed for a given package + * + * It is easier to use this via isInstalled() with the $constraint argument if you need to check + * whether a given version of a package is installed, and not just whether it exists + * + * @param string $packageName + * @return string Version constraint usable with composer/semver + */ + public static function getVersionRanges($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + $ranges = array(); + if (isset($installed['versions'][$packageName]['pretty_version'])) { + $ranges[] = $installed['versions'][$packageName]['pretty_version']; + } + if (array_key_exists('aliases', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']); + } + if (array_key_exists('replaced', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']); + } + if (array_key_exists('provided', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']); + } + + return implode(' || ', $ranges); + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['version'])) { + return null; + } + + return $installed['versions'][$packageName]['version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getPrettyVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['pretty_version'])) { + return null; + } + + return $installed['versions'][$packageName]['pretty_version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference + */ + public static function getReference($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['reference'])) { + return null; + } + + return $installed['versions'][$packageName]['reference']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path. + */ + public static function getInstallPath($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @return array + * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool} + */ + public static function getRootPackage() + { + $installed = self::getInstalled(); + + return $installed[0]['root']; + } + + /** + * Returns the raw installed.php data for custom implementations + * + * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect. + * @return array[] + * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} + */ + public static function getRawData() + { + @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED); + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + self::$installed = include __DIR__ . '/installed.php'; + } else { + self::$installed = array(); + } + } + + return self::$installed; + } + + /** + * Returns the raw data of all installed.php which are currently loaded for custom implementations + * + * @return array[] + * @psalm-return list}> + */ + public static function getAllRawData() + { + return self::getInstalled(); + } + + /** + * Lets you reload the static array from another file + * + * This is only useful for complex integrations in which a project needs to use + * this class but then also needs to execute another project's autoloader in process, + * and wants to ensure both projects have access to their version of installed.php. + * + * A typical case would be PHPUnit, where it would need to make sure it reads all + * the data it needs from this class, then call reload() with + * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure + * the project in which it runs can then also use this class safely, without + * interference between PHPUnit's dependencies and the project's dependencies. + * + * @param array[] $data A vendor/composer/installed.php data set + * @return void + * + * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $data + */ + public static function reload($data) + { + self::$installed = $data; + self::$installedByVendor = array(); + } + + /** + * @return array[] + * @psalm-return list}> + */ + private static function getInstalled() + { + if (null === self::$canGetVendors) { + self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders'); + } + + $installed = array(); + + if (self::$canGetVendors) { + foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { + if (isset(self::$installedByVendor[$vendorDir])) { + $installed[] = self::$installedByVendor[$vendorDir]; + } elseif (is_file($vendorDir.'/composer/installed.php')) { + /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ + $required = require $vendorDir.'/composer/installed.php'; + $installed[] = self::$installedByVendor[$vendorDir] = $required; + if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) { + self::$installed = $installed[count($installed) - 1]; + } + } + } + } + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ + $required = require __DIR__ . '/installed.php'; + self::$installed = $required; + } else { + self::$installed = array(); + } + } + + if (self::$installed !== array()) { + $installed[] = self::$installed; + } + + return $installed; + } +} diff --git a/plugins/tailwind-gutenberg/vendor/composer/LICENSE b/plugins/tailwind-gutenberg/vendor/composer/LICENSE new file mode 100644 index 000000000..f27399a04 --- /dev/null +++ b/plugins/tailwind-gutenberg/vendor/composer/LICENSE @@ -0,0 +1,21 @@ + +Copyright (c) Nils Adermann, Jordi Boggiano + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/plugins/tailwind-gutenberg/vendor/composer/autoload_classmap.php b/plugins/tailwind-gutenberg/vendor/composer/autoload_classmap.php new file mode 100644 index 000000000..f6bdc0669 --- /dev/null +++ b/plugins/tailwind-gutenberg/vendor/composer/autoload_classmap.php @@ -0,0 +1,11 @@ + $vendorDir . '/composer/InstalledVersions.php', + 'Twg\\Plugin' => $baseDir . '/src/Plugin.php', +); diff --git a/plugins/tailwind-gutenberg/vendor/composer/autoload_namespaces.php b/plugins/tailwind-gutenberg/vendor/composer/autoload_namespaces.php new file mode 100644 index 000000000..15a2ff3ad --- /dev/null +++ b/plugins/tailwind-gutenberg/vendor/composer/autoload_namespaces.php @@ -0,0 +1,9 @@ + array($baseDir . '/src'), +); diff --git a/plugins/tailwind-gutenberg/vendor/composer/autoload_real.php b/plugins/tailwind-gutenberg/vendor/composer/autoload_real.php new file mode 100644 index 000000000..60987e4b0 --- /dev/null +++ b/plugins/tailwind-gutenberg/vendor/composer/autoload_real.php @@ -0,0 +1,38 @@ +register(true); + + return $loader; + } +} diff --git a/plugins/tailwind-gutenberg/vendor/composer/autoload_static.php b/plugins/tailwind-gutenberg/vendor/composer/autoload_static.php new file mode 100644 index 000000000..114f45c9c --- /dev/null +++ b/plugins/tailwind-gutenberg/vendor/composer/autoload_static.php @@ -0,0 +1,37 @@ + + array ( + 'Twg\\' => 4, + ), + ); + + public static $prefixDirsPsr4 = array ( + 'Twg\\' => + array ( + 0 => __DIR__ . '/../..' . '/src', + ), + ); + + public static $classMap = array ( + 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', + 'Twg\\Plugin' => __DIR__ . '/../..' . '/src/Plugin.php', + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->prefixLengthsPsr4 = ComposerStaticInitec010574960aec756ba2bd6df8a9d53b::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInitec010574960aec756ba2bd6df8a9d53b::$prefixDirsPsr4; + $loader->classMap = ComposerStaticInitec010574960aec756ba2bd6df8a9d53b::$classMap; + + }, null, ClassLoader::class); + } +} diff --git a/plugins/tailwind-gutenberg/vendor/composer/installed.json b/plugins/tailwind-gutenberg/vendor/composer/installed.json new file mode 100644 index 000000000..f20a6c47c --- /dev/null +++ b/plugins/tailwind-gutenberg/vendor/composer/installed.json @@ -0,0 +1,5 @@ +{ + "packages": [], + "dev": false, + "dev-package-names": [] +} diff --git a/plugins/tailwind-gutenberg/vendor/composer/installed.php b/plugins/tailwind-gutenberg/vendor/composer/installed.php new file mode 100644 index 000000000..865602934 --- /dev/null +++ b/plugins/tailwind-gutenberg/vendor/composer/installed.php @@ -0,0 +1,23 @@ + array( + 'name' => 'devgateway/tailwind-gutenberg', + 'pretty_version' => 'dev-main', + 'version' => 'dev-main', + 'reference' => '70d1b0066e3ef05aad8c0fdadb1f4f595f2b6c09', + 'type' => 'wordpress-plugin', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'dev' => false, + ), + 'versions' => array( + 'devgateway/tailwind-gutenberg' => array( + 'pretty_version' => 'dev-main', + 'version' => 'dev-main', + 'reference' => '70d1b0066e3ef05aad8c0fdadb1f4f595f2b6c09', + 'type' => 'wordpress-plugin', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'dev_requirement' => false, + ), + ), +); diff --git a/plugins/tailwind-gutenberg/vendor/composer/platform_check.php b/plugins/tailwind-gutenberg/vendor/composer/platform_check.php new file mode 100644 index 000000000..adfb472fb --- /dev/null +++ b/plugins/tailwind-gutenberg/vendor/composer/platform_check.php @@ -0,0 +1,26 @@ += 80000)) { + $issues[] = 'Your Composer dependencies require a PHP version ">= 8.0.0". You are running ' . PHP_VERSION . '.'; +} + +if ($issues) { + if (!headers_sent()) { + header('HTTP/1.1 500 Internal Server Error'); + } + if (!ini_get('display_errors')) { + if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { + fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL); + } elseif (!headers_sent()) { + echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL; + } + } + trigger_error( + 'Composer detected issues in your platform: ' . implode(' ', $issues), + E_USER_ERROR + ); +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2c7b170e2..b32061bdc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -86,7 +86,7 @@ importers: version: 17.2.3 dotenv-webpack: specifier: ^8.1.1 - version: 8.1.1(webpack@5.99.5(webpack-cli@5.1.4)) + version: 8.1.1(webpack@5.99.5) semantic-release: specifier: ^24.2.9 version: 24.2.9(typescript@5.9.3) @@ -95,7 +95,7 @@ importers: version: 8.0.2(semantic-release@24.2.9(typescript@5.9.3)) ts-loader: specifier: ^9.5.4 - version: 9.5.4(typescript@5.9.3)(webpack@5.99.5(webpack-cli@5.1.4)) + version: 9.5.4(typescript@5.9.3)(webpack@5.99.5) typescript: specifier: ^5.9.3 version: 5.9.3 @@ -151,6 +151,37 @@ importers: specifier: ^3.6.1 version: 3.6.1(sass@1.86.3)(typescript@5.9.3) + plugins/tailwind-gutenberg: + dependencies: + '@wordpress/block-editor': + specifier: ^15.5.0 + version: 15.5.0(@emotion/is-prop-valid@1.3.1)(@types/react-dom@18.3.6(@types/react@18.3.1))(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@wordpress/blocks': + specifier: ^15.5.0 + version: 15.5.0(react@18.3.1) + '@wordpress/components': + specifier: ^30.5.0 + version: 30.5.0(@emotion/is-prop-valid@1.3.1)(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@wordpress/compose': + specifier: ^7.32.0 + version: 7.32.0(react@18.3.1) + '@wordpress/element': + specifier: ^6.32.0 + version: 6.32.0 + '@wordpress/hooks': + specifier: ^4.32.0 + version: 4.32.0 + '@wordpress/i18n': + specifier: ^6.5.0 + version: 6.5.0 + classnames: + specifier: ^2.5.1 + version: 2.5.1 + devDependencies: + '@wordpress/scripts': + specifier: ^30.25.0 + version: 30.25.0(@playwright/test@1.51.1)(@types/eslint@9.6.1)(@types/node@24.7.0)(@types/webpack@4.41.40)(babel-plugin-macros@3.1.0)(file-loader@6.2.0(webpack@5.99.5))(node-notifier@8.0.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint-scss@6.12.0(stylelint@16.18.0(typescript@5.9.3)))(type-fest@4.41.0)(typescript@5.9.3) + plugins/wp-react-blocks-plugin/blocks: dependencies: '@devgateway/dvz-wp-commons': @@ -261,7 +292,7 @@ importers: version: 17.2.3 dotenv-webpack: specifier: ^8.1.1 - version: 8.1.1(webpack@5.99.5(webpack-cli@5.1.4)) + version: 8.1.1(webpack@5.99.5) npm-run-all: specifier: ^4.1.5 version: 4.1.5 @@ -961,10 +992,6 @@ packages: resolution: {integrity: sha512-ZCYtZciz1IWJB4U61UPu4KEaqyfj+r5T1Q5mqPo+IBpcG9kHv30Z0aD8LXPgC1trYa6rK0orRyAhqUgk4MjmEg==} engines: {node: '>=6.9.0'} - '@babel/types@7.27.1': - resolution: {integrity: sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==} - engines: {node: '>=6.9.0'} - '@babel/types@7.28.4': resolution: {integrity: sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==} engines: {node: '>=6.9.0'} @@ -2321,9 +2348,6 @@ packages: '@types/eslint@9.6.1': resolution: {integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==} - '@types/estree@1.0.7': - resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==} - '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} @@ -2581,6 +2605,7 @@ packages: '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + deprecated: Potential CWE-502 - Update to 1.3.1 or higher '@use-gesture/core@10.3.1': resolution: {integrity: sha512-WcINiDt8WjqBdUXye25anHiNxPc0VOrlT8F6LLkU6cycrOGUDyY/yyFmsg3k8i5OLvv25llc0QC45GhR/C8llw==} @@ -3479,6 +3504,7 @@ packages: basic-ftp@5.0.5: resolution: {integrity: sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==} engines: {node: '>=10.0.0'} + deprecated: Security vulnerability fixed in 5.2.1, please upgrade batch@0.6.1: resolution: {integrity: sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==} @@ -3585,9 +3611,6 @@ packages: caniuse-api@3.0.0: resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==} - caniuse-lite@1.0.30001717: - resolution: {integrity: sha512-auPpttCq6BDEG8ZAuHJIplGw6GODhjw+/11e7IjpnYCxZcW/ONgPs0KVBJ0d1bY3e2+7PRe5RCLyP+PfwVgkYw==} - caniuse-lite@1.0.30001718: resolution: {integrity: sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw==} @@ -4378,10 +4401,6 @@ packages: resolution: {integrity: sha512-MtUbM072wlJNyeYAe0mhzrD+M6DIJa96CZAOBBrhDbgKnB4MApIKefcyAB1eOdYn8cUNZgvwBvEzdoAYsxgEIw==} engines: {node: '>= 0.4'} - es-abstract@1.23.9: - resolution: {integrity: sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==} - engines: {node: '>= 0.4'} - es-define-property@1.0.1: resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} engines: {node: '>= 0.4'} @@ -5004,7 +5023,7 @@ packages: glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Glob versions prior to v9 are no longer supported + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me global-modules@0.2.3: resolution: {integrity: sha512-JeXuCbvYzYXcwE6acL9V2bAOeSIGl4dD+iwLY9iUx2VBJJ80R18HCn+JCwHM9Oegdfya3lEkGCdaRkSyc10hDA==} @@ -8437,8 +8456,8 @@ packages: third-party-web@0.26.6: resolution: {integrity: sha512-GsjP92xycMK8qLTcQCacgzvffYzEqe29wyz3zdKVXlfRD5Kz1NatCTOZEeDaSd6uCZXvGd2CNVtQ89RNIhJWvA==} - third-party-web@0.29.2: - resolution: {integrity: sha512-fegtha91tq2DHphyoiBXVHjVi2YG9zFaRnboT9C28tO1en9Y3wJsfspuy40F+u5wl3hHVbw7cnd1b67kEGHb8g==} + third-party-web@0.30.0: + resolution: {integrity: sha512-p+PfyL5U0ediGzvwPzMSdD9MnC8rHW6nWJJVLIQvqN7RsSqfaZ+7n4c9GILqH36EYR0eXB9CzAeCRnKFr/9uJg==} through2@2.0.5: resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==} @@ -8788,6 +8807,7 @@ packages: uuid@8.3.2: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). hasBin: true uuid@9.0.1: @@ -8911,6 +8931,7 @@ packages: whatwg-encoding@2.0.0: resolution: {integrity: sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==} engines: {node: '>=12'} + deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation whatwg-mimetype@3.0.0: resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==} @@ -9168,7 +9189,7 @@ snapshots: '@babel/parser': 7.28.4 '@babel/template': 7.27.2 '@babel/traverse': 7.27.1 - '@babel/types': 7.27.1 + '@babel/types': 7.28.4 convert-source-map: 2.0.0 debug: 4.4.1 gensync: 1.0.0-beta.2 @@ -9188,14 +9209,14 @@ snapshots: '@babel/generator@7.27.1': dependencies: '@babel/parser': 7.28.4 - '@babel/types': 7.27.1 + '@babel/types': 7.28.4 '@jridgewell/gen-mapping': 0.3.8 '@jridgewell/trace-mapping': 0.3.25 jsesc: 3.1.0 '@babel/helper-annotate-as-pure@7.27.1': dependencies: - '@babel/types': 7.27.1 + '@babel/types': 7.28.4 '@babel/helper-compilation-targets@7.27.2': dependencies: @@ -9239,14 +9260,14 @@ snapshots: '@babel/helper-member-expression-to-functions@7.27.1': dependencies: '@babel/traverse': 7.27.1 - '@babel/types': 7.27.1 + '@babel/types': 7.28.4 transitivePeerDependencies: - supports-color '@babel/helper-module-imports@7.27.1': dependencies: '@babel/traverse': 7.27.1 - '@babel/types': 7.27.1 + '@babel/types': 7.28.4 transitivePeerDependencies: - supports-color @@ -9261,7 +9282,7 @@ snapshots: '@babel/helper-optimise-call-expression@7.27.1': dependencies: - '@babel/types': 7.27.1 + '@babel/types': 7.28.4 '@babel/helper-plugin-utils@7.27.1': {} @@ -9286,7 +9307,7 @@ snapshots: '@babel/helper-skip-transparent-expression-wrappers@7.27.1': dependencies: '@babel/traverse': 7.27.1 - '@babel/types': 7.27.1 + '@babel/types': 7.28.4 transitivePeerDependencies: - supports-color @@ -9300,14 +9321,14 @@ snapshots: dependencies: '@babel/template': 7.27.2 '@babel/traverse': 7.27.1 - '@babel/types': 7.27.1 + '@babel/types': 7.28.4 transitivePeerDependencies: - supports-color '@babel/helpers@7.27.0': dependencies: '@babel/template': 7.27.2 - '@babel/types': 7.27.1 + '@babel/types': 7.28.4 '@babel/parser@7.28.4': dependencies: @@ -9734,7 +9755,7 @@ snapshots: '@babel/helper-module-imports': 7.27.1 '@babel/helper-plugin-utils': 7.27.1 '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.25.7) - '@babel/types': 7.27.1 + '@babel/types': 7.28.4 transitivePeerDependencies: - supports-color @@ -9745,7 +9766,7 @@ snapshots: '@babel/helper-module-imports': 7.27.1 '@babel/helper-plugin-utils': 7.27.1 '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.25.7) - '@babel/types': 7.27.1 + '@babel/types': 7.28.4 transitivePeerDependencies: - supports-color @@ -10013,7 +10034,7 @@ snapshots: dependencies: '@babel/core': 7.25.7 '@babel/helper-plugin-utils': 7.27.1 - '@babel/types': 7.27.1 + '@babel/types': 7.28.4 esutils: 2.0.3 '@babel/preset-react@7.26.3(@babel/core@7.25.7)': @@ -10060,7 +10081,7 @@ snapshots: dependencies: '@babel/code-frame': 7.27.1 '@babel/parser': 7.28.4 - '@babel/types': 7.27.1 + '@babel/types': 7.28.4 '@babel/traverse@7.27.1': dependencies: @@ -10068,17 +10089,12 @@ snapshots: '@babel/generator': 7.27.1 '@babel/parser': 7.28.4 '@babel/template': 7.27.2 - '@babel/types': 7.27.1 + '@babel/types': 7.28.4 debug: 4.4.1 globals: 11.12.0 transitivePeerDependencies: - supports-color - '@babel/types@7.27.1': - dependencies: - '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.27.1 - '@babel/types@7.28.4': dependencies: '@babel/helper-string-parser': 7.27.1 @@ -10940,7 +10956,7 @@ snapshots: '@paulirish/trace_engine@0.0.52': dependencies: legacy-javascript: 0.0.1 - third-party-web: 0.29.2 + third-party-web: 0.30.0 '@pkgr/core@0.2.4': {} @@ -11511,7 +11527,7 @@ snapshots: '@svgr/hast-util-to-babel-ast@8.0.0': dependencies: - '@babel/types': 7.27.1 + '@babel/types': 7.28.4 entities: 4.5.0 '@svgr/plugin-jsx@8.1.0(@svgr/core@8.1.0(typescript@5.9.3))': @@ -11571,23 +11587,23 @@ snapshots: '@types/babel__core@7.20.5': dependencies: '@babel/parser': 7.28.4 - '@babel/types': 7.27.1 + '@babel/types': 7.28.4 '@types/babel__generator': 7.27.0 '@types/babel__template': 7.4.4 '@types/babel__traverse': 7.20.7 '@types/babel__generator@7.27.0': dependencies: - '@babel/types': 7.27.1 + '@babel/types': 7.28.4 '@types/babel__template@7.4.4': dependencies: '@babel/parser': 7.28.4 - '@babel/types': 7.27.1 + '@babel/types': 7.28.4 '@types/babel__traverse@7.20.7': dependencies: - '@babel/types': 7.27.1 + '@babel/types': 7.28.4 '@types/body-parser@1.19.5': dependencies: @@ -11621,8 +11637,6 @@ snapshots: '@types/estree': 1.0.8 '@types/json-schema': 7.0.15 - '@types/estree@1.0.7': {} - '@types/estree@1.0.8': {} '@types/express-serve-static-core@4.19.6': @@ -12053,17 +12067,17 @@ snapshots: '@webassemblyjs/ast': 1.14.1 '@xtuc/long': 4.2.2 - '@webpack-cli/configtest@2.1.1(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack-dev-server@4.15.2)(webpack@5.99.5))(webpack@5.99.5)': + '@webpack-cli/configtest@2.1.1(webpack-cli@5.1.4)(webpack@5.99.5)': dependencies: webpack: 5.99.5(webpack-cli@5.1.4) webpack-cli: 5.1.4(webpack-bundle-analyzer@4.10.2)(webpack-dev-server@4.15.2)(webpack@5.99.5) - '@webpack-cli/info@2.0.2(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack-dev-server@4.15.2)(webpack@5.99.5))(webpack@5.99.5)': + '@webpack-cli/info@2.0.2(webpack-cli@5.1.4)(webpack@5.99.5)': dependencies: webpack: 5.99.5(webpack-cli@5.1.4) webpack-cli: 5.1.4(webpack-bundle-analyzer@4.10.2)(webpack-dev-server@4.15.2)(webpack@5.99.5) - '@webpack-cli/serve@2.0.5(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack-dev-server@4.15.2)(webpack@5.99.5))(webpack-dev-server@4.15.2)(webpack@5.99.5)': + '@webpack-cli/serve@2.0.5(webpack-cli@5.1.4)(webpack-dev-server@4.15.2)(webpack@5.99.5)': dependencies: webpack: 5.99.5(webpack-cli@5.1.4) webpack-cli: 5.1.4(webpack-bundle-analyzer@4.10.2)(webpack-dev-server@4.15.2)(webpack@5.99.5) @@ -13427,7 +13441,7 @@ snapshots: array-buffer-byte-length: 1.0.2 call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.9 + es-abstract: 1.23.10 es-errors: 1.3.0 get-intrinsic: 1.3.0 is-array-buffer: 3.0.5 @@ -13509,7 +13523,7 @@ snapshots: babel-plugin-jest-hoist@29.6.3: dependencies: '@babel/template': 7.27.2 - '@babel/types': 7.27.1 + '@babel/types': 7.28.4 '@types/babel__core': 7.20.5 '@types/babel__traverse': 7.20.7 @@ -13662,7 +13676,7 @@ snapshots: browserslist@4.24.5: dependencies: - caniuse-lite: 1.0.30001717 + caniuse-lite: 1.0.30001718 electron-to-chromium: 1.5.151 node-releases: 2.0.19 update-browserslist-db: 1.1.3(browserslist@4.24.5) @@ -13735,8 +13749,6 @@ snapshots: lodash.memoize: 4.1.2 lodash.uniq: 4.5.0 - caniuse-lite@1.0.30001717: {} - caniuse-lite@1.0.30001718: {} capital-case@1.0.4: @@ -14473,7 +14485,7 @@ snapshots: dependencies: dotenv: 8.6.0 - dotenv-webpack@8.1.1(webpack@5.99.5(webpack-cli@5.1.4)): + dotenv-webpack@8.1.1(webpack@5.99.5): dependencies: dotenv-defaults: 2.0.2 webpack: 5.99.5(webpack-cli@5.1.4) @@ -14622,60 +14634,6 @@ snapshots: unbox-primitive: 1.1.0 which-typed-array: 1.1.19 - es-abstract@1.23.9: - dependencies: - array-buffer-byte-length: 1.0.2 - arraybuffer.prototype.slice: 1.0.4 - available-typed-arrays: 1.0.7 - call-bind: 1.0.8 - call-bound: 1.0.4 - data-view-buffer: 1.0.2 - data-view-byte-length: 1.0.2 - data-view-byte-offset: 1.0.1 - es-define-property: 1.0.1 - es-errors: 1.3.0 - es-object-atoms: 1.1.1 - es-set-tostringtag: 2.1.0 - es-to-primitive: 1.3.0 - function.prototype.name: 1.1.8 - get-intrinsic: 1.3.0 - get-proto: 1.0.1 - get-symbol-description: 1.1.0 - globalthis: 1.0.4 - gopd: 1.2.0 - has-property-descriptors: 1.0.2 - has-proto: 1.2.0 - has-symbols: 1.1.0 - hasown: 2.0.2 - internal-slot: 1.1.0 - is-array-buffer: 3.0.5 - is-callable: 1.2.7 - is-data-view: 1.0.2 - is-regex: 1.2.1 - is-shared-array-buffer: 1.0.4 - is-string: 1.1.1 - is-typed-array: 1.1.15 - is-weakref: 1.1.1 - math-intrinsics: 1.1.0 - object-inspect: 1.13.4 - object-keys: 1.1.1 - object.assign: 4.1.7 - own-keys: 1.0.1 - regexp.prototype.flags: 1.5.4 - safe-array-concat: 1.1.3 - safe-push-apply: 1.0.0 - safe-regex-test: 1.1.0 - set-proto: 1.0.0 - string.prototype.trim: 1.2.10 - string.prototype.trimend: 1.0.9 - string.prototype.trimstart: 1.0.8 - typed-array-buffer: 1.0.3 - typed-array-byte-length: 1.0.3 - typed-array-byte-offset: 1.0.4 - typed-array-length: 1.0.7 - unbox-primitive: 1.1.0 - which-typed-array: 1.1.19 - es-define-property@1.0.1: {} es-errors@1.3.0: {} @@ -16345,7 +16303,7 @@ snapshots: '@babel/generator': 7.27.1 '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.25.7) '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.25.7) - '@babel/types': 7.27.1 + '@babel/types': 7.28.4 '@jest/expect-utils': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 @@ -18145,7 +18103,7 @@ snapshots: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.9 + es-abstract: 1.23.10 es-errors: 1.3.0 es-object-atoms: 1.1.1 get-intrinsic: 1.3.0 @@ -18843,7 +18801,7 @@ snapshots: dependencies: call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.9 + es-abstract: 1.23.10 es-object-atoms: 1.1.1 string.prototype.repeat@1.0.0: @@ -18857,7 +18815,7 @@ snapshots: call-bound: 1.0.4 define-data-property: 1.1.4 define-properties: 1.2.1 - es-abstract: 1.23.9 + es-abstract: 1.23.10 es-object-atoms: 1.1.1 has-property-descriptors: 1.0.2 @@ -19130,7 +19088,7 @@ snapshots: third-party-web@0.26.6: {} - third-party-web@0.29.2: {} + third-party-web@0.30.0: {} through2@2.0.5: dependencies: @@ -19195,7 +19153,7 @@ snapshots: dependencies: typescript: 5.9.3 - ts-loader@9.5.4(typescript@5.9.3)(webpack@5.99.5(webpack-cli@5.1.4)): + ts-loader@9.5.4(typescript@5.9.3)(webpack@5.99.5): dependencies: chalk: 4.1.2 enhanced-resolve: 5.18.1 @@ -19539,9 +19497,9 @@ snapshots: webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack-dev-server@4.15.2)(webpack@5.99.5): dependencies: '@discoveryjs/json-ext': 0.5.7 - '@webpack-cli/configtest': 2.1.1(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack-dev-server@4.15.2)(webpack@5.99.5))(webpack@5.99.5) - '@webpack-cli/info': 2.0.2(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack-dev-server@4.15.2)(webpack@5.99.5))(webpack@5.99.5) - '@webpack-cli/serve': 2.0.5(webpack-cli@5.1.4(webpack-bundle-analyzer@4.10.2)(webpack-dev-server@4.15.2)(webpack@5.99.5))(webpack-dev-server@4.15.2)(webpack@5.99.5) + '@webpack-cli/configtest': 2.1.1(webpack-cli@5.1.4)(webpack@5.99.5) + '@webpack-cli/info': 2.0.2(webpack-cli@5.1.4)(webpack@5.99.5) + '@webpack-cli/serve': 2.0.5(webpack-cli@5.1.4)(webpack-dev-server@4.15.2)(webpack@5.99.5) colorette: 2.0.20 commander: 10.0.1 cross-spawn: 7.0.6 @@ -19617,7 +19575,7 @@ snapshots: webpack@5.99.5(webpack-cli@5.1.4): dependencies: '@types/eslint-scope': 3.7.7 - '@types/estree': 1.0.7 + '@types/estree': 1.0.8 '@webassemblyjs/ast': 1.14.1 '@webassemblyjs/wasm-edit': 1.14.1 '@webassemblyjs/wasm-parser': 1.14.1 From 20b4693de3b8a8ec9be4165cc19681e63d852b23 Mon Sep 17 00:00:00 2001 From: Timothy Mugo Date: Mon, 21 Sep 2026 14:03:15 +0300 Subject: [PATCH 03/13] feat: add live Tailwind preview in the block editor canvas (Milestone 2) Bundles @tailwindcss/browser as a static asset (not imported into canvas.js itself, since it self-executes against whatever `document` it runs in) and injects it directly into the post-editor iframe's own document, where its built-in MutationObserver picks up class changes and recompiles reactively. This replaces the spec's `compile()`/`build()` pseudocode, which does not match the package's actual API (a self-executing global bundle, no exports) - verified by installing it and reading dist/index.global.js before writing any of this. Also adds an `editor.BlockListBlock` filter applying twgClasses to the live canvas wrapper: the existing extraProps filter only affects serialized save() markup, not the DOM rendered while editing, so the canvas compiler would never see the classes without it. Preflight is intentionally excluded from the canvas import to avoid resetting editor chrome styles. Iframe injection uses the querySelector approach (not the spec's component-route-first preference) with a MutationObserver + capturing load listener for resilience across device-preview remounts, chosen for predictability over unstable/private Gutenberg APIs given end-to-end testing is deferred until all milestones are done. Co-Authored-By: Claude Sonnet 5 --- plugins/tailwind-gutenberg/js/canvas.js | 65 +++++++++++++++++++++++ plugins/tailwind-gutenberg/js/editor.js | 45 ++++++++++++++-- plugins/tailwind-gutenberg/package.json | 5 +- plugins/tailwind-gutenberg/src/Plugin.php | 26 +++++++-- pnpm-lock.yaml | 9 ++++ 5 files changed, 140 insertions(+), 10 deletions(-) create mode 100644 plugins/tailwind-gutenberg/js/canvas.js diff --git a/plugins/tailwind-gutenberg/js/canvas.js b/plugins/tailwind-gutenberg/js/canvas.js new file mode 100644 index 000000000..2df3092c8 --- /dev/null +++ b/plugins/tailwind-gutenberg/js/canvas.js @@ -0,0 +1,65 @@ +const IFRAME_SELECTOR = 'iframe[name="editor-canvas"]'; +const MARKER_ATTRIBUTE = 'data-twg-canvas'; + +// Preflight is intentionally excluded: it resets margins/borders and would +// visibly wreck the editor chrome outside the canvas. +const CANVAS_THEME_CSS = `@import "tailwindcss/theme" layer(theme); +@import "tailwindcss/utilities" layer(utilities);`; + +function injectIntoIframe( iframe ) { + const doc = iframe.contentDocument; + + if ( + ! doc || + ! doc.head || + doc.head.querySelector( `script[${ MARKER_ATTRIBUTE }]` ) + ) { + return; + } + + const themeStyle = doc.createElement( 'style' ); + themeStyle.setAttribute( 'type', 'text/tailwindcss' ); + themeStyle.textContent = CANVAS_THEME_CSS; + doc.head.appendChild( themeStyle ); + + const script = doc.createElement( 'script' ); + script.setAttribute( MARKER_ATTRIBUTE, '1' ); + script.src = window.twgCanvasData.tailwindBrowserUrl; + doc.head.appendChild( script ); +} + +function tryInject() { + const iframe = document.querySelector( IFRAME_SELECTOR ); + + if ( ! iframe ) { + return; + } + + try { + injectIntoIframe( iframe ); + } catch ( error ) { + // The iframe's document can be mid-navigation (e.g. right after a + // device-preview switch recreates it); the observers below retry on + // the next mutation/load, so failures here are silently dropped. + } +} + +// Device-preview switches and other editor UI destroy and recreate the +// iframe, so a one-shot check at boot isn't enough. +new window.MutationObserver( tryInject ).observe( document.body, { + childList: true, + subtree: true, +} ); + +// iframe 'load' events don't bubble, so this has to be a capturing listener. +document.addEventListener( + 'load', + ( event ) => { + if ( event.target.matches?.( IFRAME_SELECTOR ) ) { + tryInject(); + } + }, + true +); + +tryInject(); diff --git a/plugins/tailwind-gutenberg/js/editor.js b/plugins/tailwind-gutenberg/js/editor.js index fbc474015..1c67bad7b 100644 --- a/plugins/tailwind-gutenberg/js/editor.js +++ b/plugins/tailwind-gutenberg/js/editor.js @@ -34,14 +34,19 @@ const withTwgInspectorControl = createHigherOrderComponent( ( BlockEdit ) => { { isSelected && ( - setAttributes( { [ ATTRIBUTE_NAME ]: value } ) + setAttributes( { + [ ATTRIBUTE_NAME ]: value, + } ) } /> @@ -62,6 +67,40 @@ function applyTwgExtraProps( extraProps, _blockType, attributes ) { return extraProps; } +// Mirrors applyTwgExtraProps, but for the live editing canvas: getSaveContent +// only affects the serialized save() markup, not the DOM the block renders +// while being edited, so the canvas-side Tailwind compiler would never see +// these classes without also applying them here. +const withTwgListBlockClass = createHigherOrderComponent( + ( BlockListBlock ) => { + return ( props ) => { + const classes = + props.attributes && props.attributes[ ATTRIBUTE_NAME ]; + + if ( ! classes ) { + return ; + } + + return ( + + ); + }; + }, + 'withTwgListBlockClass' +); + addFilter( 'blocks.registerBlockType', 'twg/attributes', addTwgAttribute ); addFilter( 'editor.BlockEdit', 'twg/inspector', withTwgInspectorControl ); -addFilter( 'blocks.getSaveContent.extraProps', 'twg/extra-props', applyTwgExtraProps ); +addFilter( + 'blocks.getSaveContent.extraProps', + 'twg/extra-props', + applyTwgExtraProps +); +addFilter( + 'editor.BlockListBlock', + 'twg/list-block-class', + withTwgListBlockClass +); diff --git a/plugins/tailwind-gutenberg/package.json b/plugins/tailwind-gutenberg/package.json index b3b18a4b1..d7b32b38f 100644 --- a/plugins/tailwind-gutenberg/package.json +++ b/plugins/tailwind-gutenberg/package.json @@ -6,14 +6,15 @@ "description": "Editor integration for the Tailwind Classes for Gutenberg plugin.", "author": "Development Gateway", "scripts": { - "build": "wp-scripts build js/editor.js", - "start": "wp-scripts start js/editor.js", + "build": "wp-scripts build js/editor.js js/canvas.js && mkdir -p build && cp node_modules/@tailwindcss/browser/dist/index.global.js build/tailwindcss-browser.js", + "start": "wp-scripts start js/editor.js js/canvas.js", "lint:js": "wp-scripts lint-js" }, "devDependencies": { "@wordpress/scripts": "^30.25.0" }, "dependencies": { + "@tailwindcss/browser": "^4.3.3", "@wordpress/block-editor": "^15.5.0", "@wordpress/blocks": "^15.5.0", "@wordpress/components": "^30.5.0", diff --git a/plugins/tailwind-gutenberg/src/Plugin.php b/plugins/tailwind-gutenberg/src/Plugin.php index 875d2325b..a3486b8ba 100644 --- a/plugins/tailwind-gutenberg/src/Plugin.php +++ b/plugins/tailwind-gutenberg/src/Plugin.php @@ -11,22 +11,38 @@ public static function init(): void { } public static function enqueue_editor_assets(): void { - $asset_path = TWG_PLUGIN_DIR . 'build/editor.asset.php'; + self::enqueue_script_from_asset( 'twg-editor', 'editor' ); + + if ( self::enqueue_script_from_asset( 'twg-canvas', 'canvas' ) ) { + wp_localize_script( + 'twg-canvas', + 'twgCanvasData', + array( + 'tailwindBrowserUrl' => plugins_url( 'build/tailwindcss-browser.js', TWG_PLUGIN_FILE ), + ) + ); + } + } + + private static function enqueue_script_from_asset( string $handle, string $entry ): bool { + $asset_path = TWG_PLUGIN_DIR . "build/{$entry}.asset.php"; if ( ! file_exists( $asset_path ) ) { - return; + return false; } $asset = include $asset_path; wp_register_script( - 'twg-editor', - plugins_url( 'build/editor.js', TWG_PLUGIN_FILE ), + $handle, + plugins_url( "build/{$entry}.js", TWG_PLUGIN_FILE ), $asset['dependencies'], $asset['version'], true ); - wp_enqueue_script( 'twg-editor' ); + wp_enqueue_script( $handle ); + + return true; } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b32061bdc..e99b3e338 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -153,6 +153,9 @@ importers: plugins/tailwind-gutenberg: dependencies: + '@tailwindcss/browser': + specifier: ^4.3.3 + version: 4.3.3 '@wordpress/block-editor': specifier: ^15.5.0 version: 15.5.0(@emotion/is-prop-valid@1.3.1)(@types/react-dom@18.3.6(@types/react@18.3.1))(@types/react@18.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -2289,6 +2292,9 @@ packages: resolution: {integrity: sha512-LnhVjMWyMQV9ZmeEy26maJk+8HTIbd59cH4F2MJ439k9DqejRisfFNGAPvRYlKETuh9LrImlS8aKsBgKjMA8WA==} engines: {node: '>=14'} + '@tailwindcss/browser@4.3.3': + resolution: {integrity: sha512-fFaqX18SiyiukLoEI7/cVc0B65mkTslDNo2TxazSDZ9rKs5umOEoujmEkpy8z6GrMdoVzBDbWD8j5DTKLyAWWw==} + '@tannin/compile@1.1.0': resolution: {integrity: sha512-n8m9eNDfoNZoxdvWiTfW/hSPhehzLJ3zW7f8E7oT6mCROoMNWCB4TYtv041+2FMAxweiE0j7i1jubQU4MEC/Gg==} @@ -8812,6 +8818,7 @@ packages: uuid@9.0.1: resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} + deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). hasBin: true v8-to-istanbul@9.3.0: @@ -11563,6 +11570,8 @@ snapshots: - supports-color - typescript + '@tailwindcss/browser@4.3.3': {} + '@tannin/compile@1.1.0': dependencies: '@tannin/evaluate': 1.2.0 From b27ce8880b00ef9f5b07558a42e79ec9d46ba602 Mon Sep 17 00:00:00 2001 From: Timothy Mugo Date: Mon, 21 Sep 2026 14:08:33 +0300 Subject: [PATCH 04/13] feat: add frontend CSS pipeline via REST upload (Milestone 3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit No Node.js exists in this deployment's production runtime (PHP-FPM Alpine, confirmed in the earlier spec compatibility review), so there is no server-side Tailwind compile step. Instead, canvas.js locates the