-
Notifications
You must be signed in to change notification settings - Fork 92
Feat/more blocks #279
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Feat/more blocks #279
Conversation
Introduces a new 'Helpful Feedback' block with customizable voting buttons, styling options, and server-side rendering for docs articles. Updates Contributors block to restrict rendering to docs post type and registers the new block in the main index.
Replaced all instances of the 'block-responsive' text domain with 'wedocs' in ColorsControls.js for consistency with the plugin's localization. Also fixed a variable reference in ListItems.js to use attributes.containerBackgroundColor directly.
Introduces a new AI-Powered Summary block with collapsible functionality, custom icons, and extensive style controls. Adds a new Doc Actions block (initial files). Refactors Contributors block to use shared style panels and improves InspectorControls organization. Updates block.json files for better support of color, spacing, and typography options. Adds a common controls utility for Inspector panels.
WalkthroughThis PR removes built assets under assets/build, updates tooling and localization, deletes legacy built bundles, and adds many new Gutenberg blocks and shared editor controls (src/blocks/*), plus new REST endpoints for AI summaries in includes/API/API.php. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related issues
Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 14
Note
Due to the large number of review comments, Critical severity comments were prioritized as inline comments.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
src/blocks/DocsGrid/edit.js (2)
534-536:Spinnercomponent is not imported.The
Spinnercomponent is used on line 535 but is not imported from@wordpress/components. This will cause a runtime error whenloadingis true.Proposed fix
Add
Spinnerto the imports from@wordpress/components:import { PanelBody, SelectControl, ToggleControl, PanelRow, - FormTokenField + FormTokenField, + Spinner } from '@wordpress/components';
157-162: Use camelCase for SVG attributes in JSX.React/JSX requires camelCase for DOM attributes.
stroke-widthandstroke-linecapshould bestrokeWidthandstrokeLinecap.Proposed fix
- <svg fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> + <svg fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor"> <path - stroke-linecap="round" - stroke-linejoin="round" + strokeLinecap="round" + strokeLinejoin="round" d="M11.25 11.25l.041-.02a.75.75 0 011.063.852l-.708 2.836a.75.75 0 001.063.853l.041-.021M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9-3.75h.008v.008H12V8.25z" ></path> </svg>package.json (1)
43-64: Remove trailing whitespace / formatting drift to avoid lint-pkg-json failures.
Line 43 looks like a whitespace-only change;wp-scripts lint-pkg-jsoncan fail on formatting/trailing spaces depending on configuration.Proposed fix
"devDependencies": { "@wordpress/scripts": "^30.15.0", "@wordpress/components": "^28.10.0", "@wordpress/icons": "^10.10.0", "@wordpress/block-editor": "^14.5.0", "archiver": "^5.3.1", "autoprefixer": "^10.4.13", "css-loader": "^6.7.3", "postcss": "^8.4.21", "replace-in-file": "^6.3.5", "style-loader": "^3.3.1", "tailwindcss": "^3.2.4", "tailwindcss-scoped-preflight": "^3.4.12" }, - + "dependencies": { "@dnd-kit/core": "^6.0.7", "@dnd-kit/sortable": "^7.0.2", "@dnd-kit/utilities": "^3.2.1", "@headlessui/react": "^1.7.7", "@heroicons/react": "^2.0.14", "@tailwindcss/forms": "^0.5.3", "@wordpress/api-fetch": "^6.21.0", "@wordpress/core-data": "^6.0.0", "@wordpress/data": "^8.3.0", "@wordpress/element": "^5.0.0", "@wordpress/i18n": "^4.23.0", "daisyui": "^2.49.0", "he": "^1.2.0", "less-loader": "^11.1.0", "react-responsive-carousel": "^3.2.23", "react-router-dom": "^6.6.2", "sweetalert2": "^11.7.1" } }
🤖 Fix all issues with AI agents
In @includes/Assets.php:
- Around line 85-89: The code attempts to wp_enqueue_style('wedocs-block-style')
but the wp_register_style call for 'wedocs-block-style' is commented out,
causing enqueue errors; either restore/uncomment the wp_register_style call that
registers 'wedocs-block-style' (ensure correct handle, URL $assets_url .
'/build/style-block.css' and dependency/version args match $block_dependencies)
or remove the wp_enqueue_style('wedocs-block-style') calls so you don't enqueue
an unregistered handle; update the occurrences where 'wedocs-block-style' is
enqueued to match the chosen fix.
In @readme.md:
- Around line 26-39: Remove the commented Mozart configuration block that
references "WPMUDEV\\PluginTest\\Vendor\\", "WPMUDEV_PluginTest_", and the
unrelated package "google/apiclient" from readme.md; replace it only if you
intend to document real configuration, in which case update the namespace and
prefix to the actual plugin values (e.g., "WeDevs\\WeDocs\\" per composer.json)
and list only real dependencies, or simply delete the entire commented block to
avoid misleading/template content.
In @src/blocks/AdvanceContributors/render.php:
- Line 503: The top-level call to
render_wedocs_advance_contributors_block($attributes, $content, $block) is
invalid because $attributes, $content and $block are undefined in this scope;
remove that invocation so the file only defines the function and lets WordPress
invoke it via the block.json "render": "file:./render.php" callback; keep the
render_wedocs_advance_contributors_block function definition intact and ensure
no other top-level calls pass undefined variables.
- Around line 116-289: The nested declaration of generate_responsive_css inside
render_wedocs_advance_contributors_block will cause a "Cannot redeclare
generate_responsive_css()" fatal error on multiple renders; fix by either moving
generate_responsive_css out of render_wedocs_advance_contributors_block into
file scope (so it’s declared once) or wrap its declaration in if
(!function_exists('generate_responsive_css')) { ... } or convert it to a local
closure ($generate_responsive_css = function(...) { ... }) and update all calls
inside render_wedocs_advance_contributors_block to use the closure variable.
In @src/blocks/commonControls/ColorsControls.js:
- Around line 17-20: ColorsControls currently reads device and colorsControls
incorrectly: ensure you get device from props/attributes (e.g., read device from
attributes or add a device prop to the component) and default colorsControls to
an object before indexing; update the ColorsControls signature/usage so device
is defined (or default to a known key like "desktop") and compute currentColors
as (attributes.colorsControls || {})[device || 'desktop'] to avoid undefined
errors and preserve the original props contract with setAttributes remaining
unchanged.
In @src/blocks/Contributors/render.php:
- Around line 124-155: The 'all' case treats $author_id inconsistently as an
array and scalar; normalize it to an array first by retrieving the raw value
from get_post_field('wedocs_contributors', $post->ID), ensuring if it's
empty/null you use an empty array, and if it's a scalar convert it to a
single-element array; then iterate that normalized $authors array to call
get_userdata() and populate $contributors; remove the redundant
get_userdata($author_id) scalar check, and ensure $contributor_ids is
initialized as an array (e.g., from the normalized $authors) before
comparing/adding revision authors via wp_get_post_revisions to avoid warnings
and duplicate entries.
In @src/blocks/HelpfulFeedback/render.php:
- Around line 37-52: Cookie data in $_COOKIE['wedocs_response'] is used directly
to build $previous via explode which is unsafe; validate that the cookie exists
and is a string, sanitize it (e.g. run sanitize_text_field or similar), split it
into parts, then normalize each part to integers or expected safe values (use
array_map('intval') and array_filter to remove non-numeric/zero entries) before
assigning to $previous and using in_array($post_id, $previous); also ensure
$voted_option is initialized and only set from trusted sources ($user_vote after
sanitizing get_post_meta output) so you don't display based on unsanitized
cookie contents.
- Line 206: Replace the hardcoded enqueue in
src/blocks/HelpfulFeedback/render.php (the wp_enqueue_script call that
references '/assets/js/helpful-feedback.js') by removing that manual enqueue
entirely and instead add a viewScript entry in the block’s block.json so
WordPress will register the compiled src/blocks/HelpfulFeedback/view.js as the
front-end view script; update block.json’s viewScript to point to the built
asset name (the same name used by your build/webpack) and rebuild assets so the
viewScript is registered automatically, matching how other blocks in the plugin
are structured.
In @src/blocks/HelpfulFeedback/view.js:
- Around line 118-163: The showThankYou function inserts unescaped
user-controlled values (thankYouMessage, data.yes_votes, data.no_votes) into
HTML via template literals, creating an XSS vector; fix by escaping or treating
those values as plain text instead of raw HTML: stop interpolating
thankYouMessage and the vote counts into the thankYouContent string, build the
thank-you DOM using safe methods (e.g., create elements and set textContent or
use jQuery .text() for those fields) and only insert sanitized/static HTML for
icons/structure; ensure any $container.append(now-built-node) or similar uses
the safe node rather than a string containing user data.
In @src/blocks/HelpfulModal/block.json:
- Around line 10-51: You have two duplicate "supports" objects in block.json;
remove the redundant one and merge their properties into a single "supports"
declaration so nothing is overwritten: ensure the final supports object includes
anchor: true, align: ["wide","full"], spacing.blockGap: true plus all color,
typography, spacing, and __experimentalBorder entries from both blocks so no
settings are lost.
In @src/blocks/HelpfulModal/save.js:
- Around line 1-3: The file is missing the required export for a save function
used by server-side rendered blocks; add and export a save function named save
that returns null (i.e., export function save() { return null; }) so the block
registration has the expected save implementation and won't fail at runtime.
In @src/blocks/LastUpdated/render.php:
- Around line 30-50: The function wedocs_format_last_updated_date is being
(re)declared inside the block render callback causing a fatal "Cannot redeclare"
error when the block renders multiple times; fix it by moving the
wedocs_format_last_updated_date definition out of the render callback into a
top-level/shared helper loaded once (or wrap the definition with if
(!function_exists('wedocs_format_last_updated_date')) { ... } so it only defines
once), and update the render code to call the now-top-level function instead of
declaring it inline.
In @src/blocks/Search/edit.js:
- Around line 51-56: searchWrapperStyles uses an invalid CSS value 'relative'
for flexDirection; update the ternary in searchWrapperStyles (and any matching
logic as in save.js) so that when buttonPosition !== 'outside' it returns a
valid flexDirection (e.g., 'column') instead of 'relative', keeping the existing
gap logic intact.
In @src/blocks/Search/save.js:
- Around line 36-41: The style object searchWrapperStyles sets flexDirection to
an invalid value 'relative' in src/blocks/Search/save.js; change the conditional
to a valid flexDirection (e.g., buttonPosition === 'outside' ? 'row' : 'column')
and, if the intent was to use CSS positioning, add a separate position:
'relative' property instead of misusing flexDirection; update the const
searchWrapperStyles accordingly.
🟠 Major comments (34)
src/blocks/AdvanceContributors/render.php-337-348 (1)
337-348: Display control hide logic appears inverted.The current logic adds hide classes if any display control exists for a device, rather than checking for a specific "hide" value. This will incorrectly hide the block when any display property (like
display: flex) is set.🐛 Suggested fix
// Handle display controls for hiding on devices $display_classes = []; -if (isset($display_controls['desktop']) && $display_controls['desktop']) { +if (isset($display_controls['desktop']['display']) && $display_controls['desktop']['display'] === 'none') { $display_classes[] = 'wedocs-hide-desktop'; } -if (isset($display_controls['tablet']) && $display_controls['tablet']) { +if (isset($display_controls['tablet']['display']) && $display_controls['tablet']['display'] === 'none') { $display_classes[] = 'wedocs-hide-tablet'; } -if (isset($display_controls['mobile']) && $display_controls['mobile']) { +if (isset($display_controls['mobile']['display']) && $display_controls['mobile']['display'] === 'none') { $display_classes[] = 'wedocs-hide-mobile'; }src/blocks/Search/save.js-49-63 (1)
49-63: Hover styles via CSS custom properties won't work without accompanying CSS.The
--hover-bgand--hover-colorcustom properties are set but there's no CSS rule that uses them for:hoverstates. The button hover effect won't work on the frontend.Consider adding a stylesheet rule or using
wp_add_inline_stylein PHP to handle the hover states:.wedocs-search-button:hover { background-color: var(--hover-bg) !important; color: var(--hover-color) !important; }src/blocks/Search/save.js-87-92 (1)
87-92: Potential SSR hydration mismatch withwindow.location.origin.Using
windowin the save function can cause hydration mismatches since the server doesn't have access towindow. For a search form, consider using an empty string or a relative path, as the form will submit to the current page by default.Proposed fix
<form className="wedocs-search-form" role="search" method="get" - action={typeof window !== 'undefined' ? window.location.origin : ''} + action="" style={searchWrapperStyles} >src/blocks/Search/edit.js-14-18 (1)
14-18: Update WordPress component imports to use stable exports.
BoxControlwas stabilized in WordPress 6.8 (April 2025) and should be imported directly instead of using the__experimentalBoxControlalias.UnitControlremains problematic—it's flagged for deprecation warnings in WordPress 6.8 and requires review to address these warnings or find a replacement.Update the import statement to use
BoxControlfrom stable exports and evaluateUnitControlusage for deprecation compliance.src/blocks/HelpfulModal/edit.js-25-29 (1)
25-29: MigrateBoxControlandBorderControlto stable exports.Lines 27–28 import
__experimentalBoxControland__experimentalBorderControl, which were stabilized in WordPress 6.7/6.8. Update to:BoxControl, BorderControl,
ToolsPanel,ToolsPanelItem, andDividerremain experimental with no stable replacements; document or track for future updates once they stabilize.src/blocks/HelpfulFeedback/edit.js.backup-1-513 (1)
1-513: Remove backup file from repository.The file
src/blocks/HelpfulFeedback/edit.js.backupis tracked in git and contains an older version (different from the currentedit.js). Backup files should not be committed to version control—use Git history to track previous versions instead. The active code imports only./edit, making the.backupfile redundant.src/blocks/HelpfulFeedback/render.php-26-26 (1)
26-26: Sanitize IP address and handle proxy headers.Direct access to
$_SERVER['REMOTE_ADDR']without sanitization is a security concern. Additionally, this approach doesn't account for proxy headers, which can result in inaccurate IP tracking for users behind proxies or load balancers.🔒 Proposed fix
-$user_ip = $_SERVER['REMOTE_ADDR'] ?? ''; +// Get the real user IP, accounting for proxies +$user_ip = ''; +if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { + $ip_list = explode(',', sanitize_text_field(wp_unslash($_SERVER['HTTP_X_FORWARDED_FOR']))); + $user_ip = trim($ip_list[0]); +} elseif (!empty($_SERVER['REMOTE_ADDR'])) { + $user_ip = sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR'])); +} +// Validate IP format +if (!filter_var($user_ip, FILTER_VALIDATE_IP)) { + $user_ip = ''; +}package.json-29-34 (1)
29-34: Verify Node.js compatibility and test build with @wordpress/scripts v30.15.0 before merging.Upgrading
@wordpress/scriptsfrom v25 (Node ≥14) to v30.15.0 (Node ≥18.12.0) introduces breaking changes in CLI flags, webpack/babel configuration, and block.json entry-point detection. Confirm that your CI environment and local development setup have Node.js ≥18.12.0, run a full build and test suite locally to catch any webpack/babel/entry-point regressions, and ensure the lockfile is updated (package-lock.json or equivalent) if the repo uses one. The@wordpress/block-editorv14.5.0 has no peer-dependency conflict with wp-scripts v30, but compatibility depends on the WordPress/Gutenberg version where your blocks run—verify that during QA.src/blocks/ReadingProgress/index.js-1-10 (1)
1-10: Importeditor.scsssoeditorStyle: file:./index.cssis actually generated.Right now only
style.scssis imported; if nothing importseditor.scss, the build may not emitindex.css, breaking the block’s declarededitorStyle.Proposed fix
import { registerBlockType } from '@wordpress/blocks'; import './style.scss'; +import './editor.scss'; import Edit from './edit'; import save from './save'; import metadata from './block.json'; registerBlockType(metadata.name, { edit: Edit, save, });src/blocks/ReadingProgress/block.json-1-14 (1)
1-14: Fix CSS asset filename inblock.json:style-index.csswon't be generated by webpack.Block.json declares
style: "file:./style-index.css", but onlystyle.scssis imported inindex.js, which generatesindex.css. The non-standard filenamestyle-index.cssdoesn't match MiniCSSExtractPlugin's output naming convention ([name].css). Either rename the declared style to"file:./index.css"or create a separate entry point. Also note thateditor.scssexists but is never imported; if editor-specific styles are needed, import it inindex.js.src/blocks/ReadingProgress/style.scss-80-83 (1)
80-83: Critical: Global smooth-scroll affects entire site.Setting
scroll-behavior: smoothon thehtmlelement applies site-wide, not just to the Reading Progress block. This can:
- Override existing site styles or user preferences
- Conflict with accessibility settings (users with vestibular disorders may prefer reduced motion)
- Create unexpected behavior for other scrolling functionality
♻️ Recommended fix: Remove or scope the smooth scroll behavior
Option 1: Remove the global rule entirely (recommended):
-// Smooth scrolling enhancement -html { - scroll-behavior: smooth; -} -Option 2: If smooth scrolling is needed for the block's functionality, scope it properly or add a prefers-reduced-motion check:
-html { +html:not([data-user-prefers-reduced-motion]) { scroll-behavior: smooth; } + +@media (prefers-reduced-motion: reduce) { + html { + scroll-behavior: auto; + } +}src/blocks/Contributors/index.js-1-42 (1)
1-42: Remove function wrapper fromicon.src— not a supported API pattern. Theiconconfiguration inregisterBlockTypedoes not support functions. Per WordPress block icon documentation, valid forms are: Dashicon string, JSX element, component reference, or object{ src, foreground?, background? }wheresrcis a JSX element or component—not a function.Also,
./save.jsexists and should be imported and used instead ofsave: () => null, consistent with other blocks in the codebase.Refactor
import { registerBlockType } from "@wordpress/blocks"; import Edit from "./edit"; import metadata from "./block.json"; import "./style.scss"; import { Icon } from '@wordpress/components'; import { starFilled } from '@wordpress/icons'; +import Save from "./save"; +const ContributorsProIcon = ( + <span style={{ position: 'relative', display: 'inline-block' }}> + <Icon icon={ starFilled } /> + <span + style={{ + position: 'absolute', + top: '22px', + right: '-2px', + background: '#4f47e6', + color: 'white', + fontSize: '9px', + padding: '2px 3px', + borderRadius: '3px', + fontWeight: 'bold', + lineHeight: 1 + }} + > + PRO + </span> + </span> +); registerBlockType(metadata.name, { edit: Edit, - save: () => null, + save: Save, - icon: { - src:() => ( - <span style={{ position: 'relative', display: 'inline-block' }}> - <Icon icon={ starFilled } /> - <span - style={{ - position: 'absolute', - top: '22px', - right: '-2px', - background: '#4f47e6', - color: 'white', - fontSize: '9px', - padding: '2px 3px', - borderRadius: '3px', - fontWeight: 'bold', - lineHeight: 1 - }} - > - PRO - </span> - </span> - ), - } + icon: ContributorsProIcon });src/blocks/AISummary/style.scss-57-76 (1)
57-76: Hardcoded max-height will clip long content.The collapsible implementation uses
max-height: 1000px(line 67) as the open state. Content exceeding this height will be clipped and inaccessible to users. Additionally, the!importantdeclarations on lines 62 and 69 reduce style flexibility.Consider using JavaScript to calculate actual content height or using a sufficiently large value with a note documenting the limitation.
💡 Alternative approaches
Option 1: Increase max-height with documentation
&.open { .ai-summary-content { - max-height: 1000px; + max-height: 9999px; /* Large enough for most content */ opacity: 1; margin-top: 15px !important; }Option 2: Calculate height dynamically in view.js
In
src/blocks/AISummary/view.js, measure and set the actual content height:const content = block.querySelector('.ai-summary-content'); if (content) { const height = content.scrollHeight; content.style.maxHeight = height + 'px'; }src/blocks/AISummary/view.js-12-40 (1)
12-40: State restoration logic may override intended default behavior.The initialization flow sets the initial state based on
isOpenByDefault(lines 13-15), but then immediately restores from localStorage (lines 31-40). This means:
- If a user previously closed the block,
localStoragewill have'closed'- The block will remain closed even if
isOpenByDefaultistrue- Users can never experience the "open by default" behavior after their first interaction
This may be intentional (user preference takes precedence), but consider whether the
isOpenByDefaultattribute should have priority or if there should be a way to reset to defaults.💡 Possible fix: Respect isOpenByDefault when no saved state exists
- // Set initial state - if (isOpenByDefault) { - block.classList.add('open'); - } - // Toggle on click if (header) { header.addEventListener('click', () => { block.classList.toggle('open'); // Save state to localStorage const blockId = block.getAttribute('data-block-id'); if (blockId) { const isOpen = block.classList.contains('open'); localStorage.setItem(`ai-summary-${blockId}`, isOpen ? 'open' : 'closed'); } }); } // Restore state from localStorage const blockId = block.getAttribute('data-block-id'); if (blockId) { const savedState = localStorage.getItem(`ai-summary-${blockId}`); if (savedState === 'open') { block.classList.add('open'); } else if (savedState === 'closed') { block.classList.remove('open'); + } else { + // No saved state, use default + if (isOpenByDefault) { + block.classList.add('open'); + } } + } else if (isOpenByDefault) { + // No blockId, use default + block.classList.add('open'); } }); });src/blocks/Contributors/render.php-160-169 (1)
160-169: Remove mock contributor fallback from production code.Injecting a fake "Demo Author" masks real data issues and confuses users. Consider returning an empty container or a "No contributors found" message instead.
Proposed fix
if (empty($contributors)) { - // For demo purposes, add mock contributors if none found - $contributors = [ - (object) [ - 'ID' => 1, - 'display_name' => 'Demo Author', - 'user_email' => 'demo@example.com' - ] - ]; + // Return empty if no contributors found + return ''; }Or display a message:
if (empty($contributors)) { return '<div class="wedocs-contributors"><p>' . esc_html__('No contributors found.', 'wedocs-plugin') . '</p></div>'; }src/blocks/DocActions/edit.js-199-209 (1)
199-209: SpacingPanel prop mismatch.Based on
CommonControls.js,SpacingPanelexpectspadding/marginandonPaddingChange/onMarginChangeprops, notvalues/onChange. The current usage won't work correctly.🐛 Proposed fix for SpacingPanel usage
<SpacingPanel title={__('Padding', 'wedocs-plugin')} - values={padding} - onChange={(value) => setAttributes({ padding: value })} + padding={padding} + onPaddingChange={(value) => setAttributes({ padding: value })} + showMargin={false} /> <SpacingPanel title={__('Margin', 'wedocs-plugin')} - values={margin} - onChange={(value) => setAttributes({ margin: value })} + margin={margin} + onMarginChange={(value) => setAttributes({ margin: value })} + showPadding={false} />src/blocks/LastUpdated/edit.js-39-50 (1)
39-50: Potential null reference when accessing padding/margin properties.Same issue as in DocActions - if
paddingormarginareundefined, accessing properties like.topwill throw.🐛 Proposed fix with defensive defaults
const blockProps = useBlockProps({ className: 'wp-block-wedocs-last-updated', 'data-block-id': blockId, style: { color: textColor, fontSize, fontWeight, textAlign, - padding: `${padding.top} ${padding.right} ${padding.bottom} ${padding.left}`, - margin: `${margin.top} ${margin.right} ${margin.bottom} ${margin.left}` + padding: padding ? `${padding.top} ${padding.right} ${padding.bottom} ${padding.left}` : undefined, + margin: margin ? `${margin.top} ${margin.right} ${margin.bottom} ${margin.left}` : undefined } });src/blocks/LastUpdated/edit.js-167-177 (1)
167-177: SpacingPanel prop mismatch.Same issue as DocActions -
SpacingPanelexpectspadding/marginandonPaddingChange/onMarginChange, notvalues/onChange.🐛 Proposed fix for SpacingPanel usage
<SpacingPanel title={__('Padding', 'wedocs-plugin')} - values={padding} - onChange={(value) => setAttributes({ padding: value })} + padding={padding} + onPaddingChange={(value) => setAttributes({ padding: value })} + showMargin={false} /> <SpacingPanel title={__('Margin', 'wedocs-plugin')} - values={margin} - onChange={(value) => setAttributes({ margin: value })} + margin={margin} + onMarginChange={(value) => setAttributes({ margin: value })} + showPadding={false} />src/blocks/Contributors/edit.js-287-295 (1)
287-295: Incorrect Gravatar hash implementation.
btoa()is Base64 encoding, not MD5 hashing. Gravatar requires an MD5 hash of the lowercase, trimmed email. This code will produce invalid avatar URLs.🐛 Proposed fix
Since computing MD5 in the browser requires a library (or SubtleCrypto for SHA, not MD5), consider removing this fallback or using a different approach:
-// If no avatar URL and we have an email, use Gravatar -if (!avatarUrl && contributor.email) { - try { - const emailHash = btoa(contributor.email.toLowerCase().trim()); - avatarUrl = `https://www.gravatar.com/avatar/${emailHash}?s=48&d=identicon`; - } catch (e) { - avatarUrl = avatar1; - } -} +// Fallback to demo avatar if no avatar URL available +// Note: Gravatar requires MD5 hash which needs additional library +if (!avatarUrl) { + avatarUrl = avatar1; +}Alternatively, if Gravatar support is required, use a library like
crypto-jsor fetch the avatar URL from the server.src/blocks/DocActions/edit.js-46-53 (1)
46-53: Potential null reference when accessing padding/margin properties.If
paddingormarginattributes areundefinedornull, accessing.top,.right, etc. will throw a runtime error. Add defensive checks or ensure defaults are always provided.🐛 Proposed fix with defensive defaults
const blockProps = useBlockProps({ className: 'wp-block-wedocs-doc-actions', 'data-block-id': blockId, style: { - padding: `${padding.top} ${padding.right} ${padding.bottom} ${padding.left}`, - margin: `${margin.top} ${margin.right} ${margin.bottom} ${margin.left}` + padding: padding ? `${padding.top} ${padding.right} ${padding.bottom} ${padding.left}` : undefined, + margin: margin ? `${margin.top} ${margin.right} ${margin.bottom} ${margin.left}` : undefined } });src/blocks/Contributors/edit.js-39-55 (1)
39-55: Potential null reference when accessing padding/margin/borderWidth properties.Multiple object properties are accessed without null checks. If any of these attributes are
undefined, the component will crash.🐛 Proposed fix with optional chaining
const blockProps = useBlockProps({ className: `wedocs-contributors ${attributes.additionalCssClass}`, style: { backgroundColor: attributes.backgroundType === 'classic' ? attributes.backgroundColor : undefined, backgroundImage: attributes.backgroundType === 'gradient' ? attributes.backgroundGradient : (attributes.backgroundImage?.url ? `url(${attributes.backgroundImage.url})` : undefined), - padding: `${attributes.padding.top} ${attributes.padding.right} ${attributes.padding.bottom} ${attributes.padding.left}`, - margin: `${attributes.margin.top} ${attributes.margin.right} ${attributes.margin.bottom} ${attributes.margin.left}`, + padding: attributes.padding ? `${attributes.padding.top} ${attributes.padding.right} ${attributes.padding.bottom} ${attributes.padding.left}` : undefined, + margin: attributes.margin ? `${attributes.margin.top} ${attributes.margin.right} ${attributes.margin.bottom} ${attributes.margin.left}` : undefined, borderStyle: attributes.borderStyle !== 'none' ? attributes.borderStyle : undefined, borderWidth: attributes.borderStyle !== 'none' ? - `${attributes.borderWidth.top} ${attributes.borderWidth.right} ${attributes.borderWidth.bottom} ${attributes.borderWidth.left}` : undefined, + (attributes.borderWidth ? `${attributes.borderWidth.top} ${attributes.borderWidth.right} ${attributes.borderWidth.bottom} ${attributes.borderWidth.left}` : undefined) : undefined, // ... } });Committable suggestion skipped: line range outside the PR's diff.
src/blocks/commonControls/BorderAndShadowControls.js-21-25 (1)
21-25: Guard missingborderAndShadowControlsto avoid indexing crashes.Same pattern: if
attributes.borderAndShadowControlsisn’t initialized,borderAndShadowControls[device]throws.src/blocks/commonControls/AlignmentControls.js-26-29 (1)
26-29: Guard against missingattributes.alignmentControlsto avoid editor crashes.If
alignmentControlsisn’t initialized in block attributes,alignmentControls[device]will throw.Proposed fix
const AlignmentControls = ({ attributes, setAttributes, device }) => { - const { alignmentControls } = attributes; - const currentAlignment = alignmentControls[device] || {}; + const alignmentControls = attributes?.alignmentControls ?? {}; + const currentAlignment = alignmentControls?.[device] ?? {};src/blocks/commonControls/ColorsControls.js-30-320 (1)
30-320: Use theisTextColor/isBackgroundColor/isLinkColorflags (or remove them).Right now the component always renders all controls, ignoring the flags, which makes the public API misleading.
Example pattern (wrap sections)
- <div className="wedocs-colors-controls"> - {/* Text Color Control */} - <Dropdown ... /> + <div className="wedocs-colors-controls"> + {isTextColor && ( + <> + {/* Text Color Control */} + <Dropdown /* ... */ /> + </> + )} ... - {/* Background Color Control */} - <Dropdown ... /> + {isBackgroundColor && ( + <> + {/* Background Color Control */} + <Dropdown /* ... */ /> + </> + )} ... - {/* Link Color Control */} - <Dropdown ... /> + {isLinkColor && ( + <> + {/* Link Color Control */} + <Dropdown /* ... */ /> + </> + )} </div>src/blocks/commonControls/BackgroundImageControls.js-14-17 (1)
14-17: Fix FocalPointPicker value and add guard for missingbackgroundImageControls.
value={currentBackgroundImage.bgFocalPoint || {}}passes an invalid shape toFocalPointPicker, which requires an object with numericxandyproperties (0–1 range). Also,backgroundImageControls[device]will throw if the attribute isn't defined.Proposed fix
const BackgroundImageControls = ({ attributes, setAttributes, device }) => { - const { backgroundImageControls } = attributes; - const currentBackgroundImage = backgroundImageControls[device] || {}; + const backgroundImageControls = attributes?.backgroundImageControls ?? {}; + const currentBackgroundImage = backgroundImageControls[device] ?? {}; ... <FocalPointPicker ... - value={currentBackgroundImage.bgFocalPoint || {}} + value={currentBackgroundImage.bgFocalPoint ?? { x: 0.5, y: 0.5 }}Also applies to: 110–119
src/blocks/TableOfContents/ColorsControls.js-21-28 (1)
21-28: Add fallback empty arrays for undefined theme colors/gradients.
getSettings()may not returncolorsorgradientsproperties (or they may be undefined), which will cause runtime errors when passed toColorPaletteandGradientPicker. Apply a fallback to empty arrays:const { themeColors, themeGradients } = useSelect((select) => { const editorSettings = select('core/block-editor').getSettings(); return { themeColors: editorSettings.colors || [], themeGradients: editorSettings.gradients || [], }; });The grouped palette format
colors={[{ colors: themeColors, name: '...' }]}is correct and intentional—it's the proper way to render palette groups inColorPalette.Also applies to: 62-82, 125-151, 163-191, 248-310, and
./src/blocks/commonControls/ColorsControls.js(lines 21-28, 62-82, 125-151, 163-191, 248-310)src/blocks/TableOfContents/Inspector.js-126-126 (1)
126-126: Duplicate containerBackgroundColor control.The containerBackgroundColor control appears twice in the Styles panel:
- Line 126: Inside the ListItems component
- Lines 131-135: Directly in the Container Style panel
This creates duplicate UI controls for the same attribute. Based on the context, it appears the ListItems component was intended to handle this control, so the duplicate at lines 131-135 should be removed.
🔧 Proposed fix
<InspectorControls group='styles'> <ListItems attributes={attributes} setAttributes={setAttributes} /> <PanelBody title={__('Container Style', 'wedocs')} initialOpen={false}> <h4>{__('TOC Container', 'wedocs')}</h4> - {/* <div style={{ marginBottom: '20px' }}> */} - <label>{__('Background Color', 'wedocs')}</label> - <ColorPicker - color={containerBackgroundColor} - onChange={(value) => setAttributes({ containerBackgroundColor: value })} - /> - {/* </div> */} - <UnitControl label={__('Width', 'wedocs')}Also applies to: 131-135
src/blocks/TableOfContents/controls/ListItems.js-12-18 (1)
12-18: Duplicate containerBackgroundColor control.The containerBackgroundColor control is also present in Inspector.js (lines 131-135) within the Container Style panel. This creates two separate UI controls for the same attribute, which can confuse users and lead to maintenance issues.
Consider removing this duplicate control from either ListItems.js or Inspector.js to maintain a single source of truth for this setting.
src/blocks/TableOfContents/view.js-63-73 (1)
63-73: Race condition: TOC generated before heading IDs are assigned.The TOC HTML is generated and inserted (lines 64-66) before heading IDs are assigned (lines 69-73). This means the TOC links reference the
heading.idwhich may be undefined at generation time, causing broken links.Move the ID assignment before TOC generation.
🐛 Proposed fix
function generateTOC(tocBlock) { const supportedHeadings = getSupportedHeadings(tocBlock); const showHierarchy = tocBlock.dataset.showHierarchy !== 'false'; const showNumbering = tocBlock.dataset.showNumbering === 'true'; // Find all headings in the page content const contentArea = document.querySelector('.entry-content, .post-content, main, .content') || document.body; const headingSelector = supportedHeadings.map(h => h.toLowerCase()).join(', '); const headings = contentArea.querySelectorAll(headingSelector); if (headings.length === 0) { const tocContent = tocBlock.querySelector('.toc-content'); tocContent.innerHTML = '<div class="toc-empty-state"><p>No headings found in the content.</p></div>'; return; } + // Add IDs to headings if they don't have them BEFORE generating TOC + headings.forEach(function(heading, index) { + if (!heading.id) { + heading.id = 'toc-heading-' + index; + } + }); + // Generate TOC HTML const tocHTML = generateTOCHTML(headings, supportedHeadings, showHierarchy, showNumbering); const tocContent = tocBlock.querySelector('.toc-content'); tocContent.innerHTML = tocHTML; - - // Add IDs to headings if they don't have them - headings.forEach(function(heading, index) { - if (!heading.id) { - heading.id = 'toc-heading-' + index; - } - }); }src/blocks/SocialShare/render.php-24-55 (1)
24-55: Fix potential PHP warnings from undefined array keys in object attributes.
Direct indexing like$enabled_platforms['facebook'](Line 152) and$container_padding['top'](Line 92) will warn if the attribute object is missing those keys (common with partial updates / old content). Normalize with defaults viawp_parse_args()and use??when reading keys.Proposed normalization pattern
-$enabled_platforms = $attributes['enabledPlatforms'] ?? [ - 'facebook' => true, - 'twitter' => true, - 'linkedin' => true, - 'pinterest' => true -]; +$enabled_platforms = wp_parse_args( + is_array($attributes['enabledPlatforms'] ?? null) ? $attributes['enabledPlatforms'] : [], + [ + 'facebook' => true, + 'twitter' => true, + 'linkedin' => true, + 'pinterest' => true, + ] +); -$container_padding = $attributes['containerPadding'] ?? ['top' => '20px', 'right' => '0px', 'bottom' => '20px', 'left' => '0px']; -$container_margin = $attributes['containerMargin'] ?? ['top' => '20px', 'right' => '0px', 'bottom' => '20px', 'left' => '0px']; +$container_padding = wp_parse_args( + is_array($attributes['containerPadding'] ?? null) ? $attributes['containerPadding'] : [], + ['top' => '20px', 'right' => '0px', 'bottom' => '20px', 'left' => '0px'] +); +$container_margin = wp_parse_args( + is_array($attributes['containerMargin'] ?? null) ? $attributes['containerMargin'] : [], + ['top' => '20px', 'right' => '0px', 'bottom' => '20px', 'left' => '0px'] +);Also applies to: 92-97, 152-230
src/blocks/SocialShare/render.php-155-229 (1)
155-229: Addaria-labelfor icon-only share links (title isn’t sufficient).
WhenbuttonStyleisicon-only, the<a>contains only an SVG. Add anaria-label="Share on Facebook"etc. to make the control accessible to screen readers.Example change (apply to each platform link)
<a href="<?php echo esc_url($facebook_url); ?>" target="_blank" rel="noopener noreferrer" + aria-label="<?php echo esc_attr(sprintf(__('Share on %s', 'wedocs'), $platform_names['facebook'])); ?>" class="wedocs-social-share-btn wedocs-social-share-facebook <?php echo esc_attr($size_class . ' ' . $shape_class); ?>"src/blocks/SocialShare/view.js-39-54 (1)
39-54: Add consent check before emitting analytics events.The code only verifies
typeof gtag !== 'undefined'(line 45) but does not check user consent. This sends tracking events regardless of whether the user has opted in, which violates GDPR/CCPA requirements. Even if GA is loaded after consent is obtained, this implementation lacks a compliance gate.The same pattern exists in
src/blocks/HelpfulModal/view.js(line 42).Wrap both gtag calls with a consent check—e.g.,
window.gtag_is_restrictedor your site's consent object—before emitting events.src/blocks/SocialShare/render.php-14-16 (1)
14-16: Use block context for post ID; rely less on global post state.
get_post_type()(line 14) andget_the_ID()(line 58) depend on the global post, which may be unavailable or incorrect in editor preview, REST API, or non-standard rendering contexts. Add"usesContext": ["postId"]to block.json, then update render.php to prefer$block->context['postId']with a fallback toget_the_ID():Required changes
Update block.json:
{ "name": "wedocs/social-share", + "usesContext": ["postId"], "render": "file:./render.php", "attributes": {Update render.php (lines 13–58):
-// Check if this is a docs post type -if (get_post_type() !== 'docs') { - return ''; -} - -// Start output buffering -ob_start(); - -// Extract attributes with defaults +// Resolve post ID from block context or global state +$post_id = 0; +if (isset($block) && is_object($block) && !empty($block->context['postId'])) { + $post_id = (int) $block->context['postId']; +} else { + $post_id = (int) get_the_ID(); +} + +// Validate post exists and is docs type +if (!$post_id || get_post_type($post_id) !== 'docs') { + return ''; +} + +// Start output buffering +ob_start(); + +// Extract attributes with defaults $label_text = $attributes['labelText'] ?? 'Share This Article:'; - -// Get current post data -$post_id = get_the_ID(); $post_title = get_the_title($post_id);Also applies to: HelpfulFeedback/render.php (lines 13–22)
src/blocks/SocialShare/edit.js-489-547 (1)
489-547: Fix 'text-only' button style rendering and add i18n for platform names.Two issues in the button preview rendering:
- When
buttonStyleis'text-only', the icon SVG is still rendered (lines 498, 513, 528, 543). For text-only style, icons should be hidden.- Platform names ('Facebook', 'Twitter', 'LinkedIn', 'Pinterest') are hardcoded and not internationalized.
🐛 Proposed fixes
{enabledPlatforms.facebook && ( <button className={`wedocs-social-share-btn wedocs-social-share-facebook ${getButtonSizeClass()} ${getButtonShapeClass()}`} style={{ backgroundColor: getButtonColor('facebook'), color: buttonIconColor }} disabled > - {socialIcons.facebook} - {buttonStyle === 'icon-text' && <span>Facebook</span>} - {buttonStyle === 'text-only' && <span>Facebook</span>} + {buttonStyle !== 'text-only' && socialIcons.facebook} + {buttonStyle !== 'icon-only' && <span>{__('Facebook', 'wedocs')}</span>} </button> )} {enabledPlatforms.twitter && ( <button className={`wedocs-social-share-btn wedocs-social-share-twitter ${getButtonSizeClass()} ${getButtonShapeClass()}`} style={{ backgroundColor: getButtonColor('twitter'), color: buttonIconColor }} disabled > - {socialIcons.twitter} - {buttonStyle === 'icon-text' && <span>Twitter</span>} - {buttonStyle === 'text-only' && <span>Twitter</span>} + {buttonStyle !== 'text-only' && socialIcons.twitter} + {buttonStyle !== 'icon-only' && <span>{__('Twitter', 'wedocs')}</span>} </button> )} {enabledPlatforms.linkedin && ( <button className={`wedocs-social-share-btn wedocs-social-share-linkedin ${getButtonSizeClass()} ${getButtonShapeClass()}`} style={{ backgroundColor: getButtonColor('linkedin'), color: buttonIconColor }} disabled > - {socialIcons.linkedin} - {buttonStyle === 'icon-text' && <span>LinkedIn</span>} - {buttonStyle === 'text-only' && <span>LinkedIn</span>} + {buttonStyle !== 'text-only' && socialIcons.linkedin} + {buttonStyle !== 'icon-only' && <span>{__('LinkedIn', 'wedocs')}</span>} </button> )} {enabledPlatforms.pinterest && ( <button className={`wedocs-social-share-btn wedocs-social-share-pinterest ${getButtonSizeClass()} ${getButtonShapeClass()}`} style={{ backgroundColor: getButtonColor('pinterest'), color: buttonIconColor }} disabled > - {socialIcons.pinterest} - {buttonStyle === 'icon-text' && <span>Pinterest</span>} - {buttonStyle === 'text-only' && <span>Pinterest</span>} + {buttonStyle !== 'text-only' && socialIcons.pinterest} + {buttonStyle !== 'icon-only' && <span>{__('Pinterest', 'wedocs')}</span>} </button> )}
| // wp_register_style( | ||
| // 'wedocs-block-style', | ||
| // $assets_url . '/build/style-block.css', | ||
| // $block_dependencies['version'] | ||
| // ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical: Attempting to enqueue unregistered stylesheet.
The wedocs-block-style registration is commented out here, but lines 129 and 155 still attempt to enqueue this style. WordPress will trigger errors when trying to enqueue an unregistered stylesheet, and the style won't load.
🐛 Proposed fix
Either uncomment the registration if the style is needed:
- // wp_register_style(
- // 'wedocs-block-style',
- // $assets_url . '/build/style-block.css',
- // $block_dependencies['version']
- // );
+ wp_register_style(
+ 'wedocs-block-style',
+ $assets_url . '/build/style-block.css',
+ array(),
+ $block_dependencies['version']
+ );Or remove the enqueue calls if the style is no longer needed:
- wp_enqueue_style( 'wedocs-block-style' );(at line 129)
- wp_enqueue_style( 'wedocs-block-style' );(at line 155)
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // wp_register_style( | |
| // 'wedocs-block-style', | |
| // $assets_url . '/build/style-block.css', | |
| // $block_dependencies['version'] | |
| // ); | |
| wp_register_style( | |
| 'wedocs-block-style', | |
| $assets_url . '/build/style-block.css', | |
| array(), | |
| $block_dependencies['version'] | |
| ); |
🤖 Prompt for AI Agents
In @includes/Assets.php around lines 85 - 89, The code attempts to
wp_enqueue_style('wedocs-block-style') but the wp_register_style call for
'wedocs-block-style' is commented out, causing enqueue errors; either
restore/uncomment the wp_register_style call that registers 'wedocs-block-style'
(ensure correct handle, URL $assets_url . '/build/style-block.css' and
dependency/version args match $block_dependencies) or remove the
wp_enqueue_style('wedocs-block-style') calls so you don't enqueue an
unregistered handle; update the occurrences where 'wedocs-block-style' is
enqueued to match the chosen fix.
| // { | ||
| // "extra": { | ||
| // "mozart": { | ||
| // "dep_namespace": "WPMUDEV\\PluginTest\\Vendor\\", | ||
| // "dep_directory": "/vendor-prefixed/", | ||
| // "classmap_directory": "/classes/", | ||
| // "classmap_prefix": "WPMUDEV_PluginTest_", | ||
| // "packages": [ | ||
| // "google/apiclient" | ||
| // ], | ||
| // "delete_vendor_directories": false | ||
| // } | ||
| // } | ||
| // } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
git ls-files | head -20Repository: weDevsOfficial/wedocs-plugin
Length of output: 519
🏁 Script executed:
cat readme.mdRepository: weDevsOfficial/wedocs-plugin
Length of output: 1969
🏁 Script executed:
find . -name "composer.json" -o -name "composer.lock" | head -5Repository: weDevsOfficial/wedocs-plugin
Length of output: 103
🏁 Script executed:
rg "PluginTest|google/apiclient|Mozart" --max-count=20Repository: weDevsOfficial/wedocs-plugin
Length of output: 241
🏁 Script executed:
cat composer.jsonRepository: weDevsOfficial/wedocs-plugin
Length of output: 634
🏁 Script executed:
rg "wedocs|weDocs|namespace|WPMUDEV" --type php -A 2 | head -50Repository: weDevsOfficial/wedocs-plugin
Length of output: 2076
🏁 Script executed:
rg '"google/apiclient"' .Repository: weDevsOfficial/wedocs-plugin
Length of output: 113
Remove the Mozart configuration block from readme.md—it contains incorrect namespace and unrelated dependency references.
The commented Mozart configuration in lines 26-39 references "WPMUDEV\PluginTest\Vendor\" and "WPMUDEV_PluginTest_", which do not match the actual plugin context. The plugin uses the namespace "WeDevs\WeDocs\" (defined in composer.json), not PluginTest. Additionally, google/apiclient is not a dependency of this plugin and does not appear anywhere in composer.json.
This appears to be placeholder or template code that was inadvertently left in the readme. Either remove this configuration block or, if intentional documentation is needed, clarify its purpose and ensure values match the actual plugin configuration.
🤖 Prompt for AI Agents
In @readme.md around lines 26 - 39, Remove the commented Mozart configuration
block that references "WPMUDEV\\PluginTest\\Vendor\\", "WPMUDEV_PluginTest_",
and the unrelated package "google/apiclient" from readme.md; replace it only if
you intend to document real configuration, in which case update the namespace
and prefix to the actual plugin values (e.g., "WeDevs\\WeDocs\\" per
composer.json) and list only real dependencies, or simply delete the entire
commented block to avoid misleading/template content.
| function generate_responsive_css($block_id, $controls, $device_suffix = '') { | ||
| $css = ''; | ||
|
|
||
| if (empty($controls)) { | ||
| return $css; | ||
| } | ||
|
|
||
| $selector = '.' . $block_id . $device_suffix; | ||
| $rules = []; | ||
|
|
||
| // Typography | ||
| if (isset($controls['fontFamily']) && $controls['fontFamily']) { | ||
| $rules[] = 'font-family: ' . esc_attr($controls['fontFamily']); | ||
| } | ||
| if (isset($controls['fontSize']) && $controls['fontSize']) { | ||
| $rules[] = 'font-size: ' . esc_attr($controls['fontSize']); | ||
| } | ||
| if (isset($controls['fontAppearance']['fontWeight']) && $controls['fontAppearance']['fontWeight']) { | ||
| $rules[] = 'font-weight: ' . esc_attr($controls['fontAppearance']['fontWeight']); | ||
| } | ||
| if (isset($controls['fontAppearance']['fontStyle']) && $controls['fontAppearance']['fontStyle']) { | ||
| $rules[] = 'font-style: ' . esc_attr($controls['fontAppearance']['fontStyle']); | ||
| } | ||
| if (isset($controls['lineHeight']) && $controls['lineHeight']) { | ||
| $rules[] = 'line-height: ' . esc_attr($controls['lineHeight']); | ||
| } | ||
| if (isset($controls['letterSpacing']) && $controls['letterSpacing']) { | ||
| $rules[] = 'letter-spacing: ' . esc_attr($controls['letterSpacing']); | ||
| } | ||
| if (isset($controls['textDecoration']) && $controls['textDecoration']) { | ||
| $rules[] = 'text-decoration: ' . esc_attr($controls['textDecoration']); | ||
| } | ||
| if (isset($controls['textTransform']) && $controls['textTransform']) { | ||
| $rules[] = 'text-transform: ' . esc_attr($controls['textTransform']); | ||
| } | ||
|
|
||
| // Colors | ||
| if (isset($controls['textColor']) && $controls['textColor']) { | ||
| $rules[] = 'color: ' . esc_attr($controls['textColor']); | ||
| } | ||
| if (isset($controls['backgroundColor']) && $controls['backgroundColor']) { | ||
| $rules[] = 'background-color: ' . esc_attr($controls['backgroundColor']); | ||
| } | ||
|
|
||
| // Dimensions | ||
| if (isset($controls['width']) && $controls['width']) { | ||
| $rules[] = 'width: ' . esc_attr($controls['width']); | ||
| } | ||
| if (isset($controls['minWidth']) && $controls['minWidth']) { | ||
| $rules[] = 'min-width: ' . esc_attr($controls['minWidth']); | ||
| } | ||
| if (isset($controls['maxWidth']) && $controls['maxWidth']) { | ||
| $rules[] = 'max-width: ' . esc_attr($controls['maxWidth']); | ||
| } | ||
| if (isset($controls['height']) && $controls['height']) { | ||
| $rules[] = 'height: ' . esc_attr($controls['height']); | ||
| } | ||
| if (isset($controls['minHeight']) && $controls['minHeight']) { | ||
| $rules[] = 'min-height: ' . esc_attr($controls['minHeight']); | ||
| } | ||
| if (isset($controls['maxHeight']) && $controls['maxHeight']) { | ||
| $rules[] = 'max-height: ' . esc_attr($controls['maxHeight']); | ||
| } | ||
|
|
||
| // Padding | ||
| if (isset($controls['padding']) && is_array($controls['padding'])) { | ||
| $padding_values = []; | ||
| $padding_values[] = $controls['padding']['top'] ?? '0'; | ||
| $padding_values[] = $controls['padding']['right'] ?? '0'; | ||
| $padding_values[] = $controls['padding']['bottom'] ?? '0'; | ||
| $padding_values[] = $controls['padding']['left'] ?? '0'; | ||
| $rules[] = 'padding: ' . implode(' ', $padding_values); | ||
| } | ||
|
|
||
| // Margin | ||
| if (isset($controls['margin']) && is_array($controls['margin'])) { | ||
| $margin_values = []; | ||
| $margin_values[] = $controls['margin']['top'] ?? '0'; | ||
| $margin_values[] = $controls['margin']['right'] ?? '0'; | ||
| $margin_values[] = $controls['margin']['bottom'] ?? '0'; | ||
| $margin_values[] = $controls['margin']['left'] ?? '0'; | ||
| $rules[] = 'margin: ' . implode(' ', $margin_values); | ||
| } | ||
|
|
||
| // Alignment | ||
| if (isset($controls['textAlign']) && $controls['textAlign']) { | ||
| $rules[] = 'text-align: ' . esc_attr($controls['textAlign']); | ||
| } | ||
| if (isset($controls['alignItems']) && $controls['alignItems']) { | ||
| $rules[] = 'align-items: ' . esc_attr($controls['alignItems']); | ||
| } | ||
| if (isset($controls['justifyContent']) && $controls['justifyContent']) { | ||
| $rules[] = 'justify-content: ' . esc_attr($controls['justifyContent']); | ||
| } | ||
| if (isset($controls['direction']) && $controls['direction']) { | ||
| $rules[] = 'flex-direction: ' . esc_attr($controls['direction']); | ||
| } | ||
|
|
||
| // Border | ||
| if (isset($controls['border']) && is_array($controls['border'])) { | ||
| $border = $controls['border']; | ||
| if (isset($border['width']) && isset($border['style']) && isset($border['color'])) { | ||
| $rules[] = 'border: ' . esc_attr($border['width']) . ' ' . esc_attr($border['style']) . ' ' . esc_attr($border['color']); | ||
| } | ||
| } | ||
|
|
||
| // Border Radius | ||
| if (isset($controls['borderRadius']) && is_array($controls['borderRadius'])) { | ||
| $radius = $controls['borderRadius']; | ||
| $radius_values = []; | ||
| $radius_values[] = $radius['topLeft'] ?? '0'; | ||
| $radius_values[] = $radius['topRight'] ?? '0'; | ||
| $radius_values[] = $radius['bottomRight'] ?? '0'; | ||
| $radius_values[] = $radius['bottomLeft'] ?? '0'; | ||
| $rules[] = 'border-radius: ' . implode(' ', $radius_values); | ||
| } | ||
|
|
||
| // Box Shadow | ||
| if (isset($controls['boxShadow']) && is_array($controls['boxShadow']) && isset($controls['boxShadow']['x'])) { | ||
| $shadow = $controls['boxShadow']; | ||
| $shadow_value = ''; | ||
| if (isset($shadow['inset']) && $shadow['inset']) { | ||
| $shadow_value .= 'inset '; | ||
| } | ||
| $shadow_value .= esc_attr($shadow['x']) . ' '; | ||
| $shadow_value .= esc_attr($shadow['y']) . ' '; | ||
| $shadow_value .= esc_attr($shadow['blur']) . ' '; | ||
| $shadow_value .= esc_attr($shadow['spread']) . ' '; | ||
| $shadow_value .= esc_attr($shadow['color']); | ||
| $rules[] = 'box-shadow: ' . $shadow_value; | ||
| } | ||
|
|
||
| // Background Image | ||
| if (isset($controls['bgUrl']) && $controls['bgUrl']) { | ||
| $rules[] = 'background-image: url(' . esc_url($controls['bgUrl']) . ')'; | ||
|
|
||
| if (isset($controls['bgSize']) && $controls['bgSize']) { | ||
| $rules[] = 'background-size: ' . esc_attr($controls['bgSize']); | ||
| } | ||
|
|
||
| if (isset($controls['bgFocalPoint']) && is_array($controls['bgFocalPoint'])) { | ||
| $focal = $controls['bgFocalPoint']; | ||
| $rules[] = 'background-position: ' . ($focal['x'] * 100) . '% ' . ($focal['y'] * 100) . '%'; | ||
| } | ||
|
|
||
| if (isset($controls['bgRepeat'])) { | ||
| $rules[] = 'background-repeat: ' . ($controls['bgRepeat'] ? 'repeat' : 'no-repeat'); | ||
| } | ||
|
|
||
| if (isset($controls['bgFixed']) && $controls['bgFixed']) { | ||
| $rules[] = 'background-attachment: fixed'; | ||
| } | ||
| } | ||
|
|
||
| // Display | ||
| if (isset($controls['display']) && $controls['display']) { | ||
| $rules[] = 'display: ' . esc_attr($controls['display']); | ||
| } | ||
| if (isset($controls['visibility']) && $controls['visibility']) { | ||
| $rules[] = 'visibility: ' . esc_attr($controls['visibility']); | ||
| } | ||
| if (isset($controls['overflow']) && $controls['overflow']) { | ||
| $rules[] = 'overflow: ' . esc_attr($controls['overflow']); | ||
| } | ||
| if (isset($controls['zIndex']) && $controls['zIndex']) { | ||
| $rules[] = 'z-index: ' . esc_attr($controls['zIndex']); | ||
| } | ||
|
|
||
| if (!empty($rules)) { | ||
| $css .= $selector . ' { ' . implode('; ', $rules) . '; }'; | ||
| } | ||
|
|
||
| return $css; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical: Function redefinition will cause fatal error when block is rendered multiple times.
The generate_responsive_css() function is defined inside render_wedocs_advance_contributors_block(). On the second render of this block on the same page, PHP will throw a fatal error: "Cannot redeclare generate_responsive_css()".
🐛 Proposed fix - use conditional function declaration or static closure
+// Define helper function outside the render function, or use a static check
+if (!function_exists('generate_wedocs_responsive_css')) {
+ function generate_wedocs_responsive_css($block_id, $controls, $device_suffix = '') {
+ // ... existing implementation ...
+ }
+}
function render_wedocs_advance_contributors_block($attributes, $content, $block) {
// ... existing code ...
- // Helper function to generate responsive CSS
- function generate_responsive_css($block_id, $controls, $device_suffix = '') {
- // ... implementation ...
- }
// Generate CSS for all devices
// ... use generate_wedocs_responsive_css instead of generate_responsive_css ...Alternatively, use a closure:
$generate_responsive_css = function($block_id, $controls, $device_suffix = '') {
// ... implementation ...
};
// Then call it as:
$responsive_css .= $generate_responsive_css($block_id, $device_controls);Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In @src/blocks/AdvanceContributors/render.php around lines 116 - 289, The nested
declaration of generate_responsive_css inside
render_wedocs_advance_contributors_block will cause a "Cannot redeclare
generate_responsive_css()" fatal error on multiple renders; fix by either moving
generate_responsive_css out of render_wedocs_advance_contributors_block into
file scope (so it’s declared once) or wrap its declaration in if
(!function_exists('generate_responsive_css')) { ... } or convert it to a local
closure ($generate_responsive_css = function(...) { ... }) and update all calls
inside render_wedocs_advance_contributors_block to use the closure variable.
|
|
||
| return $output; | ||
| } | ||
| render_wedocs_advance_contributors_block( $attributes, $content, $block ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Incorrect function invocation outside render context.
This line calls render_wedocs_advance_contributors_block() at the file's top level, but $attributes, $content, and $block are not defined in this scope. This will cause undefined variable warnings/errors.
For WordPress block rendering, the function should only be registered as a callback in block.json (which is already done via "render": "file:./render.php"), and WordPress will invoke it with the proper parameters.
🐛 Remove the erroneous invocation
return $output;
}
-render_wedocs_advance_contributors_block( $attributes, $content, $block );📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| render_wedocs_advance_contributors_block( $attributes, $content, $block ); |
🤖 Prompt for AI Agents
In @src/blocks/AdvanceContributors/render.php at line 503, The top-level call to
render_wedocs_advance_contributors_block($attributes, $content, $block) is
invalid because $attributes, $content and $block are undefined in this scope;
remove that invocation so the file only defines the function and lets WordPress
invoke it via the block.json "render": "file:./render.php" callback; keep the
render_wedocs_advance_contributors_block function definition intact and ensure
no other top-level calls pass undefined variables.
| const ColorsControls = ({ attributes, setAttributes, isTextColor = false, isBackgroundColor = false, isLinkColor = false }) => { | ||
| const { colorsControls } = attributes; | ||
| const currentColors = colorsControls[device] || {}; | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix broken props contract (device is undefined) and default colorsControls.
As written, this component can’t work: it indexes colorsControls[device] but device is not provided, and colorsControls may be undefined.
Proposed fix
-const ColorsControls = ({ attributes, setAttributes, isTextColor = false, isBackgroundColor = false, isLinkColor = false }) => {
- const { colorsControls } = attributes;
- const currentColors = colorsControls[device] || {};
+const ColorsControls = ({
+ attributes,
+ setAttributes,
+ device,
+ isTextColor = true,
+ isBackgroundColor = true,
+ isLinkColor = true,
+}) => {
+ const colorsControls = attributes?.colorsControls ?? {};
+ const currentColors = colorsControls?.[device] ?? {};🤖 Prompt for AI Agents
In @src/blocks/commonControls/ColorsControls.js around lines 17 - 20,
ColorsControls currently reads device and colorsControls incorrectly: ensure you
get device from props/attributes (e.g., read device from attributes or add a
device prop to the component) and default colorsControls to an object before
indexing; update the ColorsControls signature/usage so device is defined (or
default to a known key like "desktop") and compute currentColors as
(attributes.colorsControls || {})[device || 'desktop'] to avoid undefined errors
and preserve the original props contract with setAttributes remaining unchanged.
| "supports": { | ||
| "html": false, | ||
| "anchor": true, | ||
| "align": ["wide", "full"], | ||
| "color": { | ||
| "background": true, | ||
| "text": true, | ||
| "link": true, | ||
| "__experimentalDefaultControls": { | ||
| "background": true, | ||
| "text": true | ||
| } | ||
| }, | ||
| "typography": { | ||
| "fontSize": true, | ||
| "lineHeight": true, | ||
| "__experimentalFontFamily": true, | ||
| "__experimentalFontWeight": true, | ||
| "__experimentalDefaultControls": { | ||
| "fontSize": true | ||
| } | ||
| }, | ||
| "spacing": { | ||
| "margin": true, | ||
| "padding": true, | ||
| "blockGap": true, | ||
| "__experimentalDefaultControls": { | ||
| "padding": true, | ||
| "margin": true | ||
| } | ||
| }, | ||
| "__experimentalBorder": { | ||
| "color": true, | ||
| "radius": true, | ||
| "style": true, | ||
| "width": true, | ||
| "__experimentalDefaultControls": { | ||
| "color": true, | ||
| "radius": true | ||
| } | ||
| } | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Duplicate supports key will cause configuration loss.
The supports object is defined twice in this JSON file (lines 10-51 and lines 282-332). JSON does not allow duplicate keys—the second declaration overwrites the first. This causes the following settings to be lost:
anchor: truealign: ["wide", "full"]spacing.blockGap: true
Merge both supports blocks into a single declaration to retain all configuration.
🔧 Suggested fix: Merge supports blocks
Remove the first supports block (lines 10-51) and update the second block (starting at line 282) to include the missing properties:
"supports": {
"html": false,
+ "anchor": true,
+ "align": ["wide", "full"],
"color": {
"gradients": true,
"link": true,
"__experimentalDefaultControls": {
"background": true,
"text": true,
"link": true
}
},
...
"spacing": {
"padding": true,
"margin": true,
+ "blockGap": true,
"__experimentalDefaultControls": {
"padding": true,
"margin": true
}
},
...
},Also applies to: 282-332
🧰 Tools
🪛 Biome (2.1.2)
[error] 10-10: The key supports was already declared.
This where a duplicated key was declared again.
If a key is defined multiple times, only the last definition takes effect. Previous definitions are ignored.
(lint/suspicious/noDuplicateObjectKeys)
🤖 Prompt for AI Agents
In @src/blocks/HelpfulModal/block.json around lines 10 - 51, You have two
duplicate "supports" objects in block.json; remove the redundant one and merge
their properties into a single "supports" declaration so nothing is overwritten:
ensure the final supports object includes anchor: true, align: ["wide","full"],
spacing.blockGap: true plus all color, typography, spacing, and
__experimentalBorder entries from both blocks so no settings are lost.
|
|
||
|
|
||
|
No newline at end of file |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing save function implementation.
This file is empty but should export a save function that returns null for server-side rendered blocks. Without this, the block registration will fail or behave unexpectedly.
🔧 Required implementation
+/**
+ * Save function for the Need More Help - Contact Modal block.
+ * Returns null because this block uses server-side rendering via render.php.
+ *
+ * @return {null} Null - rendering handled server-side.
+ */
+export default function Save() {
+ return null;
+}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| /** | |
| * Save function for the Need More Help - Contact Modal block. | |
| * Returns null because this block uses server-side rendering via render.php. | |
| * | |
| * @return {null} Null - rendering handled server-side. | |
| */ | |
| export default function Save() { | |
| return null; | |
| } |
🤖 Prompt for AI Agents
In @src/blocks/HelpfulModal/save.js around lines 1 - 3, The file is missing the
required export for a save function used by server-side rendered blocks; add and
export a save function named save that returns null (i.e., export function
save() { return null; }) so the block registration has the expected save
implementation and won't fail at runtime.
| function wedocs_format_last_updated_date($timestamp, $format) { | ||
| $date = new DateTime(); | ||
| $date->setTimestamp($timestamp); | ||
|
|
||
| switch ($format) { | ||
| case 'MM/DD/YYYY': | ||
| return $date->format('m/d/Y'); | ||
| case 'DD/MM/YYYY': | ||
| return $date->format('d/m/Y'); | ||
| case 'YYYY-MM-DD': | ||
| return $date->format('Y-m-d'); | ||
| case 'Month DD, YYYY': | ||
| return $date->format('F j, Y'); | ||
| case 'DD Month YYYY': | ||
| return $date->format('j F Y'); | ||
| case 'relative': | ||
| return human_time_diff($timestamp, current_time('timestamp')) . ' ago'; | ||
| default: | ||
| return $date->format('m/d/Y'); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fatal error: Function redefinition on multiple block renders.
The wedocs_format_last_updated_date function is defined inside the render callback without a function_exists guard. If this block is used multiple times on a page, PHP will throw a fatal "Cannot redeclare" error.
🐛 Proposed fix: Move function outside or add existence check
+if (!function_exists('wedocs_format_last_updated_date')) {
// Format date based on selected format
function wedocs_format_last_updated_date($timestamp, $format) {
$date = new DateTime();
$date->setTimestamp($timestamp);
switch ($format) {
case 'MM/DD/YYYY':
return $date->format('m/d/Y');
case 'DD/MM/YYYY':
return $date->format('d/m/Y');
case 'YYYY-MM-DD':
return $date->format('Y-m-d');
case 'Month DD, YYYY':
return $date->format('F j, Y');
case 'DD Month YYYY':
return $date->format('j F Y');
case 'relative':
return human_time_diff($timestamp, current_time('timestamp')) . ' ago';
default:
return $date->format('m/d/Y');
}
}
+}Alternatively, move this function to a shared helper file that's loaded once.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| function wedocs_format_last_updated_date($timestamp, $format) { | |
| $date = new DateTime(); | |
| $date->setTimestamp($timestamp); | |
| switch ($format) { | |
| case 'MM/DD/YYYY': | |
| return $date->format('m/d/Y'); | |
| case 'DD/MM/YYYY': | |
| return $date->format('d/m/Y'); | |
| case 'YYYY-MM-DD': | |
| return $date->format('Y-m-d'); | |
| case 'Month DD, YYYY': | |
| return $date->format('F j, Y'); | |
| case 'DD Month YYYY': | |
| return $date->format('j F Y'); | |
| case 'relative': | |
| return human_time_diff($timestamp, current_time('timestamp')) . ' ago'; | |
| default: | |
| return $date->format('m/d/Y'); | |
| } | |
| } | |
| if (!function_exists('wedocs_format_last_updated_date')) { | |
| function wedocs_format_last_updated_date($timestamp, $format) { | |
| $date = new DateTime(); | |
| $date->setTimestamp($timestamp); | |
| switch ($format) { | |
| case 'MM/DD/YYYY': | |
| return $date->format('m/d/Y'); | |
| case 'DD/MM/YYYY': | |
| return $date->format('d/m/Y'); | |
| case 'YYYY-MM-DD': | |
| return $date->format('Y-m-d'); | |
| case 'Month DD, YYYY': | |
| return $date->format('F j, Y'); | |
| case 'DD Month YYYY': | |
| return $date->format('j F Y'); | |
| case 'relative': | |
| return human_time_diff($timestamp, current_time('timestamp')) . ' ago'; | |
| default: | |
| return $date->format('m/d/Y'); | |
| } | |
| } | |
| } |
🤖 Prompt for AI Agents
In @src/blocks/LastUpdated/render.php around lines 30 - 50, The function
wedocs_format_last_updated_date is being (re)declared inside the block render
callback causing a fatal "Cannot redeclare" error when the block renders
multiple times; fix it by moving the wedocs_format_last_updated_date definition
out of the render callback into a top-level/shared helper loaded once (or wrap
the definition with if (!function_exists('wedocs_format_last_updated_date')) {
... } so it only defines once), and update the render code to call the
now-top-level function instead of declaring it inline.
| const searchWrapperStyles = { | ||
| display: 'flex', | ||
| flexDirection: buttonPosition === 'outside' ? 'row' : 'relative', | ||
| width: searchWidth, | ||
| gap: buttonPosition === 'outside' ? '8px' : '0', | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Invalid CSS value: flexDirection: 'relative' - same issue as save.js.
This matches the bug in save.js. flexDirection doesn't accept 'relative' as a value.
Proposed fix
const searchWrapperStyles = {
display: 'flex',
- flexDirection: buttonPosition === 'outside' ? 'row' : 'relative',
+ flexDirection: 'row',
+ position: buttonPosition === 'inside' ? 'relative' : 'static',
width: searchWidth,
gap: buttonPosition === 'outside' ? '8px' : '0',
};📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const searchWrapperStyles = { | |
| display: 'flex', | |
| flexDirection: buttonPosition === 'outside' ? 'row' : 'relative', | |
| width: searchWidth, | |
| gap: buttonPosition === 'outside' ? '8px' : '0', | |
| }; | |
| const searchWrapperStyles = { | |
| display: 'flex', | |
| flexDirection: 'row', | |
| position: buttonPosition === 'inside' ? 'relative' : 'static', | |
| width: searchWidth, | |
| gap: buttonPosition === 'outside' ? '8px' : '0', | |
| }; |
🤖 Prompt for AI Agents
In @src/blocks/Search/edit.js around lines 51 - 56, searchWrapperStyles uses an
invalid CSS value 'relative' for flexDirection; update the ternary in
searchWrapperStyles (and any matching logic as in save.js) so that when
buttonPosition !== 'outside' it returns a valid flexDirection (e.g., 'column')
instead of 'relative', keeping the existing gap logic intact.
| const searchWrapperStyles = { | ||
| display: 'flex', | ||
| justifyContent: alignment, | ||
| flexDirection: buttonPosition === 'outside' ? 'row' : 'relative', | ||
| width: searchWidth, | ||
| gap: buttonPosition === 'outside' ? '8px' : '0', | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Invalid CSS value: flexDirection: 'relative' is not valid.
flexDirection accepts row, row-reverse, column, or column-reverse. The value 'relative' appears to be a copy-paste error from position: relative.
Proposed fix
const searchWrapperStyles = {
display: 'flex',
- flexDirection: buttonPosition === 'outside' ? 'row' : 'relative',
+ flexDirection: 'row',
+ position: buttonPosition === 'inside' ? 'relative' : 'static',
width: searchWidth,
gap: buttonPosition === 'outside' ? '8px' : '0',
};📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const searchWrapperStyles = { | |
| display: 'flex', | |
| justifyContent: alignment, | |
| flexDirection: buttonPosition === 'outside' ? 'row' : 'relative', | |
| width: searchWidth, | |
| gap: buttonPosition === 'outside' ? '8px' : '0', | |
| }; | |
| const searchWrapperStyles = { | |
| display: 'flex', | |
| flexDirection: 'row', | |
| position: buttonPosition === 'inside' ? 'relative' : 'static', | |
| width: searchWidth, | |
| gap: buttonPosition === 'outside' ? '8px' : '0', | |
| }; |
🤖 Prompt for AI Agents
In @src/blocks/Search/save.js around lines 36 - 41, The style object
searchWrapperStyles sets flexDirection to an invalid value 'relative' in
src/blocks/Search/save.js; change the conditional to a valid flexDirection
(e.g., buttonPosition === 'outside' ? 'row' : 'column') and, if the intent was
to use CSS positioning, add a separate position: 'relative' property instead of
misusing flexDirection; update the const searchWrapperStyles accordingly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 12
🤖 Fix all issues with AI agents
In @src/blocks/DocActions/block.json:
- Around line 57-79: The default colors risk making filled buttons invisible
because backgroundColor is empty while textColor is "#ffffff"; update the
block.json defaults so backgroundColor is a sensible dark color (e.g., "#333333"
or "#000000") to match textColor, and ensure hoverBackgroundColor, borderColor
and borderRadius defaults remain consistent with the filled style; modify the
"backgroundColor" default value in src/blocks/DocActions/block.json and test the
"buttonStyle" = "filled" rendering to confirm legibility.
In @src/blocks/DocActions/view.js:
- Around line 165-185: The switch block in view.js declares "const level =
parseInt(tag[1])" directly inside case labels (e.g., case 'h1' ... case 'h6')
which causes block-scoping issues; fix by introducing an explicit block for the
header cases (for example wrap the header cases with { ... } and move the const
level declaration inside that block) or alternatively declare level with let
before the switch and assign inside each header case; update the cases handling
'h1'..'h6' accordingly in the same switch in the view() function so the const is
properly scoped.
- Around line 71-95: The ChatGPT opener uses the wrong domain and query param;
in function handleOpenChatGPT replace the URL construction
`https://chatgpt.com/?prompt=${encodedPrompt}` with
`https://chat.openai.com/?q=${encodedPrompt}` so the encodedPrompt is passed
with the documented `q` parameter and opens the correct ChatGPT UI (keep
encodeURIComponent and window.open as-is).
In @src/blocks/FontSizeSwitcher/block.json:
- Around line 48-57: The fontSizeOptions attribute in block.json is declared but
never used—replace the hardcoded size arrays in save.js, edit.js, and view.js
with a reference to the block attribute (fontSizeOptions) so the components read
the configured options instead of fixed values; locate where sizes are currently
defined in the Edit, Save and View components (the arrays around lines mentioned
in the review) and destructure or access attributes.fontSizeOptions (or
props.attributes.fontSizeOptions) and fall back to the block.json default if
undefined, ensuring all three files use the same single source of truth for font
sizes.
- Line 3: The block metadata uses "apiVersion": 3 which requires WP 6.3+, but
the plugin's minimum WP version is 5.6; either change each block's apiVersion
from 3 to 2 (e.g., update src/blocks/FontSizeSwitcher/block.json and the other
12 block.json files to "apiVersion": 2) to maintain compatibility with WP
5.6–6.2, or update the plugin's minimum WordPress version in the plugin
header/readme (Requires at least) to 6.3 to match apiVersion 3—pick one approach
and apply it consistently across all blocks and the plugin metadata.
In @src/blocks/FontSizeSwitcher/edit.js:
- Around line 113-131: The JSX uses a selected attribute on an <option> which is
invalid; update the renderDropdown function to set the default selection via the
<select>'s defaultValue prop (e.g., defaultValue="Default") and remove the
selected attribute from the <option>s; also add explicit value attributes to
each <option> (e.g., value="Small", value="Default", etc.) to ensure the
selected value matches correctly.
In @src/blocks/FontSizeSwitcher/save.js:
- Around line 60-122: The save.js file duplicates rendering logic from edit.js:
extract shared UI renderers into a reusable module and import them in both
files. Create a new module (e.g. RenderControls.js) exporting renderButtons,
renderSlider, and renderDropdown that accept configurable inputs (e.g.
fontSizeOptions, buttonBaseStyle, defaultSize) and move the JSX implementations
there; then replace the local renderButtons, renderSlider, renderDropdown in
both save.js and edit.js with imports from that module (referencing the same
function names) so both files call the shared helpers instead of duplicating
code.
- Around line 60-81: The renderButtons function currently uses a hardcoded sizes
array; replace it to iterate over the fontSizeOptions block attribute instead
(use the fontSizeOptions prop/attribute exposed to save.js) and map each option
to the button elements (preserve data-size, data-label, title and label
rendering). Update the logic in save.js renderButtons to read fontSizeOptions
(fallback to a sensible default if undefined) so the UI reflects block.json
configuration; mirror the same change in edit.js and ensure block.json's schema
fields (label/title/value) match the property names you use.
- Around line 103-122: The renderDropdown function currently hardcodes option
elements and incorrectly uses the selected attribute; change it to set the
default selection via the select's defaultValue prop (remove selected on the
option), and generate the option list by mapping over the fontSizeOptions
attribute (same source used by renderButtons) to create <option> elements with
appropriate value and label; ensure defaultValue falls back to the current
fontSize attribute (or a sensible default like "16px") so the dropdown reflects
the saved setting.
In @src/blocks/FontSizeSwitcher/view.js:
- Around line 33-39: Wrap all localStorage access in try-catch: when reading the
stored font size (the localStorage.getItem call that sets storedSize/currentSize
before calling applyFontSize) catch any exception and fall back to defaultSize
before calling applyFontSize(currentSize); likewise wrap every
localStorage.setItem call (the places that persist font size changes) in
try-catch and silently handle/log the error so the UI still updates
(applyFontSize) even if storage is unavailable; reference the
storageKey/storedSize/currentSize variables and the applyFontSize function when
making these changes.
- Around line 159-186: applyFontSize is compounding because it reads
getComputedStyle which returns the last adjusted size; instead, on first run
capture and persist each element's original font size (e.g., store in a
data-original-font-size attribute or a WeakMap keyed by element) and on every
subsequent call use that stored original value multiplied by the new scaleFactor
(derived from defaultSize and incoming size) to compute and set
el.style.fontSize; ensure you only set the original value when absent and
continue to add the class/data-adjusted-size to contentElement as before.
🧹 Nitpick comments (7)
src/blocks/DocActions/save.js (2)
25-46: Consider extracting icon definitions to a shared module.The
getIconSVGfunction is duplicated in bothsave.jsandedit.js. Extract it to a shared utility file to follow DRY principles and ensure consistency.♻️ Suggested refactor
Create a new file
src/blocks/DocActions/icons.js:export const getIconSVG = (type) => { const icons = { copy: ( <svg width="20" height="20" viewBox="0 0 20 20" fill="none"> <rect x="6" y="6" width="10" height="10" rx="1" stroke="currentColor" strokeWidth="1.5"/> <path d="M4 14H3C2.44772 14 2 13.5523 2 13V3C2 2.44772 2.44772 2 3 2H13C13.5523 2 14 2.44772 14 3V4" stroke="currentColor" strokeWidth="1.5"/> </svg> ), chatgpt: ( <svg width="20" height="20" viewBox="0 0 20 20" fill="none"> <path d="M10 2C5.58172 2 2 5.58172 2 10C2 14.4183 5.58172 18 10 18C14.4183 18 18 14.4183 18 10C18 5.58172 14.4183 2 10 2Z" stroke="currentColor" strokeWidth="1.5"/> <circle cx="10" cy="10" r="3" fill="currentColor"/> </svg> ), claude: ( <svg width="20" height="20" viewBox="0 0 20 20" fill="none"> <path d="M10 2L13 8L19 9L14.5 13L16 19L10 16L4 19L5.5 13L1 9L7 8L10 2Z" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/> </svg> ) }; return icons[type]; };Then import in both files:
+import { getIconSVG } from './icons';
61-66: Consider extracting container style logic to a shared utility.The
containerStylelogic (lines 61-66) is duplicated inedit.js(lines 97-102). Extract this to a shared helper function to maintain consistency and follow DRY principles.src/blocks/DocActions/view.js (1)
191-194: Remove unused function.The
getPageContentfunction is defined but never called. The same logic is duplicated inline at line 48 inhandleCopyMarkdown. Either remove this function or refactorhandleCopyMarkdownto use it.♻️ Suggested refactor
Option 1: Remove the unused function:
- function getPageContent() { - const content = document.querySelector('.entry-content, .post-content, main'); - return content ? content.textContent.trim() : document.body.textContent.trim(); - } -Option 2: Use the function in
handleCopyMarkdown:async function handleCopyMarkdown(button) { try { // Get page content - const content = document.querySelector('.entry-content, .post-content, main'); + const content = getPageContent(); if (!content) { showNotification(button, 'Content not found', 'error'); return; }Note: If using Option 2, update
getPageContentto return the element, not the text content.src/blocks/DocActions/edit.js (1)
64-72: Remove inappropriategapproperty from button styles.Line 71 adds
gap: spacingto individual button styles, butgapis a flexbox container property. The buttons themselves are not flex containers in this context (the container at line 228 is). This property will have no effect and should be removed to avoid confusion.If the intent is to control spacing between the icon and text within each button, that should be handled via CSS in
editor.scssorstyle.scssrather than inline styles.♻️ Proposed fix
const getButtonStyles = () => ({ backgroundColor: buttonStyle === 'filled' ? backgroundColor : 'transparent', color: textColor, borderColor, borderWidth, borderRadius, - fontSize, - gap: spacing + fontSize });src/blocks/FontSizeSwitcher/style.scss (1)
115-140: Recommended: Reduce !important usage in responsive styles.Lines 118, 122, 123, 127, 130, and 136 use
!importantto override styles. This suggests specificity issues and makes styles harder to maintain or override in themes. Consider refactoring to use more specific selectors or restructuring the cascade to avoid needing!important.For example, increase specificity naturally:
@media (max-width: 768px) { .wp-block-wedocs-font-size-switcher { .font-size-switcher-container { gap: 6px; } .font-size-option { padding: 6px 10px; font-size: 13px; } // ... etc } }If inline styles are the culprit, document that the responsive overrides are intentional for mobile UX.
src/blocks/FontSizeSwitcher/view.js (1)
91-91: Consider specifying radix forparseIntcalls.While modern browsers default to base 10 for decimal strings, explicitly providing the radix (e.g.,
parseInt(currentSize, 10)) is a defensive best practice. This applies to lines 91, 108, and 122.src/blocks/FontSizeSwitcher/edit.js (1)
86-95: Consider usingsize.valueas key instead of index.While using index is acceptable for this static array, using the unique
size.valuewould be slightly more idiomatic and future-proof if the array were ever filtered or reordered.- <button - key={index} + <button + key={size.value}
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (13)
src/blocks/DocActions/block.jsonsrc/blocks/DocActions/edit.jssrc/blocks/DocActions/save.jssrc/blocks/DocActions/view.jssrc/blocks/FontSizeSwitcher/block.jsonsrc/blocks/FontSizeSwitcher/edit.jssrc/blocks/FontSizeSwitcher/editor.scsssrc/blocks/FontSizeSwitcher/index.jssrc/blocks/FontSizeSwitcher/save.jssrc/blocks/FontSizeSwitcher/style.scsssrc/blocks/FontSizeSwitcher/view.jssrc/blocks/index.jswedocs.php
🧰 Additional context used
🧬 Code graph analysis (6)
src/blocks/FontSizeSwitcher/save.js (2)
src/blocks/FontSizeSwitcher/edit.js (7)
attributes(15-33)blockProps(42-49)containerStyle(51-57)buttonBaseStyle(59-68)renderButtons(77-96)renderSlider(98-111)renderDropdown(113-131)src/blocks/FontSizeSwitcher/view.js (7)
displayStyle(12-12)buttonStyle(17-17)contentSelector(10-10)defaultSize(11-11)activeColor(13-13)textColor(14-14)borderColor(16-16)
src/blocks/DocActions/save.js (2)
src/blocks/DocActions/edit.js (1)
attributes(20-39)src/blocks/DocActions/view.js (1)
promptTemplate(11-11)
src/blocks/FontSizeSwitcher/view.js (1)
src/blocks/TableOfContents/view.js (1)
computedStyle(284-284)
src/blocks/FontSizeSwitcher/edit.js (2)
src/blocks/FontSizeSwitcher/save.js (1)
blockProps(24-39)src/blocks/FontSizeSwitcher/view.js (7)
buttonStyle(17-17)textColor(14-14)borderColor(16-16)activeColor(13-13)defaultSize(11-11)displayStyle(12-12)contentSelector(10-10)
src/blocks/DocActions/edit.js (2)
src/blocks/DocActions/save.js (2)
attributes(4-23)blockProps(48-59)src/blocks/DocActions/view.js (1)
promptTemplate(11-11)
src/blocks/DocActions/view.js (2)
src/blocks/FontSizeSwitcher/view.js (1)
bgColor(15-15)src/blocks/TableOfContents/view.js (4)
content(159-159)text(105-105)title(158-158)level(104-104)
🪛 Biome (2.1.2)
src/blocks/DocActions/view.js
[error] 172-172: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
🔇 Additional comments (18)
src/blocks/DocActions/save.js (1)
78-113: LGTM!The conditional rendering and button structure are correct. The buttons properly use the computed styles and data attributes for frontend behavior.
src/blocks/DocActions/block.json (1)
1-23: LGTM!Block metadata is properly configured with appropriate supports and schema reference.
src/blocks/DocActions/view.js (2)
5-43: LGTM!The event listener setup properly initializes hover effects and click handlers for each action button.
45-69: Verify content selector compatibility across themes.The content selector at line 48 uses common WordPress theme patterns (
.entry-content, .post-content, main), but may not work on all themes. Consider verifying this works with popular WordPress themes or documenting theme requirements.src/blocks/DocActions/edit.js (3)
1-46: LGTM!Imports and component initialization are properly structured. The
useEffectcorrectly sets a uniqueblockIdon first render.
113-225: LGTM!Inspector controls are well-organized with clear sections and proper internationalization. The use of shared
ColorSettingsPanel,BorderPanel, andSpacingPanelcomponents promotes consistency across blocks.
227-248: LGTM!The editor preview correctly mirrors the save output with conditional button rendering and proper styling.
src/blocks/FontSizeSwitcher/index.js (1)
1-10: LGTM!Standard block registration following WordPress Gutenberg best practices.
src/blocks/FontSizeSwitcher/editor.scss (1)
5-27: Good editor UX design.Disabling pointer events and showing a preview notice prevents confusion about non-functional controls in the editor while clearly communicating frontend interactivity.
src/blocks/FontSizeSwitcher/save.js (1)
24-39: Good data attribute strategy for frontend hydration.Storing configuration in data attributes enables the frontend view script to read block settings without additional API calls.
src/blocks/FontSizeSwitcher/style.scss (1)
143-167: Excellent accessibility support.The focus-visible outlines, high-contrast mode adjustments, and reduced-motion support demonstrate strong attention to WCAG compliance and inclusive design.
src/blocks/FontSizeSwitcher/view.js (4)
6-31: LGTM!The initialization logic correctly reads configuration from data attributes with sensible fallbacks, and gracefully handles missing content elements with a console warning.
135-157: LGTM!The dropdown control initialization follows the same clean pattern as other controls with proper event handling and display updates.
188-217: LGTM!The utility functions are clean. The
updateDisplaysizeMap provides friendly labels for common sizes and gracefully falls back to the raw value for slider positions not in the map.
57-79: Thedata-labelattribute is correctly set in the button markup at save.js line 74 asdata-label={size.title}. Each size object in the sizes array includes atitleproperty (e.g., 'Large', 'Extra Large'), so the attribute will never be null and users will see the proper label display in view.js line 77.Likely an incorrect or invalid review comment.
src/blocks/FontSizeSwitcher/edit.js (3)
35-49: LGTM!The
useEffectcorrectly initializes a uniqueblockIdon first render, and block props are properly configured with styling.
142-244: LGTM!The InspectorControls implementation follows WordPress block patterns correctly with proper i18n, logical panel organization, and conditional rendering for dependent controls.
246-270: LGTM!The block render output is well-structured with proper use of
blockProps, clean conditional rendering, and appropriate inline styling.
| "backgroundColor": { | ||
| "type": "string", | ||
| "default": "" | ||
| }, | ||
| "textColor": { | ||
| "type": "string", | ||
| "default": "#ffffff" | ||
| }, | ||
| "borderColor": { | ||
| "type": "string", | ||
| "default": "#444444" | ||
| }, | ||
| "borderWidth": { | ||
| "type": "string", | ||
| "default": "1px" | ||
| }, | ||
| "borderRadius": { | ||
| "type": "string", | ||
| "default": "6px" | ||
| }, | ||
| "hoverBackgroundColor": { | ||
| "type": "string", | ||
| "default": "#333333" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential visibility issue with default color configuration.
The default textColor is white (#ffffff) while backgroundColor is empty. When a user switches buttonStyle to "filled" without configuring a background color, the white text on a transparent/empty background may be invisible against light-colored page backgrounds, impacting usability.
Consider setting a sensible default backgroundColor value (e.g., "#333333" or "#000000") to ensure the filled button style is immediately usable.
🤖 Prompt for AI Agents
In @src/blocks/DocActions/block.json around lines 57 - 79, The default colors
risk making filled buttons invisible because backgroundColor is empty while
textColor is "#ffffff"; update the block.json defaults so backgroundColor is a
sensible dark color (e.g., "#333333" or "#000000") to match textColor, and
ensure hoverBackgroundColor, borderColor and borderRadius defaults remain
consistent with the filled style; modify the "backgroundColor" default value in
src/blocks/DocActions/block.json and test the "buttonStyle" = "filled" rendering
to confirm legibility.
| function handleOpenChatGPT(promptTemplate) { | ||
| const pageTitle = document.title; | ||
| const pageUrl = window.location.href; | ||
| const prompt = promptTemplate | ||
| .replace(/{title}/g, pageTitle) | ||
| .replace(/{url}/g, pageUrl); | ||
|
|
||
| const encodedPrompt = encodeURIComponent(prompt); | ||
| const url = `https://chatgpt.com/?prompt=${encodedPrompt}`; | ||
|
|
||
| window.open(url, '_blank'); | ||
| } | ||
|
|
||
| function handleOpenClaude(promptTemplate) { | ||
| const pageTitle = document.title; | ||
| const pageUrl = window.location.href; | ||
| const prompt = promptTemplate | ||
| .replace(/{title}/g, pageTitle) | ||
| .replace(/{url}/g, pageUrl); | ||
|
|
||
| const encodedPrompt = encodeURIComponent(prompt); | ||
| const url = `https://claude.ai/new?q=${encodedPrompt}`; | ||
|
|
||
| window.open(url, '_blank'); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🌐 Web query:
ChatGPT pre-filled prompt URL format query parameter
💡 Result:
You can pre-fill ChatGPT by adding a URL query parameter. Common formats found in practice:
-
Open and auto-run (fills + submits): https://chat.openai.com/?q=URL_ENCODED_PROMPT. Example: https://chat.openai.com/?q=write%20a%20poem%20about%20cats. [1][2]
-
Fill only (no auto-submit): use prompt in the fragment/hash (used by extensions/users): https://chat.openai.com/#prompt=URL_ENCODED_PROMPT (some tools also support &autoSubmit=1 to run). Example: https://chat.openai.com/#prompt=Explain%20quantum%20computing&autoSubmit=1. [3][4]
-
You can also include a model parameter, e.g. &model=o3 (behavior falls back if not available). [4]
Always URL-encode the prompt (spaces → %20, special characters escaped). [1][2]
Sources: [1] GenAI Secret Sauce (example ?q=...), [2] GitHub extension README (examples, ?q & model), [3] ChatGPTToolkit/DeepWiki README (fragment prompt & autoSubmit examples), [4] LinkedIn post noting model and q/prompt usage.
🌐 Web query:
Claude AI pre-filled prompt URL format how to open
💡 Result:
Short answer: To prefill the user input in Claude's web UI use a URL like
https://claude.ai/new?q=URL-ENCODED_PROMPT
(e.g. https://claude.ai/new?q=teach%20me%20how%20to%20make%20a%20PBJ). This fills the chat box but does not auto-send — sending still requires Enter/click. For programmatic prefilling of Claude's response (the Assistant message) use the API messages parameter with an "assistant" role (see Anthropic docs). [1][2][3]
Sources:
- Claude web prefill example. [1]
- Anthropic docs: prefilling Assistant via API (messages with role "assistant"). [2]
- Community notes about prefilled URL behavior (no auto-submit). [3]
Fix ChatGPT URL domain and parameter.
The ChatGPT URL on line 79 uses an incorrect domain and parameter. Change https://chatgpt.com/?prompt=${encodedPrompt} to https://chat.openai.com/?q=${encodedPrompt} to match the current ChatGPT web interface. The domain chatgpt.com does not host the ChatGPT UI, and the ?q= parameter is the documented format for pre-filling prompts.
The Claude URL on line 92 is correct and requires no changes.
🤖 Prompt for AI Agents
In @src/blocks/DocActions/view.js around lines 71 - 95, The ChatGPT opener uses
the wrong domain and query param; in function handleOpenChatGPT replace the URL
construction `https://chatgpt.com/?prompt=${encodedPrompt}` with
`https://chat.openai.com/?q=${encodedPrompt}` so the encodedPrompt is passed
with the documented `q` parameter and opens the correct ChatGPT UI (keep
encodeURIComponent and window.open as-is).
| switch (tag) { | ||
| case 'h1': | ||
| case 'h2': | ||
| case 'h3': | ||
| case 'h4': | ||
| case 'h5': | ||
| case 'h6': | ||
| const level = parseInt(tag[1]); | ||
| markdown += '\n' + '#'.repeat(level) + ' '; | ||
| break; | ||
| case 'p': | ||
| markdown += '\n\n'; | ||
| break; | ||
| case 'li': | ||
| markdown += '\n- '; | ||
| break; | ||
| case 'code': | ||
| markdown += '`'; | ||
| break; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix switch statement variable scoping issue.
The const level declaration at line 172 is directly in a switch case without a block, which can cause scoping issues and is flagged by static analysis. Wrap the case logic in a block.
As per static analysis hints from Biome.
🔧 Proposed fix
switch (tag) {
case 'h1':
case 'h2':
case 'h3':
case 'h4':
case 'h5':
- case 'h6':
+ case 'h6': {
const level = parseInt(tag[1]);
markdown += '\n' + '#'.repeat(level) + ' ';
break;
+ }
case 'p':
markdown += '\n\n';
break;
case 'li':
markdown += '\n- ';
break;
case 'code':
markdown += '`';
break;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| switch (tag) { | |
| case 'h1': | |
| case 'h2': | |
| case 'h3': | |
| case 'h4': | |
| case 'h5': | |
| case 'h6': | |
| const level = parseInt(tag[1]); | |
| markdown += '\n' + '#'.repeat(level) + ' '; | |
| break; | |
| case 'p': | |
| markdown += '\n\n'; | |
| break; | |
| case 'li': | |
| markdown += '\n- '; | |
| break; | |
| case 'code': | |
| markdown += '`'; | |
| break; | |
| } | |
| } | |
| switch (tag) { | |
| case 'h1': | |
| case 'h2': | |
| case 'h3': | |
| case 'h4': | |
| case 'h5': | |
| case 'h6': { | |
| const level = parseInt(tag[1]); | |
| markdown += '\n' + '#'.repeat(level) + ' '; | |
| break; | |
| } | |
| case 'p': | |
| markdown += '\n\n'; | |
| break; | |
| case 'li': | |
| markdown += '\n- '; | |
| break; | |
| case 'code': | |
| markdown += '`'; | |
| break; | |
| } |
🧰 Tools
🪛 Biome (2.1.2)
[error] 172-172: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
🤖 Prompt for AI Agents
In @src/blocks/DocActions/view.js around lines 165 - 185, The switch block in
view.js declares "const level = parseInt(tag[1])" directly inside case labels
(e.g., case 'h1' ... case 'h6') which causes block-scoping issues; fix by
introducing an explicit block for the header cases (for example wrap the header
cases with { ... } and move the const level declaration inside that block) or
alternatively declare level with let before the switch and assign inside each
header case; update the cases handling 'h1'..'h6' accordingly in the same switch
in the view() function so the const is properly scoped.
| @@ -0,0 +1,110 @@ | |||
| { | |||
| "$schema": "https://schemas.wp.org/trunk/block.json", | |||
| "apiVersion": 3, | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🌐 Web query:
WordPress Block API version 3 minimum required WordPress version
💡 Result:
Block API version 3 requires WordPress 6.3 or newer. [1][2]
Sources:
[1] WordPress Block Editor Handbook — API Versions (Version 3 (>= WordPress 6.3)).
[2] WordPress.org support discussion referencing API version 3 introduced in WordPress 6.3.
🏁 Script executed:
# Search for WordPress version constraints in the repository
git ls-files | head -20Repository: weDevsOfficial/wedocs-plugin
Length of output: 519
🏁 Script executed:
# Check composer.json for WordPress version constraints
if [ -f composer.json ]; then
cat composer.json | jq '.require | select(. != null)'
fi
# Check for readme.txt in .wordpress-org directory
if [ -f .wordpress-org/readme.txt ]; then
head -40 .wordpress-org/readme.txt
fi
# Check for main plugin file (likely wedocs.php)
fd "wedocs\.php" -t f | head -5Repository: weDevsOfficial/wedocs-plugin
Length of output: 132
🏁 Script executed:
# Read the wedocs.php file to find WordPress version requirement in plugin header
head -50 wedocs.php | grep -i "requires\|WordPress"Repository: weDevsOfficial/wedocs-plugin
Length of output: 181
🏁 Script executed:
# Read more of the plugin header to find the Requires at least field
head -30 wedocs.phpRepository: weDevsOfficial/wedocs-plugin
Length of output: 1068
🏁 Script executed:
# Check for readme.txt files which typically declare WordPress minimum version
fd "readme\.txt" -t f | head -10
# Also check package.json for any version constraints
if [ -f package.json ]; then
cat package.json | jq '.engines // empty'
fiRepository: weDevsOfficial/wedocs-plugin
Length of output: 82
🏁 Script executed:
# Read readme.txt to find WordPress minimum version
head -40 readme.txtRepository: weDevsOfficial/wedocs-plugin
Length of output: 2180
🏁 Script executed:
# Find all block.json files and check their apiVersion values
find . -name "block.json" -type f | head -20Repository: weDevsOfficial/wedocs-plugin
Length of output: 555
🏁 Script executed:
# Once we have block.json files, let's check their apiVersion values
fd "block\.json" -t f -x cat {} \; | grep -A 1 -B 1 "apiVersion" | head -50Repository: weDevsOfficial/wedocs-plugin
Length of output: 1482
Block API version 3 is incompatible with the plugin's minimum WordPress version.
The plugin declares support for WordPress 5.6 (readme.txt: "Requires at least: 5.6"), but Block API version 3 requires WordPress 6.3 or newer. All 13 blocks in the codebase use apiVersion: 3, which means none of them will function on WordPress 5.6 through 6.2. Either downgrade blocks to apiVersion: 2 for compatibility with WordPress 5.6+, or update the minimum WordPress requirement to 6.3.
🤖 Prompt for AI Agents
In @src/blocks/FontSizeSwitcher/block.json at line 3, The block metadata uses
"apiVersion": 3 which requires WP 6.3+, but the plugin's minimum WP version is
5.6; either change each block's apiVersion from 3 to 2 (e.g., update
src/blocks/FontSizeSwitcher/block.json and the other 12 block.json files to
"apiVersion": 2) to maintain compatibility with WP 5.6–6.2, or update the
plugin's minimum WordPress version in the plugin header/readme (Requires at
least) to 6.3 to match apiVersion 3—pick one approach and apply it consistently
across all blocks and the plugin metadata.
| "fontSizeOptions": { | ||
| "type": "array", | ||
| "default": [ | ||
| { "label": "Small", "value": "14px" }, | ||
| { "label": "Default", "value": "16px" }, | ||
| { "label": "Medium", "value": "18px" }, | ||
| { "label": "Large", "value": "20px" }, | ||
| { "label": "Extra Large", "value": "24px" } | ||
| ] | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Search for all references to fontSizeOptions
rg -n "fontSizeOptions" -C 2Repository: weDevsOfficial/wedocs-plugin
Length of output: 406
🏁 Script executed:
#!/bin/bash
# First, find the block directory and examine the save.js file
find . -name "save.js" | grep -i fontsizeswitcherRepository: weDevsOfficial/wedocs-plugin
Length of output: 109
🏁 Script executed:
#!/bin/bash
# Also check the complete structure of the FontSizeSwitcher block
find . -path "*FontSizeSwitcher*" -type f | head -20Repository: weDevsOfficial/wedocs-plugin
Length of output: 348
🏁 Script executed:
#!/bin/bash
# Read save.js to check the implementation
cat -n src/blocks/FontSizeSwitcher/save.jsRepository: weDevsOfficial/wedocs-plugin
Length of output: 5171
🏁 Script executed:
#!/bin/bash
# Read edit.js to check if fontSizeOptions is used
cat -n src/blocks/FontSizeSwitcher/edit.jsRepository: weDevsOfficial/wedocs-plugin
Length of output: 9836
🏁 Script executed:
#!/bin/bash
# Read view.js to check for usage
cat -n src/blocks/FontSizeSwitcher/view.jsRepository: weDevsOfficial/wedocs-plugin
Length of output: 8458
🏁 Script executed:
#!/bin/bash
# Read index.js
cat -n src/blocks/FontSizeSwitcher/index.jsRepository: weDevsOfficial/wedocs-plugin
Length of output: 370
fontSizeOptions attribute is defined but completely unused—hardcoded sizes in save.js (lines 60-67), edit.js (lines 77-84), and view.js will always override it.
The fontSizeOptions attribute in block.json is declared with customizable defaults but never destructured or referenced in any component. Instead, the size options are hardcoded in three separate locations (save.js, edit.js, and view.js), making the attribute inert. Any developer attempting to customize font sizes via this block attribute will find their changes ignored. Either use the attribute across all files or remove it from block.json to avoid confusion.
🤖 Prompt for AI Agents
In @src/blocks/FontSizeSwitcher/block.json around lines 48 - 57, The
fontSizeOptions attribute in block.json is declared but never used—replace the
hardcoded size arrays in save.js, edit.js, and view.js with a reference to the
block attribute (fontSizeOptions) so the components read the configured options
instead of fixed values; locate where sizes are currently defined in the Edit,
Save and View components (the arrays around lines mentioned in the review) and
destructure or access attributes.fontSizeOptions (or
props.attributes.fontSizeOptions) and fall back to the block.json default if
undefined, ensuring all three files use the same single source of truth for font
sizes.
| const renderButtons = () => { | ||
| const sizes = [ | ||
| { label: 'S', value: '14px', title: 'Small' }, | ||
| { label: 'M', value: '16px', title: 'Default' }, | ||
| { label: 'L', value: '18px', title: 'Medium' }, | ||
| { label: 'XL', value: '20px', title: 'Large' }, | ||
| { label: '2XL', value: '24px', title: 'Extra Large' } | ||
| ]; | ||
|
|
||
| return sizes.map((size, index) => ( | ||
| <button | ||
| key={index} | ||
| className="font-size-option" | ||
| data-size={size.value} | ||
| data-label={size.title} | ||
| style={buttonBaseStyle} | ||
| title={size.title} | ||
| > | ||
| {size.label} | ||
| </button> | ||
| )); | ||
| }; | ||
|
|
||
| const renderSlider = () => ( | ||
| <div className="font-size-slider-controls" style={{ display: 'flex', alignItems: 'center', gap: '10px', flex: 1, maxWidth: '300px' }}> | ||
| <button className="font-size-decrease" style={{ ...buttonBaseStyle, padding: '6px 10px' }} title="Decrease font size"> | ||
| A- | ||
| </button> | ||
| <input | ||
| type="range" | ||
| className="font-size-slider" | ||
| min="12" | ||
| max="28" | ||
| step="2" | ||
| defaultValue="16" | ||
| style={{ flex: 1 }} | ||
| /> | ||
| <button className="font-size-increase" style={{ ...buttonBaseStyle, padding: '6px 10px' }} title="Increase font size"> | ||
| A+ | ||
| </button> | ||
| </div> | ||
| ); | ||
|
|
||
| const renderDropdown = () => ( | ||
| <select | ||
| className="font-size-dropdown" | ||
| style={{ | ||
| ...buttonBaseStyle, | ||
| padding: '8px 32px 8px 12px', | ||
| appearance: 'none', | ||
| backgroundImage: `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23333' d='M6 9L1 4h10z'/%3E%3C/svg%3E")`, | ||
| backgroundRepeat: 'no-repeat', | ||
| backgroundPosition: 'right 10px center', | ||
| minWidth: '120px' | ||
| }} | ||
| > | ||
| <option value="14px">Small</option> | ||
| <option value="16px" selected>Default</option> | ||
| <option value="18px">Medium</option> | ||
| <option value="20px">Large</option> | ||
| <option value="24px">Extra Large</option> | ||
| </select> | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Major: Code duplication with edit.js.
The renderButtons(), renderSlider(), and renderDropdown() functions are nearly identical to those in edit.js (per relevant snippets). Consider extracting shared rendering logic into a common utility module to follow DRY principles.
Example approach:
// src/blocks/FontSizeSwitcher/components/RenderControls.js
export const renderButtons = (fontSizeOptions, buttonBaseStyle) => { ... }
export const renderSlider = (buttonBaseStyle) => { ... }
export const renderDropdown = (fontSizeOptions, buttonBaseStyle, defaultSize) => { ... }Then import and use in both edit.js and save.js.
🤖 Prompt for AI Agents
In @src/blocks/FontSizeSwitcher/save.js around lines 60 - 122, The save.js file
duplicates rendering logic from edit.js: extract shared UI renderers into a
reusable module and import them in both files. Create a new module (e.g.
RenderControls.js) exporting renderButtons, renderSlider, and renderDropdown
that accept configurable inputs (e.g. fontSizeOptions, buttonBaseStyle,
defaultSize) and move the JSX implementations there; then replace the local
renderButtons, renderSlider, renderDropdown in both save.js and edit.js with
imports from that module (referencing the same function names) so both files
call the shared helpers instead of duplicating code.
| const renderButtons = () => { | ||
| const sizes = [ | ||
| { label: 'S', value: '14px', title: 'Small' }, | ||
| { label: 'M', value: '16px', title: 'Default' }, | ||
| { label: 'L', value: '18px', title: 'Medium' }, | ||
| { label: 'XL', value: '20px', title: 'Large' }, | ||
| { label: '2XL', value: '24px', title: 'Extra Large' } | ||
| ]; | ||
|
|
||
| return sizes.map((size, index) => ( | ||
| <button | ||
| key={index} | ||
| className="font-size-option" | ||
| data-size={size.value} | ||
| data-label={size.title} | ||
| style={buttonBaseStyle} | ||
| title={size.title} | ||
| > | ||
| {size.label} | ||
| </button> | ||
| )); | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical: Hardcoded sizes ignore the fontSizeOptions attribute.
The sizes array hardcoded here (14px, 16px, 18px, 20px, 24px with specific labels) completely ignores the fontSizeOptions attribute defined in block.json (lines 48-57), which provides a different schema and allows customization. This makes the attribute unusable and creates a misleading API.
♻️ Proposed fix to use fontSizeOptions attribute
const renderButtons = () => {
- const sizes = [
- { label: 'S', value: '14px', title: 'Small' },
- { label: 'M', value: '16px', title: 'Default' },
- { label: 'L', value: '18px', title: 'Medium' },
- { label: 'XL', value: '20px', title: 'Large' },
- { label: '2XL', value: '24px', title: 'Extra Large' }
- ];
+ const { fontSizeOptions } = attributes;
- return sizes.map((size, index) => (
+ return fontSizeOptions.map((size, index) => (
<button
key={index}
className="font-size-option"
data-size={size.value}
data-label={size.label}
style={buttonBaseStyle}
- title={size.title}
+ title={size.label}
>
{size.label}
</button>
));
};Note: You'll also need to update edit.js similarly and adjust the block.json fontSizeOptions schema to match the required fields.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const renderButtons = () => { | |
| const sizes = [ | |
| { label: 'S', value: '14px', title: 'Small' }, | |
| { label: 'M', value: '16px', title: 'Default' }, | |
| { label: 'L', value: '18px', title: 'Medium' }, | |
| { label: 'XL', value: '20px', title: 'Large' }, | |
| { label: '2XL', value: '24px', title: 'Extra Large' } | |
| ]; | |
| return sizes.map((size, index) => ( | |
| <button | |
| key={index} | |
| className="font-size-option" | |
| data-size={size.value} | |
| data-label={size.title} | |
| style={buttonBaseStyle} | |
| title={size.title} | |
| > | |
| {size.label} | |
| </button> | |
| )); | |
| }; | |
| const renderButtons = () => { | |
| const { fontSizeOptions } = attributes; | |
| return fontSizeOptions.map((size, index) => ( | |
| <button | |
| key={index} | |
| className="font-size-option" | |
| data-size={size.value} | |
| data-label={size.label} | |
| style={buttonBaseStyle} | |
| title={size.label} | |
| > | |
| {size.label} | |
| </button> | |
| )); | |
| }; |
🤖 Prompt for AI Agents
In @src/blocks/FontSizeSwitcher/save.js around lines 60 - 81, The renderButtons
function currently uses a hardcoded sizes array; replace it to iterate over the
fontSizeOptions block attribute instead (use the fontSizeOptions prop/attribute
exposed to save.js) and map each option to the button elements (preserve
data-size, data-label, title and label rendering). Update the logic in save.js
renderButtons to read fontSizeOptions (fallback to a sensible default if
undefined) so the UI reflects block.json configuration; mirror the same change
in edit.js and ensure block.json's schema fields (label/title/value) match the
property names you use.
| const renderDropdown = () => ( | ||
| <select | ||
| className="font-size-dropdown" | ||
| style={{ | ||
| ...buttonBaseStyle, | ||
| padding: '8px 32px 8px 12px', | ||
| appearance: 'none', | ||
| backgroundImage: `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23333' d='M6 9L1 4h10z'/%3E%3C/svg%3E")`, | ||
| backgroundRepeat: 'no-repeat', | ||
| backgroundPosition: 'right 10px center', | ||
| minWidth: '120px' | ||
| }} | ||
| > | ||
| <option value="14px">Small</option> | ||
| <option value="16px" selected>Default</option> | ||
| <option value="18px">Medium</option> | ||
| <option value="20px">Large</option> | ||
| <option value="24px">Extra Large</option> | ||
| </select> | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical: Invalid selected attribute and hardcoded options.
Two issues:
- HTML5 violation: Line 117 uses the
selectedattribute on an<option>. In React/JSX, usedefaultValueon the<select>element instead. - Ignores fontSizeOptions: The dropdown options are hardcoded and don't respect the
fontSizeOptionsattribute, same as renderButtons().
🐛 Proposed fix
const renderDropdown = () => (
<select
className="font-size-dropdown"
+ defaultValue={defaultSize}
style={{
...buttonBaseStyle,
padding: '8px 32px 8px 12px',
appearance: 'none',
backgroundImage: `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23333' d='M6 9L1 4h10z'/%3E%3C/svg%3E")`,
backgroundRepeat: 'no-repeat',
backgroundPosition: 'right 10px center',
minWidth: '120px'
}}
>
- <option value="14px">Small</option>
- <option value="16px" selected>Default</option>
- <option value="18px">Medium</option>
- <option value="20px">Large</option>
- <option value="24px">Extra Large</option>
+ {fontSizeOptions.map((size, index) => (
+ <option key={index} value={size.value}>
+ {size.label}
+ </option>
+ ))}
</select>
);Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In @src/blocks/FontSizeSwitcher/save.js around lines 103 - 122, The
renderDropdown function currently hardcodes option elements and incorrectly uses
the selected attribute; change it to set the default selection via the select's
defaultValue prop (remove selected on the option), and generate the option list
by mapping over the fontSizeOptions attribute (same source used by
renderButtons) to create <option> elements with appropriate value and label;
ensure defaultValue falls back to the current fontSize attribute (or a sensible
default like "16px") so the dropdown reflects the saved setting.
| // Get stored font size from localStorage | ||
| const storageKey = 'wedocs-font-size'; | ||
| const storedSize = localStorage.getItem(storageKey); | ||
| let currentSize = storedSize || defaultSize; | ||
|
|
||
| // Apply initial font size | ||
| applyFontSize(currentSize); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wrap localStorage access in try-catch for resilience.
localStorage can throw SecurityError in private browsing mode (Safari), when storage is disabled, or when quota is exceeded. This could break the block initialization in restricted environments.
Suggested fix
// Get stored font size from localStorage
const storageKey = 'wedocs-font-size';
-const storedSize = localStorage.getItem(storageKey);
+let storedSize = null;
+try {
+ storedSize = localStorage.getItem(storageKey);
+} catch (e) {
+ // localStorage unavailable (private mode, disabled cookies, etc.)
+}
let currentSize = storedSize || defaultSize;Also wrap localStorage.setItem calls (lines 69, 102, 113, 127, 149) in try-catch blocks.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Get stored font size from localStorage | |
| const storageKey = 'wedocs-font-size'; | |
| const storedSize = localStorage.getItem(storageKey); | |
| let currentSize = storedSize || defaultSize; | |
| // Apply initial font size | |
| applyFontSize(currentSize); | |
| // Get stored font size from localStorage | |
| const storageKey = 'wedocs-font-size'; | |
| let storedSize = null; | |
| try { | |
| storedSize = localStorage.getItem(storageKey); | |
| } catch (e) { | |
| // localStorage unavailable (private mode, disabled cookies, etc.) | |
| } | |
| let currentSize = storedSize || defaultSize; | |
| // Apply initial font size | |
| applyFontSize(currentSize); |
🤖 Prompt for AI Agents
In @src/blocks/FontSizeSwitcher/view.js around lines 33 - 39, Wrap all
localStorage access in try-catch: when reading the stored font size (the
localStorage.getItem call that sets storedSize/currentSize before calling
applyFontSize) catch any exception and fall back to defaultSize before calling
applyFontSize(currentSize); likewise wrap every localStorage.setItem call (the
places that persist font size changes) in try-catch and silently handle/log the
error so the UI still updates (applyFontSize) even if storage is unavailable;
reference the storageKey/storedSize/currentSize variables and the applyFontSize
function when making these changes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 12
🤖 Fix all issues with AI agents
In @includes/API/API.php:
- Around line 301-303: The validate_callback closures (anonymous functions used
for 'validate_callback') declare unused $request and $key parameters which
static analysis flags; remove the unused parameters from each closure so they
accept only $param (apply to the four occurrences shown), or replace all four
with a single instance method like validate_numeric_id that takes one argument
and returns is_numeric($param) and reference it via ['validate_callback' => [
$this, 'validate_numeric_id' ]] in each endpoint to eliminate the extra
parameters and duplicate logic.
- Around line 338-351: The endpoint registered via register_rest_route for
'/ai-summary/generate' currently uses permission_callback => '__return_true',
allowing unauthenticated callers to invoke generate_ai_summary; change the
permission_callback to validate the request (e.g., verify REST nonce and
current_user_can or is_user_logged_in), or implement a custom permission
callback that checks wp_verify_nonce from the request headers and capability
checks (e.g., current_user_can('edit_posts')) and return WP_Error on failure;
additionally add optional rate-limiting/caching in generate_ai_summary (use
transients keyed by user ID or IP) to throttle repeated calls and prevent abuse.
In @src/blocks/AISummary/block.json:
- Around line 1-10: The PR includes a new block source manifest
"wedocs/ai-summary" (src/blocks/AISummary/block.json) but the PR
title/description claims only rebuilt assets under assets/build/*; update the PR
to accurately reflect that you are adding a new block source (or split this
commit into a separate PR that adds the new block and keep the current PR
limited to build artifacts), and ensure the review summary and changelog mention
the new block manifest ("AI-Powered Summary") so reviewers/users are not
surprised.
In @src/blocks/AISummary/edit.js:
- Around line 75-97: Remove or gate noisy console logging that may leak PII:
eliminate the console.log calls in loadSavedSummary (the "No saved summary
found..." log) and the other debug logs around generating AI summaries (lines
~110-131). Either delete those console.log statements or wrap them behind a
debug flag/ENV check (e.g., DEBUG or NODE_ENV/WORDPRESS_DEBUG) so they only run
in non-production/development builds; keep error handling and use processLogger
or setError for user-visible errors instead of unconditional console output.
- Around line 99-176: The generateSummary/clearSummary/saveCurrentSummary
handlers currently show a generic AI error; first use the editor data store
getCurrentPostType() (select('core/editor').getCurrentPostType()) to
early-return with setError('AI Summary is available only for Docs') and avoid
calling apiFetch when post type !== 'docs', and also use that same post-type
check to disable the UI buttons where they are rendered; additionally, in each
catch block (in generateSummary, clearSummary, saveCurrentSummary) parse the
apiFetch error (inspect err.code or err.status and err.message) and when a
404/invalid-post-type is detected replace the generic message with
setError(__('AI Summary is available only for Docs', 'wedocs-plugin')) otherwise
keep the existing AI-config error message.
- Around line 10-11: The import currently uses the deprecated
__experimentalBoxControl; replace that import with the stable BoxControl from
'@wordpress/components' (i.e., swap __experimentalBoxControl for BoxControl in
the import list), or better yet, if the component only manipulates
border-radius, import and use BorderRadiusControl instead of a generic box
control; update any usage of __experimentalBoxControl in the AISummary edit
component to the chosen stable symbol (BoxControl or BorderRadiusControl) and
adjust props names if needed to match the stable API.
In @src/blocks/AISummary/view.js:
- Around line 66-75: The click handler attached to generateBtn disables the
button and applies "Generating..." styling but only restores these UI states in
the error path, causing the button to remain disabled/styled after a successful
generation; update the async click handler (the anonymous function added via
generateBtn.addEventListener('click', async function() { ... })) to ensure the
generateBtn.disabled, generateBtn.textContent, generateBtn.style.opacity and
generateBtn.style.cursor are restored on success as well (or alternatively
hide/remove the button once content is inserted); perform the restoration in a
finally-like block or after the success branch so both success and error paths
reset the UI, and apply the same fix to the similar code block around lines
93-104 that manipulates the same generateBtn state.
- Around line 62-126: The code injects untrusted data.summary into the DOM via
contentDiv.innerHTML = `<div>${data.summary}</div>`, creating an XSS risk;
change both the generate flow handler (the generateBtn click listener) and the
load flow that use the same pattern to assign the summary as plain text (use
contentDiv.textContent or equivalent DOM text node insertion for data.summary)
or, if HTML is required, pass data.summary through a vetted sanitizer before
setting innerHTML; also ensure server-side sanitization of the summary output to
double-check malicious content.
In @src/blocks/FontSizeSwitcher/view.js:
- Around line 61-64: The parseInt calls in the FontSizeSwitcher view are missing
an explicit radix; update parseInt(size) and parseInt(defaultSize) to use radix
10 (e.g., parseInt(size, 10) and parseInt(defaultSize, 10)) so newSize and
defaultSizeNum are reliably decimal, and likewise change the other occurrences
where parseInt(currentSize) is used (the calls that feed currentSize at lines
referenced) to parseInt(currentSize, 10); keep the rest of the logic
(scaleFactor calculation, newSize/defaultSizeNum usage) the same.
- Around line 33-36: Wrap all localStorage accesses in try-catch blocks so
SecurityError or other exceptions don't break the font size switcher: when
reading the key (storageKey / localStorage.getItem used to set storedSize and
currentSize) catch errors and fall back to defaultSize, and for every
localStorage.setItem call (the calls around lines where setItem is used to
persist changes) catch and silently handle/log the error so the UI still updates
without throwing; keep using the same storageKey and currentSize logic but guard
every getItem/setItem with try-catch and a safe fallback.
🧹 Nitpick comments (12)
includes/API/API.php (2)
1493-1511: Extract duplicate post validation logic.The same validation code appears in all four AI summary methods (lines 1496-1503, 1526-1533, 1555-1562, 1584-1591), checking that the post exists and has type
'docs'. This duplication violates the DRY principle and increases maintenance burden.♻️ Proposed refactor: Create a validation helper method
Add a private helper method after the existing
get_doc()method (around line 889):/** * Validate that a post ID corresponds to a valid docs post. * * @since 2.0.0 * * @param int $post_id Post ID to validate * * @return WP_Post|WP_Error Post object on success, WP_Error on failure */ private function validate_docs_post( $post_id ) { $post = get_post( $post_id ); if ( ! $post || $post->post_type !== 'docs' ) { return new WP_Error( 'wedocs_invalid_doc', __( 'Invalid documentation post.', 'wedocs' ), [ 'status' => 404 ] ); } return $post; }Then simplify each method:
public function get_ai_summary( $request ) { $post_id = (int) $request->get_param( 'id' ); - - $post = get_post( $post_id ); - if ( ! $post || $post->post_type !== 'docs' ) { - return new WP_Error( - 'wedocs_invalid_doc', - __( 'Invalid documentation post.', 'wedocs' ), - [ 'status' => 404 ] - ); - } + + $post = $this->validate_docs_post( $post_id ); + if ( is_wp_error( $post ) ) { + return $post; + } $summary = get_post_meta( $post_id, '_wedocs_ai_summary', true );Apply the same pattern to
save_ai_summary(),delete_ai_summary(), andgenerate_ai_summary().Also applies to: 1522-1541, 1552-1570, 1581-1591
1665-1666: Consider making AI summary parameters configurable.The
max_tokens(500) andtemperature(0.5) are hardcoded specifically for summary generation. While the inline comments explain the rationale, these values might benefit from being configurable or defined as constants for easier adjustment.💡 Optional improvement: Define as class constants or settings
Option 1: Define as class constants near the top of the class:
/** * AI summary generation parameters */ const AI_SUMMARY_MAX_TOKENS = 500; const AI_SUMMARY_TEMPERATURE = 0.5;Then use:
$response = $this->make_ai_api_call( $selected_provider, $provider_endpoint_config, $selected_model, $provider_config['api_key'], $prompt, $system_prompt, - 500, // Shorter max tokens for summaries - 0.5 // Lower temperature for more focused summaries + self::AI_SUMMARY_MAX_TOKENS, + self::AI_SUMMARY_TEMPERATURE );Option 2: Add to AI settings configuration to allow admin customization.
src/blocks/FontSizeSwitcher/view.js (4)
42-54: Extract duplicate text element selector to a constant.The selector
'p, li, span, div, a, td, th, h1, h2, h3, h4, h5, h6, blockquote, pre, code'appears at both line 46 and line 67. This violates the DRY principle and makes maintenance harder.♻️ Proposed refactor
At the top of the block iteration (after line 17), add:
+ const TEXT_ELEMENTS_SELECTOR = 'p, li, span, div, a, td, th, h1, h2, h3, h4, h5, h6, blockquote, pre, code'; + // Find the content elementThen use it in both places:
- const textElements = contentElement.querySelectorAll('p, li, span, div, a, td, th, h1, h2, h3, h4, h5, h6, blockquote, pre, code'); + const textElements = contentElement.querySelectorAll(TEXT_ELEMENTS_SELECTOR);
107-134: Consider tracking active button to avoid iteration.Lines 125-126 reset all buttons by iterating through them on every click. For a small number of buttons this is fine, but tracking the currently active button would eliminate unnecessary iterations.
♻️ Optional performance optimization
// Button controls function initButtonControls() { const buttons = block.querySelectorAll('.font-size-option'); + let activeButton = null; buttons.forEach(button => { const size = button.getAttribute('data-size'); const label = button.getAttribute('data-label'); // Highlight active button if (size === currentSize) { + activeButton = button; setActiveButton(button); } button.addEventListener('click', () => { currentSize = size; applyFontSize(size); localStorage.setItem(storageKey, size); // Update active state - buttons.forEach(btn => resetButtonStyle(btn)); + if (activeButton) { + resetButtonStyle(activeButton); + } + activeButton = button; setActiveButton(button); // Update current display if (currentDisplay) { currentDisplay.textContent = `(${label})`; } }); }); }
213-225: Make size labels configurable instead of hard-coded.The hard-coded
sizeMap(lines 215-221) doesn't align with potentially custom font size options. If the block uses different sizes or labels, the display will show incorrect text.Consider reading size labels from the block's configuration (data attributes) or deriving them from the fontSizeOptions defined in
block.json. This would ensure labels match the actual configured options.Based on the AI summary,
block.jsonincludes afontSizeOptionsattribute that likely contains label information.
227-241: Consider using CSS classes instead of inline styles.The helper functions directly manipulate inline styles. Using CSS classes (e.g.,
.active,.filled) would be more maintainable and allow easier theming via stylesheets.This is a minor code quality suggestion; the current implementation works correctly.
src/blocks/AISummary/block.json (2)
11-40: Avoid duplicating coresupportswith parallel custom attrs (style conflicts + poor Global Styles interop).You enable
supports.color/typography/spacing, but also storebackgroundColor,textColor,titleFontSize,padding,margin, etc. as custom attributes. This can conflict with Gutenberg’s standardstyle/preset handling and makes theme/global-style integration harder. Consider relying onsupports+styleattribute (or rename custom attrs to avoid reserved meanings likebackgroundColor/textColor).Also applies to: 74-119
4-9: Consider restricting block usage to the docs post type (endpoints are/wp/v2/docs/...).As-is, the block can be inserted anywhere, but the edit/view code targets
/wp/v2/docs/${postId}/ai-summary. If the block is intended only for weDocs docs, consider addingpostTypestoblock.json(or make the editor UX gracefully handle non-doc contexts).Also applies to: 46-49, 122-126
src/blocks/AISummary/edit.js (2)
49-72: Reduce duplicatesetAttributescalls in the init effect (avoid extra renders).You call
setAttributesup to twice in the same effect; consider batching into a single object update when bothblockIdandpostIdneed setting.
133-152: Prefer a WP modal/confirm component overwindow.confirmfor editor accessibility/consistency.
confirm()works, but it’s a jarring UX in wp-admin and limits styling/accessibility patterns. Consider a small<Modal>confirmation flow.src/blocks/AISummary/view.js (2)
41-59: GuardlocalStorageusage (can throw in some browsers/contexts).
localStorage.setItem/getItemcan throw (blocked storage, quota, privacy modes). Wrap intry/catchso the block doesn’t break on click or init.
12-28: Normalize/validatepostIdbefore building REST paths.You treat
postIdas a string and only loosely validate withisNaN. Considerconst docId = Number.parseInt(postId, 10); if (!Number.isFinite(docId)) return;and usedocIdin URLs to avoid odd edge cases.Also applies to: 63-64, 128-131
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
includes/API/API.phpsrc/blocks/AISummary/block.jsonsrc/blocks/AISummary/edit.jssrc/blocks/AISummary/save.jssrc/blocks/AISummary/view.jssrc/blocks/FontSizeSwitcher/view.js
🚧 Files skipped from review as they are similar to previous changes (1)
- src/blocks/AISummary/save.js
🧰 Additional context used
🧬 Code graph analysis (3)
src/blocks/AISummary/edit.js (2)
src/blocks/AISummary/save.js (2)
attributes(4-22)blockProps(55-69)src/blocks/AISummary/view.js (6)
blockId(51-51)postId(12-12)response(77-84)response(139-145)isCollapsible(10-10)isOpenByDefault(11-11)
includes/API/API.php (3)
assets/js/frontend.js (1)
param(61-61)includes/functions.php (1)
wedocs_get_ai_provider_configs(617-668)src/blocks/AISummary/view.js (2)
response(77-84)response(139-145)
src/blocks/AISummary/view.js (2)
src/blocks/AISummary/edit.js (3)
postId(65-65)error(45-45)loadSavedSummary(75-97)src/blocks/DocActions/view.js (1)
originalText(197-197)
🪛 PHPMD (2.15.0)
includes/API/API.php
345-345: Avoid unused parameters such as '$request'. (undefined)
(UnusedFormalParameter)
345-345: Avoid unused parameters such as '$key'. (undefined)
(UnusedFormalParameter)
🔇 Additional comments (4)
src/blocks/FontSizeSwitcher/view.js (4)
9-31: LGTM! Good graceful handling.The configuration reading with fallback defaults and the early-return pattern when content elements are not found is well-implemented.
91-104: LGTM!The initial font size application and display style routing logic is clean and well-structured.
188-210: LGTM!The dropdown controls implementation is straightforward and correct.
243-246: LGTM!The display initialization properly checks for stored values before updating.
| 'validate_callback' => function( $param, $request, $key ) { | ||
| return is_numeric( $param ); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove unused parameters from validate callbacks.
The validation closures declare $request and $key parameters but only use $param. While PHP allows this, it creates unnecessary noise flagged by static analysis tools.
♻️ Proposed fix: Remove unused parameters
'id' => [
- 'validate_callback' => function( $param, $request, $key ) {
+ 'validate_callback' => function( $param ) {
return is_numeric( $param );
}
],Apply this change to all four occurrences (lines 301-303, 313-315, 330-332, 345-347).
Alternatively, extract to a reusable method:
private function validate_numeric_id( $param ) {
return is_numeric( $param );
}Then reference it as 'validate_callback' => [ $this, 'validate_numeric_id' ] in each endpoint.
Also applies to: 313-315, 330-332, 345-347
🤖 Prompt for AI Agents
In @includes/API/API.php around lines 301 - 303, The validate_callback closures
(anonymous functions used for 'validate_callback') declare unused $request and
$key parameters which static analysis flags; remove the unused parameters from
each closure so they accept only $param (apply to the four occurrences shown),
or replace all four with a single instance method like validate_numeric_id that
takes one argument and returns is_numeric($param) and reference it via
['validate_callback' => [ $this, 'validate_numeric_id' ]] in each endpoint to
eliminate the extra parameters and duplicate logic.
| register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)/ai-summary/generate', [ | ||
| [ | ||
| 'methods' => WP_REST_Server::CREATABLE, | ||
| 'callback' => [ $this, 'generate_ai_summary' ], | ||
| 'permission_callback' => '__return_true', // Allow public access for frontend generation | ||
| 'args' => [ | ||
| 'id' => [ | ||
| 'validate_callback' => function( $param, $request, $key ) { | ||
| return is_numeric( $param ); | ||
| } | ||
| ], | ||
| ], | ||
| ], | ||
| ] ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical security issue: Public AI generation endpoint enables abuse.
The /ai-summary/generate endpoint uses __return_true for permission checks, allowing unauthenticated users to trigger AI API calls. This creates multiple vulnerabilities:
- Cost explosion: Attackers can make unlimited requests, consuming API quotas and incurring significant charges
- DoS vector: Flooding this endpoint can exhaust rate limits and degrade service
- No accountability: Anonymous requests cannot be traced or throttled per-user
The frontend code sends a nonce header, but the endpoint doesn't validate it due to __return_true.
🔒 Proposed fix: Require authentication and consider rate limiting
At minimum, change the permission callback to validate authentication:
register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)/ai-summary/generate', [
[
'methods' => WP_REST_Server::CREATABLE,
'callback' => [ $this, 'generate_ai_summary' ],
- 'permission_callback' => '__return_true', // Allow public access for frontend generation
+ 'permission_callback' => [ $this, 'ai_generate_permissions_check' ],
'args' => [
'id' => [
'validate_callback' => function( $param, $request, $key ) {
return is_numeric( $param );
}
],
],
],
] );Additionally, consider implementing:
- Rate limiting per user/IP (e.g., via transients keyed by user ID or IP address)
- Caching to prevent regenerating summaries within a time window
- Capability checks if this should be restricted to editors/admins only
🧰 Tools
🪛 PHPMD (2.15.0)
345-345: Avoid unused parameters such as '$request'. (undefined)
(UnusedFormalParameter)
345-345: Avoid unused parameters such as '$key'. (undefined)
(UnusedFormalParameter)
🤖 Prompt for AI Agents
In @includes/API/API.php around lines 338 - 351, The endpoint registered via
register_rest_route for '/ai-summary/generate' currently uses
permission_callback => '__return_true', allowing unauthenticated callers to
invoke generate_ai_summary; change the permission_callback to validate the
request (e.g., verify REST nonce and current_user_can or is_user_logged_in), or
implement a custom permission callback that checks wp_verify_nonce from the
request headers and capability checks (e.g., current_user_can('edit_posts')) and
return WP_Error on failure; additionally add optional rate-limiting/caching in
generate_ai_summary (use transients keyed by user ID or IP) to throttle repeated
calls and prevent abuse.
| { | ||
| "$schema": "https://schemas.wp.org/trunk/block.json", | ||
| "apiVersion": 3, | ||
| "name": "wedocs/ai-summary", | ||
| "version": "1.0.0", | ||
| "title": "AI-Powered Summary", | ||
| "category": "text", | ||
| "icon": "lightbulb", | ||
| "description": "Display an AI-generated summary of documentation content with collapsible functionality.", | ||
| "example": {}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PR scope mismatch: this adds new source (not “build artifacts only”).
PR objectives say only rebuilt assets/build/*, but this introduces a new block source manifest. Please reconcile the PR description/scope (or split PRs) so reviewers/users aren’t surprised.
🤖 Prompt for AI Agents
In @src/blocks/AISummary/block.json around lines 1 - 10, The PR includes a new
block source manifest "wedocs/ai-summary" (src/blocks/AISummary/block.json) but
the PR title/description claims only rebuilt assets under assets/build/*; update
the PR to accurately reflect that you are adding a new block source (or split
this commit into a separate PR that adds the new block and keep the current PR
limited to build artifacts), and ensure the review summary and changelog mention
the new block manifest ("AI-Powered Summary") so reviewers/users are not
surprised.
| "typography": { | ||
| "fontSize": true, | ||
| "lineHeight": true, | ||
| "__experimentalFontFamily": true, | ||
| "__experimentalFontWeight": true, | ||
| "__experimentalDefaultControls": { | ||
| "fontSize": true | ||
| } | ||
| }, | ||
| "spacing": { | ||
| "margin": true, | ||
| "padding": true, | ||
| "__experimentalDefaultControls": { | ||
| "padding": true, | ||
| "margin": true | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# First, let's explore the repository structure to find version requirements
find . -maxdepth 2 -type f \( -name "package.json" -o -name "README.md" -o -name "composer.json" -o -name "*.md" \) | head -20Repository: weDevsOfficial/wedocs-plugin
Length of output: 114
🏁 Script executed:
# Check for WordPress version requirements in common files
rg "wordpress|requires|version" -t md -t json --max-count=10 | head -30Repository: weDevsOfficial/wedocs-plugin
Length of output: 1980
🏁 Script executed:
# Look at the block.json file to understand apiVersion and other context
cat -n src/blocks/AISummary/block.json | head -50Repository: weDevsOfficial/wedocs-plugin
Length of output: 1461
🌐 Web query:
Gutenberg block.json typography __experimentalFontFamily __experimentalFontWeight support stable WordPress version
💡 Result:
Short answer: The experimental keys (__experimentalFontFamily, __experimentalFontWeight) were mapped to stable supports (fontFamily, fontWeight) and landed in WordPress core as part of the stabilization work included in WordPress 6.5. [1][2]
Sources
- WordPress Trac ticket describing the mapping of experimental typography supports to stable keys. [1]
- WP developer docs / WP_Theme_JSON code showing the stabilized/theme-json schema changes (since 6.5.0) and typography support usage. [2][3]
References
- core.trac.wordpress.org ticket #61728 (stabilize experimental block supports). [1]
- WP_Theme_JSON class / developer handbook (theme.json typography and schema, since 6.5.0). [2][3]
🌐 Web query:
Gutenberg block supports spacing __experimentalDefaultControls stability WordPress 6.0+
💡 Result:
Short answer:
- Spacing support (supports.spacing: margin, padding, blockGap) is a stable block supports API and available in WordPress 6.0+ (docs). [1][2]
- The experimental flag __experimentalDefaultControls is planned to be stabilized/renamed to defaultControls (tracked in core Trac ticket #61728 and Gutenberg issues); the mapping/stabilization work is documented but required syncing from Gutenberg into core. Use the stable key defaultControls when your WP core version/export supports it; otherwise continue using the experimental name until your WP version includes the core change. [3][4]
Sources:
- Block supports — spacing (WordPress Block Editor Handbook). [1]
- Layout / spacing changes (WordPress core notes, layout refactor / block-level spacing). [2]
- Trac ticket #61728 — mapping __experimentalDefaultControls → defaultControls and stabilization plan. [3]
- Gutenberg discussions/PRs referenced from the ticket (stabilization tracking). [3]
🏁 Script executed:
# Check composer.json for WordPress version requirement
cat -n composer.jsonRepository: weDevsOfficial/wedocs-plugin
Length of output: 816
🏁 Script executed:
# Check readme.md for WordPress compatibility requirements
head -100 readme.md | cat -nRepository: weDevsOfficial/wedocs-plugin
Length of output: 2242
🏁 Script executed:
# Find the main plugin file
find . -maxdepth 1 -name "*.php" -type fRepository: weDevsOfficial/wedocs-plugin
Length of output: 84
🏁 Script executed:
# Check the main plugin file header for WordPress version requirements
head -50 wedocs.php 2>/dev/null || head -50 index.php 2>/dev/null || head -50 weDocs.php 2>/dev/null || rg "Requires:\s*WordPress|minimum.*wordpress" -i -A 2 --max-count=5Repository: weDevsOfficial/wedocs-plugin
Length of output: 1728
🌐 Web query:
WordPress 6.5 typography fontFamily fontWeight block supports stable
💡 Result:
Short answer: Yes — in WordPress 6.5 the typography fontFamily support was stabilized (mapped from __experimentalFontFamily → fontFamily) and font-weight controls (fontWeight) are available via the theme.json / block supports system.
Sources:
- Trac ticket documenting stabilization mapping (__experimentalFontFamily → fontFamily). [1]
- Block supports / typography reference in the Block Editor Handbook (shows typography block support and how supports extend attributes). [2]
- Theme/typography docs showing theme.json typography options including fontFamilies and fontWeight. [3]
References:
[1] https://core.trac.wordpress.org/ticket/61728
[2] https://developer.wordpress.org/block-editor/reference-guides/block-api/block-supports/
[3] https://developer.wordpress.org/themes/global-settings-and-styles/settings/typography/
🌐 Web query:
Gutenberg __experimentalDefaultControls when will stabilize defaultControls WordPress 2025
💡 Result:
Short answer: It has not been stabilized in WordPress core as of January 9, 2026, and there is no public date published for promoting __experimentalDefaultControls to a stable API. See WordPress/Gutenberg release notes and discussions for stabilized APIs and reverts. [1][2][3]
Quick pointers to track progress:
- Follow the Make WordPress Core and WordPress Developer blog posts (stabilizations are announced there). [1][2]
- Watch the Gutenberg repository and PR/issue mentions of "__experimentalDefaultControls" on GitHub for any timeline or PR to remove the experimental prefix. (No such stabilization PR was published as of today.) [3]
Sources:
- Make WordPress Core — Miscellaneous Block Editor changes (stabilizations summary). [1]
- WordPress Developer Blog — developer updates about stabilized/reverted experimental block supports. [2]
- WordPress.org support / examples showing __experimentalDefaultControls usage (still experimental). [3]
Address the one remaining experimental key and declare minimum WordPress version.
Two of the three experimental keys have been stabilized in WordPress 6.5 (__experimentalFontFamily and __experimentalFontWeight now use stable fontFamily and fontWeight). However, __experimentalDefaultControls in spacing remains experimental with no announced stabilization timeline as of January 2026.
More importantly, since the plugin declares no minimum WordPress version requirement, clarify which version(s) this block targets. The plugin is tested with WordPress 6.5, but if it supports older versions, the typography keys may not behave as expected in those releases. Either declare a minimum WordPress requirement or add version-specific handling for the unresolved spacing.__experimentalDefaultControls key.
| __experimentalBoxControl as BoxControl | ||
| } from '@wordpress/components'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# First, let's check if the file exists and see the actual imports
fd -t f "edit.js" | grep AISummaryRepository: weDevsOfficial/wedocs-plugin
Length of output: 100
🏁 Script executed:
# Check the imports and usage around lines 10-11 and 389-395
if [ -f "src/blocks/AISummary/edit.js" ]; then
echo "=== Lines 1-20 (context around line 10-11) ==="
head -20 src/blocks/AISummary/edit.js
echo ""
echo "=== Lines 385-400 (context around line 389-395) ==="
sed -n '385,400p' src/blocks/AISummary/edit.js
fiRepository: weDevsOfficial/wedocs-plugin
Length of output: 1232
🏁 Script executed:
# Check package.json for @wordpress/components version
if [ -f "package.json" ]; then
jq '.dependencies."@wordpress/components" // .devDependencies."@wordpress/components"' package.json
fiRepository: weDevsOfficial/wedocs-plugin
Length of output: 82
🌐 Web query:
__experimentalBoxControl @wordpress/components API stability 2025
💡 Result:
Short answer: __experimentalBoxControl was stabilized in 2024–2025 and is deprecated — use the stable BoxControl import instead. The experimental export remains available for compatibility but is marked deprecated and may show warnings; migrate your code to import { BoxControl } from '@wordpress/components'. [1][2]
Sources:
- Announcements noting BoxControl stabilization and deprecation of the __experimental export (WordPress Core posts, Oct 2024 & Mar 2025). [1][2]
🌐 Web query:
@wordpress/components BoxControl stable alternative border radius
💡 Result:
Use BorderRadiusControl (the dedicated border-radius control) instead of using BoxControl for radius. BorderRadiusControl has been available in Gutenberg/WordPress component releases and the components package updates note stabilizing/updating the border-related controls (BorderBoxControl / BorderControl / BorderRadiusControl). [1][2][3]
Sources:
- @wordpress/components changelog (notes about BoxControl/BorderBoxControl/BorderControl updates). [1]
- BorderControl docs (notes that radius is a separate concern / shape). [2]
- Make WordPress Core posts listing stabilization and updates to border-related controls. [3]
🌐 Web query:
__experimentalBoxControl deprecated replacement WordPress Gutenberg
💡 Result:
Short answer: __experimentalBoxControl was stabilized — use BoxControl instead. Import it from @wordpress/components (or the stabilized package export shown in the release notes). [1][2]
Example:
- Old (experimental): import { __experimentalBoxControl as BoxControl } from '@wordpress/components';
- New (stable): import { BoxControl } from '@wordpress/components'. [1][2]
Sources:
- Make WordPress Core — Updates to user-interface components (notes about BoxControl stabilization). [1]
- Make WordPress Core / Editor components updates (BoxControl promoted to stable). [2]
Update to the stable BoxControl import; the __experimentalBoxControl API is now stable.
__experimentalBoxControl was stabilized in 2024–2025. Replace the import with import { BoxControl } from '@wordpress/components' to eliminate the deprecated experimental export. Alternatively, for border-radius specifically, consider using the dedicated BorderRadiusControl import, which is semantically more appropriate for this use case than a generic box control.
🤖 Prompt for AI Agents
In @src/blocks/AISummary/edit.js around lines 10 - 11, The import currently uses
the deprecated __experimentalBoxControl; replace that import with the stable
BoxControl from '@wordpress/components' (i.e., swap __experimentalBoxControl for
BoxControl in the import list), or better yet, if the component only manipulates
border-radius, import and use BorderRadiusControl instead of a generic box
control; update any usage of __experimentalBoxControl in the AISummary edit
component to the chosen stable symbol (BoxControl or BorderRadiusControl) and
adjust props names if needed to match the stable API.
| // Handle AI summary generation on frontend | ||
| if (!hasContent && postId) { | ||
| const generateBtn = block.querySelector('.ai-summary-generate-btn'); | ||
| if (generateBtn) { | ||
| generateBtn.addEventListener('click', async function() { | ||
| // Prevent multiple clicks | ||
| if (generateBtn.disabled) return; | ||
|
|
||
| generateBtn.disabled = true; | ||
| const originalText = generateBtn.textContent; | ||
| generateBtn.textContent = generateBtn.getAttribute('data-generating-text') || 'Generating...'; | ||
| generateBtn.style.opacity = '0.6'; | ||
| generateBtn.style.cursor = 'not-allowed'; | ||
|
|
||
| try { | ||
| const response = await fetch(`/wp-json/wp/v2/docs/${postId}/ai-summary/generate`, { | ||
| method: 'POST', | ||
| credentials: 'same-origin', | ||
| headers: { | ||
| 'Content-Type': 'application/json', | ||
| 'X-WP-Nonce': window.wpApiSettings?.nonce || '' | ||
| } | ||
| }); | ||
|
|
||
| if (!response.ok) { | ||
| const errorData = await response.json().catch(() => ({})); | ||
| throw new Error(errorData.message || 'Failed to generate summary'); | ||
| } | ||
|
|
||
| const data = await response.json(); | ||
|
|
||
| if (data.success && data.summary) { | ||
| // Replace the placeholder with the generated summary | ||
| const contentDiv = block.querySelector('.ai-summary-content'); | ||
| if (contentDiv) { | ||
| contentDiv.innerHTML = `<div>${data.summary}</div>`; | ||
| block.setAttribute('data-has-content', 'true'); | ||
| block.classList.remove('no-summary'); | ||
| block.classList.add('has-summary'); | ||
| } | ||
| } else { | ||
| throw new Error('No summary was generated'); | ||
| } | ||
| } catch (error) { | ||
| console.error('AI Summary generation error:', error); | ||
|
|
||
| // Show error message | ||
| const contentDiv = block.querySelector('.ai-summary-content'); | ||
| if (contentDiv) { | ||
| const errorMsg = document.createElement('p'); | ||
| errorMsg.style.color = '#d63638'; | ||
| errorMsg.style.fontSize = '14px'; | ||
| errorMsg.textContent = error.message || 'Failed to generate summary. Please try again later.'; | ||
| contentDiv.appendChild(errorMsg); | ||
| } | ||
|
|
||
| // Re-enable button | ||
| generateBtn.disabled = false; | ||
| generateBtn.textContent = originalText; | ||
| generateBtn.style.opacity = '1'; | ||
| generateBtn.style.cursor = 'pointer'; | ||
| } | ||
| }); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical: XSS risk from injecting data.summary via innerHTML.
Both the generate flow and load flow do contentDiv.innerHTML = \
. If data.summary` contains HTML (or attacker-controlled content via compromised endpoint/meta), this becomes script injection on the frontend.
At minimum, render as text (textContent). If you truly need HTML, sanitize with a robust HTML sanitizer (and also sanitize server-side).
Proposed safer default (render as text)
- contentDiv.innerHTML = `<div>${data.summary}</div>`;
+ const wrapper = document.createElement('div');
+ wrapper.textContent = data.summary;
+ contentDiv.replaceChildren(wrapper);Also applies to: 151-160
🤖 Prompt for AI Agents
In @src/blocks/AISummary/view.js around lines 62 - 126, The code injects
untrusted data.summary into the DOM via contentDiv.innerHTML =
`<div>${data.summary}</div>`, creating an XSS risk; change both the generate
flow handler (the generateBtn click listener) and the load flow that use the
same pattern to assign the summary as plain text (use contentDiv.textContent or
equivalent DOM text node insertion for data.summary) or, if HTML is required,
pass data.summary through a vetted sanitizer before setting innerHTML; also
ensure server-side sanitization of the summary output to double-check malicious
content.
| generateBtn.addEventListener('click', async function() { | ||
| // Prevent multiple clicks | ||
| if (generateBtn.disabled) return; | ||
|
|
||
| generateBtn.disabled = true; | ||
| const originalText = generateBtn.textContent; | ||
| generateBtn.textContent = generateBtn.getAttribute('data-generating-text') || 'Generating...'; | ||
| generateBtn.style.opacity = '0.6'; | ||
| generateBtn.style.cursor = 'not-allowed'; | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: generate button stays disabled / “Generating…” styling after success.
On success you never restore generateBtn.disabled, text, opacity, or cursor (you only restore on error). Either restore state or remove/hide the button once content is present.
One possible fix (restore on success)
if (data.success && data.summary) {
// Replace the placeholder with the generated summary
const contentDiv = block.querySelector('.ai-summary-content');
if (contentDiv) {
- contentDiv.innerHTML = `<div>${data.summary}</div>`;
+ const wrapper = document.createElement('div');
+ wrapper.textContent = data.summary;
+ contentDiv.replaceChildren(wrapper);
block.setAttribute('data-has-content', 'true');
block.classList.remove('no-summary');
block.classList.add('has-summary');
}
+ // Restore button UI (or alternatively: remove the button)
+ generateBtn.disabled = false;
+ generateBtn.textContent = originalText;
+ generateBtn.style.opacity = '1';
+ generateBtn.style.cursor = 'pointer';
} else {Also applies to: 93-104
🤖 Prompt for AI Agents
In @src/blocks/AISummary/view.js around lines 66 - 75, The click handler
attached to generateBtn disables the button and applies "Generating..." styling
but only restores these UI states in the error path, causing the button to
remain disabled/styled after a successful generation; update the async click
handler (the anonymous function added via generateBtn.addEventListener('click',
async function() { ... })) to ensure the generateBtn.disabled,
generateBtn.textContent, generateBtn.style.opacity and generateBtn.style.cursor
are restored on success as well (or alternatively hide/remove the button once
content is inserted); perform the restoration in a finally-like block or after
the success branch so both success and error paths reset the UI, and apply the
same fix to the similar code block around lines 93-104 that manipulates the same
generateBtn state.
| // Get stored font size from localStorage | ||
| const storageKey = 'wedocs-font-size'; | ||
| const storedSize = localStorage.getItem(storageKey); | ||
| let currentSize = storedSize || defaultSize; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add error handling for localStorage access.
Accessing localStorage can throw a SecurityError in private browsing mode or when storage is disabled. This will break the entire font size switcher functionality.
🛡️ Proposed fix to wrap localStorage access in try-catch
// Get stored font size from localStorage
const storageKey = 'wedocs-font-size';
- const storedSize = localStorage.getItem(storageKey);
+ let storedSize = null;
+ try {
+ storedSize = localStorage.getItem(storageKey);
+ } catch (e) {
+ console.warn('Font Size Switcher: localStorage access denied', e);
+ }
let currentSize = storedSize || defaultSize;Apply the same pattern to all localStorage.setItem() calls at lines 122, 155, 166, 180, and 202.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Get stored font size from localStorage | |
| const storageKey = 'wedocs-font-size'; | |
| const storedSize = localStorage.getItem(storageKey); | |
| let currentSize = storedSize || defaultSize; | |
| // Get stored font size from localStorage | |
| const storageKey = 'wedocs-font-size'; | |
| let storedSize = null; | |
| try { | |
| storedSize = localStorage.getItem(storageKey); | |
| } catch (e) { | |
| console.warn('Font Size Switcher: localStorage access denied', e); | |
| } | |
| let currentSize = storedSize || defaultSize; |
🤖 Prompt for AI Agents
In @src/blocks/FontSizeSwitcher/view.js around lines 33 - 36, Wrap all
localStorage accesses in try-catch blocks so SecurityError or other exceptions
don't break the font size switcher: when reading the key (storageKey /
localStorage.getItem used to set storedSize and currentSize) catch errors and
fall back to defaultSize, and for every localStorage.setItem call (the calls
around lines where setItem is used to persist changes) catch and silently
handle/log the error so the UI still updates without throwing; keep using the
same storageKey and currentSize logic but guard every getItem/setItem with
try-catch and a safe fallback.
| // Calculate the scale factor from default size | ||
| const newSize = parseInt(size); | ||
| const defaultSizeNum = parseInt(defaultSize); | ||
| const scaleFactor = newSize / defaultSizeNum; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Specify radix parameter for parseInt calls.
Lines 62 and 63 use parseInt() without specifying a radix. While unlikely with px values, omitting the radix can lead to unexpected octal interpretation of strings with leading zeros.
🔧 Proposed fix
// Calculate the scale factor from default size
- const newSize = parseInt(size);
- const defaultSizeNum = parseInt(defaultSize);
+ const newSize = parseInt(size, 10);
+ const defaultSizeNum = parseInt(defaultSize, 10);
const scaleFactor = newSize / defaultSizeNum;Apply the same fix to lines 144, 161, and 175 where parseInt(currentSize) is used.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Calculate the scale factor from default size | |
| const newSize = parseInt(size); | |
| const defaultSizeNum = parseInt(defaultSize); | |
| const scaleFactor = newSize / defaultSizeNum; | |
| // Calculate the scale factor from default size | |
| const newSize = parseInt(size, 10); | |
| const defaultSizeNum = parseInt(defaultSize, 10); | |
| const scaleFactor = newSize / defaultSizeNum; |
🤖 Prompt for AI Agents
In @src/blocks/FontSizeSwitcher/view.js around lines 61 - 64, The parseInt calls
in the FontSizeSwitcher view are missing an explicit radix; update
parseInt(size) and parseInt(defaultSize) to use radix 10 (e.g., parseInt(size,
10) and parseInt(defaultSize, 10)) so newSize and defaultSizeNum are reliably
decimal, and likewise change the other occurrences where parseInt(currentSize)
is used (the calls that feed currentSize at lines referenced) to
parseInt(currentSize, 10); keep the rest of the logic (scaleFactor calculation,
newSize/defaultSizeNum usage) the same.
| if (decreaseBtn) { | ||
| decreaseBtn.addEventListener('click', () => { | ||
| const current = parseInt(currentSize); | ||
| if (current > 12) { | ||
| const newSize = (current - 2) + 'px'; | ||
| currentSize = newSize; | ||
| applyFontSize(newSize); | ||
| localStorage.setItem(storageKey, newSize); | ||
| if (slider) slider.value = current - 2; | ||
| updateDisplay(newSize); | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| if (increaseBtn) { | ||
| increaseBtn.addEventListener('click', () => { | ||
| const current = parseInt(currentSize); | ||
| if (current < 28) { | ||
| const newSize = (current + 2) + 'px'; | ||
| currentSize = newSize; | ||
| applyFontSize(newSize); | ||
| localStorage.setItem(storageKey, newSize); | ||
| if (slider) slider.value = current + 2; | ||
| updateDisplay(newSize); | ||
| } | ||
| }); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extract hard-coded font size limits and step to configurable attributes.
Lines 162, 176 hard-code minimum (12) and maximum (28) font sizes, and lines 163, 177 hard-code the step size (2). These should be configurable via data attributes to allow flexibility per block instance.
⚙️ Proposed fix to make limits configurable
After line 17, add:
+ const minSize = parseInt(block.getAttribute('data-min-size') || '12', 10);
+ const maxSize = parseInt(block.getAttribute('data-max-size') || '28', 10);
+ const stepSize = parseInt(block.getAttribute('data-step-size') || '2', 10);Then update the decrease and increase handlers:
if (decreaseBtn) {
decreaseBtn.addEventListener('click', () => {
const current = parseInt(currentSize);
- if (current > 12) {
- const newSize = (current - 2) + 'px';
+ if (current > minSize) {
+ const newSize = (current - stepSize) + 'px';
currentSize = newSize;
applyFontSize(newSize);
localStorage.setItem(storageKey, newSize);
- if (slider) slider.value = current - 2;
+ if (slider) slider.value = current - stepSize;
updateDisplay(newSize);
}
});
}
if (increaseBtn) {
increaseBtn.addEventListener('click', () => {
const current = parseInt(currentSize);
- if (current < 28) {
- const newSize = (current + 2) + 'px';
+ if (current < maxSize) {
+ const newSize = (current + stepSize) + 'px';
currentSize = newSize;
applyFontSize(newSize);
localStorage.setItem(storageKey, newSize);
- if (slider) slider.value = current + 2;
+ if (slider) slider.value = current + stepSize;
updateDisplay(newSize);
}
});
}Also update block.json to include these new attributes.
Committable suggestion skipped: line range outside the PR's diff.
This pull request updates the build artifacts for the WordPress weDocs block, specifically the compiled JavaScript and asset manifest files. The changes are the result of a new build and do not include any direct source code modifications.
assets/build/block.js: Rebuilt the block JavaScript bundle, reflecting recent changes in the source code or dependencies.assets/build/block.asset.php: Updated the asset manifest, likely to reflect a new version hash and/or dependency changes due to the new build.Summary by CodeRabbit
New Features
Chores
✏️ Tip: You can customize this high-level summary in your review settings.