@ -1,24 +1,23 @@
|
|||||||
import { HexKey } from "@snort/system";
|
|
||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
|
|
||||||
import useModeration from "@/Hooks/useModeration";
|
import useModeration from "@/Hooks/useModeration";
|
||||||
|
|
||||||
import messages from "../messages";
|
import AsyncButton from "../Button/AsyncButton";
|
||||||
|
|
||||||
interface MuteButtonProps {
|
interface MuteButtonProps {
|
||||||
pubkey: HexKey;
|
pubkey: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const MuteButton = ({ pubkey }: MuteButtonProps) => {
|
const MuteButton = ({ pubkey }: MuteButtonProps) => {
|
||||||
const { mute, unmute, isMuted } = useModeration();
|
const { mute, unmute, isMuted } = useModeration();
|
||||||
return isMuted(pubkey) ? (
|
return isMuted(pubkey) ? (
|
||||||
<button className="secondary" type="button" onClick={() => unmute(pubkey)}>
|
<AsyncButton className="secondary" type="button" onClick={() => unmute(pubkey)}>
|
||||||
<FormattedMessage {...messages.Unmute} />
|
<FormattedMessage defaultMessage="Unmute" />
|
||||||
</button>
|
</AsyncButton>
|
||||||
) : (
|
) : (
|
||||||
<button type="button" onClick={() => mute(pubkey)}>
|
<AsyncButton type="button" onClick={() => mute(pubkey)}>
|
||||||
<FormattedMessage {...messages.Mute} />
|
<FormattedMessage defaultMessage="Mute" />
|
||||||
</button>
|
</AsyncButton>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -4,7 +4,10 @@ import { EventKind, NostrEvent, NostrLink, TaggedNostrEvent, ToNostrEventTag, Un
|
|||||||
import useLogin from "@/Hooks/useLogin";
|
import useLogin from "@/Hooks/useLogin";
|
||||||
|
|
||||||
export class MutedWordTag implements ToNostrEventTag {
|
export class MutedWordTag implements ToNostrEventTag {
|
||||||
constructor(readonly word: string) {}
|
constructor(readonly word: string) { }
|
||||||
|
equals(other: ToNostrEventTag): boolean {
|
||||||
|
return other instanceof MutedWordTag && other.word === this.word;
|
||||||
|
}
|
||||||
|
|
||||||
toEventTag(): string[] | undefined {
|
toEventTag(): string[] | undefined {
|
||||||
return ["word", this.word.toLowerCase()];
|
return ["word", this.word.toLowerCase()];
|
||||||
@ -16,7 +19,7 @@ export default function useModeration() {
|
|||||||
|
|
||||||
function isMuted(id: string) {
|
function isMuted(id: string) {
|
||||||
const link = NostrLink.publicKey(id);
|
const link = NostrLink.publicKey(id);
|
||||||
return state.muted.includes(link);
|
return state.muted.some(a => a.equals(link));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function unmute(id: string) {
|
async function unmute(id: string) {
|
||||||
|
@ -15,9 +15,11 @@ import Avatar from "@/Components/User/Avatar";
|
|||||||
import FollowButton from "@/Components/User/FollowButton";
|
import FollowButton from "@/Components/User/FollowButton";
|
||||||
import ProfileImage from "@/Components/User/ProfileImage";
|
import ProfileImage from "@/Components/User/ProfileImage";
|
||||||
import ZapModal from "@/Components/ZapModal/ZapModal";
|
import ZapModal from "@/Components/ZapModal/ZapModal";
|
||||||
|
import useModeration from "@/Hooks/useModeration";
|
||||||
import { hexToBech32 } from "@/Utils";
|
import { hexToBech32 } from "@/Utils";
|
||||||
import { LoginSessionType, LoginStore } from "@/Utils/Login";
|
import { LoginSessionType, LoginStore } from "@/Utils/Login";
|
||||||
import { ZapTarget } from "@/Utils/Zapper";
|
import { ZapTarget } from "@/Utils/Zapper";
|
||||||
|
import MuteButton from "@/Components/User/MuteButton";
|
||||||
|
|
||||||
const AvatarSection = ({
|
const AvatarSection = ({
|
||||||
user,
|
user,
|
||||||
@ -36,11 +38,14 @@ const AvatarSection = ({
|
|||||||
const [modalImage, setModalImage] = useState<string>("");
|
const [modalImage, setModalImage] = useState<string>("");
|
||||||
const [showLnQr, setShowLnQr] = useState<boolean>(false);
|
const [showLnQr, setShowLnQr] = useState<boolean>(false);
|
||||||
const [prefix, setPrefix] = useState<NostrPrefix>(CONFIG.profileLinkPrefix);
|
const [prefix, setPrefix] = useState<NostrPrefix>(CONFIG.profileLinkPrefix);
|
||||||
|
|
||||||
|
const { mute, unmute, isMuted } = useModeration();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const relays = UserRelays.getFromCache(id);
|
const relays = UserRelays.getFromCache(id);
|
||||||
const isMe = loginPubKey === id;
|
const isMe = loginPubKey === id;
|
||||||
const canWrite = !!loginPubKey && !readonly;
|
const canWrite = !!loginPubKey && !readonly;
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
const muted = id ? isMuted(id) : false;
|
||||||
|
|
||||||
const profileId = useMemo(() => {
|
const profileId = useMemo(() => {
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
@ -107,6 +112,19 @@ const AvatarSection = ({
|
|||||||
icon={{ name: "envelope", size: 16 }}
|
icon={{ name: "envelope", size: 16 }}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{canWrite && muted && <MuteButton pubkey={id} />}
|
||||||
|
{canWrite && !muted && <IconButton
|
||||||
|
className={muted ? "bg-success" : "!bg-error"}
|
||||||
|
onClick={async () => {
|
||||||
|
if (muted) {
|
||||||
|
await unmute(id);
|
||||||
|
} else {
|
||||||
|
await mute(id);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
icon={{ name: "mute", size: 16 }}
|
||||||
|
/>
|
||||||
|
}
|
||||||
{!canWrite && !isMe && (
|
{!canWrite && !isMe && (
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
@ -59,15 +59,6 @@ const ProfileTabSelectors = {
|
|||||||
),
|
),
|
||||||
value: ProfileTabType.MUTED,
|
value: ProfileTabType.MUTED,
|
||||||
},
|
},
|
||||||
Blocked: {
|
|
||||||
text: (
|
|
||||||
<>
|
|
||||||
<Icon name="block" size={16} />
|
|
||||||
<FormattedMessage defaultMessage="Blocked" />
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
value: ProfileTabType.BLOCKED,
|
|
||||||
},
|
|
||||||
Relays: {
|
Relays: {
|
||||||
text: (
|
text: (
|
||||||
<>
|
<>
|
||||||
|
@ -16,6 +16,8 @@ module.exports = {
|
|||||||
"nostr-red": "var(--heart)",
|
"nostr-red": "var(--heart)",
|
||||||
"nostr-purple": "var(--highlight)",
|
"nostr-purple": "var(--highlight)",
|
||||||
secondary: "var(--font-secondary-color)",
|
secondary: "var(--font-secondary-color)",
|
||||||
|
"warning": "var(--warning)",
|
||||||
|
"error": "var(--error)"
|
||||||
},
|
},
|
||||||
spacing: {
|
spacing: {
|
||||||
px: "1px",
|
px: "1px",
|
||||||
|
@ -101,11 +101,7 @@ export class DiffSyncTags extends EventEmitter<SafeSyncEvents> {
|
|||||||
|
|
||||||
async sync(signer: EventSigner | undefined, system: SystemInterface) {
|
async sync(signer: EventSigner | undefined, system: SystemInterface) {
|
||||||
await this.#sync.sync(system);
|
await this.#sync.sync(system);
|
||||||
|
await this.#afterSync(signer);
|
||||||
if (this.#sync.value?.content && this.contentEncrypted && signer) {
|
|
||||||
const decrypted = await signer.nip4Decrypt(this.#sync.value.content, await signer.getPubKey());
|
|
||||||
this.#decryptedContent = decrypted;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -123,6 +119,15 @@ export class DiffSyncTags extends EventEmitter<SafeSyncEvents> {
|
|||||||
next.content = await signer.nip4Encrypt(next.content, await signer.getPubKey());
|
next.content = await signer.nip4Encrypt(next.content, await signer.getPubKey());
|
||||||
}
|
}
|
||||||
await this.#sync.update(next, signer, system, !isNew);
|
await this.#sync.update(next, signer, system, !isNew);
|
||||||
|
await this.#afterSync(signer);
|
||||||
|
}
|
||||||
|
|
||||||
|
async #afterSync(signer: EventSigner | undefined) {
|
||||||
|
if (this.#sync.value?.content && this.contentEncrypted && signer) {
|
||||||
|
const decrypted = await signer.nip4Decrypt(this.#sync.value.content, await signer.getPubKey());
|
||||||
|
this.#decryptedContent = decrypted;
|
||||||
|
this.emit("change");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#nextEvent(content?: string): NotSignedNostrEvent {
|
#nextEvent(content?: string): NotSignedNostrEvent {
|
||||||
@ -141,6 +146,7 @@ export class DiffSyncTags extends EventEmitter<SafeSyncEvents> {
|
|||||||
// apply changes onto next
|
// apply changes onto next
|
||||||
next.tags = this.#applyChanges(next.tags, this.#changes);
|
next.tags = this.#applyChanges(next.tags, this.#changes);
|
||||||
if (this.#changesEncrypted.length > 0 && !content) {
|
if (this.#changesEncrypted.length > 0 && !content) {
|
||||||
|
debugger;
|
||||||
const encryptedTags = this.#applyChanges(isNew ? [] : this.encryptedTags, this.#changesEncrypted);
|
const encryptedTags = this.#applyChanges(isNew ? [] : this.encryptedTags, this.#changesEncrypted);
|
||||||
next.content = JSON.stringify(encryptedTags);
|
next.content = JSON.stringify(encryptedTags);
|
||||||
} else if (content) {
|
} else if (content) {
|
||||||
|
@ -117,12 +117,13 @@ export class UserState<TAppData> extends EventEmitter<UserStateEvents> {
|
|||||||
}
|
}
|
||||||
await Promise.all(tasks);
|
await Promise.all(tasks);
|
||||||
this.#log(
|
this.#log(
|
||||||
"Init results: profile=%O, contacts=%O, relays=%O, appdata=%O, lists=%O",
|
"Init results: signer=%s, profile=%O, contacts=%O, relays=%O, appdata=%O, lists=%O",
|
||||||
|
signer ? "yes" : "no",
|
||||||
this.#profile.json,
|
this.#profile.json,
|
||||||
this.#contacts.value,
|
this.#contacts.value,
|
||||||
this.#relays.value,
|
this.#relays.value,
|
||||||
this.#appdata?.json,
|
this.#appdata?.json,
|
||||||
[...this.#standardLists.values()].map(a => a.value),
|
[...this.#standardLists.values()].map(a => [a.value, a.encryptedTags]),
|
||||||
);
|
);
|
||||||
|
|
||||||
// update relay metadata with value from contact list if not found
|
// update relay metadata with value from contact list if not found
|
||||||
|
Reference in New Issue
Block a user