Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
64 changes: 20 additions & 44 deletions app/components/search-box.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ import { Toggle } from "@/components/ui/toggle";
import { Code, Clock } from "lucide-react";
import ManualDialog from "./manual-dialog";
import AdvancedFilterBuilder, { getCachedFilterRule } from "./filter-sphere";
import { Button } from "@/components/ui/button";

export interface Props {
query: string;
initAdvancedSearch: boolean;
onChange: (s: string) => void;
onSortChange: (sort: Sort) => void;
onHumanEraChange?: (enabled: boolean) => void;
}

export enum Sort {
Expand All @@ -34,7 +34,7 @@ export enum Sort {
IdAsc = "id:asc",
}

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

export default function Search(props: Props) {
const [useAdvancedSearch, setUseAdvancedSearch] = React.useState(
Expand All @@ -44,61 +44,37 @@ export default function Search(props: Props) {
props.initAdvancedSearch && !getCachedFilterRule(props.query)
);
const [sort, setSort] = React.useState<Sort>(Sort.Relevance);
const [humanEraEnabled, setHumanEraEnabled] = React.useState(false);

useEffect(() => {
props.onSortChange(sort);
}, [sort]);

const handlePre2023Search = () => {
const currentQuery = props.query.trim();
const dateFilter = `(${PRE_2023_DATE_FILTER})`;

// Check if the query already contains the pre-2023 date filter
if (currentQuery.includes(PRE_2023_DATE_FILTER)) {
// Already has the filter, no need to add again
return;
}

// Check if current query already has a filter (ends with parenthesis)
if (currentQuery.endsWith(")")) {
// Extract simple query and existing filter
const firstParen = currentQuery.indexOf("(");
if (firstParen !== -1) {
const simpleQuery = currentQuery.substring(0, firstParen).trim();
const existingFilter = currentQuery.substring(firstParen);
// Combine filters with AND
const combinedFilter = `(${existingFilter.slice(1, -1)} AND ${PRE_2023_DATE_FILTER})`;
const newQuery = simpleQuery ? `${simpleQuery} ${combinedFilter}` : combinedFilter;
props.onChange(newQuery);
} else {
// Should not happen, but fallback
props.onChange(`${currentQuery} ${dateFilter}`);
}
} else {
// Simple query or empty, append filter
const newQuery = currentQuery ? `${currentQuery} ${dateFilter}` : dateFilter;
props.onChange(newQuery);
useEffect(() => {
if (props.onHumanEraChange) {
props.onHumanEraChange(humanEraEnabled);
}

setUseAdvancedSearch(true);
setIsQueryMode(true);
};
}, [humanEraEnabled]);



return (
<div className="w-full font-semibold">
<div className="flex flex-row items-center justify-end mb-2">
<div className="mr-auto hidden md:block">
<ManualDialog />
</div>
<Button
variant="reverse"
size="sm"
onClick={handlePre2023Search}
className="mr-2"
title="搜索 2023 年以前的内容(人类时代)"
>
<label className="mr-2 flex items-center gap-2" htmlFor="human-era">
<Clock className="h-4 w-4" />
人类时代
</Button>
<span className="text-sm">人类时代</span>
</label>
<Switch
className="inline-block mr-2"
id="human-era"
checked={humanEraEnabled}
onCheckedChange={setHumanEraEnabled}
title="搜索 2023 年以前的内容(人类时代)"
/>
{useAdvancedSearch && (
<>
<Toggle
Expand Down
18 changes: 15 additions & 3 deletions app/components/search-results.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,22 @@ import NotFoundAnimation from "./not-found-animation";
import { Switch } from "@/components/ui/switch";
import NoMoreAnimation from "./no-more-animation";
import { Sort } from "./search-box";
import { PRE_2023_DATE_FILTER } from "./search-box";
import { CalendarDays, PenIcon } from "lucide-react";
import { addDateFilterToQuery } from "../utils/queryFilter";

async function getSearchResults(query: string, page: number, sort: Sort) {
async function getSearchResults(query: string, page: number, sort: Sort, humanEraEnabled: boolean = false) {
const baseUrl = "https://search-api.saveweb.org/api/search";
const url = new URL(baseUrl);

// Apply human era date filter if enabled
let finalQuery = query.trim();
if (humanEraEnabled) {
finalQuery = addDateFilterToQuery(finalQuery, PRE_2023_DATE_FILTER);
}

const params = new URLSearchParams({
q: query.trim(),
q: finalQuery,
f: "false",
p: page.toString(),
h: "true",
Expand Down Expand Up @@ -54,10 +63,12 @@ export default function SearchResults({
initialQuery,
initialPage,
sort,
humanEraEnabled = false,
}: {
initialQuery: string;
initialPage: number;
sort: Sort;
humanEraEnabled?: boolean;
}) {
const [query, setQuery] = useState(initialQuery);
const [page, setPage] = useState(initialPage);
Expand Down Expand Up @@ -100,7 +111,8 @@ export default function SearchResults({
const data = await getSearchResults(
query,
reset ? initialPage : page,
sort
sort,
humanEraEnabled
);
setTotalHits(totalHits + (data?.hits?.length ?? 0));
if (data.error) {
Expand Down
5 changes: 4 additions & 1 deletion app/components/search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export default function Search() {
let initSort = decodeURIComponent(searchParams.get("sort") || "relevance");
if (!initSort) initSort = "relevance";
let [sort, setSort] = useState<Sort>(initSort as Sort);
const [humanEraEnabled, setHumanEraEnabled] = useState(false);

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

Expand Down Expand Up @@ -80,15 +81,17 @@ export default function Search() {
initAdvancedSearch={isAdvancedSearch(query)}
onChange={handleInputChange}
onSortChange={setSort}
onHumanEraChange={setHumanEraEnabled}
/>
</form>

{query && (
<SearchResults
key={`${query}-${sort}`} // 添加 key 属性确保查询变化时重新渲染
key={`${query}-${sort}-${humanEraEnabled}`} // 添加 key 属性确保查询变化时重新渲染
initialQuery={query} // 改用 query 替代 initialQuery
initialPage={0}
sort={sort}
humanEraEnabled={humanEraEnabled}
/>
)}
</div>
Expand Down
33 changes: 33 additions & 0 deletions app/utils/queryFilter.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { describe, it, expect } from 'vitest';
import { addDateFilterToQuery } from './queryFilter';

describe('addDateFilterToQuery', () => {
const dateFilter = 'date < sec(2023-01-01)';

it('should add filter to simple query', () => {
const result = addDateFilterToQuery('技术', dateFilter);
expect(result).toBe('技术 (date < sec(2023-01-01))');
});

it('should add filter to empty query', () => {
const result = addDateFilterToQuery('', dateFilter);
expect(result).toBe('(date < sec(2023-01-01))');
});

it('should not duplicate filter if already present', () => {
const query = '技术 (date < sec(2023-01-01))';
const result = addDateFilterToQuery(query, dateFilter);
expect(result).toBe(query);
});

it('should combine with existing filter using AND', () => {
const query = '技术 (author=test)';
const result = addDateFilterToQuery(query, dateFilter);
expect(result).toBe('技术 (author=test AND date < sec(2023-01-01))');
});

it('should handle query with whitespace', () => {
const result = addDateFilterToQuery(' 技术 ', dateFilter);
expect(result).toBe('技术 (date < sec(2023-01-01))');
});
});
47 changes: 47 additions & 0 deletions app/utils/queryFilter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* Utility functions for combining search queries with filters
*/

/**
* Checks if a query already contains a specific date filter
* Uses more specific matching to avoid false positives
*/
function hasDateFilter(query: string, dateFilter: string): boolean {
// Match the filter when it's wrapped in parentheses or at the end
const filterPattern = new RegExp(`\\(${dateFilter.replace(/[()]/g, '\\$&')}\\)`, 'i');
return filterPattern.test(query);
}

/**
* Combines a search query with a date filter
* @param query - The original search query
* @param dateFilter - The date filter expression to add
* @returns The combined query with the date filter applied
*/
export function addDateFilterToQuery(query: string, dateFilter: string): string {
const trimmedQuery = query.trim();
const filterExpression = `(${dateFilter})`;

// Check if the query already contains the date filter
if (hasDateFilter(trimmedQuery, dateFilter)) {
return trimmedQuery;
}

// Check if current query already has a filter (ends with parenthesis)
if (trimmedQuery.endsWith(")")) {
// Extract simple query and existing filter
const firstParen = trimmedQuery.indexOf("(");
if (firstParen !== -1) {
const simpleQuery = trimmedQuery.substring(0, firstParen).trim();
const existingFilter = trimmedQuery.substring(firstParen);
// Combine filters with AND
const combinedFilter = `(${existingFilter.slice(1, -1)} AND ${dateFilter})`;
return simpleQuery ? `${simpleQuery} ${combinedFilter}` : combinedFilter;
}
// Should not happen, but fallback
return `${trimmedQuery} ${filterExpression}`;
}

// Simple query or empty, append filter
return trimmedQuery ? `${trimmedQuery} ${filterExpression}` : filterExpression;
}