Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions packages/reflex-site-shared/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,13 @@ the exported `render_docgen_document` helper directly.

Add `defer` to a `python demo exec` fence to mount a heavy preview as it approaches the viewport. The example's source code remains in the initial HTML; the preview reserves 450px of height and stays mounted once shown. This is useful for pages containing many Plotly charts.

### Component search

The shared search matches complete `rx.*` identifiers to words in documentation
titles: `rx.button` searches for `button`, `rx.radio_group` for `radio group`,
and `rx.alert_dialog.root` for `alert dialog root`. The input keeps the original
text. Ordinary search phrases are sent unchanged.

## Marketing footer

Use the marketing site's responsive five-column directory footer in another
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Normalize component searches such as `rx.button` and `rx.radio_group` to match documentation titles, improving component page relevance.
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"icons/search.svg",
"components/marketing-date.jsx",
"homepage/lib/use-isomorphic-layout-effect.ts",
"components/search-query.mjs",
)


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import React, {
} from "react";
import { createPortal } from "react-dom";

import { normalizeSearchQuery } from "./search-query.mjs";

const ALGOLIA_APP_ID = "WLK9YABRW4";
const ALGOLIA_SEARCH_API_KEY = "7abb638647d3aa25e7a6d29dfdca2212";
const ALGOLIA_INDEX_NAME = "reflex_dev_wlk9yabrw4_pages";
Expand Down Expand Up @@ -447,7 +449,7 @@ export function AlgoliaSearch() {
"X-Algolia-Application-Id": ALGOLIA_APP_ID,
},
body: JSON.stringify({
query: trimmedQuery,
query: normalizeSearchQuery(trimmedQuery),
hitsPerPage: 10,
attributesToRetrieve: [
"objectID",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* Convert a complete Reflex component identifier to searchable title words.
*
* @param {string} query A trimmed search query.
* @returns {string} The component's title words, or the unchanged query.
*/
export function normalizeSearchQuery(query) {
return /^rx\.[a-z]\w*(?:\.[a-z]\w*)*$/i.test(query)
? query.slice(3).replace(/[_.]+/g, " ")
: query;
}
83 changes: 83 additions & 0 deletions tests/units/reflex_site_shared/test_algolia.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,94 @@
"""Tests for the shared keyword-only Algolia search."""

import json
import re
import shutil
import subprocess
from pathlib import Path

import pytest
from reflex_site_shared.components.algolia import AlgoliaSearch, Search
from reflex_site_shared.plugins import SharedSiteStylesPlugin


@pytest.mark.skipif(shutil.which("node") is None, reason="node missing")
@pytest.mark.parametrize(
("query", "expected_query"),
[
("rx.button", "button"),
("rx.input", "input"),
("rx.radio_group", "radio group"),
("rx.data_table", "data table"),
("rx.alert_dialog.root", "alert dialog root"),
("rx.scroll_area", "scroll area"),
("rx.button.", "rx.button."),
("RX.BUTTON", "BUTTON"),
("button", "button"),
("radio group", "radio group"),
("on_click", "on_click"),
("how to style rx.button", "how to style rx.button"),
("example.rx.button", "example.rx.button"),
("https://rx.button", "https://rx.button"),
("rx.", "rx."),
],
)
def test_algolia_search_normalizes_component_queries(
query: str, expected_query: str, tmp_path: Path
) -> None:
"""Normalize component names through the same module imported by the client.

Args:
query: Trimmed search text.
expected_query: Query sent to Algolia.
tmp_path: Temporary directory for the published JavaScript module.
"""
assets = dict(SharedSiteStylesPlugin().get_static_assets())
module = tmp_path / "search-query.mjs"
module.write_text(
assets[Path("public/components/search-query.mjs")], encoding="utf-8"
)
script = """
import { pathToFileURL } from "node:url";
const { normalizeSearchQuery } = await import(pathToFileURL(process.argv[1]));
console.log(JSON.stringify(normalizeSearchQuery(JSON.parse(process.argv[2]))));
"""
result = subprocess.run(
[
"node",
"--input-type=module",
"--eval",
script,
str(module),
json.dumps(query),
],
capture_output=True,
encoding="utf-8",
timeout=10,
check=True,
)
assert json.loads(result.stdout) == expected_query
Comment thread
carlosabadia marked this conversation as resolved.
Resolved


def test_algolia_search_wires_normalization_into_requests() -> None:
"""Normalize requests through the published helper while keeping raw cache keys."""
assets = dict(SharedSiteStylesPlugin().get_static_assets())
source = assets[Path("public/components/AlgoliaSearch.tsx")]

assert re.search(
r"import\s*\{\s*normalizeSearchQuery\s*,?\s*\}\s*from\s*"
r"[\"']\./search-query\.mjs[\"']",
source,
)
assert re.search(
r"\bquery\s*:\s*normalizeSearchQuery\s*\(\s*trimmedQuery\s*,?\s*\)",
source,
)
assert re.search(
r"\bconst\s+cacheKey\s*=\s*trimmedQuery\.toLocaleLowerCase\(\s*\)",
source,
)


def test_algolia_search_uses_local_keyword_only_component() -> None:
"""Use the local Algolia UI without shipping an AI search dependency."""
assert AlgoliaSearch.library == "$/public/components/AlgoliaSearch"
Expand Down
1 change: 1 addition & 0 deletions tests/units/reflex_site_shared/test_plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ def test_shared_site_styles_plugin_emits_package_css():
Path("public/icons/search.svg"),
Path("public/components/marketing-date.jsx"),
Path("public/homepage/lib/use-isomorphic-layout-effect.ts"),
Path("public/components/search-query.mjs"),
]
assert all(content.strip() for _path, content in assets)
assert "ph-conversations-widget" in assets[1][1]
Expand Down
Loading