=== tlloancy Catalog Search for WooCommerce ===
Contributors: tlloancy
Tags: woocommerce, search, product search, live search, shop
Requires at least: 6.5
Tested up to: 7.1
Requires PHP: 8.1
Requires Plugins: woocommerce
Stable tag: 1.2.1
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Lightweight WooCommerce product search. Inverted index, ranked results, typo correction, discreet overlay, search-term stats.

== Description ==

tlloancy Catalog Search for WooCommerce is a small WooCommerce product search for shops that do not need a heavy search engine, without giving up relevance.

It builds a compact inverted index in two dedicated tables and answers from a REST route. No external service, no jQuery, about 20 KB of front-end code loaded only on pages that show the magnifier.

**What is indexed**

* Always: product title, SKU (parent and every variation, with and without separators), product categories and their parents.
* Optional: tags, brands, attribute values (color, size, material…), the first 60 words of the short description.

**How results are ranked**

Ranking happens in SQL, on the index: an exact SKU comes first, then an exact title word, then a word that starts the title, then prefixes in the title, then categories, brands, tags, attributes and description. Every typed word must match. Ties go to best sellers, then to the newest product.

**Typo correction**

When nothing matches, the engine looks for the closest indexed word (one letter off, two from seven letters) and searches again. The panel says which spelling the results are for. This can be turned off.

**Search UI**

* Overlay (default): dimmed backdrop, centered field, close button. Popover: panel under the magnifier, no backdrop.
* Each result shows a thumbnail, the product name with the typed letters in bold, the SKU when that is what matched, the category, and the price. A sale shows the regular price struck through. A variable product shows the price range.
* Arrow keys move through results, Enter opens the highlighted product, Escape closes.
* A “See all N results” link follows when there are more matches than the panel shows.
* Enter in the field goes to the WooCommerce product search results page. With the matching setting on, that page lists the same products in the same order as the suggestions.
* Recent searches (last five) can be shown when the field is empty. They live in the visitor's browser only.
* The panel follows the on-screen keyboard on phones.

**Freshness**

A product is reindexed when it is saved from the editor, the REST API, WP-CLI or the CSV importer, when its stock status or visibility changes (including stock reductions from orders), and when a variation changes. Renaming a category, tag, brand or attribute term queues a rebuild. A “Rebuild index” button sits in Settings.

**Multilingual**

With Polylang or WPML, each product is indexed with its language and the visitor only sees products in the current language.

**What it does not do**

* It does not count keystrokes — only a commit (Enter, result click, or a stable empty result)
* It does not store IP addresses or WordPress accounts
* It does not call any external service

Insert the Catalog Search for WooCommerce block on the shop / product archive template. Settings live under Settings → Catalog Search for WooCommerce.

== Installation ==

1. Install and activate WooCommerce
2. Upload the `tlloancy-catalog-search-for-woocommerce` folder to `/wp-content/plugins/`
3. Activate the plugin (tables are created immediately; the catalog is indexed a few seconds later via WP-Cron)
4. In the Site Editor, add the Catalog Search for WooCommerce block on the shop template (same row as category filters if you want)
5. Open Settings → Catalog Search for WooCommerce to choose layout, overlay or popover, which fields to index, and optionally enable stats

== Frequently Asked Questions ==

= Does it replace WordPress search? =

Live suggestions always come from its own index and REST route. The WooCommerce product search results page (`?s=…&post_type=product`) is served from the index when the “Results page” setting is on, so Enter shows the same products in the same order as the panel. Blog search and other post types are untouched.

= Which products are indexed? =

Published products that WooCommerce would show in search: not hidden, not catalog-only. Variation SKUs are included. Out-of-stock products are skipped when WooCommerce is set to hide them, and they leave the index as soon as their stock status changes.

= How are results ordered? =

By a score computed in SQL: exact SKU, then exact title word, then a word that starts the title, then title prefixes, then categories, brands, tags, attributes and short description. Equal scores are broken by total sales, then by product ID (newest first). Use the `tllocs_stopwords` filter to change which short words are ignored.

= What happens when I mistype? =

If no product matches, each word of four letters or more that matches nothing is replaced by the closest indexed word (edit distance 1, or 2 from seven letters) and the search runs again. The panel shows “Results for ‘…’” with the corrected spelling. Turn this off under Settings → Search → Typos.

= Can I search in attributes, brands, tags, or the description? =

Yes. Tick them under Settings → Search → Also search in. Saving reindexes the catalog in the background. Title, SKU and categories are always indexed.

= How are searches counted? =

Statistics are off until you enable them. Then only a commit is stored: submit, click on a result, or a stable empty result after 600 ms. The same query from the same visitor within 30 seconds counts once.

= Is a visitor identifier stored? =

Only if statistics are on. A random UUID cookie (`tllocs_vid`, 30 days, HttpOnly) is used to dedupe stats. No IP, no user login. The suggested privacy-policy text is added under Settings → Privacy. Recent searches, when enabled, are kept in the browser's localStorage and never sent.

= How large a catalog can it handle? =

The index lives in two dedicated database tables with an index on the token column, and ranking is a single SQL query. Saving a product updates that document only. A full rebuild (activation, schema change, button in Settings) runs in paged batches through WP-Cron. Responses are cached in the object cache when the site has a persistent one.

= Where do I put the magnifier? =

Use the Catalog Search for WooCommerce block. Layout (vertical / horizontal) is a setting, so the same block works on a row of filters or below the title. You can also use the `[tllocs_search]` shortcode.

= Does it work with Polylang or WPML? =

Yes. Each product is indexed with its language and the panel only lists products in the visitor's current language. Filters `tllocs_doc_lang` and `tllocs_current_lang` cover other multilingual setups.

== Privacy ==

Optional search-term stats (off by default) store normalized queries and counters in a dedicated table. The visitor cookie is a random UUID for deduplication only. Recent searches stay in the browser.

== Changelog ==

= 1.2.1 =
* Block: text and background color, margin and padding are available in the editor sidebar. The magnifier follows the chosen text color; a background turns it into a round badge.
* Block: inside a Row block, the magnifier sits inline whatever the Layout setting says, so it no longer pushes its neighbors (result count, sorting, another search field) off the screen.
* Editor: the block preview shows the magnifier instead of a text line.
* Docs: “Placing the block” section in both guides.

= 1.2.0 =
* Ranking moved into SQL: exact SKU, exact title word, title start, title prefix, then categories, brands, tags, attributes, description. Ties go to best sellers. The former 500-candidate cap is gone.
* Typo correction: when nothing matches, the closest indexed word is used and the panel says which spelling the results are for. Setting to turn it off.
* Optional fields: tags, brands, attribute values, short description (first 60 words). Tags and brands are on by default; changing the list reindexes in the background.
* SKUs are indexed with and without separators, so “ABC123” finds “ABC-123”.
* Parent categories are indexed with their children.
* Enter now goes to the WooCommerce product search results page. New setting (on by default) serves that page from the index, in the same order as the suggestions; a sort picked by the visitor is kept.
* Results panel: typed letters in bold, SKU and category line, “See all N results” link, arrow-key navigation with Enter to open, screen-reader status, on-screen keyboard handling on phones, product page prefetch on hover.
* Recent searches (last five, browser only) shown when the field is empty. Setting to turn it off.
* “Results shown” setting (4 to 20).
* Products are reindexed on WooCommerce save events (REST, WP-CLI, CSV import), stock status changes, visibility changes, and variation changes. Renaming or deleting a category, tag, brand or attribute term queues a rebuild.
* Settings page: Display, Search and Statistics sections, index status (products, entries, last rebuild) and a “Rebuild index” button.
* Object-cache layer for search responses, invalidated on every index change.
* Polylang / WPML: products carry their language; the panel lists the current language only.
* Stale requests are aborted when the visitor keeps typing.
* The REST response is now an object: `items`, `total`, `q` (corrected query or null).
* Schema change: the token table gains `field` and `pos`, the document table gains `sales` and `lang`. A rebuild is queued on update.

= 1.1.0 =
* Live results show a thumbnail, the product name, and the price.
* A sale shows the regular price struck through. A variable product shows the price range.

= 1.0.0 =
* First release.

== Upgrade Notice ==

= 1.2.1 =
Block color and spacing controls; the magnifier stays inline inside a Row block.

= 1.2.0 =
Ranked results, typo correction, optional fields, Enter goes to the results page, keyboard navigation, recent searches. The index is rebuilt once after the update.

= 1.1.0 =
Search results show a thumbnail and the price.

= 1.0.0 =
Initial release.
