=== Kody Search ===
Contributors: maticparker
Tags: search, instant search, ajax search, live search, posts
Requires at least: 6.3
Tested up to: 7.1
Requires PHP: 8.0
Stable tag: 1.0
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Instant dropdown search for your posts and pages. Results appear as you type, with no page reload.

== Description ==

Kody Search adds a fast dropdown search to your site. A visitor starts typing and matching
posts and pages appear straight away, with the query terms highlighted and no page reload.

**Fast, and light on your server.** The search index loads in a single request the first
time a visitor uses the search box. Every keystroke after that is matched in their browser,
so your server sees one request per visit, not one per keystroke, and results stay instant
no matter how many people are searching at once.

**Looks like part of your site.** The search box picks up your active theme's fonts and
form styling. There is nothing to skin and no CSS to write.

**Nothing to tune.** No relevance weights, no ranking rules, no settings you have to get
right before results are good. Add the block and it works.

**Accessible.** Full keyboard navigation, correct combobox semantics, and screen reader
announcements for results.

**Stays out of the way.** Scripts and styles load only on pages that actually have a search
box, and the public search endpoint is rate limited against abuse.

**Full page results too.** A dedicated results page with pagination and a running count,
linked from the dropdown as "View all results".

= What it searches =

Post titles, Search Tags, and categories. Search Tags is a field Kody Search adds to the
editor, kept separate from your site's regular Tags so tagging something for search does not
also change your navigation. Kody Search does not search the body text of articles.

= Where it fits =

Sites up to roughly 15,000 posts and pages. If you need full text search across article
bodies, or you run a larger site, a search plugin backed by a server index is the better
choice, and Kody Search will tell you on its settings screen once you cross that size.

== Installation ==

1. Install and activate the plugin.
2. Open **Kody Search** in the admin menu. This builds the initial index.
3. Add the **Kody Search** block, or the `[kody_search]` shortcode, wherever you want the
   search box, for example your header or sidebar.
4. Optional: add the **Kody Search Page** block or `[kody_search_page]` shortcode to a page
   and select that page under Kody Search, so the dropdown's "View all results" link has
   somewhere to go.

== Development ==

This plugin ships its complete source. Nothing is obfuscated. Only the two block
editor scripts are compiled; all PHP, all CSS, and the front-end JavaScript in
`assets/` run exactly as shipped, with no build step.

= What is compiled, and from what =

The only generated files in the plugin are:

* build/kody-search/index.js
* build/kody-search/index.asset.php
* build/kody-search-page/index.js
* build/kody-search-page/index.asset.php

They are the webpack build of the block editor UI. Each `build/` directory has one
matching source directory shipped alongside it:

* blocks-src/kody-search/edit.js         ->  build/kody-search/index.js
* blocks-src/kody-search/block.json      ->  build/kody-search/block.json       (copied unchanged)
* blocks-src/kody-search-page/edit.js    ->  build/kody-search-page/index.js
* blocks-src/kody-search-page/block.json ->  build/kody-search-page/block.json  (copied unchanged)

`edit.js` is ordinary JSX built on the `@wordpress/*` packages (`@wordpress/blocks`,
`@wordpress/block-editor`, `@wordpress/components`, `@wordpress/data`,
`@wordpress/core-data`, `@wordpress/i18n`, `@wordpress/element`). `index.asset.php` is generated by
`@wordpress/scripts`: it lists the `wp-*` script handles the compiled file depends on
and a content hash used for cache busting.

= Build tooling (all included in the plugin) =

* package.json       - the two npm scripts and the dev dependencies
* webpack.config.js  - two explicit entry points (blocks-src/{block}/edit.js) and
                       copy-webpack-plugin, which copies each block.json into build/
* babel.config.js    - forces Babel's *classic* JSX runtime (pragma `createElement`,
                       resolved through `import { createElement, Fragment } from
                       '@wordpress/element'` at the top of each edit.js) instead of the
                       automatic runtime. The automatic runtime would add a dependency
                       on the `react-jsx-runtime` script handle, registered in WordPress
                       core only since 6.6; the classic runtime keeps the compiled
                       output dependent only on handles present since WP 5.0, so the
                       "Requires at least: 6.3" header stays accurate. The pragma names
                       an imported identifier rather than the `wp.element.*` global so
                       that build-time dependency extraction can see it and list
                       `wp-element` in index.asset.php.

Dependencies (see package.json):

* @wordpress/scripts ^33
* copy-webpack-plugin ^12

Developed and built with Node.js 20 and npm 10.

= Reproduce the block build =

From the plugin directory:

    npm install
    npm run build

This rebuilds the four generated files above from `blocks-src/` and re-copies the two
`block.json` files. `npm run start` runs the same build in watch mode. Building against
an unchanged `blocks-src/` reproduces the same `build/` output (the hash in
`index.asset.php` is derived from the compiled bytes, so it only changes when the
source does).

`BlockModule` (src/Modules/BlockModule.php) registers both blocks from `build/` via
`register_block_type()`, reading each `block.json` + `index.asset.php` - so after a
rebuild the editor picks up the change with no other edits.

= PHP =

All PHP is PSR-4 autoloaded (`Kody\Search\` -> `src/`) through Composer; `composer.json`
is included. There are no runtime Composer dependencies - the only thing shipped under
`vendor/` is the generated autoloader. Regenerate it with:

    composer install --no-dev --optimize-autoloader

= Translations =

`languages/kody-search.pot` is generated with WP-CLI:

    wp i18n make-pot . languages/kody-search.pot --slug=kody-search --domain=kody-search

== Frequently Asked Questions ==

= What does it search? =

Post titles, Search Tags, and categories. Not the body text of articles, and not your
site's regular Tags. If a word matters for finding a post, add it to that post's Search
Tags panel.

= What content ends up in the index? =

Published posts and pages only, of the types ticked under "Content to search". Drafts,
pending and scheduled posts, private posts, and password-protected posts are all left out.
A scheduled post is added by itself the moment it goes live, and a published post that
later becomes a draft, gets a password, or is trashed is removed from the index on save.

This matters because the index is downloaded by the visitor's browser: everything in it is
content any visitor could already reach on your site.

= How does indexing work? =

The index builds the first time you open the Kody Search screen, then updates on its own
whenever a post is published, edited, trashed, or deleted. For bulk changes that skip the
editor, such as an import, a migration, or renaming a category, there is a Rebuild Index
button that refreshes everything.

= Does it need WooCommerce? =

No. Kody Search covers posts and pages and has no WooCommerce dependency.

= Does it work with custom post types? =

The settings screen offers posts and pages. Other post types can be added by a developer
with the `kody_search_post_types` filter, and they are then indexed and searchable like
any other content.

= Does it need JavaScript? =

Yes. Matching happens in the visitor's browser, which is what makes results instant and
keeps the load off your server. With JavaScript disabled the search box renders but does
not return results. Your theme's own search form is unaffected and keeps working.

= Does it replace the WordPress search page? =

No. Kody Search adds its own box and its own optional results page, and leaves the
built-in search, your theme's search template, and `?s=` URLs exactly as they were.

= Does it send anything to an external service? =

No. There are no external requests of any kind, no analytics, no telemetry and no
accounts. Everything runs on your own site, and the search index is stored in your own
database.

= Does it work with WPML or Polylang? =

Partly, and it does no language filtering of its own.

On Polylang, a rebuild covers every language. On WPML, a rebuild covers only the language
you are viewing the admin in, so use the Rebuild Index button once per language, or expect
the other languages to drop out.

Either way, results are not filtered by language: a visitor may see matches from every
language you have indexed. If language-aware search matters to your site, a plugin built
for multilingual search is the better fit.

= Can I change how the search box looks? =

Yes. The Design tab has spacing and sizing controls. Past that the box follows your theme,
so it matches without any CSS.

= Can one search box cover only certain content? =

Yes. The block has a "Limit this search box" panel, and the shortcode takes a `post_types`
attribute. Leave it unset to search everything.

= Can I put more than one search box on a page? =

Yes, as many inline search boxes as you like, each working independently. The full
results view (the **Kody Search Page** block or `[kody_search_page]` shortcode) is
different: use one per page. It is meant to be the main content of a dedicated results
page, and a second one on the same page will not run.

= How large a site does it handle? =

Up to roughly 15,000 posts and pages. Beyond that, results would only cover part of your
content, and the plugin says so on its settings screen. A search plugin backed by a server
index is the better fit at that size.

= Is the search endpoint protected against abuse? =

Yes, it is rate limited per visitor. Sites behind a CDN or proxy can adjust how visitors
are identified with the `kody_search_rate_limit_identifier` filter.

= Does it work with page caching? =

Yes. If a cached page carries an expired security token, the search box refreshes it
automatically instead of failing.

= Does it work on WordPress Multisite? =

Yes. Network-activate it and every site gets its own search index; sites you add later
are set up automatically. You can also activate it on individual sites instead. Each
site's index and settings are its own, and settings live on that site's Kody Search
screen, not a network screen.

= What happens when I delete the plugin? =

Deleting it from the Plugins screen removes its database table and every option it
created, so nothing is left behind. Deactivating changes nothing, so you can safely turn
it off and on again. Your posts and pages are never touched either way.

== Screenshots ==

1. Instant results as you type, with the matched words highlighted
2. General tab: index status and search settings
3. Design tab: spacing and sizing controls
4. Usage tab: blocks, shortcodes, and a quick start guide

== Changelog ==

= 1.0 =
* Initial release.
