!dismissedPubkeys.includes(s.pubkey) && !mutedPubkeys.includes(s.pubkey) && !follows.includes(s.pubkey)
+ );
// Load follow suggestions on mount (only for logged-in users with follows)
useEffect(() => {
@@ -384,8 +392,8 @@ export function SearchView() {
{suggestionsLoading && (
Finding suggestions...
)}
- {suggestions.map((s) => s.profile && (
-
+ {visibleSuggestions.map((s) => s.profile && (
+
useUIStore.getState().openProfile(s.pubkey)}>
{s.profile.picture ? (
+
))}
- {suggestionsLoaded && suggestions.length === 0 && (
+ {suggestionsLoaded && visibleSuggestions.length === 0 && (
Follow more people to see suggestions here.
diff --git a/src/components/shared/SettingsView.tsx b/src/components/shared/SettingsView.tsx
index da67c2f..5a20fc9 100644
--- a/src/components/shared/SettingsView.tsx
+++ b/src/components/shared/SettingsView.tsx
@@ -45,6 +45,74 @@ function MuteSection() {
);
}
+function MutedKeywordsSection() {
+ const { mutedKeywords, addKeyword, removeKeyword } = useMuteStore();
+ const [input, setInput] = useState("");
+ const [error, setError] = useState
(null);
+
+ const handleAdd = () => {
+ const trimmed = input.trim().toLowerCase();
+ if (trimmed.length < 2) {
+ setError("Minimum 2 characters");
+ return;
+ }
+ if (mutedKeywords.includes(trimmed)) {
+ setError("Already muted");
+ return;
+ }
+ addKeyword(trimmed);
+ setInput("");
+ setError(null);
+ };
+
+ const handleKeyDown = (e: React.KeyboardEvent) => {
+ if (e.key === "Enter") handleAdd();
+ if (e.key === "Escape") setInput("");
+ };
+
+ return (
+
+
+ Muted keywords {mutedKeywords.length > 0 && `(${mutedKeywords.length})`}
+
+
+ Notes containing these words or phrases will be hidden from your feeds.
+
+ {mutedKeywords.length > 0 && (
+
+ {mutedKeywords.map((kw) => (
+
+ {kw}
+
+
+ ))}
+
+ )}
+
+ { setInput(e.target.value); setError(null); }}
+ onKeyDown={handleKeyDown}
+ placeholder="word or phrase to mute"
+ className="flex-1 bg-bg border border-border px-3 py-1.5 text-text text-[12px] focus:outline-none focus:border-accent/50 placeholder:text-text-dim"
+ />
+
+
+ {error && {error}
}
+
+ );
+}
+
function RelayRow({ url, onRemove }: { url: string; onRemove: () => void }) {
const ndk = getNDK();
const relay = ndk.pool?.relays.get(url);
@@ -267,7 +335,7 @@ function ExportSection() {
function NotificationSection() {
const [settings, setSettings] = useState(getNotificationSettings);
- const toggle = (key: "mentions" | "dms" | "zaps") => {
+ const toggle = (key: "mentions" | "dms" | "zaps" | "followers") => {
const next = { ...settings, [key]: !settings[key] };
setSettings(next);
saveNotificationSettings(next);
@@ -275,10 +343,11 @@ function NotificationSection() {
if (next[key]) ensurePermission().catch(() => {});
};
- const items: Array<{ key: "mentions" | "dms" | "zaps"; label: string }> = [
+ const items: Array<{ key: "mentions" | "dms" | "zaps" | "followers"; label: string }> = [
{ key: "mentions", label: "Mentions" },
{ key: "dms", label: "Direct messages" },
{ key: "zaps", label: "Zaps received" },
+ { key: "followers", label: "New followers" },
];
return (
@@ -326,6 +395,7 @@ export function SettingsView() {
+
);
diff --git a/src/components/thread/ThreadView.tsx b/src/components/thread/ThreadView.tsx
index a7ce24c..94d9f1e 100644
--- a/src/components/thread/ThreadView.tsx
+++ b/src/components/thread/ThreadView.tsx
@@ -2,6 +2,7 @@ import { useEffect, useRef, useState } from "react";
import { NDKEvent } from "@nostr-dev-kit/ndk";
import { useUIStore } from "../../stores/ui";
import { useUserStore } from "../../stores/user";
+import { useMuteStore } from "../../stores/mute";
import { useProfile } from "../../hooks/useProfile";
import { useReactionCount } from "../../hooks/useReactionCount";
import { useZapCount } from "../../hooks/useZapCount";
@@ -145,6 +146,7 @@ function RootNote({ event }: { event: NDKEvent }) {
export function ThreadView() {
const { selectedNote, goBack } = useUIStore();
const { loggedIn } = useUserStore();
+ const { mutedPubkeys, contentMatchesMutedKeyword } = useMuteStore();
if (!selectedNote) { goBack(); return null; }
const event = selectedNote;
@@ -239,7 +241,9 @@ export function ThreadView() {
)}
- {replies.map((reply) => (
+ {replies
+ .filter((r) => !mutedPubkeys.includes(r.pubkey) && !contentMatchesMutedKeyword(r.content))
+ .map((reply) => (