mirror of
https://github.com/jkingsman/Remote-Terminal-for-MeshCore.git
synced 2026-07-05 17:32:10 +02:00
Add better search management and operators + contact search quick link
This commit is contained in:
@@ -19,6 +19,8 @@ interface SearchResult {
|
||||
sender_name: string | null;
|
||||
}
|
||||
|
||||
const SEARCH_OPERATOR_RE = /(?<!\S)(user|channel):(?:"((?:[^"\\]|\\.)*)"|(\S+))/gi;
|
||||
|
||||
export interface SearchNavigateTarget {
|
||||
id: number;
|
||||
type: 'PRIV' | 'CHAN';
|
||||
@@ -30,6 +32,10 @@ export interface SearchViewProps {
|
||||
contacts: Contact[];
|
||||
channels: Channel[];
|
||||
onNavigateToMessage: (target: SearchNavigateTarget) => void;
|
||||
prefillRequest?: {
|
||||
query: string;
|
||||
nonce: number;
|
||||
} | null;
|
||||
}
|
||||
|
||||
function highlightMatch(text: string, query: string): React.ReactNode[] {
|
||||
@@ -53,7 +59,34 @@ function highlightMatch(text: string, query: string): React.ReactNode[] {
|
||||
return parts;
|
||||
}
|
||||
|
||||
export function SearchView({ contacts, channels, onNavigateToMessage }: SearchViewProps) {
|
||||
function getHighlightQuery(query: string): string {
|
||||
const fragments: string[] = [];
|
||||
let lastIndex = 0;
|
||||
let foundOperator = false;
|
||||
|
||||
for (const match of query.matchAll(SEARCH_OPERATOR_RE)) {
|
||||
foundOperator = true;
|
||||
fragments.push(query.slice(lastIndex, match.index));
|
||||
lastIndex = (match.index ?? 0) + match[0].length;
|
||||
}
|
||||
|
||||
if (!foundOperator) {
|
||||
return query;
|
||||
}
|
||||
|
||||
fragments.push(query.slice(lastIndex));
|
||||
return fragments
|
||||
.map((fragment) => fragment.trim())
|
||||
.filter(Boolean)
|
||||
.join(' ');
|
||||
}
|
||||
|
||||
export function SearchView({
|
||||
contacts,
|
||||
channels,
|
||||
onNavigateToMessage,
|
||||
prefillRequest = null,
|
||||
}: SearchViewProps) {
|
||||
const [query, setQuery] = useState('');
|
||||
const [debouncedQuery, setDebouncedQuery] = useState('');
|
||||
const [results, setResults] = useState<SearchResult[]>([]);
|
||||
@@ -62,6 +95,7 @@ export function SearchView({ contacts, channels, onNavigateToMessage }: SearchVi
|
||||
const [offset, setOffset] = useState(0);
|
||||
const abortRef = useRef<AbortController | null>(null);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const highlightQuery = getHighlightQuery(debouncedQuery);
|
||||
|
||||
// Debounce query
|
||||
useEffect(() => {
|
||||
@@ -78,6 +112,17 @@ export function SearchView({ contacts, channels, onNavigateToMessage }: SearchVi
|
||||
setHasMore(false);
|
||||
}, [debouncedQuery]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!prefillRequest) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nextQuery = prefillRequest.query.trim();
|
||||
setQuery(nextQuery);
|
||||
setDebouncedQuery(nextQuery);
|
||||
inputRef.current?.focus();
|
||||
}, [prefillRequest]);
|
||||
|
||||
// Fetch search results
|
||||
useEffect(() => {
|
||||
if (!debouncedQuery) {
|
||||
@@ -193,7 +238,11 @@ export function SearchView({ contacts, channels, onNavigateToMessage }: SearchVi
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
{!debouncedQuery && (
|
||||
<div className="p-8 text-center text-muted-foreground text-sm">
|
||||
Type to search across all messages
|
||||
<p>Type to search across all messages</p>
|
||||
<p className="mt-2 text-xs">
|
||||
Tip: use <code>user:</code> or <code>channel:</code> for keys or names, and wrap names
|
||||
with spaces in them in quotes.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -246,7 +295,7 @@ export function SearchView({ contacts, channels, onNavigateToMessage }: SearchVi
|
||||
result.sender_name && result.text.startsWith(`${result.sender_name}: `)
|
||||
? result.text.slice(result.sender_name.length + 2)
|
||||
: result.text,
|
||||
debouncedQuery
|
||||
highlightQuery
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user