Skip to content

Conversation

@arifulhoque7
Copy link
Contributor

@arifulhoque7 arifulhoque7 commented Sep 25, 2025

πŸ“Œ PR Description Close issue

Feature: Convert Query-based Search to AJAX Search – WeDocs Docs Home

Summary:
This PR replaces the traditional query-based search with an AJAX-powered live search in the WeDocs documentation home page. The new search implementation provides a faster and smoother user experience by fetching results dynamically without a full page reload.

Changes Implemented:

  • Replaced query parameter–based search with AJAX calls.
  • Implemented live search dropdown that updates results as the user types.
  • Enhanced user experience with instant feedback and loading states.
  • Optimized search query for performance and reduced unnecessary database calls.
  • Added fallback for non-JS environments (graceful degradation).
  • Applied styling adjustments for the new search results container.
  • Ensured accessibility (keyboard navigation, ARIA roles for results).

Testing Steps:

  1. Navigate to the WeDocs documentation home page.
  2. Type in the search field and verify that results appear dynamically without page reload.
  3. Confirm that search results update with each keystroke.
  4. Test responsiveness on mobile, tablet, and desktop.
  5. Disable JavaScript and verify that query-based search still works as a fallback.
  6. Validate that existing documentation browsing functionality is unaffected.

Summary by CodeRabbit

  • New Features
    • Always enable the single-document search modal for easier in-page searching.
  • Bug Fixes
    • Prevent errors and broken states with safer handling when elements are missing.
    • Only mount the search modal when available, avoiding unintended DOM changes.
    • More reliable navigation highlighting and modal open/close behavior.
  • Style
    • Refined positioning and padding for the search input and button.
  • UI Changes
    • Search input is now read-only with a dedicated button to trigger the modal.

arifulhoque7 and others added 2 commits September 23, 2025 17:19
- Convert doc-search-form.php from form submission to modal-based search
- Remove Button Style and Button Size controls from PrintButton block
- Fix JavaScript null reference errors in frontend.js
- Enable search modal functionality on all pages (not just single docs)
- Add defensive programming to prevent querySelector errors
- Maintain original design while enabling quick search functionality
- Fix CSS positioning to keep search icon on right side

Changes:
- templates/doc-search-form.php: Added modal integration with CSS fixes
- assets/js/frontend.js: Fixed null reference errors and enabled modal on all pages
- includes/Frontend.php: Set isSingleDoc to true for universal modal access
- PrintButton block: Removed buttonStyle and buttonSize attributes and controls
@coderabbitai
Copy link

coderabbitai bot commented Sep 25, 2025

Walkthrough

Enables and guards the single-document search modal across the app, adds null-safe checks and error handling in frontend JS, adjusts PHP to always set isSingleDoc true, and updates the search form template with styling, a readonly input, and a button-based trigger.

Changes

Cohort / File(s) Summary of changes
Frontend JS safety and modal logic
assets/js/frontend.js
Adds null checks, optional chaining, and try/catch around search hit processing; removes early return to always init single-doc search; mounts modal only if weDocs_Vars.searchModal exists; guards show/close operations; verifies ancestor element before toggling classes.
Script localization behavior
includes/Frontend.php
Sets isSingleDoc to true unconditionally in Frontend::register_scripts; no signature changes.
Search form template tweaks
templates/doc-search-form.php
Adds inline styles; appends wedocs-single-search-input class; sets input readonly; changes submit input to a button element.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

A bunny taps keys with a gentle thrum,
Nulls are tamed, the errors go numb.
A modal hops in when the flags align,
The search field shimmersβ€”readonly fine.
With button clicks and softened light,
I nose-twitch, ship itβ€”everything’s right. πŸ‡βœ¨

Pre-merge checks and finishing touches

βœ… Passed checks (3 passed)
Check name Status Explanation
Description Check βœ… Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check βœ… Passed The title succinctly describes the central changeβ€”integration of the search form modalβ€”clearly reflecting the main purpose of this pull request without extraneous details.
Docstring Coverage βœ… Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • πŸ“ Generate Docstrings
πŸ§ͺ Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

❀️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
assets/js/frontend.js (2)

223-228: Potential runtime error: missing .search-clean element.

[0] on an empty jQuery set yields undefined; undefined.style will throw. Also treat whitespace as blank.

Apply:

-      $( e.target ).next( '.search-clean' )[0].style.display = `${ searchValue ? 'block' : 'none' }`;
+      const $clean = $( e.target ).next( '.search-clean' );
+      const hasValue = !!( searchValue && searchValue.trim().length );
+      if ( $clean.length ) {
+        $clean[0].style.display = hasValue ? 'block' : 'none';
+      }

231-254: Escape dynamic content to avoid XSS; prefer permalink over guid.

HTML is constructed with unescaped titles/URLs and guid (not reliable). Escape text and use permalink consistently.

Apply:

-      const ulNode = document.createElement( 'ul' );
+      const ulNode = document.createElement( 'ul' );
+      const esc = (s) => String(s ?? '').replace(/[&<>"']/g, (m) => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[m]));
...
-        let url = data?.article ? data?.article?.permalink :
+        let url = data?.article ? data?.article?.permalink :
           ( data?.section ? data?.section?.permalink : data?.parent?.permalink ),
           title = data?.article ? data?.article?.post_title :
             ( data?.section ? data?.section?.post_title : data?.parent?.post_title ),
           parentNavigation = data?.section ?
             `<div class='parent-doc-nav'>
               ${ weDocs_Vars.docNavLabel }
-              <span data-url="${ data?.parent?.guid }" class="doc-search-hit-path">
-                ${ this.extractedTitle( data?.parent?.post_title, 35 ) }
+              <span data-url="${ esc( data?.parent?.permalink ) }" class="doc-search-hit-path">
+                ${ esc( this.extractedTitle( data?.parent?.post_title, 35 ) ) }
               </span>
             </div>` : '',
           sectionNavigation = data?.article ?
             `<div class='section-doc-nav'>
               ${ weDocs_Vars.sectionNavLabel }
-              <span data-url="${ data?.section?.guid }" class="doc-search-hit-path">
-                ${ this.extractedTitle( data?.section?.post_title, 35 ) }
+              <span data-url="${ esc( data?.section?.permalink ) }" class="doc-search-hit-path">
+                ${ esc( this.extractedTitle( data?.section?.post_title, 35 ) ) }
               </span>
             </div>` : '';
...
-        title = this.extractedTitle( title );
+        title = this.extractedTitle( title );
...
-            href="${ url }"
+            href="${ esc( url ) }"
...
-                <div class="doc-search-hit-title">${ title }</div>
+                <div class="doc-search-hit-title">${ esc( title ) }</div>

Optional: build the DOM via createElement/textContent to avoid string concatenation entirely.

Also applies to: 265-291, 294-299

includes/Frontend.php (1)

83-90: isSingleDoc is hard-coded true but scripts still enqueue only on single docs.

JS now initializes the modal everywhere, but wedocs-scripts is only enqueued in enqueue_single_scripts() for is_singular('docs'). This likely prevents the new search from working on the docs home/archive.

Apply:

-    public function enqueue_single_scripts() {
-        if ( is_singular( 'docs' ) ) {
+    public function enqueue_single_scripts() {
+        if ( is_singular( 'docs' ) || is_post_type_archive( 'docs' ) ) {
             self::enqueue_assets();
         }
     }

If the search form can appear elsewhere, consider enqueuing when the form/template is rendered (action hook) or enqueue globally and lazy‑mount.

🧹 Nitpick comments (5)
templates/doc-search-form.php (1)

1-10: Move inline styles to the stylesheet (and avoid !important).

Inline <style> in a template complicates overrides and caching. Move these rules into assets/build/frontend.css (or relevant stylesheet) and drop !important by increasing selector specificity.

assets/js/frontend.js (3)

169-179: Prefer success over complete for parsing JSON; set docs only on success.

Avoid reading responseJSON on failures. Use the success callback and check resp.data.

Apply:

-      $.ajax({
-        data,
-        url      : weDocs_Vars.ajaxurl,
-        type     : 'POST',
-        error    : ( e ) => console.log( e ),
-        complete : ( response ) => {
-          if ( response?.responseJSON?.data ) {
-            this.docs = response?.responseJSON?.data;
-          }
-        },
-      });
+      $.ajax({
+        data,
+        url      : weDocs_Vars.ajaxurl,
+        type     : 'POST',
+        success  : ( resp ) => {
+          if ( resp?.data ) {
+            this.docs = resp.data;
+          }
+        },
+        error    : ( e ) => console.log( e ),
+      });

189-199: Prevent default browser shortcut on Ctrl/Cmd+K.

Some browsers use Ctrl/Cmd+K for search; prevent default to reliably open the modal.

Apply:

-        if ( ( event.ctrlKey || event.metaKey ) && event.key === 'k' ) {
-          this.showSinglePageSearchModal();
+        if ( ( event.ctrlKey || event.metaKey ) && event.key === 'k' ) {
+          event.preventDefault();
+          this.showSinglePageSearchModal();

206-211: Selector too strict; may miss non-text inputs.

Use a generic input selector and also trigger input event after clearing.

Apply:

-      $( this ).hide();
-      $( this ).prev( 'input[type="text"]' ).val( null ).focus();
+      $( this ).hide();
+      $( this ).prev( 'input' ).val( '' ).trigger( 'input' ).focus();
includes/Frontend.php (1)

78-90: Consider removing isSingleDoc if unused, or make it accurate.

With the JS gate removed, isSingleDoc may be dead weight. Either drop it or set it to the actual condition to prevent confusion.

πŸ“œ Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between c80fc84 and 309f0b7.

πŸ“’ Files selected for processing (3)
  • assets/js/frontend.js (7 hunks)
  • includes/Frontend.php (1 hunks)
  • templates/doc-search-form.php (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
assets/js/frontend.js (1)
src/utils/menuFix.js (1)
  • $ (2-2)
πŸ”‡ Additional comments (3)
assets/js/frontend.js (2)

181-187: LGTM: Guarded modal mount.

Mounting only when weDocs_Vars.searchModal exists avoids DOM pollution and errors.


440-444: LGTM: Auto-expand current ancestor.

Null-safe check and class toggling look correct.

includes/Frontend.php (1)

65-70: Versioning: use built asset’s mtime for wedocs-scripts (if applicable).

If the script is bundled to assets/build/frontend.js, prefer that path for cache-busting consistency; otherwise ignore.

Can you confirm whether assets/js/frontend.js is the served file in production? If not, we should switch the handle URL and version to the built asset.

Comment on lines +11 to +29
<form role='search' method='get' class='search-form wedocs-search-form wedocs-single-search-input' action='<?php echo esc_url( home_url( '/' ) ) ?>'>
<div class='wedocs-search-input'>
<input
name='s'
readonly
type='search'
class='search-field'
value='<?php get_search_query(); ?>'
title='<?php echo esc_attr_x( 'Search for:', 'label', 'wedocs' ); ?>'
placeholder='<?php echo esc_attr_x( 'Search for a topic or question', 'placeholder', 'wedocs' ); ?>'
/>
<input type='hidden' name='post_type' value='docs' />
<button type='submit' class='search-submit'>
<button type='button' class='search-submit'>
<svg width='15' height='16' fill='none'>
<path fill-rule='evenodd' d='M11.856 10.847l2.883 2.883a.89.89 0 0 1 0 1.257c-.173.174-.401.261-.629.261s-.455-.087-.629-.261l-2.883-2.883c-1.144.874-2.532 1.353-3.996 1.353a6.56 6.56 0 0 1-4.671-1.935c-2.576-2.575-2.576-6.765 0-9.341C3.179.934 4.839.247 6.603.247s3.424.687 4.671 1.935a6.56 6.56 0 0 1 1.935 4.67 6.55 6.55 0 0 1-1.353 3.995zM3.189 3.439c-1.882 1.882-1.882 4.945 0 6.827.912.912 2.124 1.414 3.414 1.414s2.502-.502 3.414-1.414 1.414-2.124 1.414-3.413-.502-2.502-1.414-3.413-2.124-1.414-3.414-1.414-2.502.502-3.414 1.414z' fill='#fff' />
</svg>
</button>
</div>
</form>
</form>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | πŸ”΄ Critical

Non‑JS fallback is broken: readonly input + type="button" prevents search.

With JS disabled, users can neither type nor submit. Remove readonly and make the button a submit, then have JS intercept to open the modal (progressive enhancement). Add basic a11y attributes.

Apply:

-<form role='search' method='get' class='search-form wedocs-search-form wedocs-single-search-input' action='<?php echo esc_url( home_url( '/' ) ) ?>'>
+<form role='search' method='get' class='search-form wedocs-search-form wedocs-single-search-input' action='<?php echo esc_url( home_url( '/' ) ) ?>'>

         <input
             name='s'
-            readonly
             type='search'
             class='search-field'
-            value='<?php get_search_query(); ?>'
+            value='<?php echo esc_attr( get_search_query() ); ?>'
+            aria-label='<?php echo esc_attr_x( 'Search the documentation', 'aria-label', 'wedocs' ); ?>'
             title='<?php echo esc_attr_x( 'Search for:', 'label', 'wedocs' ); ?>'
             placeholder='<?php echo esc_attr_x( 'Search for a topic or question', 'placeholder', 'wedocs' ); ?>'
         />
         <input type='hidden' name='post_type' value='docs' />
-        <button type='button' class='search-submit'>
+        <button type='submit' class='search-submit' aria-haspopup='dialog' aria-controls='wedocs-single-doc-search-modal' aria-expanded='false'>

In JS, prevent default on this form/button to open the modal when JS is available.

πŸ“ 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.

Suggested change
<form role='search' method='get' class='search-form wedocs-search-form wedocs-single-search-input' action='<?php echo esc_url( home_url( '/' ) ) ?>'>
<div class='wedocs-search-input'>
<input
name='s'
readonly
type='search'
class='search-field'
value='<?php get_search_query(); ?>'
title='<?php echo esc_attr_x( 'Search for:', 'label', 'wedocs' ); ?>'
placeholder='<?php echo esc_attr_x( 'Search for a topic or question', 'placeholder', 'wedocs' ); ?>'
/>
<input type='hidden' name='post_type' value='docs' />
<button type='submit' class='search-submit'>
<button type='button' class='search-submit'>
<svg width='15' height='16' fill='none'>
<path fill-rule='evenodd' d='M11.856 10.847l2.883 2.883a.89.89 0 0 1 0 1.257c-.173.174-.401.261-.629.261s-.455-.087-.629-.261l-2.883-2.883c-1.144.874-2.532 1.353-3.996 1.353a6.56 6.56 0 0 1-4.671-1.935c-2.576-2.575-2.576-6.765 0-9.341C3.179.934 4.839.247 6.603.247s3.424.687 4.671 1.935a6.56 6.56 0 0 1 1.935 4.67 6.55 6.55 0 0 1-1.353 3.995zM3.189 3.439c-1.882 1.882-1.882 4.945 0 6.827.912.912 2.124 1.414 3.414 1.414s2.502-.502 3.414-1.414 1.414-2.124 1.414-3.413-.502-2.502-1.414-3.413-2.124-1.414-3.414-1.414-2.502.502-3.414 1.414z' fill='#fff' />
</svg>
</button>
</div>
</form>
</form>
<form role='search' method='get' class='search-form wedocs-search-form wedocs-single-search-input' action='<?php echo esc_url( home_url( '/' ) ) ?>'>
<div class='wedocs-search-input'>
<input
name='s'
type='search'
class='search-field'
value='<?php echo esc_attr( get_search_query() ); ?>'
aria-label='<?php echo esc_attr_x( 'Search the documentation', 'aria-label', 'wedocs' ); ?>'
title='<?php echo esc_attr_x( 'Search for:', 'label', 'wedocs' ); ?>'
placeholder='<?php echo esc_attr_x( 'Search for a topic or question', 'placeholder', 'wedocs' ); ?>'
/>
<input type='hidden' name='post_type' value='docs' />
<button
type='submit'
class='search-submit'
aria-haspopup='dialog'
aria-controls='wedocs-single-doc-search-modal'
aria-expanded='false'
>
<svg width='15' height='16' fill='none'>
<path
fill-rule='evenodd'
d='M11.856 10.847l2.883 2.883a.89.89 0 0 1 0 1.257c-.173.174-.401.261-.629.261s-.455-.087-.629-.261l-2.883-2.883c-1.144.874-2.532 1.353-3.996 1.353a6.56 6.56 0 0 1-4.671-1.935c-2.576-2.575-2.576-6.765 0-9.341C3.179.934 4.839.247 6.603.247s3.424.687 4.671 1.935a6.56 6.56 0 0 1 1.935 4.67 6.55 6.55 0 0 1-1.353 3.995zM3.189 3.439c-1.882 1.882-1.882 4.945 0 6.827.912.912 2.124 1.414 3.414 1.414s2.502-.502 3.414-1.414 1.414-2.124 1.414-3.413-.502-2.502-1.414-3.413-2.124-1.414-3.414-1.414-2.502.502-3.414 1.414z'
fill='#fff'
/>
</svg>
</button>
</div>
</form>

Comment on lines 14 to 21
name='s'
readonly
type='search'
class='search-field'
value='<?php get_search_query(); ?>'
title='<?php echo esc_attr_x( 'Search for:', 'label', 'wedocs' ); ?>'
placeholder='<?php echo esc_attr_x( 'Search for a topic or question', 'placeholder', 'wedocs' ); ?>'
/>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | πŸ”΄ Critical

Fix missing echo and escape the search value.

get_search_query() returns a value; it isn't echoed. Also escape for attribute context.

Apply:

-            value='<?php get_search_query(); ?>'
+            value='<?php echo esc_attr( get_search_query() ); ?>'
πŸ“ 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.

Suggested change
name='s'
readonly
type='search'
class='search-field'
value='<?php get_search_query(); ?>'
title='<?php echo esc_attr_x( 'Search for:', 'label', 'wedocs' ); ?>'
placeholder='<?php echo esc_attr_x( 'Search for a topic or question', 'placeholder', 'wedocs' ); ?>'
/>
name='s'
readonly
type='search'
class='search-field'
value='<?php echo esc_attr( get_search_query() ); ?>'
title='<?php echo esc_attr_x( 'Search for:', 'label', 'wedocs' ); ?>'
placeholder='<?php echo esc_attr_x( 'Search for a topic or question', 'placeholder', 'wedocs' ); ?>'
/>
πŸ€– Prompt for AI Agents
In templates/doc-search-form.php around lines 14 to 21, the input value uses
get_search_query() but does not echo it and is not escaped for an attribute;
update the value attribute to echo the result wrapped with esc_attr(
get_search_query() ) so the search query is output and safely escaped for HTML
attribute context.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants