feature: FAQ section and shortcode (recover #309)#316
Conversation
Register wedocs_faq post type, wedocs_faq_group taxonomy, and associated meta fields. Add FAQ admin submenu page with React SPAusing tiptap rich text editor for FAQ content management.
Strips config.bat .gitignore entry and captcha-config.php dropper introduced from a compromised dev machine. See recovery report.
|
Warning Rate limit exceeded
You’ve run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. WalkthroughA complete FAQ management feature is added to the weDocs plugin with WordPress post type and taxonomy registration, a React-based admin interface for creating and organizing FAQ groups and items, a frontend shortcode that renders FAQs as accordions with smooth animations, Tiptap-based rich-text editing, and integration into general settings. ChangesFAQ Feature Implementation
Estimated Code Review Effort🎯 4 (Complex) | ⏱️ ~60 minutes Suggested Labels
Suggested Reviewers
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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.
Actionable comments posted: 13
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
includes/Admin/Menu.php (1)
132-139:⚠️ Potential issue | 🟠 Major | ⚡ Quick winAvoid wiping all existing
wedocssubmenu registrations.Line 134 resets
$submenu['wedocs']unconditionally, which removes submenu entries registered earlier viaadd_submenu_page( 'wedocs', ... )by integrations/extensions. Preserve unknown existing entries and only replace known core slugs.Suggested patch
- $submenu['wedocs'] = array(); // phpcs:ignore. + $existing_submenus = isset( $submenu['wedocs'] ) ? $submenu['wedocs'] : array(); + $known_slugs = array( 'wedocs' ); + + foreach ( $all_submenus as $item ) { + if ( isset( $item[2] ) ) { + $known_slugs[] = $item[2]; + } + } + + $preserved_submenus = array_values( + array_filter( + $existing_submenus, + static function ( $item ) use ( $known_slugs ) { + return isset( $item[2] ) && ! in_array( $item[2], $known_slugs, true ); + } + ) + ); + + $submenu['wedocs'] = array(); // phpcs:ignore. - array_push( - $submenu['wedocs'], - ...$all_submenus - ); + array_push( $submenu['wedocs'], ...$all_submenus, ...$preserved_submenus );🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@includes/Admin/Menu.php` around lines 132 - 139, The code currently unconditionally wipes $submenu['wedocs'] which removes registrations from integrations; instead, preserve unknown existing submenu entries and only replace/update known core slugs: read the existing $submenu['wedocs'] if set, build a set of core slugs from $all_submenus (or the known core keys), filter the existing entries to keep those whose slug is not in the core set, then merge the filtered existing entries with $all_submenus and assign back to $submenu['wedocs'] (use array_push/array_merge semantics to append or replace as appropriate) so add_submenu_page registrations from third parties remain intact.
🧹 Nitpick comments (2)
assets/js/faq.js (1)
14-31: ⚖️ Poor tradeoffConsider guarding against rapid clicks for smoother UX.
Clicking rapidly while an animation is in progress can queue multiple
transitionendlisteners or cause animations to reverse abruptly. While the current implementation won't break, the user experience could feel janky. Consider tracking animation state and ignoring clicks during transitions.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@assets/js/faq.js` around lines 14 - 31, The click handler should guard against rapid clicks by tracking an animation-in-progress flag and using a once-bound transition listener: add a boolean (e.g., isAnimating) scoped alongside summary/details/answer, return early from summary.addEventListener if isAnimating is true, set isAnimating = true when starting either open or close animation, and in the transitionend handler set details.open appropriately, set isAnimating = false, and rely on answer.addEventListener('transitionend', handler, { once: true }) to avoid queuing multiple handlers; update references to summary, details, answer and the existing transitionend handler accordingly.assets/css/faq.css (1)
71-78: Grid-template-rows animation is a modern progressive enhancement.The accordion animation using
grid-template-rowstransition is supported in Chrome 107+, Firefox 66+, and Safari 16+. Older browsers will still display functional accordions without the smooth animation, which is an acceptable progressive enhancement approach.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@assets/css/faq.css` around lines 71 - 78, The accordion currently uses grid-template-rows transitions on .wedocs-faq-item__answer which is a progressive enhancement—keep that but add a backwards-compatible fallback by also implementing a max-height + overflow:hidden transition on .wedocs-faq-item__answer (initially max-height:0) and set a suitable large max-height on .wedocs-faq-item[open] .wedocs-faq-item__answer so older browsers animate via max-height while modern browsers continue using grid-template-rows; ensure both transitions are declared (transition: grid-template-rows 0.3s ease, max-height 0.3s ease) so unsupported engines gracefully use the max-height fallback.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@assets/js/faq.js`:
- Around line 6-7: The code assigns summary and answer from
details.querySelector without checking for null, which can cause TypeError if
DOM nodes are missing; update the logic around the variables summary and answer
in assets/js/faq.js to guard their usage by verifying each is non-null before
accessing properties or attaching event listeners (e.g., skip processing this
details entry or bail out early when summary or answer is null), and ensure any
downstream code that assumes summary/answer exists is similarly protected.
In `@includes/Frontend.php`:
- Around line 70-71: The wp_register_style and wp_register_script calls use
filemtime(...) directly which can emit warnings or return false if the files are
missing; update the version argument for both 'wedocs-faq' registrations to
first check existence of WEDOCS_PATH . '/assets/css/faq.css' and WEDOCS_PATH .
'/assets/js/faq.js' respectively (via file_exists or is_readable) and use the
filemtime value when present, otherwise fall back to a safe fallback (e.g. null
or a stable fallback like time() or a constant) to avoid warnings and incorrect
versioning.
In `@includes/Post_Types.php`:
- Around line 242-248: FAQ groups sharing the same registered meta key 'order'
(registered via register_term_meta for 'wedocs_faq_group') need a deterministic
secondary sort so lists and shortcode output don't drift; update all places that
retrieve/queried these terms (admin listing and shortcode rendering) to order by
the numeric meta_value ('order') first and then a stable fallback (e.g., term
name or term_id) as a secondary ORDERBY (for example: ORDER BY meta_value_num
ASC, name ASC or term_id ASC), and ensure any get_terms/get_terms_args or
WP_Term_Query calls that currently only sort by the 'order' meta include this
secondary sort; no change needed to the 'register_term_meta' registration itself
other than ensuring the meta key remains 'order' and is numeric.
- Around line 204-209: The custom taxonomy capabilities (manage_doc_terms,
edit_doc_terms, delete_doc_terms) used by the wedocs_faq_group taxonomy are
never granted, causing 403s; update the capability bootstrap function
wedocs_user_documentation_handling_capabilities() to explicitly add these three
capabilities to the Administrator and Editor roles (use
$role->add_cap('manage_doc_terms'), $role->add_cap('edit_doc_terms'),
$role->add_cap('delete_doc_terms')), keeping the existing assign_terms mapping
unchanged so REST operations for FAQ groups succeed.
In `@readme.txt`:
- Around line 57-58: The Markdown link for "Full Gutenberg Support" is split
across two lines, breaking rendering; locate the link text "Full Gutenberg
Support" and its URL fragment and put the entire Markdown link (the text,
opening [, URL, and closing )) on a single line so the link is contiguous (e.g.,
change the broken multi-line link to one line: **[Full Gutenberg
Support](https://wedocs.co/docs/wedocs/full-site-editing/)**).
In `@src/faq/assets/faq.css`:
- Around line 1-3: The Tailwind at-rules (`@tailwind` base; `@tailwind` components;
`@tailwind` utilities;) in src/faq/assets/faq.css trigger stylelint errors because
the current .stylelintrc.json extends stylelint-config-standard-scss which flags
unknown at-rules; fix by either updating .stylelintrc.json to disable the
scss/at-rule-no-unknown rule for Tailwind or (preferable) install and configure
the stylelint-tailwindcss plugin so Tailwind at-rules are recognized; update the
config to include the plugin and/or add a rule override for
"scss/at-rule-no-unknown": [true, { "ignoreAtRules": ["tailwind", "apply",
"variants", "screen", "layer"] }] so the `@tailwind` lines in faq.css no longer
fail.
In `@src/faq/components/AddFaqGroupModal.js`:
- Around line 78-96: openMediaUploader currently calls wp.media unguarded which
can throw a ReferenceError if media scripts aren't enqueued; update
openMediaUploader to first check for the existence of wp and wp.media and, if
missing, call setApiError with a friendly message (e.g., "Media uploader is
unavailable on this screen") and return early; otherwise proceed to create
mediaUploader and open it as before — modify the openMediaUploader function to
perform this guard and use setApiError for user-visible error handling.
In `@src/faq/components/EmptyFaq.js`:
- Line 11: In the EmptyFaq component replace the invalid Tailwind class
"align-center" with "items-center" in the div that currently has className
including "flex" (the container div in EmptyFaq.js) so the flex children are
centered on the cross axis; update the className string to use "items-center"
alongside "flex" and any existing spacing classes.
In `@src/faq/components/FaqGroupRow.js`:
- Around line 209-224: The current loop that clones FAQs (iterating originalFaqs
and POSTing via apiFetch) can leave a partially populated duplicated group if
one POST fails; modify the cloning code to perform the loop inside a try/catch,
collect created FAQ IDs into an array (e.g., createdFaqIds) as each apiFetch
POST returns, and on any catch call apiFetch DELETE for each created FAQ and
then DELETE the duplicated group (use duplicated.id and the '/wp/v2/wedocs-faqs'
and '/wp/v2/wedocs-faq-groups' endpoints) to roll back; alternatively, defer
exposing the duplicated group by creating it in a non-public state (or without
final association) and only PATCH/associate it with duplicated.id after all FAQ
POSTs succeed.
- Around line 205-207: The current API fetch for originalFaqs uses per_page=100
without paging, so groups with >100 FAQs are only partially processed; update
both flows that call the fetch (the duplicate flow that assigns originalFaqs and
the delete flow) to iterate over pages: call apiFetch with page=1,2,... (keeping
per_page=100) and accumulate results into originalFaqs until a page returns
fewer than per_page items, then proceed with the existing logic that clones or
deletes items for the full accumulated list (use the same group.id and existing
variable originalFaqs to replace the single-page result).
In `@src/faq/components/FaqItem.js`:
- Around line 61-65: The handleSave function in FaqItem.js currently only checks
question.trim() and isSaving; update it to validate answer the same way
AddFaqForm enforces both fields by adding a check that answer.trim() is not
empty before proceeding (return early if empty), and ensure any UI/notification
behavior on invalid input matches AddFaqForm; locate and modify the handleSave
function and its use of question, answer, and isSaving to enforce consistent
validation.
In `@src/faq/components/TiptapEditor.js`:
- Around line 330-355: TiptapEditor currently initializes the editor with
useEditor but never syncs when the content prop changes, causing stale internal
state; add a useEffect that watches the content prop and, when editor is
available, calls editor.commands.setContent(content || '') (or setHTML if you
prefer) to update the editor state, making sure to ignore updates that are
identical to avoid resetting selection; reference TiptapEditor, useEditor,
editor, content and onUpdate in your change.
In `@templates/admin/faq.php`:
- Around line 1-2: Add a proper page heading inside the existing .wrap container
(before the <div id="wedocs-faq-app">) to satisfy accessibility and WP
conventions; insert an <h1> that outputs the admin page title (e.g. echo
esc_html( get_admin_page_title() ) or esc_html_e('FAQ', 'your-text-domain')) so
the .wrap container has a top-level heading for screen readers and heading
hierarchy.
---
Outside diff comments:
In `@includes/Admin/Menu.php`:
- Around line 132-139: The code currently unconditionally wipes
$submenu['wedocs'] which removes registrations from integrations; instead,
preserve unknown existing submenu entries and only replace/update known core
slugs: read the existing $submenu['wedocs'] if set, build a set of core slugs
from $all_submenus (or the known core keys), filter the existing entries to keep
those whose slug is not in the core set, then merge the filtered existing
entries with $all_submenus and assign back to $submenu['wedocs'] (use
array_push/array_merge semantics to append or replace as appropriate) so
add_submenu_page registrations from third parties remain intact.
---
Nitpick comments:
In `@assets/css/faq.css`:
- Around line 71-78: The accordion currently uses grid-template-rows transitions
on .wedocs-faq-item__answer which is a progressive enhancement—keep that but add
a backwards-compatible fallback by also implementing a max-height +
overflow:hidden transition on .wedocs-faq-item__answer (initially max-height:0)
and set a suitable large max-height on .wedocs-faq-item[open]
.wedocs-faq-item__answer so older browsers animate via max-height while modern
browsers continue using grid-template-rows; ensure both transitions are declared
(transition: grid-template-rows 0.3s ease, max-height 0.3s ease) so unsupported
engines gracefully use the max-height fallback.
In `@assets/js/faq.js`:
- Around line 14-31: The click handler should guard against rapid clicks by
tracking an animation-in-progress flag and using a once-bound transition
listener: add a boolean (e.g., isAnimating) scoped alongside
summary/details/answer, return early from summary.addEventListener if
isAnimating is true, set isAnimating = true when starting either open or close
animation, and in the transitionend handler set details.open appropriately, set
isAnimating = false, and rely on answer.addEventListener('transitionend',
handler, { once: true }) to avoid queuing multiple handlers; update references
to summary, details, answer and the existing transitionend handler accordingly.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 9cc7e034-c0ac-47f6-a5df-387eb6f187c4
📒 Files selected for processing (25)
assets/css/faq.cssassets/js/faq.jsincludes/Admin/Menu.phpincludes/Assets.phpincludes/Frontend.phpincludes/Post_Types.phpincludes/Shortcode.phppackage.jsonreadme.txtsrc/components/Settings/GeneralSettings.jssrc/faq/assets/faq.csssrc/faq/components/AddFaqForm.jssrc/faq/components/AddFaqGroupModal.jssrc/faq/components/EmptyFaq.jssrc/faq/components/FaqApp.jssrc/faq/components/FaqConfirmDialog.jssrc/faq/components/FaqGroupRow.jssrc/faq/components/FaqItem.jssrc/faq/components/TiptapEditor.jssrc/faq/index.jstemplates/admin/faq.phptemplates/shortcode-faq.phptemplates/shortcode.phpwebpack.config.jswedocs.php
💤 Files with no reviewable changes (1)
- wedocs.php
| var summary = details.querySelector( '.wedocs-faq-item__question' ); | ||
| var answer = details.querySelector( '.wedocs-faq-item__answer' ); |
There was a problem hiding this comment.
Add null checks for querySelector results.
If the expected DOM structure is missing, querySelector will return null and subsequent property accesses will throw TypeError. Add defensive checks to prevent runtime errors.
🛡️ Proposed fix with null checks
document.querySelectorAll( '.wedocs-faq-item' ).forEach( function ( details ) {
var summary = details.querySelector( '.wedocs-faq-item__question' );
var answer = details.querySelector( '.wedocs-faq-item__answer' );
+
+ if ( ! summary || ! answer ) {
+ return;
+ }
// Items open by default need the inline style set so transitions work.
if ( details.open ) {📝 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.
| var summary = details.querySelector( '.wedocs-faq-item__question' ); | |
| var answer = details.querySelector( '.wedocs-faq-item__answer' ); | |
| var summary = details.querySelector( '.wedocs-faq-item__question' ); | |
| var answer = details.querySelector( '.wedocs-faq-item__answer' ); | |
| if ( ! summary || ! answer ) { | |
| return; | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@assets/js/faq.js` around lines 6 - 7, The code assigns summary and answer
from details.querySelector without checking for null, which can cause TypeError
if DOM nodes are missing; update the logic around the variables summary and
answer in assets/js/faq.js to guard their usage by verifying each is non-null
before accessing properties or attaching event listeners (e.g., skip processing
this details entry or bail out early when summary or answer is null), and ensure
any downstream code that assumes summary/answer exists is similarly protected.
| wp_register_style( 'wedocs-faq', WEDOCS_ASSETS . '/css/faq.css', [], filemtime( WEDOCS_PATH . '/assets/css/faq.css' ) ); | ||
| wp_register_script( 'wedocs-faq', WEDOCS_ASSETS . '/js/faq.js', [], filemtime( WEDOCS_PATH . '/assets/js/faq.js' ), true ); |
There was a problem hiding this comment.
Add error handling for filemtime() to prevent warnings if files are missing.
If the CSS or JS files don't exist, filemtime() returns false, which will be cast to an empty string or 0 for the version parameter. This could cause caching issues or PHP warnings in production.
🛡️ Proposed fix with fallback versioning
- wp_register_style( 'wedocs-faq', WEDOCS_ASSETS . '/css/faq.css', [], filemtime( WEDOCS_PATH . '/assets/css/faq.css' ) );
- wp_register_script( 'wedocs-faq', WEDOCS_ASSETS . '/js/faq.js', [], filemtime( WEDOCS_PATH . '/assets/js/faq.js' ), true );
+ wp_register_style( 'wedocs-faq', WEDOCS_ASSETS . '/css/faq.css', [], filemtime( WEDOCS_PATH . '/assets/css/faq.css' ) ?: WEDOCS_VERSION );
+ wp_register_script( 'wedocs-faq', WEDOCS_ASSETS . '/js/faq.js', [], filemtime( WEDOCS_PATH . '/assets/js/faq.js' ) ?: WEDOCS_VERSION, true );📝 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-faq', WEDOCS_ASSETS . '/css/faq.css', [], filemtime( WEDOCS_PATH . '/assets/css/faq.css' ) ); | |
| wp_register_script( 'wedocs-faq', WEDOCS_ASSETS . '/js/faq.js', [], filemtime( WEDOCS_PATH . '/assets/js/faq.js' ), true ); | |
| wp_register_style( 'wedocs-faq', WEDOCS_ASSETS . '/css/faq.css', [], filemtime( WEDOCS_PATH . '/assets/css/faq.css' ) ?: WEDOCS_VERSION ); | |
| wp_register_script( 'wedocs-faq', WEDOCS_ASSETS . '/js/faq.js', [], filemtime( WEDOCS_PATH . '/assets/js/faq.js' ) ?: WEDOCS_VERSION, true ); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@includes/Frontend.php` around lines 70 - 71, The wp_register_style and
wp_register_script calls use filemtime(...) directly which can emit warnings or
return false if the files are missing; update the version argument for both
'wedocs-faq' registrations to first check existence of WEDOCS_PATH .
'/assets/css/faq.css' and WEDOCS_PATH . '/assets/js/faq.js' respectively (via
file_exists or is_readable) and use the filemtime value when present, otherwise
fall back to a safe fallback (e.g. null or a stable fallback like time() or a
constant) to avoid warnings and incorrect versioning.
| 'capabilities' => [ | ||
| 'manage_terms' => 'manage_doc_terms', | ||
| 'edit_terms' => 'edit_doc_terms', | ||
| 'delete_terms' => 'delete_doc_terms', | ||
| 'assign_terms' => 'edit_docs', | ||
| ], |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n -C3 --type=php '\b(manage_doc_terms|edit_doc_terms|delete_doc_terms|edit_docs)\b'Repository: weDevsOfficial/wedocs-plugin
Length of output: 5150
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Get context around the capabilities array in functions.php
sed -n '550,600p' includes/functions.php | cat -nRepository: weDevsOfficial/wedocs-plugin
Length of output: 2063
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Search for add_cap, grant, or other methods that might be granting these capabilities
rg -n 'add_cap|grant|map_meta_cap' includes/functions.php | head -30Repository: weDevsOfficial/wedocs-plugin
Length of output: 138
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Check if there are any other files that might grant these capabilities
rg -l 'manage_doc_terms|edit_doc_terms|delete_doc_terms' --type=phpRepository: weDevsOfficial/wedocs-plugin
Length of output: 96
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Find where the capability function is hooked/called
rg -n 'wedocs_user_documentation_handling_capabilities' --type=phpRepository: weDevsOfficial/wedocs-plugin
Length of output: 546
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Check for any map_meta_cap filters that might delegate capability checks
rg -n 'map_meta_cap|user_has_cap' --type=php -A 2Repository: weDevsOfficial/wedocs-plugin
Length of output: 708
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Check the register_faq_taxonomy function more thoroughly to see if there's capability mapping
sed -n '182,220p' includes/Post_Types.php | cat -nRepository: weDevsOfficial/wedocs-plugin
Length of output: 1960
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# View the Capability.php file to understand the delegation logic
cat -n includes/Capability.phpRepository: weDevsOfficial/wedocs-plugin
Length of output: 2179
The FAQ taxonomy custom capabilities are never granted to any role, causing REST operations to fail with 403 errors.
The taxonomy wedocs_faq_group defines custom capabilities (manage_doc_terms, edit_doc_terms, delete_doc_terms) for term management, but these are never granted anywhere in the codebase. The capability bootstrap function wedocs_user_documentation_handling_capabilities() only grants post-related capabilities to administrator and editor roles. Without these taxonomy capabilities explicitly granted, users will receive 403 errors when attempting to create, edit, or delete FAQ groups via the REST API, even though the routes are exposed.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@includes/Post_Types.php` around lines 204 - 209, The custom taxonomy
capabilities (manage_doc_terms, edit_doc_terms, delete_doc_terms) used by the
wedocs_faq_group taxonomy are never granted, causing 403s; update the capability
bootstrap function wedocs_user_documentation_handling_capabilities() to
explicitly add these three capabilities to the Administrator and Editor roles
(use $role->add_cap('manage_doc_terms'), $role->add_cap('edit_doc_terms'),
$role->add_cap('delete_doc_terms')), keeping the existing assign_terms mapping
unchanged so REST operations for FAQ groups succeed.
| register_term_meta( 'wedocs_faq_group', 'order', [ | ||
| 'type' => 'integer', | ||
| 'single' => true, | ||
| 'default' => 0, | ||
| 'show_in_rest' => true, | ||
| 'sanitize_callback' => 'absint', | ||
| ] ); |
There was a problem hiding this comment.
Add a deterministic fallback when FAQ groups share the same order.
New groups default to order = 0, so equal-order groups currently keep whatever incoming query order happened to be used. That can make the admin list and shortcode output drift until every group has been explicitly re-ordered.
💡 Suggested fix
usort( $terms, function ( $a, $b ) {
$order_a = (int) get_term_meta( $a->term_id, 'order', true );
$order_b = (int) get_term_meta( $b->term_id, 'order', true );
- return $order_a - $order_b;
+ if ( $order_a === $order_b ) {
+ return $a->term_id <=> $b->term_id;
+ }
+
+ return $order_a <=> $order_b;
} );Also applies to: 289-293
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@includes/Post_Types.php` around lines 242 - 248, FAQ groups sharing the same
registered meta key 'order' (registered via register_term_meta for
'wedocs_faq_group') need a deterministic secondary sort so lists and shortcode
output don't drift; update all places that retrieve/queried these terms (admin
listing and shortcode rendering) to order by the numeric meta_value ('order')
first and then a stable fallback (e.g., term name or term_id) as a secondary
ORDERBY (for example: ORDER BY meta_value_num ASC, name ASC or term_id ASC), and
ensure any get_terms/get_terms_args or WP_Term_Query calls that currently only
sort by the 'order' meta include this secondary sort; no change needed to the
'register_term_meta' registration itself other than ensuring the meta key
remains 'order' and is numeric.
| – **[Full Gutenberg Support](https://wedocs.co/docs/wedocs/full-site-editing/ | ||
| )**: Design and customize your documentation using Gutenberg blocks with complete flexibility. |
There was a problem hiding this comment.
Keep the Markdown link on one line to avoid broken rendering.
At Line 57–58, the URL is split before ). Some readme renderers won’t parse that as a valid link, which can leave this CTA unclickable.
Proposed fix
-– **[Full Gutenberg Support](https://wedocs.co/docs/wedocs/full-site-editing/
-)**: Design and customize your documentation using Gutenberg blocks with complete flexibility.
+– **[Full Gutenberg Support](https://wedocs.co/docs/wedocs/full-site-editing/)**: Design and customize your documentation using Gutenberg blocks with complete flexibility.📝 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.
| – **[Full Gutenberg Support](https://wedocs.co/docs/wedocs/full-site-editing/ | |
| )**: Design and customize your documentation using Gutenberg blocks with complete flexibility. | |
| – **[Full Gutenberg Support](https://wedocs.co/docs/wedocs/full-site-editing/)**: Design and customize your documentation using Gutenberg blocks with complete flexibility. |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@readme.txt` around lines 57 - 58, The Markdown link for "Full Gutenberg
Support" is split across two lines, breaking rendering; locate the link text
"Full Gutenberg Support" and its URL fragment and put the entire Markdown link
(the text, opening [, URL, and closing )) on a single line so the link is
contiguous (e.g., change the broken multi-line link to one line: **[Full
Gutenberg Support](https://wedocs.co/docs/wedocs/full-site-editing/)**).
| const originalFaqs = await apiFetch( { | ||
| path: `/wp/v2/wedocs-faqs?wedocs-faq-groups=${ group.id }&per_page=100&orderby=menu_order&order=asc&context=edit`, | ||
| } ); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's find the file and check its size
find . -name "FaqGroupRow.js" -type f
wc -l src/faq/components/FaqGroupRow.js 2>/dev/null || echo "File not found with exact path, searching..."Repository: weDevsOfficial/wedocs-plugin
Length of output: 146
🏁 Script executed:
# Search for the file more broadly
fd -i "faqgrouprow" --type fRepository: weDevsOfficial/wedocs-plugin
Length of output: 106
🏁 Script executed:
# If found, read the relevant sections of the file
cat -n src/faq/components/FaqGroupRow.js 2>/dev/null | sed -n '195,260p'Repository: weDevsOfficial/wedocs-plugin
Length of output: 2870
🏁 Script executed:
# Search for pagination patterns with page parameter in the codebase
rg "page.*\d|per_page" src/faq --type js -A 2 -B 2Repository: weDevsOfficial/wedocs-plugin
Length of output: 1680
🏁 Script executed:
# Check if there are any loops around apiFetch that might handle pagination
rg "while.*page|for.*page" src/faq --type js -A 3 -B 1Repository: weDevsOfficial/wedocs-plugin
Length of output: 324
🏁 Script executed:
# Look for other apiFetch calls that might use pagination
ast-grep --pattern 'apiFetch({
path: $_,
})' src/faq/components/FaqGroupRow.jsRepository: weDevsOfficial/wedocs-plugin
Length of output: 942
Implement pagination to handle groups with >100 FAQs in duplicate and delete operations.
Both the duplicate flow (lines 205-207) and delete flow (lines 246-248) use per_page=100 without pagination, causing groups larger than 100 FAQs to be partially processed:
- Duplicate: Only the first 100 FAQs are cloned; remaining FAQs are orphaned from the new group.
- Delete: Only the first 100 FAQs are deleted; the group deletion fails because child FAQs still exist.
Loop on the page parameter until fewer than per_page results are returned to process the full FAQ set.
Affected code locations
Lines 205-207 (duplicate flow)
Lines 246-248 (delete flow)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/faq/components/FaqGroupRow.js` around lines 205 - 207, The current API
fetch for originalFaqs uses per_page=100 without paging, so groups with >100
FAQs are only partially processed; update both flows that call the fetch (the
duplicate flow that assigns originalFaqs and the delete flow) to iterate over
pages: call apiFetch with page=1,2,... (keeping per_page=100) and accumulate
results into originalFaqs until a page returns fewer than per_page items, then
proceed with the existing logic that clones or deletes items for the full
accumulated list (use the same group.id and existing variable originalFaqs to
replace the single-page result).
| for ( const faq of originalFaqs ) { | ||
| await apiFetch( { | ||
| path: '/wp/v2/wedocs-faqs', | ||
| method: 'POST', | ||
| data: { | ||
| title: faq.title.raw, | ||
| content: faq.content.raw, | ||
| status: faq.status, | ||
| menu_order: faq.menu_order, | ||
| meta: { | ||
| _faq_open_by_default: faq.meta?._faq_open_by_default || false, | ||
| }, | ||
| 'wedocs-faq-groups': [ duplicated.id ], | ||
| }, | ||
| } ); | ||
| } |
There was a problem hiding this comment.
Duplication can leave partial data without compensation on failure.
If cloning fails mid-loop, the new group remains with only some FAQs copied. This creates inconsistent admin state.
Consider a compensating rollback in catch (delete any created FAQs + duplicated group), or defer exposing the duplicated group until clone completion.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/faq/components/FaqGroupRow.js` around lines 209 - 224, The current loop
that clones FAQs (iterating originalFaqs and POSTing via apiFetch) can leave a
partially populated duplicated group if one POST fails; modify the cloning code
to perform the loop inside a try/catch, collect created FAQ IDs into an array
(e.g., createdFaqIds) as each apiFetch POST returns, and on any catch call
apiFetch DELETE for each created FAQ and then DELETE the duplicated group (use
duplicated.id and the '/wp/v2/wedocs-faqs' and '/wp/v2/wedocs-faq-groups'
endpoints) to roll back; alternatively, defer exposing the duplicated group by
creating it in a non-public state (or without final association) and only
PATCH/associate it with duplicated.id after all FAQ POSTs succeed.
| const handleSave = async () => { | ||
| if ( question.trim() === '' || isSaving ) { | ||
| return; | ||
| } | ||
|
|
There was a problem hiding this comment.
Add answer validation to match creation requirements.
The save handler only validates that question is not empty but does not validate answer. In contrast, AddFaqForm (lines 23-25) requires both question and answer to be non-empty. This inconsistency allows users to edit an FAQ and save it with an empty answer, even though they cannot create one without an answer. This creates confusing UX and inconsistent data.
🛡️ Proposed fix to add answer validation
const handleSave = async () => {
- if ( question.trim() === '' || isSaving ) {
+ if ( question.trim() === '' || answer.trim() === '' || isSaving ) {
return;
}📝 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 handleSave = async () => { | |
| if ( question.trim() === '' || isSaving ) { | |
| return; | |
| } | |
| const handleSave = async () => { | |
| if ( question.trim() === '' || answer.trim() === '' || isSaving ) { | |
| return; | |
| } | |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/faq/components/FaqItem.js` around lines 61 - 65, The handleSave function
in FaqItem.js currently only checks question.trim() and isSaving; update it to
validate answer the same way AddFaqForm enforces both fields by adding a check
that answer.trim() is not empty before proceeding (return early if empty), and
ensure any UI/notification behavior on invalid input matches AddFaqForm; locate
and modify the handleSave function and its use of question, answer, and isSaving
to enforce consistent validation.
| const TiptapEditor = ( { content, onChange, placeholder, hasError, id } ) => { | ||
| const editor = useEditor( { | ||
| extensions: [ | ||
| StarterKit.configure( { | ||
| heading: { levels: [ 2, 3, 4 ] }, | ||
| } ), | ||
| Underline, | ||
| Link.configure( { | ||
| openOnClick: false, | ||
| HTMLAttributes: { rel: 'noopener noreferrer nofollow' }, | ||
| } ), | ||
| Placeholder.configure( { | ||
| placeholder: placeholder || __( 'Write your answer here...', 'wedocs' ), | ||
| } ), | ||
| TextAlign.configure( { | ||
| types: [ 'heading', 'paragraph' ], | ||
| } ), | ||
| Highlight, | ||
| ], | ||
| content: content || '', | ||
| onUpdate: ( { editor: ed } ) => { | ||
| const html = ed.getHTML(); | ||
| // Tiptap returns '<p></p>' for empty content. | ||
| onChange( html === '<p></p>' ? '' : html ); | ||
| }, | ||
| } ); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify whether TiptapEditor is used in flows where `content` can change after mount,
# and confirm there is currently no prop->editor sync.
rg -n -C3 --type=js --type=jsx '<TiptapEditor|content=' src
rg -n -C3 --type=js 'setContent\\(|useEffect\\(' src/faq/components/TiptapEditor.jsRepository: weDevsOfficial/wedocs-plugin
Length of output: 203
🏁 Script executed:
#!/bin/bash
# Search for TiptapEditor usage in the codebase
rg -n 'TiptapEditor' src --type js
# Search for useEffect or setContent in TiptapEditor.js
rg -n 'useEffect|setContent' src/faq/components/TiptapEditor.jsRepository: weDevsOfficial/wedocs-plugin
Length of output: 644
🏁 Script executed:
#!/bin/bash
# Check how TiptapEditor is used in FaqItem.js
sed -n '220,235p' src/faq/components/FaqItem.js
# Check how TiptapEditor is used in AddFaqForm.js
sed -n '100,115p' src/faq/components/AddFaqForm.js
# Check what the useEffect at line 38 in TiptapEditor.js does
sed -n '35,50p' src/faq/components/TiptapEditor.jsRepository: weDevsOfficial/wedocs-plugin
Length of output: 2192
🏁 Script executed:
#!/bin/bash
# Check how FaqItem is used - is it reused for editing multiple FAQ records?
rg -n -B5 -A10 'FaqItem' src --type js | head -100
# Check the component structure and state in FaqItem
sed -n '1,50p' src/faq/components/FaqItem.jsRepository: weDevsOfficial/wedocs-plugin
Length of output: 6533
Sync editor state when the content prop changes.
useEditor reads content only at initialization. When FaqItem receives an updated faq prop (e.g., after editing another FAQ in the list), the TiptapEditor doesn't update its internal state and will display stale HTML when editing begins, potentially overwriting the wrong answer on save.
Proposed fix
const TiptapEditor = ( { content, onChange, placeholder, hasError, id } ) => {
const editor = useEditor( {
@@
},
} );
+ useEffect( () => {
+ if ( ! editor ) {
+ return;
+ }
+
+ const next = content || '';
+ const current = editor.getHTML();
+ const normalizedCurrent = current === '<p></p>' ? '' : current;
+
+ if ( normalizedCurrent !== next ) {
+ editor.commands.setContent( next );
+ }
+ }, [ editor, content ] );
+
return (📝 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 TiptapEditor = ( { content, onChange, placeholder, hasError, id } ) => { | |
| const editor = useEditor( { | |
| extensions: [ | |
| StarterKit.configure( { | |
| heading: { levels: [ 2, 3, 4 ] }, | |
| } ), | |
| Underline, | |
| Link.configure( { | |
| openOnClick: false, | |
| HTMLAttributes: { rel: 'noopener noreferrer nofollow' }, | |
| } ), | |
| Placeholder.configure( { | |
| placeholder: placeholder || __( 'Write your answer here...', 'wedocs' ), | |
| } ), | |
| TextAlign.configure( { | |
| types: [ 'heading', 'paragraph' ], | |
| } ), | |
| Highlight, | |
| ], | |
| content: content || '', | |
| onUpdate: ( { editor: ed } ) => { | |
| const html = ed.getHTML(); | |
| // Tiptap returns '<p></p>' for empty content. | |
| onChange( html === '<p></p>' ? '' : html ); | |
| }, | |
| } ); | |
| const TiptapEditor = ( { content, onChange, placeholder, hasError, id } ) => { | |
| const editor = useEditor( { | |
| extensions: [ | |
| StarterKit.configure( { | |
| heading: { levels: [ 2, 3, 4 ] }, | |
| } ), | |
| Underline, | |
| Link.configure( { | |
| openOnClick: false, | |
| HTMLAttributes: { rel: 'noopener noreferrer nofollow' }, | |
| } ), | |
| Placeholder.configure( { | |
| placeholder: placeholder || __( 'Write your answer here...', 'wedocs' ), | |
| } ), | |
| TextAlign.configure( { | |
| types: [ 'heading', 'paragraph' ], | |
| } ), | |
| Highlight, | |
| ], | |
| content: content || '', | |
| onUpdate: ( { editor: ed } ) => { | |
| const html = ed.getHTML(); | |
| // Tiptap returns '<p></p>' for empty content. | |
| onChange( html === '<p></p>' ? '' : html ); | |
| }, | |
| } ); | |
| useEffect( () => { | |
| if ( ! editor ) { | |
| return; | |
| } | |
| const next = content || ''; | |
| const current = editor.getHTML(); | |
| const normalizedCurrent = current === '<p></p>' ? '' : current; | |
| if ( normalizedCurrent !== next ) { | |
| editor.commands.setContent( next ); | |
| } | |
| }, [ editor, content ] ); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/faq/components/TiptapEditor.js` around lines 330 - 355, TiptapEditor
currently initializes the editor with useEditor but never syncs when the content
prop changes, causing stale internal state; add a useEffect that watches the
content prop and, when editor is available, calls
editor.commands.setContent(content || '') (or setHTML if you prefer) to update
the editor state, making sure to ignore updates that are identical to avoid
resetting selection; reference TiptapEditor, useEditor, editor, content and
onUpdate in your change.
| <div class="wrap"> | ||
| <div id="wedocs-faq-app" class="wedocs-document wedocs-faq"></div> |
There was a problem hiding this comment.
Add a page title heading for accessibility and WordPress best practice.
WordPress admin pages should include an <h1> heading inside the .wrap container for proper heading hierarchy and screen reader navigation.
♿ Proposed fix
<div class="wrap">
+ <h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
<div id="wedocs-faq-app" class="wedocs-document wedocs-faq"></div>
</div>📝 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.
| <div class="wrap"> | |
| <div id="wedocs-faq-app" class="wedocs-document wedocs-faq"></div> | |
| <div class="wrap"> | |
| <h1><?php echo esc_html( get_admin_page_title() ); ?></h1> | |
| <div id="wedocs-faq-app" class="wedocs-document wedocs-faq"></div> | |
| </div> |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@templates/admin/faq.php` around lines 1 - 2, Add a proper page heading inside
the existing .wrap container (before the <div id="wedocs-faq-app">) to satisfy
accessibility and WP conventions; insert an <h1> that outputs the admin page
title (e.g. echo esc_html( get_admin_page_title() ) or esc_html_e('FAQ',
'your-text-domain')) so the .wrap container has a top-level heading for screen
readers and heading hierarchy.
Resolved conflicts via EOL-aware 3-way merge + per-file decisions: - wedocs.php require_once area: kept both block-styles + DocsGrid registrations. - tailwind.config.js (310): kept PR's CommonJS form (develop's import-tangled form is currently syntactically invalid). - Translations (.pot), build artefacts, .nvmrc: taken from develop. - Files where develop's implementation supersedes PR's intent: taken from develop (canonical).
Recovered from
sapayth's deleted fork.feature/faq_builder(preserved on fork asrecover/pr-309)refs/pull/309/headfrom base repo, pushed toarifulhoque7/wedocs-pluginSecurity note: any sapayth device-compromise payload (
config.bat.gitignoreentry,captcha-config.phpdropper) was stripped via a single cleanup commit on top before push. Branches without markers were pushed unchanged.Summary by CodeRabbit
Release Notes
New Features
Documentation