Merge branch 'next' into nip-72

This commit is contained in:
hzrd149
2023-09-16 11:41:38 -05:00
13 changed files with 100 additions and 20 deletions

View File

@@ -0,0 +1,5 @@
---
"nostrudel": patch
---
Small fix for url RegExp

View File

@@ -0,0 +1,5 @@
---
"nostrudel": minor
---
Add Muted words option in display settings

View File

@@ -2,7 +2,7 @@ export const getMatchNostrLink = () =>
/(nostr:|@)?((npub|note|nprofile|nevent|nrelay|naddr)1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{58,})/gi;
export const getMatchHashtag = () => /(^|[^\p{L}])#([\p{L}\p{N}]+)/gu;
export const getMatchLink = () =>
/https?:\/\/([a-zA-Z0-9\.\-]+\.[a-zA-Z]+)([\p{Letter}\p{Number}&\.-\/\?=#\-@%\+_,:]*)/gu;
/https?:\/\/([a-zA-Z0-9\.\-]+\.[a-zA-Z]+)([\p{Letter}\p{Number}&\.-\/\?=#\-@%\+_,:!]*)/gu;
export const getMatchEmoji = () => /:([a-zA-Z0-9_]+):/gi;
// read more https://www.regular-expressions.info/unicode.html#category

View File

@@ -1,7 +1,8 @@
import { useCallback } from "react";
import { useToast } from "@chakra-ui/react";
import appSettings, { replaceSettings } from "../services/settings/app-settings";
import useSubject from "./use-subject";
import { useToast } from "@chakra-ui/react";
import { AppSettings } from "../services/settings/migrations";
export default function useAppSettings() {

View File

@@ -0,0 +1,20 @@
import { useCallback } from "react";
import { useCurrentAccount } from "./use-current-account";
import useWordMuteFilter from "./use-mute-word-filter";
import useUserMuteFilter from "./use-user-mute-filter";
import { NostrEvent } from "../types/nostr-event";
export default function useClientSideMuteFilter() {
const account = useCurrentAccount();
const wordMuteFilter = useWordMuteFilter();
const mustListFilter = useUserMuteFilter(account?.pubkey);
return useCallback(
(event: NostrEvent) => {
return wordMuteFilter(event) || mustListFilter(event);
},
[wordMuteFilter, mustListFilter],
);
}

View File

@@ -0,0 +1,25 @@
import { useCallback, useMemo } from "react";
import { NostrEvent } from "../types/nostr-event";
import useAppSettings from "./use-app-settings";
export default function useWordMuteFilter() {
const { mutedWords } = useAppSettings();
const regexp = useMemo(() => {
if (!mutedWords) return;
const words = mutedWords
.split(/[,\n]/g)
.map((s) => s.trim())
.filter(Boolean);
return new RegExp(`(?:^|\\s|#)(?:${words.join("|")})(?:\\s|$)`, "i");
}, [mutedWords]);
return useCallback(
(event: NostrEvent) => {
if (!regexp) return false;
return event.content.match(regexp) !== null;
},
[mutedWords],
);
}

View File

@@ -5,8 +5,8 @@ import { useReadRelayUrls } from "../hooks/use-client-relays";
import { useCurrentAccount } from "../hooks/use-current-account";
import { TimelineLoader } from "../classes/timeline-loader";
import timelineCacheService from "../services/timeline-cache";
import useUserMuteFilter from "../hooks/use-user-mute-filter";
import { NostrEvent } from "../types/nostr-event";
import useClientSideMuteFilter from "../hooks/use-client-side-mute-filter";
type NotificationTimelineContextType = {
timeline?: TimelineLoader;
@@ -31,7 +31,7 @@ export default function NotificationTimelineProvider({ children }: PropsWithChil
: undefined;
}, [account?.pubkey]);
const userMuteFilter = useUserMuteFilter(account?.pubkey);
const userMuteFilter = useClientSideMuteFilter();
const eventFilter = useCallback(
(event: NostrEvent) => {
if (userMuteFilter(event)) return false;

View File

@@ -22,14 +22,21 @@ export type AppSettingsV0 = {
redditRedirect?: string;
youtubeRedirect?: string;
};
export type AppSettingsV1 = Omit<AppSettingsV0, "version"> & {
version: 1;
mutedWords?: string;
};
export function isV0(settings: { version: number }): settings is AppSettingsV0 {
return settings.version === undefined || settings.version === 0;
}
export function isV1(settings: { version: number }): settings is AppSettingsV1 {
return settings.version === 1;
}
export type AppSettings = AppSettingsV0;
export type AppSettings = AppSettingsV1;
export const defaultSettings: AppSettings = {
version: 0,
version: 1,
colorMode: "system",
blurImages: true,
autoShowMedia: true,
@@ -49,8 +56,9 @@ export const defaultSettings: AppSettings = {
youtubeRedirect: undefined,
};
export function upgradeSettings(settings: { version: number }) {
if (isV0(settings)) return settings;
export function upgradeSettings(settings: { version: number }): AppSettings | null {
if (isV0(settings)) return { ...settings, version: 1 };
if (isV1(settings)) return settings;
return null;
}

View File

@@ -12,12 +12,12 @@ import RelaySelectionButton from "../../components/relay-selection/relay-selecti
import PeopleListProvider, { usePeopleListContext } from "../../providers/people-list-provider";
import RelaySelectionProvider, { useRelaySelectionContext } from "../../providers/relay-selection-provider";
import { NostrRequestFilter } from "../../types/nostr-query";
import useUserMuteFilter from "../../hooks/use-user-mute-filter";
import useClientSideMuteFilter from "../../hooks/use-client-side-mute-filter";
function HomePage() {
const timelinePageEventFilter = useTimelinePageEventFilter();
const showReplies = useDisclosure();
const muteFilter = useUserMuteFilter();
const muteFilter = useClientSideMuteFilter();
const eventFilter = useCallback(
(event: NostrEvent) => {
if (muteFilter(event)) return false;

View File

@@ -6,7 +6,7 @@ import { Note } from "../../../components/note";
import { countReplies, ThreadItem } from "../../../helpers/thread";
import { TrustProvider } from "../../../providers/trust";
import ReplyForm from "./reply-form";
import useUserMuteFilter from "../../../hooks/use-user-mute-filter";
import useClientSideMuteFilter from "../../../hooks/use-client-side-mute-filter";
export type ThreadItemProps = {
post: ThreadItem;
@@ -19,18 +19,20 @@ export const ThreadPost = ({ post, initShowReplies, focusId }: ThreadItemProps)
const toggle = () => setShowReplies((v) => !v);
const showReplyForm = useDisclosure();
const muteFilter = useUserMuteFilter();
const muteFilter = useClientSideMuteFilter();
const [alwaysShow, setAlwaysShow] = useState(false);
const numberOfReplies = countReplies(post);
const isMuted = muteFilter(post.event);
if (isMuted && numberOfReplies === 0) return null;
return (
<Flex direction="column" gap="2">
{isMuted && !alwaysShow ? (
<Alert status="warning">
<AlertIcon />
Muted user
Muted user or note
<Button size="xs" ml="auto" onClick={() => setAlwaysShow(true)}>
Show anyway
</Button>

View File

@@ -12,6 +12,7 @@ import {
FormHelperText,
Input,
Select,
Textarea,
} from "@chakra-ui/react";
import { AppSettings } from "../../services/settings/migrations";
import { AppearanceIcon } from "../../components/icons";
@@ -75,6 +76,19 @@ export default function DisplaySettings() {
<span>Enabled: shows a warning for notes with NIP-36 Content Warning</span>
</FormHelperText>
</FormControl>
<FormControl>
<FormLabel htmlFor="muted-words" mb="0">
Muted words
</FormLabel>
<Textarea id="muted-words" {...register("mutedWords")} placeholder="Broccoli, Spinach, Artichoke..." />
<FormHelperText>
<span>
Comma separated list of words, phrases or hashtags you never want to see in notes. (case insensitive)
</span>
<br />
<span>Be careful its easy to hide all notes if you add common words.</span>
</FormHelperText>
</FormControl>
</Flex>
</AccordionPanel>
</AccordionItem>

View File

@@ -1,5 +1,6 @@
import { useCallback, useMemo } from "react";
import { Divider, Flex, Heading, SimpleGrid } from "@chakra-ui/react";
import useTimelineLoader from "../../hooks/use-timeline-loader";
import IntersectionObserverProvider from "../../providers/intersection-observer";
import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback";
@@ -15,14 +16,14 @@ import TimelineActionAndStatus from "../../components/timeline-page/timeline-act
import useParsedStreams from "../../hooks/use-parsed-streams";
import { NostrRequestFilter } from "../../types/nostr-query";
import { useAppTitle } from "../../hooks/use-app-title";
import useUserMuteFilter from "../../hooks/use-user-mute-filter";
import { NostrEvent } from "../../types/nostr-event";
import VerticalPageLayout from "../../components/vertical-page-layout";
import useClientSideMuteFilter from "../../hooks/use-client-side-mute-filter";
function StreamsPage() {
useAppTitle("Streams");
const relays = useRelaySelectionRelays();
const userMuteFilter = useUserMuteFilter();
const userMuteFilter = useClientSideMuteFilter();
const eventFilter = useCallback(
(event: NostrEvent) => {

View File

@@ -6,21 +6,20 @@ import { ParsedStream, STREAM_CHAT_MESSAGE_KIND, getATag } from "../../../../hel
import useTimelineLoader from "../../../../hooks/use-timeline-loader";
import { NostrEvent } from "../../../../types/nostr-event";
import { useRelaySelectionRelays } from "../../../../providers/relay-selection-provider";
import { useCurrentAccount } from "../../../../hooks/use-current-account";
import useStreamGoal from "../../../../hooks/use-stream-goal";
import { NostrQuery } from "../../../../types/nostr-query";
import useUserMuteFilter from "../../../../hooks/use-user-mute-filter";
import useClientSideMuteFilter from "../../../../hooks/use-client-side-mute-filter";
export default function useStreamChatTimeline(stream: ParsedStream) {
const account = useCurrentAccount();
const streamRelays = useRelaySelectionRelays();
const hostMuteFilter = useUserMuteFilter(stream.host);
const userMuteFilter = useUserMuteFilter(account?.pubkey);
const muteFilter = useClientSideMuteFilter();
const eventFilter = useCallback(
(event: NostrEvent) => !(hostMuteFilter(event) || userMuteFilter(event)),
[hostMuteFilter, userMuteFilter],
(event: NostrEvent) => !(hostMuteFilter(event) || muteFilter(event)),
[hostMuteFilter, muteFilter],
);
const goal = useStreamGoal(stream);