/** * Search and Replace Block Editor - Final Fix * Version: 8.7.0 * Author: Wiz Consults * Date: 2025-06-15 16:30:00 UTC * User: The Wiz * * 🎯 FINAL FIX: Replaced editPost with the more reliable resetBlocks method to ensure editor content is updated correctly after replacement. 🎯 */ (function() { 'use strict'; // Prevent multiple initializations if (window.srbeInitialized) { return; } window.srbeInitialized = true; const { registerPlugin } = wp.plugins; const { PluginDocumentSettingPanel } = wp.editPost; const { useState, useEffect, useRef, createElement: el } = wp.element; const { Button, TextControl, CheckboxControl, Notice, Spinner, PanelRow, Card, CardHeader, CardBody, Flex, FlexItem } = wp.components; const { useSelect, useDispatch } = wp.data; /** * Main Search Replace Panel Component */ function SearchReplacePanel() { const [searchTerm, setSearchTerm] = useState(''); const [replaceTerm, setReplaceTerm] = useState(''); const [caseSensitive, setCaseSensitive] = useState(false); const [wholeWords, setWholeWords] = useState(false); const [matches, setMatches] = useState([]); const [isSearching, setIsSearching] = useState(false); const [isReplacing, setIsReplacing] = useState(false); const [isQuickReplacing, setIsQuickReplacing] = useState(false); const [notice, setNotice] = useState(null); const searchInputRef = useRef(null); const debounceTimeoutRef = useRef(null); const postId = useSelect(select => select('core/editor').getCurrentPostId(), []); // Auto-focus search input on mount useEffect(() => { setTimeout(() => { if (searchInputRef.current) { searchInputRef.current.focus(); } }, 300); }, []); // Debounced search useEffect(() => { clearTimeout(debounceTimeoutRef.current); if (searchTerm && searchTerm.length > 0) { debounceTimeoutRef.current = setTimeout(() => performSearch(), 500); } else { setMatches([]); setNotice(null); } return () => clearTimeout(debounceTimeoutRef.current); }, [searchTerm, caseSensitive, wholeWords]); function makeAjaxRequest(action, data) { return new Promise((resolve, reject) => { const formData = new FormData(); formData.append('action', action); formData.append('nonce', srbeData.nonce); Object.keys(data).forEach(key => { formData.append(key, data[key]); }); fetch(srbeData.ajaxUrl, { method: 'POST', body: formData, }) .then(response => { if (!response.ok) throw new Error('Network response was not ok.'); return response.json(); }) .then(result => { if (result.success) { resolve(result.data); } else { reject(new Error(result.data.message || 'Unknown server error.')); } }) .catch(error => reject(error)); }); } function updateEditorContent(newContent, noticeMessage) { const { parse } = wp.blocks; const { resetBlocks } = wp.data.dispatch('core/block-editor'); if (newContent && typeof resetBlocks === 'function' && typeof parse === 'function') { const newBlocks = parse(newContent); resetBlocks(newBlocks); setNotice({ type: 'success', message: noticeMessage }); } else { throw new Error("Could not update editor content."); } } function performSearch() { if (!searchTerm) return; setIsSearching(true); setNotice(null); makeAjaxRequest('srbe_search_content', { postId, searchTerm, caseSensitive, wholeWords }) .then(result => { setMatches(result.matches || []); setNotice({ type: result.count > 0 ? 'success' : 'info', message: `${result.count} ${srbeData.strings.matchesFound || 'matches found'}` }); }) .catch(error => setNotice({ type: 'error', message: `Search error: ${error.message}` })) .finally(() => setIsSearching(false)); } function performReplace() { if (!searchTerm || matches.length === 0) return; setIsReplacing(true); setNotice(null); makeAjaxRequest('srbe_replace_content', { postId, searchTerm, replaceTerm, caseSensitive, wholeWords }) .then(result => { updateEditorContent(result.newContent, `${result.replacements} replacements completed!`); setTimeout(clearForm, 2500); }) .catch(error => setNotice({ type: 'error', message: `Replace error: ${error.message}` })) .finally(() => setIsReplacing(false)); } function performQuickEmDashReplace() { setIsQuickReplacing(true); setNotice(null); makeAjaxRequest('srbe_quick_replace_em_dash', { postId }) .then(result => { updateEditorContent(result.newContent, `${result.replacements} em dashes replaced successfully!`); }) .catch(error => setNotice({ type: 'error', message: `Quick replace error: ${error.message}` })) .finally(() => setIsQuickReplacing(false)); } function clearForm() { setSearchTerm(''); setReplaceTerm(''); setCaseSensitive(false); setWholeWords(false); setMatches([]); setNotice(null); if (searchInputRef.current) { searchInputRef.current.focus(); } } return el( PluginDocumentSettingPanel, { name: 'search-replace-panel-final', title: srbeData.strings.panelTitle || 'Search & Replace', icon: 'search', className: 'srbe-panel' }, el(PanelRow, null, el(Card, { className: 'srbe-quick-replace-card' }, el(CardHeader, null, el('h4', { className: 'srbe-quick-title' }, srbeData.strings.quickReplaceTitle || 'Quick Actions')), el(CardBody, null, el('div', { className: 'srbe-quick-actions' }, el(Button, { isSecondary: true, onClick: performQuickEmDashReplace, disabled: isQuickReplacing, className: 'srbe-quick-em-dash-btn' }, isQuickReplacing ? el(Spinner) : (srbeData.strings.quickEmDashButton || 'Fix Em Dashes') ), el('p', { className: 'srbe-quick-help' }, srbeData.strings.quickEmDashHelp || 'Replace em dash variants with ", "') ) ) ) ), notice && el(PanelRow, null, el(Notice, { status: notice.type, isDismissible: true, onRemove: () => setNotice(null), className: 'srbe-notice' }, notice.message)), el(PanelRow, null, el(TextControl, { label: srbeData.strings.searchLabel, value: searchTerm, onChange: setSearchTerm, ref: searchInputRef })), el(PanelRow, null, el(TextControl, { label: srbeData.strings.replaceLabel, value: replaceTerm, onChange: setReplaceTerm })), el(PanelRow, null, el('div', { className: 'srbe-options' }, el(CheckboxControl, { label: srbeData.strings.caseSensitive, checked: caseSensitive, onChange: setCaseSensitive, className: 'srbe-checkbox' }), el(CheckboxControl, { label: srbeData.strings.wholeWords, checked: wholeWords, onChange: setWholeWords, className: 'srbe-checkbox' }) ) ), isSearching && el(PanelRow, null, el('div', { className: 'srbe-searching' }, el(Spinner), 'Searching...')), !isSearching && matches.length > 0 && el(PanelRow, null, el(Card, { className: 'srbe-matches-card' }, el(CardHeader, null, el('h4', { className: 'srbe-matches-title' }, `${matches.length} matches found`)), el(CardBody, null, el('div', { className: 'srbe-matches-list' }, matches.slice(0, 5).map((match, index) => el('div', { key: index, className: 'srbe-match-item' }, `"${match.text}"`)), matches.length > 5 && el('div', { className: 'srbe-more-matches' }, `... and ${matches.length - 5} more`) ) ) ) ), el(PanelRow, null, el(Flex, { className: 'srbe-actions', direction: 'column', gap: 2 }, el(FlexItem, null, el(Button, { isPrimary: true, onClick: performReplace, disabled: !searchTerm || matches.length === 0 || isReplacing, className: 'srbe-replace-btn' }, isReplacing ? el(Spinner) : (srbeData.strings.replaceAllButton || 'Replace All') )), el(FlexItem, null, el(Button, { isSecondary: true, onClick: clearForm, disabled: isReplacing, className: 'srbe-clear-btn' }, srbeData.strings.clearButton || 'Clear')) ) ) ); } registerPlugin('search-replace-block-editor-final', { render: SearchReplacePanel, icon: 'search' }); })();