about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorCaidan Williams <caidan@internet.dev>2025-08-22 18:25:35 -0700
committerCaidan Williams <caidan@internet.dev>2025-08-26 13:01:36 -0700
commit6ddc268b91d990da6ef754323310227690378556 (patch)
treeb209645dea05ed9d4e2128ec53da49f0fec47d13 /src
parent0517768cd32c96e96eb6ff80b23c3a8db284dfa0 (diff)
downloadvoidsky-6ddc268b91d990da6ef754323310227690378556.tar.zst
feat: prevent search engines from indexing Discover feed content as page descriptions
Diffstat (limited to 'src')
-rw-r--r--src/view/com/feeds/FeedPage.tsx81
-rw-r--r--src/view/com/util/NoSnippetWrapper.tsx29
2 files changed, 72 insertions, 38 deletions
diff --git a/src/view/com/feeds/FeedPage.tsx b/src/view/com/feeds/FeedPage.tsx
index 0e7813b7c..26c28729c 100644
--- a/src/view/com/feeds/FeedPage.tsx
+++ b/src/view/com/feeds/FeedPage.tsx
@@ -29,6 +29,7 @@ import {FAB} from '../util/fab/FAB'
 import {type ListMethods} from '../util/List'
 import {LoadLatestBtn} from '../util/load-latest/LoadLatestBtn'
 import {MainScrollProvider} from '../util/MainScrollProvider'
+import {NoSnippetWrapper} from '../util/NoSnippetWrapper'
 
 const POLL_FREQ = 60e3 // 60sec
 
@@ -132,45 +133,49 @@ export function FeedPage({
 
   const shouldPrefetch = isNative && isPageAdjacent
   return (
-    <View testID={testID} {...(isDiscoverFeed && {'data-nosnippet': true})}>
-      <MainScrollProvider>
-        <FeedFeedbackProvider value={feedFeedback}>
-          <PostFeed
-            testID={testID ? `${testID}-feed` : undefined}
-            enabled={isPageFocused || shouldPrefetch}
-            feed={feed}
-            feedParams={feedParams}
-            pollInterval={POLL_FREQ}
-            disablePoll={hasNew || !isPageFocused}
-            scrollElRef={scrollElRef}
-            onScrolledDownChange={setIsScrolledDown}
-            onHasNew={setHasNew}
-            renderEmptyState={renderEmptyState}
-            renderEndOfFeed={renderEndOfFeed}
-            headerOffset={headerOffset}
-            savedFeedConfig={savedFeedConfig}
-            isVideoFeed={isVideoFeed}
+    <NoSnippetWrapper enabled={isDiscoverFeed}>
+      <View testID={testID}>
+        <MainScrollProvider>
+          <FeedFeedbackProvider value={feedFeedback}>
+            <PostFeed
+              testID={testID ? `${testID}-feed` : undefined}
+              enabled={isPageFocused || shouldPrefetch}
+              feed={feed}
+              feedParams={feedParams}
+              pollInterval={POLL_FREQ}
+              disablePoll={hasNew || !isPageFocused}
+              scrollElRef={scrollElRef}
+              onScrolledDownChange={setIsScrolledDown}
+              onHasNew={setHasNew}
+              renderEmptyState={renderEmptyState}
+              renderEndOfFeed={renderEndOfFeed}
+              headerOffset={headerOffset}
+              savedFeedConfig={savedFeedConfig}
+              isVideoFeed={isVideoFeed}
+            />
+          </FeedFeedbackProvider>
+        </MainScrollProvider>
+        {(isScrolledDown || hasNew) && (
+          <LoadLatestBtn
+            onPress={onPressLoadLatest}
+            label={_(msg`Load new posts`)}
+            showIndicator={hasNew}
           />
-        </FeedFeedbackProvider>
-      </MainScrollProvider>
-      {(isScrolledDown || hasNew) && (
-        <LoadLatestBtn
-          onPress={onPressLoadLatest}
-          label={_(msg`Load new posts`)}
-          showIndicator={hasNew}
-        />
-      )}
+        )}
 
-      {hasSession && (
-        <FAB
-          testID="composeFAB"
-          onPress={onPressCompose}
-          icon={<ComposeIcon2 strokeWidth={1.5} size={29} style={s.white} />}
-          accessibilityRole="button"
-          accessibilityLabel={_(msg({message: `New post`, context: 'action'}))}
-          accessibilityHint=""
-        />
-      )}
-    </View>
+        {hasSession && (
+          <FAB
+            testID="composeFAB"
+            onPress={onPressCompose}
+            icon={<ComposeIcon2 strokeWidth={1.5} size={29} style={s.white} />}
+            accessibilityRole="button"
+            accessibilityLabel={_(
+              msg({message: `New post`, context: 'action'}),
+            )}
+            accessibilityHint=""
+          />
+        )}
+      </View>
+    </NoSnippetWrapper>
   )
 }
diff --git a/src/view/com/util/NoSnippetWrapper.tsx b/src/view/com/util/NoSnippetWrapper.tsx
new file mode 100644
index 000000000..fcf658249
--- /dev/null
+++ b/src/view/com/util/NoSnippetWrapper.tsx
@@ -0,0 +1,29 @@
+import {type ViewProps} from 'react-native'
+// @ts-expect-error untyped
+import {unstable_createElement} from 'react-native-web'
+
+import {isWeb} from '#/platform/detection'
+
+interface Props extends ViewProps {
+  enabled: boolean
+}
+
+/**
+ * NoSnippetWrapper prevents search engines from displaying snippets of its content.
+ *
+ * If running on web and enabled, wraps children in a <div> with data-nosnippet attribute.
+ * Otherwise, renders children directly.
+ *
+ * @param enabled - Whether to apply the data-nosnippet attribute.
+ * @param viewProps - Additional props for the wrapper element.
+ */
+export function NoSnippetWrapper({enabled, ...viewProps}: Props) {
+  if (isWeb && enabled) {
+    return unstable_createElement('div', {
+      ...viewProps,
+      'data-nosnippet': '',
+    })
+  }
+
+  return <>{viewProps.children}</>
+}