about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/view/screens/Search/Search.tsx50
-rw-r--r--src/view/shell/desktop/Search.tsx99
2 files changed, 114 insertions, 35 deletions
diff --git a/src/view/screens/Search/Search.tsx b/src/view/screens/Search/Search.tsx
index f2b674245..94aab2d96 100644
--- a/src/view/screens/Search/Search.tsx
+++ b/src/view/screens/Search/Search.tsx
@@ -42,9 +42,13 @@ import {useSetDrawerOpen} from '#/state/shell'
 import {useAnalytics} from '#/lib/analytics/analytics'
 import {MagnifyingGlassIcon} from '#/lib/icons'
 import {useModerationOpts} from '#/state/queries/preferences'
-import {SearchResultCard} from '#/view/shell/desktop/Search'
+import {
+  MATCH_HANDLE,
+  SearchLinkCard,
+  SearchProfileCard,
+} from '#/view/shell/desktop/Search'
 import {useSetMinimalShellMode, useSetDrawerSwipeDisabled} from '#/state/shell'
-import {isWeb} from '#/platform/detection'
+import {isNative, isWeb} from '#/platform/detection'
 import {listenSoftReset} from '#/state/events'
 import {s} from '#/lib/styles'
 
@@ -509,6 +513,11 @@ export function SearchScreen(
     onPressCancelSearch()
   }, [onPressCancelSearch])
 
+  const queryMaybeHandle = React.useMemo(() => {
+    const match = MATCH_HANDLE.exec(query)
+    return match && match[1]
+  }, [query])
+
   useFocusEffect(
     React.useCallback(() => {
       setMinimalShellMode(false)
@@ -615,18 +624,31 @@ export function SearchScreen(
               dataSet={{stableGutters: '1'}}
               keyboardShouldPersistTaps="handled"
               keyboardDismissMode="on-drag">
-              {searchResults.length ? (
-                searchResults.map((item, i) => (
-                  <SearchResultCard
-                    key={item.did}
-                    profile={item}
-                    moderation={moderateProfile(item, moderationOpts)}
-                    style={i === 0 ? {borderTopWidth: 0} : {}}
-                  />
-                ))
-              ) : (
-                <EmptyState message={_(msg`No results found for ${query}`)} />
-              )}
+              <SearchLinkCard
+                label={_(msg`Search for "${query}"`)}
+                onPress={isNative ? onSubmit : undefined}
+                to={
+                  isNative
+                    ? undefined
+                    : `/search?q=${encodeURIComponent(query)}`
+                }
+                style={{borderBottomWidth: 1}}
+              />
+
+              {queryMaybeHandle ? (
+                <SearchLinkCard
+                  label={_(msg`Go to @${queryMaybeHandle}`)}
+                  to={`/profile/${queryMaybeHandle}`}
+                />
+              ) : null}
+
+              {searchResults.map(item => (
+                <SearchProfileCard
+                  key={item.did}
+                  profile={item}
+                  moderation={moderateProfile(item, moderationOpts)}
+                />
+              ))}
 
               <View style={{height: 200}} />
             </ScrollView>
diff --git a/src/view/shell/desktop/Search.tsx b/src/view/shell/desktop/Search.tsx
index df49da55b..4a9483733 100644
--- a/src/view/shell/desktop/Search.tsx
+++ b/src/view/shell/desktop/Search.tsx
@@ -29,13 +29,63 @@ import {UserAvatar} from '#/view/com/util/UserAvatar'
 import {useActorAutocompleteFn} from '#/state/queries/actor-autocomplete'
 import {useModerationOpts} from '#/state/queries/preferences'
 
-export function SearchResultCard({
-  profile,
+export const MATCH_HANDLE =
+  /@?([a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*(?:\.[a-zA-Z]{2,}))/
+
+export function SearchLinkCard({
+  label,
+  to,
+  onPress,
   style,
+}: {
+  label: string
+  to?: string
+  onPress?: () => void
+  style?: ViewStyle
+}) {
+  const pal = usePalette('default')
+
+  const inner = (
+    <View
+      style={[pal.border, {paddingVertical: 16, paddingHorizontal: 12}, style]}>
+      <Text type="md" style={[pal.text]}>
+        {label}
+      </Text>
+    </View>
+  )
+
+  if (onPress) {
+    return (
+      <TouchableOpacity
+        onPress={onPress}
+        accessibilityLabel={label}
+        accessibilityHint="">
+        {inner}
+      </TouchableOpacity>
+    )
+  }
+
+  return (
+    <Link href={to} asAnchor anchorNoUnderline>
+      <View
+        style={[
+          pal.border,
+          {paddingVertical: 16, paddingHorizontal: 12},
+          style,
+        ]}>
+        <Text type="md" style={[pal.text]}>
+          {label}
+        </Text>
+      </View>
+    </Link>
+  )
+}
+
+export function SearchProfileCard({
+  profile,
   moderation,
 }: {
   profile: AppBskyActorDefs.ProfileViewBasic
-  style: ViewStyle
   moderation: ProfileModeration
 }) {
   const pal = usePalette('default')
@@ -50,9 +100,7 @@ export function SearchResultCard({
       <View
         style={[
           pal.border,
-          style,
           {
-            borderTopWidth: 1,
             flexDirection: 'row',
             alignItems: 'center',
             gap: 12,
@@ -147,6 +195,11 @@ export function DesktopSearch() {
     navigation.dispatch(StackActions.push('Search', {q: query}))
   }, [query, navigation, setSearchResults])
 
+  const queryMaybeHandle = React.useMemo(() => {
+    const match = MATCH_HANDLE.exec(query)
+    return match && match[1]
+  }, [query])
+
   return (
     <View style={[styles.container, pal.view]}>
       <View
@@ -198,22 +251,26 @@ export function DesktopSearch() {
             </View>
           ) : (
             <>
-              {searchResults.length ? (
-                searchResults.map((item, i) => (
-                  <SearchResultCard
-                    key={item.did}
-                    profile={item}
-                    moderation={moderateProfile(item, moderationOpts)}
-                    style={i === 0 ? {borderTopWidth: 0} : {}}
-                  />
-                ))
-              ) : (
-                <View>
-                  <Text style={[pal.textLight, styles.noResults]}>
-                    <Trans>No results found for {query}</Trans>
-                  </Text>
-                </View>
-              )}
+              <SearchLinkCard
+                label={_(msg`Search for "${query}"`)}
+                to={`/search?q=${encodeURIComponent(query)}`}
+                style={{borderBottomWidth: 1}}
+              />
+
+              {queryMaybeHandle ? (
+                <SearchLinkCard
+                  label={_(msg`Go to @${queryMaybeHandle}`)}
+                  to={`/profile/${queryMaybeHandle}`}
+                />
+              ) : null}
+
+              {searchResults.map(item => (
+                <SearchProfileCard
+                  key={item.did}
+                  profile={item}
+                  moderation={moderateProfile(item, moderationOpts)}
+                />
+              ))}
             </>
           )}
         </View>