Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
3 changes: 2 additions & 1 deletion locale/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,8 @@
"content1": "Try changing your keywords and search again.",
"content2": "If you can't find what you're looking for, seek help from our <0>Discord channels</0>."
},
"searchTip": "Use double quotes for exact match. For example, \"time_zone\"."
"searchTip": "Use double quotes for exact match. For example, \"time_zone\".",
"filters": "Filter search results:"
},
"tools": {
"tidbOperator": {
Expand Down
3 changes: 2 additions & 1 deletion locale/zh/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,8 @@
"content1": "请尝试使用其他关键词再次搜索。",
"content2": "如果您无法找到需要的内容,请尝试前往 <0>AskTUG (TiDB User Group)</0> 进行提问。"
},
"searchTip": "使用半角双引号进行精确搜索。例如,\"time_zone\"。"
"searchTip": "使用半角双引号进行精确搜索。例如,\"time_zone\"。",
"filters": "过滤搜索结果:"
},
"tools": {
"tidbOperator": {
Expand Down
93 changes: 89 additions & 4 deletions src/components/Search/Results.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,16 @@ import Chip from "@mui/material/Chip";
import {
getSearchCategoryLabelKey,
resolveSearchCategory,
type SearchCategory,
} from "shared/utils/searchCategory";

export default function SearchResults(props: {
loading: boolean;
className?: string;
data: any[];
onFilterChange?: (category: SearchCategory | null) => void;
}) {
const { data, loading } = props;
const { data, loading, onFilterChange } = props;

const [page, setPage] = React.useState(0);
const [rowsPerPage, setRowsPerPage] = React.useState(10);
Expand Down Expand Up @@ -101,7 +103,11 @@ export default function SearchResults(props: {
</Typography> */}
<Stack spacing={4}>
{filteredDataMemo.map((item) => (
<SearchItem key={item.objectID} data={item} />
<SearchItem
key={item.objectID}
data={item}
onFilterChange={onFilterChange}
/>
))}
</Stack>
{data.length === 0 && !loading && (
Expand Down Expand Up @@ -177,8 +183,11 @@ function SearchItemSkeleton() {
);
}

function SearchItem(props: { data: any }) {
const { data } = props;
function SearchItem(props: {
data: any;
onFilterChange?: (category: SearchCategory | null) => void;
}) {
const { data, onFilterChange } = props;
const { t } = useI18next();
const category = React.useMemo(
() => resolveSearchCategory(data.url),
Expand Down Expand Up @@ -214,12 +223,21 @@ function SearchItem(props: { data: any }) {
size="small"
variant="outlined"
label={categoryLabel}
clickable
onClick={(e) => {
e.preventDefault();
onFilterChange?.(category);
}}
sx={{
height: "20px",
fontSize: "12px",
borderRadius: "10px",
backgroundColor: "carbon.100",
color: "carbon.800",
cursor: "pointer",
"&:hover": {
backgroundColor: "carbon.200",
},
}}
Comment thread
qiancai marked this conversation as resolved.
Outdated
/>
)}
Expand Down Expand Up @@ -267,3 +285,70 @@ function SearchItem(props: { data: any }) {
</Stack>
);
}

export function SearchFilterBar(props: {
categoryCountMap: Map<SearchCategory, number>;
activeFilter: SearchCategory | null;
onFilterChange: (category: SearchCategory | null) => void;
visible: boolean;
}) {
const { categoryCountMap, activeFilter, onFilterChange, visible } = props;
const { t } = useI18next();

if (!visible || categoryCountMap.size <= 1) {
return null;
}

const entries = Array.from(categoryCountMap.entries());
Comment thread
qiancai marked this conversation as resolved.
Outdated

return (
<Stack
direction="row"
alignItems="center"
sx={{ paddingTop: "0.75rem", flexWrap: "wrap", gap: "0.5rem" }}
>
<Typography
variant="body2"
sx={{ fontWeight: 500, color: "carbon.700", whiteSpace: "nowrap" }}
>
<Trans i18nKey="search.filters" />
</Typography>
{entries.map(([category, count]) => {
const label = t(getSearchCategoryLabelKey(category));
if (!label) return null;
const isActive = activeFilter === category;
return (
<Chip
key={category}
size="small"
variant={isActive ? "filled" : "outlined"}
label={`${label} (${count})`}
clickable
onClick={() => onFilterChange(category)}
sx={{
height: "24px",
fontSize: "12px",
borderRadius: "12px",
cursor: "pointer",
...(isActive
? {
backgroundColor: "carbon.800",
color: "white",
"&:hover": {
backgroundColor: "carbon.700",
},
}
: {
backgroundColor: "carbon.100",
color: "carbon.800",
"&:hover": {
backgroundColor: "carbon.200",
},
}),
}}
/>
);
})}
</Stack>
);
}
48 changes: 47 additions & 1 deletion src/templates/DocSearchTemplate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import "styles/docTemplate.css";

import Layout from "components/Layout";
import SearchResults from "components/Search/Results";
import { SearchFilterBar } from "components/Search/Results";
import SearchInput from "components/Search";
import { Tip } from "components/MDXComponents";
import Seo from "components/Seo";
Expand All @@ -20,6 +21,10 @@ import { Locale, TOCNamespace } from "shared/interface";
import { FeedbackSurveyCampaign } from "components/Campaign/FeedbackSurvey";
import ScrollToTopBtn from "components/Button/ScrollToTopBtn";
import { useIsAutoTranslation } from "shared/useIsAutoTranslation";
import {
resolveSearchCategory,
type SearchCategory,
} from "shared/utils/searchCategory";

const SEARCH_INDEX_BY_LANGUAGE: Partial<Record<Locale, string>> = {
[Locale.en]: "en-tidb-all-stable",
Expand All @@ -40,22 +45,49 @@ export default function DocSearchTemplate({
}: DocSearchTemplateProps) {
const [isLoading, setIsLoading] = React.useState(false);
const [results, setResults] = React.useState<any[]>([]);
const [activeFilter, setActiveFilter] =
React.useState<SearchCategory | null>(null);

const { language } = useI18next();
const { search } = useLocation();

const categoryCountMap = React.useMemo(() => {
const map = new Map<SearchCategory, number>();
for (const item of results) {
const cat = resolveSearchCategory(item.url);
if (cat) map.set(cat, (map.get(cat) || 0) + 1);
}
return map;
}, [results]);
Comment thread
qiancai marked this conversation as resolved.

const filteredResults = React.useMemo(() => {
if (!activeFilter) return results;
return results.filter(
(item) => resolveSearchCategory(item.url) === activeFilter
);
}, [results, activeFilter]);

const handleFilterChange = React.useCallback(
(category: SearchCategory | null) => {
setActiveFilter((prev) => (prev === category ? null : category));
},
[]
);

const execSearch = React.useCallback(
(query: string) => {
const trimmedQuery = query.trim();
if (!trimmedQuery) {
setResults([]);
setActiveFilter(null);
setIsLoading(false);
return;
}

const indexName = SEARCH_INDEX_BY_LANGUAGE[language as Locale];
if (!indexName) {
setResults([]);
setActiveFilter(null);
setIsLoading(false);
return;
}
Expand All @@ -69,11 +101,13 @@ export default function DocSearchTemplate({
})
.then(({ hits }) => {
setResults(hits);
setActiveFilter(null);
setIsLoading(false);
})
.catch((reason: any) => {
console.error(reason);
setResults([]);
setActiveFilter(null);
setIsLoading(false);
});
},
Expand All @@ -85,12 +119,14 @@ export default function DocSearchTemplate({
const query = searchParams.get("q") || "";
if (language === Locale.ja) {
setResults([]);
setActiveFilter(null);
setIsLoading(false);
return;
}

if (!query.trim()) {
setResults([]);
setActiveFilter(null);
setIsLoading(false);
return;
}
Expand Down Expand Up @@ -127,10 +163,20 @@ export default function DocSearchTemplate({
}}
/>
</Stack>
<SearchFilterBar
categoryCountMap={categoryCountMap}
activeFilter={activeFilter}
onFilterChange={handleFilterChange}
visible={!isLoading && results.length > 0}
/>
<Tip>
<Trans i18nKey="search.searchTip" />
</Tip>
<SearchResults loading={isLoading} data={results} />
<SearchResults
loading={isLoading}
data={filteredResults}
onFilterChange={handleFilterChange}
/>
<Box
sx={{
width: "fit-content",
Expand Down