Muted words: phase 1

This commit is contained in:
2023-09-24 13:33:12 +01:00
parent 4d629f5087
commit 0e9ca7e2e3
16 changed files with 161 additions and 39 deletions

View File

@ -164,10 +164,6 @@
min-height: unset;
}
.hidden-note button {
max-height: 30px;
}
.expand-note {
padding: 0 0 16px 0;
font-weight: 400;

View File

@ -67,14 +67,14 @@ export interface NoteProps {
const HiddenNote = ({ children }: { children: React.ReactNode }) => {
const [show, setShow] = useState(false);
return show ? (
<>{children}</>
children
) : (
<div className="card note hidden-note">
<div className="header">
<p>
<FormattedMessage {...messages.MutedAuthor} />
<FormattedMessage defaultMessage="This note has been muted" />
</p>
<button onClick={() => setShow(true)}>
<button type="button" onClick={() => setShow(true)}>
<FormattedMessage {...messages.Show} />
</button>
</div>
@ -116,8 +116,7 @@ export function NoteInner(props: NoteProps) {
const navigate = useNavigate();
const [showReactions, setShowReactions] = useState(false);
const deletions = useMemo(() => getReactions(related, ev.id, EventKind.Deletion), [related]);
const { isMuted } = useModeration();
const isOpMuted = isMuted(ev?.pubkey);
const { isEventMuted } = useModeration();
const { ref, inView } = useInView({ triggerOnce: true });
const login = useLogin();
const { pinned, bookmarked } = login;
@ -466,5 +465,5 @@ export function NoteInner(props: NoteProps) {
</div>
);
return !ignoreModeration && isOpMuted ? <HiddenNote>{note}</HiddenNote> : note;
return !ignoreModeration && isEventMuted(ev) ? <HiddenNote>{note}</HiddenNote> : note;
}

View File

@ -8,7 +8,7 @@ import useEventPublisher from "Hooks/useEventPublisher";
import { getMutedKeys } from "Feed/MuteList";
import useModeration from "Hooks/useModeration";
import useLogin from "Hooks/useLogin";
import { addSubscription, setBlocked, setBookmarked, setFollows, setMuted, setPinned, setRelays, setTags } from "Login";
import { SnortAppData, addSubscription, setAppData, setBlocked, setBookmarked, setFollows, setMuted, setPinned, setRelays, setTags } from "Login";
import { SnortPubKey } from "Const";
import { SubscriptionEvent } from "Subscription";
import useRelaysFeedFollows from "./RelaysFeedFollows";
@ -38,7 +38,9 @@ export default function useLoginFeed() {
leaveOpen: true,
});
b.withFilter().authors([pubKey]).kinds([EventKind.ContactList]);
b.withFilter().authors([pubKey]).kinds([EventKind.AppData]).tag("d", ["snort"]);
b.withFilter()
.relay("wss://relay.snort.social")
.kinds([EventKind.SnortSubscriptions])
.authors([bech32ToHex(SnortPubKey)])
.tag("p", [pubKey])
@ -97,6 +99,14 @@ export default function useLoginFeed() {
}
}),
).then(a => addSubscription(login, ...a.filter(a => a !== undefined).map(unwrap)));
const appData = getNewest(loginFeed.data.filter(a => a.kind === EventKind.AppData));
if(appData) {
publisher.decryptGeneric(appData.content, appData.pubkey).then(d => {
setAppData(login, JSON.parse(d) as SnortAppData, appData.created_at * 1000);
})
}
}
}, [loginFeed, publisher]);

View File

@ -1,4 +1,4 @@
import { HexKey } from "@snort/system";
import { HexKey, TaggedNostrEvent } from "@snort/system";
import useEventPublisher from "Hooks/useEventPublisher";
import useLogin from "Hooks/useLogin";
import { setBlocked, setMuted } from "Login";
@ -7,7 +7,7 @@ import { System } from "index";
export default function useModeration() {
const login = useLogin();
const { muted, blocked } = login;
const { muted, blocked, appData } = login;
const publisher = useEventPublisher();
async function setMutedList(pub: HexKey[], priv: HexKey[]) {
@ -57,6 +57,14 @@ export default function useModeration() {
setMuted(login, newMuted, ts);
}
function isMutedWord(word: string) {
return appData.item.mutedWords.includes(word.toLowerCase());
}
function isEventMuted(ev: TaggedNostrEvent) {
return isMuted(ev.pubkey) || appData.item.mutedWords.some(w => ev.content.toLowerCase().includes(w));
}
return {
muted: muted.item,
mute,
@ -67,5 +75,7 @@ export default function useModeration() {
block,
unblock,
isBlocked,
isMutedWord,
isEventMuted
};
}

View File

@ -4,7 +4,7 @@ import * as secp from "@noble/curves/secp256k1";
import * as utils from "@noble/curves/abstract/utils";
import { DefaultRelays, SnortPubKey } from "Const";
import { LoginStore, UserPreferences, LoginSession, LoginSessionType } from "Login";
import { LoginStore, UserPreferences, LoginSession, LoginSessionType, SnortAppData } from "Login";
import { generateBip39Entropy, entropyToPrivateKey } from "nip6";
import { bech32ToHex, dedupeById, randomSample, sanitizeRelayUrl, unwrap } from "SnortUtils";
import { SubscriptionEvent } from "Subscription";
@ -157,6 +157,15 @@ export function setBookmarked(state: LoginSession, bookmarked: Array<string>, ts
LoginStore.updateSession(state);
}
export function setAppData(state: LoginSession, data: SnortAppData, ts: number) {
if(state.appData.timestamp >= ts) {
return;
}
state.appData.item = data;
state.appData.timestamp = ts;
LoginStore.updateSession(state);
}
export function addSubscription(state: LoginSession, ...subs: SubscriptionEvent[]) {
const newSubs = dedupeById([...(state.subscriptions || []), ...subs]);
if (newSubs.length !== state.subscriptions.length) {

View File

@ -18,6 +18,10 @@ export enum LoginSessionType {
Nip7os = "nip7_os",
}
export interface SnortAppData {
mutedWords: Array<string>
}
export interface LoginSession {
/**
* Unique ID to identify this session
@ -114,4 +118,9 @@ export interface LoginSession {
* Remote signer relays (NIP-46)
*/
remoteSignerRelays?: Array<string>;
/**
* Snort application data
*/
appData: Newest<SnortAppData>;
}

View File

@ -46,6 +46,12 @@ const LoggedOut = {
latestNotification: 0,
readNotifications: 0,
subscriptions: [],
appData: {
item: {
mutedWords: []
},
timestamp: 0,
},
} as LoginSession;
export class MultiAccountStore extends ExternalStore<LoginSession> {

View File

@ -9,6 +9,7 @@ import AccountsPage from "Pages/settings/Accounts";
import { WalletSettingsRoutes } from "Pages/settings/WalletSettings";
import { ManageHandleRoutes } from "Pages/settings/handle";
import ExportKeys from "Pages/settings/Keys";
import { ModerationSettings } from "./settings/Moderation";
import messages from "./messages";
@ -56,6 +57,10 @@ export const SettingsRoutes: RouteObject[] = [
path: "keys",
element: <ExportKeys />,
},
{
path: "moderation",
element: <ModerationSettings />,
},
...ManageHandleRoutes,
...WalletSettingsRoutes,
],

View File

@ -0,0 +1,44 @@
import { unixNowMs } from "@snort/shared";
import useLogin from "Hooks/useLogin";
import { setAppData } from "Login";
import { appendDedupe } from "SnortUtils";
import { useState } from "react";
import { FormattedMessage } from "react-intl";
export function ModerationSettings() {
const login = useLogin();
const [muteWord, setMuteWord] = useState("");
function addMutedWord() {
login.appData ??= {
item: {
mutedWords: []
},
timestamp: 0
};
setAppData(login, {
...login.appData.item,
mutedWords: appendDedupe(login.appData.item.mutedWords, [muteWord])
}, unixNowMs());
setMuteWord("");
}
return <>
<h2>
<FormattedMessage defaultMessage="Muted Words" />
</h2>
<div className="flex-column g12">
<div className="flex g8">
<input type="text" placeholder="eg. crypto" className="w-max" value={muteWord} onChange={e => setMuteWord(e.target.value)} />
<button type="button" onClick={addMutedWord}>
<FormattedMessage defaultMessage="Add" />
</button>
</div>
{login.appData.item.mutedWords.map(v => <div className="p br b flex f-space">
<div>{v}</div>
<button type="button">
<FormattedMessage defaultMessage="Delete" />
</button>
</div>)}
</div>
</>
}

View File

@ -51,6 +51,11 @@ const SettingsIndex = () => {
<FormattedMessage defaultMessage="Export Keys" />
<Icon name="arrowFront" size={16} />
</div>
<div className="settings-row" onClick={() => navigate("moderation")}>
<Icon name="shield-tick" size={24} />
<FormattedMessage defaultMessage="Moderation" />
<Icon name="arrowFront" size={16} />
</div>
<div className="settings-row" onClick={() => navigate("handle")}>
<Icon name="badge" size={24} />
<FormattedMessage defaultMessage="Nostr Adddress" />

View File

@ -133,6 +133,10 @@ code {
}
}
.b {
border: 1px solid var(--border-color);
}
.bg-primary {
background: var(--primary-gradient);
}