Skip to content

Commit 8d58881

Browse files
authored
Merge pull request #10 from saveweb/copilot/update-human-era-to-toggle
Convert Human Era to toggle with implicit API-level date filtering
2 parents 18afbba + ccddfeb commit 8d58881

File tree

5 files changed

+121
-48
lines changed

5 files changed

+121
-48
lines changed

app/components/search-box.tsx

Lines changed: 20 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,13 @@ import { Toggle } from "@/components/ui/toggle";
1717
import { Code, Clock } from "lucide-react";
1818
import ManualDialog from "./manual-dialog";
1919
import AdvancedFilterBuilder, { getCachedFilterRule } from "./filter-sphere";
20-
import { Button } from "@/components/ui/button";
2120

2221
export interface Props {
2322
query: string;
2423
initAdvancedSearch: boolean;
2524
onChange: (s: string) => void;
2625
onSortChange: (sort: Sort) => void;
26+
onHumanEraChange?: (enabled: boolean) => void;
2727
}
2828

2929
export enum Sort {
@@ -34,7 +34,7 @@ export enum Sort {
3434
IdAsc = "id:asc",
3535
}
3636

37-
const PRE_2023_DATE_FILTER = "date < sec(2023-01-01)";
37+
export const PRE_2023_DATE_FILTER = "date < sec(2023-01-01)";
3838

3939
export default function Search(props: Props) {
4040
const [useAdvancedSearch, setUseAdvancedSearch] = React.useState(
@@ -44,61 +44,37 @@ export default function Search(props: Props) {
4444
props.initAdvancedSearch && !getCachedFilterRule(props.query)
4545
);
4646
const [sort, setSort] = React.useState<Sort>(Sort.Relevance);
47+
const [humanEraEnabled, setHumanEraEnabled] = React.useState(false);
48+
4749
useEffect(() => {
4850
props.onSortChange(sort);
4951
}, [sort]);
5052

51-
const handlePre2023Search = () => {
52-
const currentQuery = props.query.trim();
53-
const dateFilter = `(${PRE_2023_DATE_FILTER})`;
54-
55-
// Check if the query already contains the pre-2023 date filter
56-
if (currentQuery.includes(PRE_2023_DATE_FILTER)) {
57-
// Already has the filter, no need to add again
58-
return;
59-
}
60-
61-
// Check if current query already has a filter (ends with parenthesis)
62-
if (currentQuery.endsWith(")")) {
63-
// Extract simple query and existing filter
64-
const firstParen = currentQuery.indexOf("(");
65-
if (firstParen !== -1) {
66-
const simpleQuery = currentQuery.substring(0, firstParen).trim();
67-
const existingFilter = currentQuery.substring(firstParen);
68-
// Combine filters with AND
69-
const combinedFilter = `(${existingFilter.slice(1, -1)} AND ${PRE_2023_DATE_FILTER})`;
70-
const newQuery = simpleQuery ? `${simpleQuery} ${combinedFilter}` : combinedFilter;
71-
props.onChange(newQuery);
72-
} else {
73-
// Should not happen, but fallback
74-
props.onChange(`${currentQuery} ${dateFilter}`);
75-
}
76-
} else {
77-
// Simple query or empty, append filter
78-
const newQuery = currentQuery ? `${currentQuery} ${dateFilter}` : dateFilter;
79-
props.onChange(newQuery);
53+
useEffect(() => {
54+
if (props.onHumanEraChange) {
55+
props.onHumanEraChange(humanEraEnabled);
8056
}
81-
82-
setUseAdvancedSearch(true);
83-
setIsQueryMode(true);
84-
};
57+
}, [humanEraEnabled]);
58+
59+
8560

8661
return (
8762
<div className="w-full font-semibold">
8863
<div className="flex flex-row items-center justify-end mb-2">
8964
<div className="mr-auto hidden md:block">
9065
<ManualDialog />
9166
</div>
92-
<Button
93-
variant="reverse"
94-
size="sm"
95-
onClick={handlePre2023Search}
96-
className="mr-2"
97-
title="搜索 2023 年以前的内容(人类时代)"
98-
>
67+
<label className="mr-2 flex items-center gap-2" htmlFor="human-era">
9968
<Clock className="h-4 w-4" />
100-
人类时代
101-
</Button>
69+
<span className="text-sm">人类时代</span>
70+
</label>
71+
<Switch
72+
className="inline-block mr-2"
73+
id="human-era"
74+
checked={humanEraEnabled}
75+
onCheckedChange={setHumanEraEnabled}
76+
title="搜索 2023 年以前的内容(人类时代)"
77+
/>
10278
{useAdvancedSearch && (
10379
<>
10480
<Toggle

app/components/search-results.tsx

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,22 @@ import NotFoundAnimation from "./not-found-animation";
1818
import { Switch } from "@/components/ui/switch";
1919
import NoMoreAnimation from "./no-more-animation";
2020
import { Sort } from "./search-box";
21+
import { PRE_2023_DATE_FILTER } from "./search-box";
2122
import { CalendarDays, PenIcon } from "lucide-react";
23+
import { addDateFilterToQuery } from "../utils/queryFilter";
2224

23-
async function getSearchResults(query: string, page: number, sort: Sort) {
25+
async function getSearchResults(query: string, page: number, sort: Sort, humanEraEnabled: boolean = false) {
2426
const baseUrl = "https://search-api.saveweb.org/api/search";
2527
const url = new URL(baseUrl);
28+
29+
// Apply human era date filter if enabled
30+
let finalQuery = query.trim();
31+
if (humanEraEnabled) {
32+
finalQuery = addDateFilterToQuery(finalQuery, PRE_2023_DATE_FILTER);
33+
}
34+
2635
const params = new URLSearchParams({
27-
q: query.trim(),
36+
q: finalQuery,
2837
f: "false",
2938
p: page.toString(),
3039
h: "true",
@@ -54,10 +63,12 @@ export default function SearchResults({
5463
initialQuery,
5564
initialPage,
5665
sort,
66+
humanEraEnabled = false,
5767
}: {
5868
initialQuery: string;
5969
initialPage: number;
6070
sort: Sort;
71+
humanEraEnabled?: boolean;
6172
}) {
6273
const [query, setQuery] = useState(initialQuery);
6374
const [page, setPage] = useState(initialPage);
@@ -100,7 +111,8 @@ export default function SearchResults({
100111
const data = await getSearchResults(
101112
query,
102113
reset ? initialPage : page,
103-
sort
114+
sort,
115+
humanEraEnabled
104116
);
105117
setTotalHits(totalHits + (data?.hits?.length ?? 0));
106118
if (data.error) {

app/components/search.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export default function Search() {
2121
let initSort = decodeURIComponent(searchParams.get("sort") || "relevance");
2222
if (!initSort) initSort = "relevance";
2323
let [sort, setSort] = useState<Sort>(initSort as Sort);
24+
const [humanEraEnabled, setHumanEraEnabled] = useState(false);
2425

2526
const [query, setQuery] = useState(initialQuery);
2627

@@ -80,15 +81,17 @@ export default function Search() {
8081
initAdvancedSearch={isAdvancedSearch(query)}
8182
onChange={handleInputChange}
8283
onSortChange={setSort}
84+
onHumanEraChange={setHumanEraEnabled}
8385
/>
8486
</form>
8587

8688
{query && (
8789
<SearchResults
88-
key={`${query}-${sort}`} // 添加 key 属性确保查询变化时重新渲染
90+
key={`${query}-${sort}-${humanEraEnabled}`} // 添加 key 属性确保查询变化时重新渲染
8991
initialQuery={query} // 改用 query 替代 initialQuery
9092
initialPage={0}
9193
sort={sort}
94+
humanEraEnabled={humanEraEnabled}
9295
/>
9396
)}
9497
</div>

app/utils/queryFilter.test.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { describe, it, expect } from 'vitest';
2+
import { addDateFilterToQuery } from './queryFilter';
3+
4+
describe('addDateFilterToQuery', () => {
5+
const dateFilter = 'date < sec(2023-01-01)';
6+
7+
it('should add filter to simple query', () => {
8+
const result = addDateFilterToQuery('技术', dateFilter);
9+
expect(result).toBe('技术 (date < sec(2023-01-01))');
10+
});
11+
12+
it('should add filter to empty query', () => {
13+
const result = addDateFilterToQuery('', dateFilter);
14+
expect(result).toBe('(date < sec(2023-01-01))');
15+
});
16+
17+
it('should not duplicate filter if already present', () => {
18+
const query = '技术 (date < sec(2023-01-01))';
19+
const result = addDateFilterToQuery(query, dateFilter);
20+
expect(result).toBe(query);
21+
});
22+
23+
it('should combine with existing filter using AND', () => {
24+
const query = '技术 (author=test)';
25+
const result = addDateFilterToQuery(query, dateFilter);
26+
expect(result).toBe('技术 (author=test AND date < sec(2023-01-01))');
27+
});
28+
29+
it('should handle query with whitespace', () => {
30+
const result = addDateFilterToQuery(' 技术 ', dateFilter);
31+
expect(result).toBe('技术 (date < sec(2023-01-01))');
32+
});
33+
});

app/utils/queryFilter.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/**
2+
* Utility functions for combining search queries with filters
3+
*/
4+
5+
/**
6+
* Checks if a query already contains a specific date filter
7+
* Uses more specific matching to avoid false positives
8+
*/
9+
function hasDateFilter(query: string, dateFilter: string): boolean {
10+
// Escape all special regex characters including backslash
11+
const escaped = dateFilter.replace(/[\\^$.*+?()[\]{}|]/g, '\\$&');
12+
// Match the filter when it's wrapped in parentheses
13+
const filterPattern = new RegExp(`\\(${escaped}\\)`, 'i');
14+
return filterPattern.test(query);
15+
}
16+
17+
/**
18+
* Combines a search query with a date filter
19+
* @param query - The original search query
20+
* @param dateFilter - The date filter expression to add
21+
* @returns The combined query with the date filter applied
22+
*/
23+
export function addDateFilterToQuery(query: string, dateFilter: string): string {
24+
const trimmedQuery = query.trim();
25+
const filterExpression = `(${dateFilter})`;
26+
27+
// Check if the query already contains the date filter
28+
if (hasDateFilter(trimmedQuery, dateFilter)) {
29+
return trimmedQuery;
30+
}
31+
32+
// Check if current query already has a filter (ends with parenthesis)
33+
if (trimmedQuery.endsWith(")")) {
34+
// Extract simple query and existing filter
35+
const firstParen = trimmedQuery.indexOf("(");
36+
if (firstParen !== -1) {
37+
const simpleQuery = trimmedQuery.substring(0, firstParen).trim();
38+
const existingFilter = trimmedQuery.substring(firstParen);
39+
// Combine filters with AND
40+
const combinedFilter = `(${existingFilter.slice(1, -1)} AND ${dateFilter})`;
41+
return simpleQuery ? `${simpleQuery} ${combinedFilter}` : combinedFilter;
42+
}
43+
// Should not happen, but fallback
44+
return `${trimmedQuery} ${filterExpression}`;
45+
}
46+
47+
// Simple query or empty, append filter
48+
return trimmedQuery ? `${trimmedQuery} ${filterExpression}` : filterExpression;
49+
}

0 commit comments

Comments
 (0)