window.jQuery(document).ready(() => { function changeLibraryPosition() { if (document.querySelector(".brandy-patterns-library-wrapper")) { setTimeout(() => { if (document.querySelector(".brandy-patterns-library-wrapper")) { observer.disconnect(); } }, 1000); return; } const Modal = wp.components.Modal; const { Spinner, SearchControl, Button } = wp.components; const BlockPatternList = wp.blockEditor.__experimentalBlockPatternsList; const { __ } = wp.i18n; // Create a React element for the App using wp.element const { createElement, createRoot, useState, useMemo, useCallback } = wp.element; const { useSelect, useDispatch } = wp.data; const { cloneBlock, createBlock } = wp.blocks; const { store: noticesStore } = wp.notices; // Patterns Library Content Component const PatternsLibraryContent = ({ onCloseDialog, patternsData }) => { const [searchValue, setSearchValue] = useState(""); const [selectedCategory, setSelectedCategory] = useState("brandy-call-to-action"); // Use provided patterns data if available, otherwise fetch from store const storePatternsData = useSelect( (select) => { // If patterns data provided via props, skip fetching from store if (patternsData) { return { patterns: [], patternCategories: [] }; } const blockEditorStore = select("core/block-editor"); const coreStore = select("core"); if (!blockEditorStore || !coreStore) { return { patterns: [], patternCategories: [] }; } const settings = blockEditorStore.getSettings(); // Get patterns from both block editor and core store let allPatterns = blockEditorStore.__experimentalGetAllowedPatterns?.() || []; // If no patterns from block editor, try from settings directly if ( allPatterns.length === 0 && settings.__experimentalBlockPatterns ) { allPatterns = settings.__experimentalBlockPatterns; } // If still no patterns, try from core store if (allPatterns.length === 0) { const corePatterns = coreStore.getBlockPatterns?.() || []; allPatterns = corePatterns; } // Filter Brandy patterns const brandyPatterns = allPatterns.filter((pattern) => pattern.categories?.some((cat) => cat.toLowerCase().includes("brandy") ) ); return { patterns: brandyPatterns, patternCategories: settings.__experimentalBlockPatternCategories?.filter((cat) => cat.name.toLowerCase().includes("brandy") ) || [], }; }, [patternsData] ); // Use patterns from props if available, otherwise from store const patterns = patternsData?.patterns || storePatternsData.patterns; const patternCategories = patternsData?.categories || storePatternsData.patternCategories; // Filter patterns based on category and search const filteredPatterns = useMemo(() => { let filtered = patterns; // Filter by category if (selectedCategory !== "all") { filtered = filtered.filter((pattern) => pattern.categories?.includes(selectedCategory) ); } // Filter by search if (searchValue) { filtered = filtered.filter((pattern) => pattern.title.toLowerCase().includes(searchValue.toLowerCase()) ); } // Ensure all patterns have required properties for BlockPatternList // Parse blocks from content if not already parsed return filtered.map((pattern) => { let blocks = pattern.blocks; // If blocks don't exist, parse them from content if (!blocks && pattern.content && wp.blocks && wp.blocks.parse) { try { blocks = wp.blocks.parse(pattern.content); } catch (e) { blocks = []; } } return { ...pattern, name: pattern.name || pattern.slug || `pattern-${Math.random()}`, title: pattern.title || "Untitled Pattern", categories: pattern.categories || [], content: pattern.content || "", description: pattern.description || "", blockTypes: pattern.blockTypes || [], blocks: blocks || [], }; }); }, [patterns, selectedCategory, searchValue]); // Handle pattern insertion const { createSuccessNotice } = useDispatch(noticesStore); const { insertBlocks } = useDispatch("core/block-editor"); const onClickPattern = useCallback( (pattern, blocks) => { const insertionPoint = wp.data.select("core/block-editor").getBlockInsertionPoint() ?.rootClientId || ""; insertBlocks(blocks, undefined, insertionPoint); createSuccessNotice(`Pattern "${pattern.title}" inserted.`, { type: "snackbar", id: "inserter-notice", }); onCloseDialog(); }, [insertBlocks, createSuccessNotice, onCloseDialog] ); // Prepare categories list const categoriesList = useMemo(() => { if (!patternCategories || patternCategories.length === 0) { return [ { name: "all", label: __("All Brandy Patterns") || "All Brandy Patterns", }, ]; } const cats = [...patternCategories] .sort((a, b) => (a.name || "").localeCompare(b.name || "")) .filter((cat) => cat.name !== "brandy"); return [ ...cats, { name: "all", label: __("All Brandy Patterns") || "All Brandy Patterns", }, ]; }, [patternCategories]); return createElement( "div", { className: "block-editor-block-patterns-explorer" }, createElement( "div", { className: "block-editor-block-patterns-explorer__sidebar" }, createElement(SearchControl, { __nextHasNoMarginBottom: true, onChange: setSearchValue, value: searchValue, label: __("Search") || "Search", placeholder: __("Search") || "Search", className: "block-editor-block-patterns-explorer__search", }), createElement( "div", { className: "block-editor-block-patterns-explorer__sidebar__categories-list", }, categoriesList.map(({ name, label }) => createElement( Button, { __next40pxDefaultSize: true, key: name, label: label, className: "block-editor-block-patterns-explorer__sidebar__categories-list__item", isPressed: selectedCategory === name, onClick: () => setSelectedCategory(name), }, label ) ) ) ), createElement( "div", { className: "block-editor-block-patterns-explorer__list" }, filteredPatterns.length > 0 ? createElement(BlockPatternList, { blockPatterns: filteredPatterns, onClickPattern: onClickPattern, isDraggable: false, }) : createElement( "div", { style: { padding: "40px", textAlign: "center", color: "#757575", }, }, createElement( "p", null, "No patterns found. Try refreshing the page or check if Brandy Blocks is properly activated." ) ) ) ); }; const App = () => { const [openDialog, setOpenDialog] = useState(false); const [isInstalling, setIsInstalling] = useState(false); const [installError, setInstallError] = useState(null); const [isPluginInstalled, setIsPluginInstalled] = useState(false); const [isLoadingPatterns, setIsLoadingPatterns] = useState(false); const [patternsData, setPatternsData] = useState(null); // Check if plugin is already installed (from PHP) const isPluginAlreadyInstalled = brandyPatternsLibrary.is_installed || false; const pluginBasename = brandyPatternsLibrary.plugin_basename || "brandy-blocks/brandy-blocks.php"; const handleOpenDialog = () => { setOpenDialog(true); }; const handleInstallPlugin = async () => { setIsInstalling(true); setInstallError(null); try { let pluginFile = pluginBasename; let pluginName = "Brandy Blocks"; // Step 1: Install the plugin only if not already installed if (!isPluginAlreadyInstalled) { const installFormData = new URLSearchParams(); installFormData.append("action", "install-plugin"); installFormData.append("slug", "brandy-blocks"); installFormData.append("_wpnonce", brandyPatternsLibrary.nonce); const installResponse = await fetch( brandyPatternsLibrary.ajax_url, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded", }, body: installFormData.toString(), } ); const installResult = await installResponse.json(); if (!installResult.success) { throw new Error( installResult.data?.errorMessage || "Failed to install plugin" ); } pluginFile = installResult.data?.plugin || pluginBasename; pluginName = installResult.data?.pluginName || "Brandy Blocks"; } // Step 2: Activate the plugin const activateFormData = new URLSearchParams(); activateFormData.append("action", "activate-plugin"); activateFormData.append("slug", "brandy-blocks"); activateFormData.append("plugin", pluginFile); activateFormData.append("name", pluginName); activateFormData.append("_wpnonce", brandyPatternsLibrary.nonce); const activateResponse = await fetch(brandyPatternsLibrary.ajax_url, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded", }, body: activateFormData.toString(), }); const activateResult = await activateResponse.json(); if (!activateResult.success) { throw new Error( activateResult.data?.errorMessage || "Failed to activate plugin" ); } // Plugin installed and activated successfully // Fetch patterns data and parse them setIsLoadingPatterns(true); await loadPatternsLibrary(); setIsPluginInstalled(true); setIsLoadingPatterns(false); } catch (error) { setInstallError(error.message); } finally { setIsInstalling(false); } }; const loadPatternsLibrary = async () => { try { // Wait a bit for WordPress to activate the plugin await new Promise((resolve) => setTimeout(resolve, 1500)); // Fetch patterns via custom AJAX endpoint const formData = new URLSearchParams(); formData.append("action", "brandy_get_patterns_after_activation"); formData.append("nonce", brandyPatternsLibrary.patterns_nonce); formData.append("_nocache", Date.now()); const response = await fetch(brandyPatternsLibrary.ajax_url, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded", "Cache-Control": "no-cache, no-store, must-revalidate", Pragma: "no-cache", }, body: formData.toString(), }); const result = await response.json(); if (!result.success) { throw new Error(result.data?.message || "Failed to fetch patterns"); } // Set patterns data to state setPatternsData(result.data); } catch (error) { setInstallError( "Plugin activated but patterns failed to load. Please refresh the page." ); } }; return createElement( "div", { className: "brandy-patterns-library-wrapper" }, createElement( "button", { className: "button", onClick: handleOpenDialog }, "Brandy Patterns Library" ), openDialog && createElement( Modal, { title: "Brandy Patterns Library", onRequestClose: () => { setOpenDialog(false); }, open: openDialog, isFullScreen: isPluginInstalled, }, createElement( "div", { className: "brandy-patterns-library-dialog" }, isLoadingPatterns ? createElement( "div", { className: "brandy-patterns-library-loading", style: { display: "flex", alignItems: "center", justifyContent: "center", padding: "40px", flexDirection: "column", gap: "20px", }, }, createElement(Spinner), createElement("p", null, "Loading patterns library...") ) : isPluginInstalled ? createElement(PatternsLibraryContent, { onCloseDialog: () => setOpenDialog(false), patternsData: patternsData, }) : createElement( "div", { className: "brandy-patterns-library-install-prompt" }, createElement( "p", { className: "brandy-patterns-library-dialog-content" }, createElement( "span", { className: "brandy-patterns-library-dialog-content-text", }, isPluginAlreadyInstalled ? "Please activate Brandy Blocks plugin to use the patterns library." : "Please install Brandy Blocks plugin to use the patterns library." ) ), createElement( "button", { className: "button button-primary", onClick: handleInstallPlugin, disabled: isInstalling, style: { marginTop: "15px" }, }, isInstalling ? createElement( "span", null, createElement(Spinner), isPluginAlreadyInstalled ? " Activating..." : " Installing..." ) : isPluginAlreadyInstalled ? "Activate Plugin" : "Install & Activate" ), installError && createElement( "p", { className: "brandy-patterns-library-error", style: { color: "red", marginTop: "10px" }, }, installError ) ) ) ) ); }; const library = document.createElement("div"); createRoot(library).render(createElement(App)); setTimeout(() => { if (document.querySelector(".brandy-patterns-library-wrapper")) { return; } document .querySelector(".editor-document-tools__left") .appendChild(library); }, 1); } const observer = new MutationObserver(function (mutations) { changeLibraryPosition(); }); if (!window.wp) { return; } const { registerPlugin } = window.wp.plugins ?? {}; const { PluginSidebar } = window.wp.editSite ?? window.wp.editPost ?? {}; if (PluginSidebar) { registerPlugin("brandy-patterns-library", { render: function () { const interval = setInterval(() => { const targetElement = document.querySelector( ".editor-header__toolbar" ); if (targetElement) { observer.observe( document.querySelector(".editor-header__toolbar"), { childList: true, subtree: true, } ); changeLibraryPosition(); clearInterval(interval); } }, 100); }, }); } });