This commit is contained in:
parent
540f29dd69
commit
24978f4e62
@ -4,7 +4,7 @@ import useLogin from "Hooks/useLogin";
|
|||||||
const MixCloudEmbed = ({ link }: { link: string }) => {
|
const MixCloudEmbed = ({ link }: { link: string }) => {
|
||||||
const feedPath = (MixCloudRegex.test(link) && RegExp.$1) + "%2F" + (MixCloudRegex.test(link) && RegExp.$2);
|
const feedPath = (MixCloudRegex.test(link) && RegExp.$1) + "%2F" + (MixCloudRegex.test(link) && RegExp.$2);
|
||||||
|
|
||||||
const { theme } = useLogin(s => ({ theme: s.preferences.theme }));
|
const theme = useLogin(s => s.appData.item.preferences.theme);
|
||||||
const lightParams = theme === "light" ? "light=1" : "light=0";
|
const lightParams = theme === "light" ? "light=1" : "light=0";
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -22,7 +22,7 @@ export default function PubkeyList({ ev, className }: { ev: NostrEvent; classNam
|
|||||||
for (const pk of ids) {
|
for (const pk of ids) {
|
||||||
try {
|
try {
|
||||||
const profile = await UserCache.get(pk);
|
const profile = await UserCache.get(pk);
|
||||||
const amtSend = login.preferences.defaultZapAmount;
|
const amtSend = login.appData.item.preferences.defaultZapAmount;
|
||||||
const lnurl = profile?.lud16 || profile?.lud06;
|
const lnurl = profile?.lud16 || profile?.lud06;
|
||||||
if (lnurl) {
|
if (lnurl) {
|
||||||
const svc = new LNURL(lnurl);
|
const svc = new LNURL(lnurl);
|
||||||
@ -72,7 +72,7 @@ export default function PubkeyList({ ev, className }: { ev: NostrEvent; classNam
|
|||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
defaultMessage="Zap all {n} sats"
|
defaultMessage="Zap all {n} sats"
|
||||||
values={{
|
values={{
|
||||||
n: <FormattedNumber value={login.preferences.defaultZapAmount * ids.length} />,
|
n: <FormattedNumber value={login.appData.item.preferences.defaultZapAmount * ids.length} />,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</AsyncButton>
|
</AsyncButton>
|
||||||
|
@ -83,7 +83,7 @@ export function NoteContextMenu({ ev, ...props }: NosteContextMenuProps) {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const sub = getCurrentSubscription(login.subscriptions);
|
const sub = getCurrentSubscription(login.subscriptions);
|
||||||
if (sub?.type === SubscriptionType.Premium && (login.preferences.autoTranslate ?? true)) {
|
if (sub?.type === SubscriptionType.Premium && (login.appData.item.preferences.autoTranslate ?? true)) {
|
||||||
translate();
|
translate();
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
@ -162,7 +162,7 @@ export function NoteContextMenu({ ev, ...props }: NosteContextMenuProps) {
|
|||||||
<FormattedMessage {...messages.Mute} />
|
<FormattedMessage {...messages.Mute} />
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
{login.preferences.enableReactions && !login.readonly && (
|
{login.appData.item.preferences.enableReactions && !login.readonly && (
|
||||||
<MenuItem onClick={() => props.react("-")}>
|
<MenuItem onClick={() => props.react("-")}>
|
||||||
<Icon name="dislike" />
|
<Icon name="dislike" />
|
||||||
<FormattedMessage {...messages.DislikeAction} />
|
<FormattedMessage {...messages.DislikeAction} />
|
||||||
@ -182,12 +182,10 @@ export function NoteContextMenu({ ev, ...props }: NosteContextMenuProps) {
|
|||||||
<Icon name="translate" />
|
<Icon name="translate" />
|
||||||
<FormattedMessage {...messages.TranslateTo} values={{ lang: langNames.of(lang.split("-")[0]) }} />
|
<FormattedMessage {...messages.TranslateTo} values={{ lang: langNames.of(lang.split("-")[0]) }} />
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
{login.preferences.showDebugMenus && (
|
|
||||||
<MenuItem onClick={() => copyEvent()}>
|
<MenuItem onClick={() => copyEvent()}>
|
||||||
<Icon name="json" />
|
<Icon name="json" />
|
||||||
<FormattedMessage {...messages.CopyJSON} />
|
<FormattedMessage {...messages.CopyJSON} />
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
|
||||||
{isMine && !login.readonly && (
|
{isMine && !login.readonly && (
|
||||||
<MenuItem onClick={() => deleteEvent()}>
|
<MenuItem onClick={() => deleteEvent()}>
|
||||||
<Icon name="trash" className="red" />
|
<Icon name="trash" className="red" />
|
||||||
|
@ -27,7 +27,7 @@ import { ToggleSwitch } from "Icons/Toggle";
|
|||||||
export function NoteCreator() {
|
export function NoteCreator() {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const uploader = useFileUpload();
|
const uploader = useFileUpload();
|
||||||
const login = useLogin(s => ({ relays: s.relays, publicKey: s.publicKey, pow: s.preferences.pow }));
|
const login = useLogin(s => ({ relays: s.relays, publicKey: s.publicKey, pow: s.appData.item.preferences.pow }));
|
||||||
const { publisher: pub } = useEventPublisher();
|
const { publisher: pub } = useEventPublisher();
|
||||||
const publisher = login.pow ? pub?.pow(login.pow, GetPowWorker()) : pub;
|
const publisher = login.pow ? pub?.pow(login.pow, GetPowWorker()) : pub;
|
||||||
const note = useNoteCreator();
|
const note = useNoteCreator();
|
||||||
|
@ -52,7 +52,7 @@ export default function NoteFooter(props: NoteFooterProps) {
|
|||||||
publicKey,
|
publicKey,
|
||||||
preferences: prefs,
|
preferences: prefs,
|
||||||
readonly,
|
readonly,
|
||||||
} = useLogin(s => ({ preferences: s.preferences, publicKey: s.publicKey, readonly: s.readonly }));
|
} = useLogin(s => ({ preferences: s.appData.item.preferences, publicKey: s.publicKey, readonly: s.readonly }));
|
||||||
const author = useUserProfile(ev.pubkey);
|
const author = useUserProfile(ev.pubkey);
|
||||||
const interactionCache = useInteractionCache(publicKey, ev.id);
|
const interactionCache = useInteractionCache(publicKey, ev.id);
|
||||||
const { publisher, system } = useEventPublisher();
|
const { publisher, system } = useEventPublisher();
|
||||||
|
@ -23,7 +23,11 @@ export default function Poll(props: PollProps) {
|
|||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const { publisher } = useEventPublisher();
|
const { publisher } = useEventPublisher();
|
||||||
const { wallet } = useWallet();
|
const { wallet } = useWallet();
|
||||||
const { preferences: prefs, publicKey: myPubKey, relays } = useLogin();
|
const {
|
||||||
|
preferences: prefs,
|
||||||
|
publicKey: myPubKey,
|
||||||
|
relays,
|
||||||
|
} = useLogin(s => ({ preferences: s.appData.item.preferences, publicKey: s.publicKey, relays: s.relays }));
|
||||||
const pollerProfile = useUserProfile(props.ev.pubkey);
|
const pollerProfile = useUserProfile(props.ev.pubkey);
|
||||||
const [tallyBy, setTallyBy] = useState<PollTally>("pubkeys");
|
const [tallyBy, setTallyBy] = useState<PollTally>("pubkeys");
|
||||||
const [error, setError] = useState("");
|
const [error, setError] = useState("");
|
||||||
|
@ -13,12 +13,15 @@ interface RevealMediaProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function RevealMedia(props: RevealMediaProps) {
|
export default function RevealMedia(props: RevealMediaProps) {
|
||||||
const login = useLogin();
|
const { preferences, follows, publicKey } = useLogin(s => ({
|
||||||
const { preferences: pref, follows, publicKey } = login;
|
preferences: s.appData.item.preferences,
|
||||||
|
follows: s.follows.item,
|
||||||
|
publicKey: s.publicKey,
|
||||||
|
}));
|
||||||
|
|
||||||
const hideNonFollows = pref.autoLoadMedia === "follows-only" && !follows.item.includes(props.creator);
|
const hideNonFollows = preferences.autoLoadMedia === "follows-only" && !follows.includes(props.creator);
|
||||||
const isMine = props.creator === publicKey;
|
const isMine = props.creator === publicKey;
|
||||||
const hideMedia = pref.autoLoadMedia === "none" || (!isMine && hideNonFollows);
|
const hideMedia = preferences.autoLoadMedia === "none" || (!isMine && hideNonFollows);
|
||||||
const hostname = new URL(props.link).hostname;
|
const hostname = new URL(props.link).hostname;
|
||||||
|
|
||||||
const url = new URL(props.link);
|
const url = new URL(props.link);
|
||||||
|
@ -118,9 +118,11 @@ const TimelineFollows = (props: TimelineFollowsProps) => {
|
|||||||
noteOnClick={props.noteOnClick}
|
noteOnClick={props.noteOnClick}
|
||||||
noteRenderer={props.noteRenderer}
|
noteRenderer={props.noteRenderer}
|
||||||
/>
|
/>
|
||||||
{sortedFeed.length > 0 && <ShowMoreInView
|
{sortedFeed.length > 0 && (
|
||||||
|
<ShowMoreInView
|
||||||
onClick={async () => await FollowsFeed.loadMore(system, login, sortedFeed[sortedFeed.length - 1].created_at)}
|
onClick={async () => await FollowsFeed.loadMore(system, login, sortedFeed[sortedFeed.length - 1].created_at)}
|
||||||
/>}
|
/>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -100,8 +100,8 @@ export function LoginUnlock() {
|
|||||||
const newPin = await PinEncrypted.create(k, pin);
|
const newPin = await PinEncrypted.create(k, pin);
|
||||||
|
|
||||||
const pub = EventPublisher.privateKey(k);
|
const pub = EventPublisher.privateKey(k);
|
||||||
if (login.preferences.pow) {
|
if (login.appData.item.preferences.pow) {
|
||||||
pub.pow(login.preferences.pow, GetPowWorker());
|
pub.pow(login.appData.item.preferences.pow, GetPowWorker());
|
||||||
}
|
}
|
||||||
LoginStore.setPublisher(login.id, pub);
|
LoginStore.setPublisher(login.id, pub);
|
||||||
LoginStore.updateSession({
|
LoginStore.updateSession({
|
||||||
@ -117,8 +117,8 @@ export function LoginUnlock() {
|
|||||||
await key.unlock(pin);
|
await key.unlock(pin);
|
||||||
const pub = createPublisher(login);
|
const pub = createPublisher(login);
|
||||||
if (pub) {
|
if (pub) {
|
||||||
if (login.preferences.pow) {
|
if (login.appData.item.preferences.pow) {
|
||||||
pub.pow(login.preferences.pow, GetPowWorker());
|
pub.pow(login.appData.item.preferences.pow, GetPowWorker());
|
||||||
}
|
}
|
||||||
LoginStore.setPublisher(login.id, pub);
|
LoginStore.setPublisher(login.id, pub);
|
||||||
LoginStore.updateSession({
|
LoginStore.updateSession({
|
||||||
|
@ -247,7 +247,7 @@ function SendSatsInput(props: {
|
|||||||
onNextStage: (v: SendSatsInputSelection) => Promise<void>;
|
onNextStage: (v: SendSatsInputSelection) => Promise<void>;
|
||||||
}) {
|
}) {
|
||||||
const { defaultZapAmount, readonly } = useLogin(s => ({
|
const { defaultZapAmount, readonly } = useLogin(s => ({
|
||||||
defaultZapAmount: s.preferences.defaultZapAmount,
|
defaultZapAmount: s.appData.item.preferences.defaultZapAmount,
|
||||||
readonly: s.readonly,
|
readonly: s.readonly,
|
||||||
}));
|
}));
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
|
@ -2,7 +2,7 @@ import { useEffect, useMemo } from "react";
|
|||||||
import { TaggedNostrEvent, EventKind, RequestBuilder, NoteCollection, NostrLink } from "@snort/system";
|
import { TaggedNostrEvent, EventKind, RequestBuilder, NoteCollection, NostrLink } from "@snort/system";
|
||||||
import { useRequestBuilder } from "@snort/system-react";
|
import { useRequestBuilder } from "@snort/system-react";
|
||||||
|
|
||||||
import { bech32ToHex, findTag, getNewest, getNewestEventTagsByKey, unwrap } from "SnortUtils";
|
import { bech32ToHex, debounce, findTag, getNewest, getNewestEventTagsByKey, unwrap } from "SnortUtils";
|
||||||
import { makeNotification, sendNotification } from "Notifications";
|
import { makeNotification, sendNotification } from "Notifications";
|
||||||
import useEventPublisher from "Hooks/useEventPublisher";
|
import useEventPublisher from "Hooks/useEventPublisher";
|
||||||
import useModeration from "Hooks/useModeration";
|
import useModeration from "Hooks/useModeration";
|
||||||
@ -40,9 +40,19 @@ export default function useLoginFeed() {
|
|||||||
useRefreshFeedCache(GiftsCache, true);
|
useRefreshFeedCache(GiftsCache, true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
system.checkSigs = login.preferences.checkSigs;
|
system.checkSigs = login.appData.item.preferences.checkSigs;
|
||||||
}, [login]);
|
}, [login]);
|
||||||
|
|
||||||
|
// write appdata after 10s of no changes
|
||||||
|
useEffect(() => {
|
||||||
|
return debounce(10_000, async () => {
|
||||||
|
if (publisher && login.appData.item) {
|
||||||
|
const ev = await publisher.appData(login.appData.item, "snort");
|
||||||
|
await system.BroadcastEvent(ev);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, [login.appData.timestamp]);
|
||||||
|
|
||||||
const subLogin = useMemo(() => {
|
const subLogin = useMemo(() => {
|
||||||
if (!login || !pubKey) return null;
|
if (!login || !pubKey) return null;
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ export default function useTimelineFeed(subject: TimelineSubject, options: Timel
|
|||||||
window: options.window,
|
window: options.window,
|
||||||
now: options.now ?? unixNow(),
|
now: options.now ?? unixNow(),
|
||||||
});
|
});
|
||||||
const pref = useLogin().preferences;
|
const pref = useLogin(s => s.appData.item.preferences);
|
||||||
|
|
||||||
const createBuilder = useCallback(() => {
|
const createBuilder = useCallback(() => {
|
||||||
if (subject.type !== "global" && subject.items.length === 0) {
|
if (subject.type !== "global" && subject.items.length === 0) {
|
||||||
|
@ -10,7 +10,7 @@ export interface ImgProxySettings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function useImgProxy() {
|
export default function useImgProxy() {
|
||||||
const settings = useLogin().preferences.imgProxyConfig;
|
const settings = useLogin(s => s.appData.item.preferences.imgProxyConfig);
|
||||||
const te = new TextEncoder();
|
const te = new TextEncoder();
|
||||||
|
|
||||||
function urlSafe(s: string) {
|
function urlSafe(s: string) {
|
||||||
|
@ -2,7 +2,7 @@ import { LoginSession, LoginStore } from "Login";
|
|||||||
import { useSyncExternalStore } from "react";
|
import { useSyncExternalStore } from "react";
|
||||||
import { useSyncExternalStoreWithSelector } from "use-sync-external-store/with-selector";
|
import { useSyncExternalStoreWithSelector } from "use-sync-external-store/with-selector";
|
||||||
|
|
||||||
export default function useLogin<T extends object = LoginSession>(selector?: (v: LoginSession) => T) {
|
export default function useLogin<T = LoginSession>(selector?: (v: LoginSession) => T) {
|
||||||
if (selector) {
|
if (selector) {
|
||||||
return useSyncExternalStoreWithSelector(
|
return useSyncExternalStoreWithSelector(
|
||||||
s => LoginStore.hook(s),
|
s => LoginStore.hook(s),
|
||||||
|
@ -2,7 +2,7 @@ import { useEffect } from "react";
|
|||||||
import useLogin from "./useLogin";
|
import useLogin from "./useLogin";
|
||||||
|
|
||||||
export function useTheme() {
|
export function useTheme() {
|
||||||
const { preferences } = useLogin();
|
const { preferences } = useLogin(s => ({ preferences: s.appData.item.preferences }));
|
||||||
|
|
||||||
function setTheme(theme: "light" | "dark") {
|
function setTheme(theme: "light" | "dark") {
|
||||||
const elm = document.documentElement;
|
const elm = document.documentElement;
|
||||||
|
@ -95,7 +95,7 @@ class LangStore extends ExternalStore<string | null> {
|
|||||||
const LangOverride = new LangStore();
|
const LangOverride = new LangStore();
|
||||||
|
|
||||||
export function useLocale() {
|
export function useLocale() {
|
||||||
const { language } = useLogin(s => ({ language: s.preferences.language }));
|
const { language } = useLogin(s => ({ language: s.appData.item.preferences.language }));
|
||||||
const loggedOutLang = useSyncExternalStore(
|
const loggedOutLang = useSyncExternalStore(
|
||||||
c => LangOverride.hook(c),
|
c => LangOverride.hook(c),
|
||||||
() => LangOverride.snapshot(),
|
() => LangOverride.snapshot(),
|
||||||
|
@ -13,7 +13,7 @@ import * as secp from "@noble/curves/secp256k1";
|
|||||||
import * as utils from "@noble/curves/abstract/utils";
|
import * as utils from "@noble/curves/abstract/utils";
|
||||||
|
|
||||||
import { DefaultRelays, SnortPubKey } from "Const";
|
import { DefaultRelays, SnortPubKey } from "Const";
|
||||||
import { LoginStore, UserPreferences, LoginSession, LoginSessionType, SnortAppData } from "Login";
|
import { LoginStore, UserPreferences, LoginSession, LoginSessionType, SnortAppData, Newest } from "Login";
|
||||||
import { generateBip39Entropy, entropyToPrivateKey } from "nip6";
|
import { generateBip39Entropy, entropyToPrivateKey } from "nip6";
|
||||||
import { bech32ToHex, dedupeById, sanitizeRelayUrl, unwrap } from "SnortUtils";
|
import { bech32ToHex, dedupeById, sanitizeRelayUrl, unwrap } from "SnortUtils";
|
||||||
import { SubscriptionEvent } from "Subscription";
|
import { SubscriptionEvent } from "Subscription";
|
||||||
@ -54,9 +54,13 @@ export function removeRelay(state: LoginSession, addr: string) {
|
|||||||
LoginStore.updateSession(state);
|
LoginStore.updateSession(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updatePreferences(state: LoginSession, p: UserPreferences) {
|
export function updatePreferences(id: string, p: UserPreferences) {
|
||||||
state.preferences = p;
|
updateAppData(id, d => {
|
||||||
LoginStore.updateSession(state);
|
return {
|
||||||
|
item: { ...d, preferences: p },
|
||||||
|
timestamp: unixNowMs(),
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function logout(id: string) {
|
export function logout(id: string) {
|
||||||
@ -183,6 +187,17 @@ export function setAppData(state: LoginSession, data: SnortAppData, ts: number)
|
|||||||
LoginStore.updateSession(state);
|
LoginStore.updateSession(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function updateAppData(id: string, fn: (data: SnortAppData) => Newest<SnortAppData>) {
|
||||||
|
const session = LoginStore.get(id);
|
||||||
|
if (session) {
|
||||||
|
const next = fn(session.appData.item);
|
||||||
|
if (next.timestamp > session.appData.timestamp) {
|
||||||
|
session.appData = next;
|
||||||
|
LoginStore.updateSession(session);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function addSubscription(state: LoginSession, ...subs: SubscriptionEvent[]) {
|
export function addSubscription(state: LoginSession, ...subs: SubscriptionEvent[]) {
|
||||||
const newSubs = dedupeById([...(state.subscriptions || []), ...subs]);
|
const newSubs = dedupeById([...(state.subscriptions || []), ...subs]);
|
||||||
if (newSubs.length !== state.subscriptions.length) {
|
if (newSubs.length !== state.subscriptions.length) {
|
||||||
|
@ -5,7 +5,7 @@ import { SubscriptionEvent } from "Subscription";
|
|||||||
/**
|
/**
|
||||||
* Stores latest copy of an item
|
* Stores latest copy of an item
|
||||||
*/
|
*/
|
||||||
interface Newest<T> {
|
export interface Newest<T> {
|
||||||
item: T;
|
item: T;
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
}
|
}
|
||||||
@ -20,6 +20,7 @@ export const enum LoginSessionType {
|
|||||||
|
|
||||||
export interface SnortAppData {
|
export interface SnortAppData {
|
||||||
mutedWords: Array<string>;
|
mutedWords: Array<string>;
|
||||||
|
preferences: UserPreferences;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LoginSession {
|
export interface LoginSession {
|
||||||
@ -104,11 +105,6 @@ export interface LoginSession {
|
|||||||
*/
|
*/
|
||||||
readNotifications: number;
|
readNotifications: number;
|
||||||
|
|
||||||
/**
|
|
||||||
* Users cusom preferences
|
|
||||||
*/
|
|
||||||
preferences: UserPreferences;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Snort subscriptions licences
|
* Snort subscriptions licences
|
||||||
*/
|
*/
|
||||||
|
@ -7,13 +7,12 @@ import { deepClone, sanitizeRelayUrl, unwrap, ExternalStore } from "@snort/share
|
|||||||
|
|
||||||
import { DefaultRelays } from "Const";
|
import { DefaultRelays } from "Const";
|
||||||
import { LoginSession, LoginSessionType, createPublisher } from "Login";
|
import { LoginSession, LoginSessionType, createPublisher } from "Login";
|
||||||
import { DefaultPreferences } from "./Preferences";
|
import { DefaultPreferences, UserPreferences } from "./Preferences";
|
||||||
|
|
||||||
const AccountStoreKey = "sessions";
|
const AccountStoreKey = "sessions";
|
||||||
const LoggedOut = {
|
const LoggedOut = {
|
||||||
id: "default",
|
id: "default",
|
||||||
type: "public_key",
|
type: "public_key",
|
||||||
preferences: DefaultPreferences,
|
|
||||||
readonly: true,
|
readonly: true,
|
||||||
tags: {
|
tags: {
|
||||||
item: [],
|
item: [],
|
||||||
@ -49,6 +48,7 @@ const LoggedOut = {
|
|||||||
appData: {
|
appData: {
|
||||||
item: {
|
item: {
|
||||||
mutedWords: [],
|
mutedWords: [],
|
||||||
|
preferences: DefaultPreferences,
|
||||||
},
|
},
|
||||||
timestamp: 0,
|
timestamp: 0,
|
||||||
},
|
},
|
||||||
@ -83,11 +83,11 @@ export class MultiAccountStore extends ExternalStore<LoginSession> {
|
|||||||
v.appData ??= {
|
v.appData ??= {
|
||||||
item: {
|
item: {
|
||||||
mutedWords: [],
|
mutedWords: [],
|
||||||
|
preferences: DefaultPreferences,
|
||||||
},
|
},
|
||||||
timestamp: 0,
|
timestamp: 0,
|
||||||
};
|
};
|
||||||
v.extraChats ??= [];
|
v.extraChats ??= [];
|
||||||
v.preferences.checkSigs ??= false;
|
|
||||||
if (v.privateKeyData) {
|
if (v.privateKeyData) {
|
||||||
v.privateKeyData = KeyStorage.fromPayload(v.privateKeyData as object);
|
v.privateKeyData = KeyStorage.fromPayload(v.privateKeyData as object);
|
||||||
}
|
}
|
||||||
@ -102,6 +102,13 @@ export class MultiAccountStore extends ExternalStore<LoginSession> {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get(id: string) {
|
||||||
|
const s = this.#accounts.get(id);
|
||||||
|
if (s) {
|
||||||
|
return { ...s };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
allSubscriptions() {
|
allSubscriptions() {
|
||||||
return [...this.#accounts.values()].map(a => a.subscriptions).flat();
|
return [...this.#accounts.values()].map(a => a.subscriptions).flat();
|
||||||
}
|
}
|
||||||
@ -246,14 +253,6 @@ export class MultiAccountStore extends ExternalStore<LoginSession> {
|
|||||||
#migrate() {
|
#migrate() {
|
||||||
let didMigrate = false;
|
let didMigrate = false;
|
||||||
|
|
||||||
// replace default tab with notes
|
|
||||||
for (const [, v] of this.#accounts) {
|
|
||||||
if ((v.preferences.defaultRootTab as string) === "posts") {
|
|
||||||
v.preferences.defaultRootTab = "notes";
|
|
||||||
didMigrate = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// update session types
|
// update session types
|
||||||
for (const [, v] of this.#accounts) {
|
for (const [, v] of this.#accounts) {
|
||||||
if (!v.type) {
|
if (!v.type) {
|
||||||
@ -283,6 +282,15 @@ export class MultiAccountStore extends ExternalStore<LoginSession> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// move preferences to appdata
|
||||||
|
for (const [, v] of this.#accounts) {
|
||||||
|
if ("preferences" in v) {
|
||||||
|
v.appData.item.preferences = v.preferences as UserPreferences;
|
||||||
|
delete v["preferences"];
|
||||||
|
didMigrate = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (didMigrate) {
|
if (didMigrate) {
|
||||||
console.debug("Finished migration in MultiAccountStore");
|
console.debug("Finished migration in MultiAccountStore");
|
||||||
this.#save();
|
this.#save();
|
||||||
|
@ -33,8 +33,6 @@ const HashTagsPage = () => {
|
|||||||
|
|
||||||
export default HashTagsPage;
|
export default HashTagsPage;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export function HashTagHeader({ tag }: { tag: string }) {
|
export function HashTagHeader({ tag }: { tag: string }) {
|
||||||
const login = useLogin();
|
const login = useLogin();
|
||||||
const isFollowing = useMemo(() => {
|
const isFollowing = useMemo(() => {
|
||||||
@ -55,29 +53,29 @@ export function HashTagHeader({ tag }: { tag: string }) {
|
|||||||
|
|
||||||
const sub = useMemo(() => {
|
const sub = useMemo(() => {
|
||||||
const rb = new RequestBuilder(`hashtag-counts:${tag}`);
|
const rb = new RequestBuilder(`hashtag-counts:${tag}`);
|
||||||
rb.withFilter()
|
rb.withFilter().kinds([EventKind.CategorizedBookmarks]).tag("d", ["follow"]).tag("t", [tag.toLowerCase()]);
|
||||||
.kinds([EventKind.CategorizedBookmarks])
|
|
||||||
.tag("d", ["follow"])
|
|
||||||
.tag("t", [tag.toLowerCase()]);
|
|
||||||
return rb;
|
return rb;
|
||||||
}, [tag]);
|
}, [tag]);
|
||||||
const followsTag = useRequestBuilder(NoteCollection, sub);
|
const followsTag = useRequestBuilder(NoteCollection, sub);
|
||||||
const pubkeys = dedupe((followsTag.data ?? []).map(a => a.pubkey));
|
const pubkeys = dedupe((followsTag.data ?? []).map(a => a.pubkey));
|
||||||
|
|
||||||
return <div className="flex items-center justify-between">
|
return (
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex flex-col g8">
|
<div className="flex flex-col g8">
|
||||||
<h2>#{tag}</h2>
|
<h2>#{tag}</h2>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
{pubkeys.slice(0, 5).map(a => <ProfileImage pubkey={a} showUsername={false} link={""} showFollowingMark={false} size={40} />)}
|
{pubkeys.slice(0, 5).map(a => (
|
||||||
{pubkeys.length > 5 && <span>
|
<ProfileImage pubkey={a} showUsername={false} link={""} showFollowingMark={false} size={40} />
|
||||||
|
))}
|
||||||
|
{pubkeys.length > 5 && (
|
||||||
|
<span>
|
||||||
+<FormattedNumber value={pubkeys.length - 5} />
|
+<FormattedNumber value={pubkeys.length - 5} />
|
||||||
</span>}
|
</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{isFollowing ? (
|
{isFollowing ? (
|
||||||
<AsyncButton
|
<AsyncButton className="secondary" onClick={() => followTags(login.tags.item.filter(t => t !== tag))}>
|
||||||
className="secondary"
|
|
||||||
onClick={() => followTags(login.tags.item.filter(t => t !== tag))}>
|
|
||||||
<FormattedMessage defaultMessage="Unfollow" />
|
<FormattedMessage defaultMessage="Unfollow" />
|
||||||
</AsyncButton>
|
</AsyncButton>
|
||||||
) : (
|
) : (
|
||||||
@ -86,4 +84,5 @@ export function HashTagHeader({ tag }: { tag: string }) {
|
|||||||
</AsyncButton>
|
</AsyncButton>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
@ -89,8 +89,8 @@ export default function ProfilePage({ id: propId, state }: ProfilePageProps) {
|
|||||||
// ignored
|
// ignored
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
const showBadges = login.preferences.showBadges ?? false;
|
const showBadges = login.appData.item.preferences.showBadges ?? false;
|
||||||
const showStatus = login.preferences.showStatus ?? true;
|
const showStatus = login.appData.item.preferences.showStatus ?? true;
|
||||||
|
|
||||||
// feeds
|
// feeds
|
||||||
const { blocked } = useModeration();
|
const { blocked } = useModeration();
|
||||||
|
@ -181,7 +181,10 @@ export const TagsTab = (params: { tag?: string }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const DefaultTab = () => {
|
const DefaultTab = () => {
|
||||||
const { preferences, publicKey } = useLogin();
|
const { preferences, publicKey } = useLogin(s => ({
|
||||||
|
preferences: s.appData.item.preferences,
|
||||||
|
publicKey: s.publicKey,
|
||||||
|
}));
|
||||||
const tab = publicKey ? preferences.defaultRootTab ?? `notes` : `trending/notes`;
|
const tab = publicKey ? preferences.defaultRootTab ?? `notes` : `trending/notes`;
|
||||||
const elm = RootTabRoutes.find(a => a.path === tab)?.element;
|
const elm = RootTabRoutes.find(a => a.path === tab)?.element;
|
||||||
return elm;
|
return elm;
|
||||||
|
@ -34,7 +34,7 @@ function ZapTarget({ target }: { target: ZapPoolRecipient }) {
|
|||||||
const login = useLogin();
|
const login = useLogin();
|
||||||
const profile = useUserProfile(target.pubkey);
|
const profile = useUserProfile(target.pubkey);
|
||||||
const hasAddress = profile?.lud16 || profile?.lud06;
|
const hasAddress = profile?.lud16 || profile?.lud06;
|
||||||
const defaultZapMount = Math.ceil(login.preferences.defaultZapAmount * (target.split / 100));
|
const defaultZapMount = Math.ceil(login.appData.item.preferences.defaultZapAmount * (target.split / 100));
|
||||||
return (
|
return (
|
||||||
<ProfilePreview
|
<ProfilePreview
|
||||||
pubkey={target.pubkey}
|
pubkey={target.pubkey}
|
||||||
@ -107,7 +107,7 @@ export default function ZapPoolPage() {
|
|||||||
values={{
|
values={{
|
||||||
number: (
|
number: (
|
||||||
<b>
|
<b>
|
||||||
<FormattedNumber value={login.preferences.defaultZapAmount} />
|
<FormattedNumber value={login.appData.item.preferences.defaultZapAmount} />
|
||||||
</b>
|
</b>
|
||||||
),
|
),
|
||||||
}}
|
}}
|
||||||
@ -119,12 +119,14 @@ export default function ZapPoolPage() {
|
|||||||
values={{
|
values={{
|
||||||
nIn: (
|
nIn: (
|
||||||
<b>
|
<b>
|
||||||
<FormattedNumber value={login.preferences.defaultZapAmount} />
|
<FormattedNumber value={login.appData.item.preferences.defaultZapAmount} />
|
||||||
</b>
|
</b>
|
||||||
),
|
),
|
||||||
nOut: (
|
nOut: (
|
||||||
<b>
|
<b>
|
||||||
<FormattedNumber value={ZapPoolController.calcAllocation(login.preferences.defaultZapAmount)} />
|
<FormattedNumber
|
||||||
|
value={ZapPoolController.calcAllocation(login.appData.item.preferences.defaultZapAmount)}
|
||||||
|
/>
|
||||||
</b>
|
</b>
|
||||||
),
|
),
|
||||||
}}
|
}}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { unixNowMs } from "@snort/shared";
|
import { unixNowMs } from "@snort/shared";
|
||||||
import useLogin from "Hooks/useLogin";
|
import useLogin from "Hooks/useLogin";
|
||||||
import { setAppData } from "Login";
|
import { updateAppData } from "Login";
|
||||||
import { appendDedupe } from "SnortUtils";
|
import { appendDedupe } from "SnortUtils";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
@ -10,32 +10,24 @@ export function ModerationSettings() {
|
|||||||
const [muteWord, setMuteWord] = useState("");
|
const [muteWord, setMuteWord] = useState("");
|
||||||
|
|
||||||
function addMutedWord() {
|
function addMutedWord() {
|
||||||
login.appData ??= {
|
updateAppData(login.id, ad => ({
|
||||||
item: {
|
item: {
|
||||||
mutedWords: [],
|
...ad,
|
||||||
},
|
|
||||||
timestamp: 0,
|
|
||||||
};
|
|
||||||
setAppData(
|
|
||||||
login,
|
|
||||||
{
|
|
||||||
...login.appData.item,
|
|
||||||
mutedWords: appendDedupe(login.appData.item.mutedWords, [muteWord]),
|
mutedWords: appendDedupe(login.appData.item.mutedWords, [muteWord]),
|
||||||
},
|
},
|
||||||
unixNowMs(),
|
timestamp: unixNowMs(),
|
||||||
);
|
}));
|
||||||
setMuteWord("");
|
setMuteWord("");
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeMutedWord(word: string) {
|
function removeMutedWord(word: string) {
|
||||||
setAppData(
|
updateAppData(login.id, ad => ({
|
||||||
login,
|
item: {
|
||||||
{
|
...ad,
|
||||||
...login.appData.item,
|
|
||||||
mutedWords: login.appData.item.mutedWords.filter(a => a !== word),
|
mutedWords: login.appData.item.mutedWords.filter(a => a !== word),
|
||||||
},
|
},
|
||||||
unixNowMs(),
|
timestamp: unixNowMs(),
|
||||||
);
|
}));
|
||||||
setMuteWord("");
|
setMuteWord("");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,8 +35,7 @@ export const AllLanguageCodes = [
|
|||||||
|
|
||||||
const PreferencesPage = () => {
|
const PreferencesPage = () => {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const login = useLogin();
|
const { id, perf } = useLogin(s => ({ id: s.id, perf: s.appData.item.preferences }));
|
||||||
const perf = login.preferences;
|
|
||||||
const { lang } = useLocale();
|
const { lang } = useLocale();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -53,7 +52,7 @@ const PreferencesPage = () => {
|
|||||||
<select
|
<select
|
||||||
value={lang}
|
value={lang}
|
||||||
onChange={e =>
|
onChange={e =>
|
||||||
updatePreferences(login, {
|
updatePreferences(id, {
|
||||||
...perf,
|
...perf,
|
||||||
language: e.target.value,
|
language: e.target.value,
|
||||||
})
|
})
|
||||||
@ -77,7 +76,7 @@ const PreferencesPage = () => {
|
|||||||
<select
|
<select
|
||||||
value={perf.theme}
|
value={perf.theme}
|
||||||
onChange={e =>
|
onChange={e =>
|
||||||
updatePreferences(login, {
|
updatePreferences(id, {
|
||||||
...perf,
|
...perf,
|
||||||
theme: e.target.value,
|
theme: e.target.value,
|
||||||
} as UserPreferences)
|
} as UserPreferences)
|
||||||
@ -102,7 +101,7 @@ const PreferencesPage = () => {
|
|||||||
<select
|
<select
|
||||||
value={perf.defaultRootTab}
|
value={perf.defaultRootTab}
|
||||||
onChange={e =>
|
onChange={e =>
|
||||||
updatePreferences(login, {
|
updatePreferences(id, {
|
||||||
...perf,
|
...perf,
|
||||||
defaultRootTab: e.target.value,
|
defaultRootTab: e.target.value,
|
||||||
} as UserPreferences)
|
} as UserPreferences)
|
||||||
@ -132,7 +131,7 @@ const PreferencesPage = () => {
|
|||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={perf.telemetry ?? true}
|
checked={perf.telemetry ?? true}
|
||||||
onChange={e => updatePreferences(login, { ...perf, telemetry: e.target.checked })}
|
onChange={e => updatePreferences(id, { ...perf, telemetry: e.target.checked })}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -149,7 +148,7 @@ const PreferencesPage = () => {
|
|||||||
className="w-max"
|
className="w-max"
|
||||||
value={perf.autoLoadMedia}
|
value={perf.autoLoadMedia}
|
||||||
onChange={e =>
|
onChange={e =>
|
||||||
updatePreferences(login, {
|
updatePreferences(id, {
|
||||||
...perf,
|
...perf,
|
||||||
autoLoadMedia: e.target.value,
|
autoLoadMedia: e.target.value,
|
||||||
} as UserPreferences)
|
} as UserPreferences)
|
||||||
@ -180,7 +179,7 @@ const PreferencesPage = () => {
|
|||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={perf.checkSigs}
|
checked={perf.checkSigs}
|
||||||
onChange={e => updatePreferences(login, { ...perf, checkSigs: e.target.checked })}
|
onChange={e => updatePreferences(id, { ...perf, checkSigs: e.target.checked })}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -197,7 +196,7 @@ const PreferencesPage = () => {
|
|||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={perf.autoTranslate}
|
checked={perf.autoTranslate}
|
||||||
onChange={e => updatePreferences(login, { ...perf, autoTranslate: e.target.checked })}
|
onChange={e => updatePreferences(id, { ...perf, autoTranslate: e.target.checked })}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -215,7 +214,7 @@ const PreferencesPage = () => {
|
|||||||
type="number"
|
type="number"
|
||||||
defaultValue={perf.pow}
|
defaultValue={perf.pow}
|
||||||
min={0}
|
min={0}
|
||||||
onChange={e => updatePreferences(login, { ...perf, pow: parseInt(e.target.value || "0") })}
|
onChange={e => updatePreferences(id, { ...perf, pow: parseInt(e.target.value || "0") })}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -228,7 +227,7 @@ const PreferencesPage = () => {
|
|||||||
type="number"
|
type="number"
|
||||||
defaultValue={perf.defaultZapAmount}
|
defaultValue={perf.defaultZapAmount}
|
||||||
min={1}
|
min={1}
|
||||||
onChange={e => updatePreferences(login, { ...perf, defaultZapAmount: parseInt(e.target.value || "0") })}
|
onChange={e => updatePreferences(id, { ...perf, defaultZapAmount: parseInt(e.target.value || "0") })}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -245,7 +244,7 @@ const PreferencesPage = () => {
|
|||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={perf.showBadges ?? false}
|
checked={perf.showBadges ?? false}
|
||||||
onChange={e => updatePreferences(login, { ...perf, showBadges: e.target.checked })}
|
onChange={e => updatePreferences(id, { ...perf, showBadges: e.target.checked })}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -262,7 +261,7 @@ const PreferencesPage = () => {
|
|||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={perf.showStatus ?? true}
|
checked={perf.showStatus ?? true}
|
||||||
onChange={e => updatePreferences(login, { ...perf, showStatus: e.target.checked })}
|
onChange={e => updatePreferences(id, { ...perf, showStatus: e.target.checked })}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -279,7 +278,7 @@ const PreferencesPage = () => {
|
|||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={perf.autoZap}
|
checked={perf.autoZap}
|
||||||
onChange={e => updatePreferences(login, { ...perf, autoZap: e.target.checked })}
|
onChange={e => updatePreferences(id, { ...perf, autoZap: e.target.checked })}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -298,7 +297,7 @@ const PreferencesPage = () => {
|
|||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={perf.imgProxyConfig !== null}
|
checked={perf.imgProxyConfig !== null}
|
||||||
onChange={e =>
|
onChange={e =>
|
||||||
updatePreferences(login, {
|
updatePreferences(id, {
|
||||||
...perf,
|
...perf,
|
||||||
imgProxyConfig: e.target.checked ? DefaultImgProxy : null,
|
imgProxyConfig: e.target.checked ? DefaultImgProxy : null,
|
||||||
})
|
})
|
||||||
@ -321,7 +320,7 @@ const PreferencesPage = () => {
|
|||||||
description: "Placeholder text for imgproxy url textbox",
|
description: "Placeholder text for imgproxy url textbox",
|
||||||
})}
|
})}
|
||||||
onChange={e =>
|
onChange={e =>
|
||||||
updatePreferences(login, {
|
updatePreferences(id, {
|
||||||
...perf,
|
...perf,
|
||||||
imgProxyConfig: {
|
imgProxyConfig: {
|
||||||
...unwrap(perf.imgProxyConfig),
|
...unwrap(perf.imgProxyConfig),
|
||||||
@ -345,7 +344,7 @@ const PreferencesPage = () => {
|
|||||||
description: "Hexidecimal 'key' input for improxy",
|
description: "Hexidecimal 'key' input for improxy",
|
||||||
})}
|
})}
|
||||||
onChange={e =>
|
onChange={e =>
|
||||||
updatePreferences(login, {
|
updatePreferences(id, {
|
||||||
...perf,
|
...perf,
|
||||||
imgProxyConfig: {
|
imgProxyConfig: {
|
||||||
...unwrap(perf.imgProxyConfig),
|
...unwrap(perf.imgProxyConfig),
|
||||||
@ -369,7 +368,7 @@ const PreferencesPage = () => {
|
|||||||
description: "Hexidecimal 'salt' input for imgproxy",
|
description: "Hexidecimal 'salt' input for imgproxy",
|
||||||
})}
|
})}
|
||||||
onChange={e =>
|
onChange={e =>
|
||||||
updatePreferences(login, {
|
updatePreferences(id, {
|
||||||
...perf,
|
...perf,
|
||||||
imgProxyConfig: {
|
imgProxyConfig: {
|
||||||
...unwrap(perf.imgProxyConfig),
|
...unwrap(perf.imgProxyConfig),
|
||||||
@ -396,7 +395,7 @@ const PreferencesPage = () => {
|
|||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={perf.enableReactions}
|
checked={perf.enableReactions}
|
||||||
onChange={e => updatePreferences(login, { ...perf, enableReactions: e.target.checked })}
|
onChange={e => updatePreferences(id, { ...perf, enableReactions: e.target.checked })}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -413,7 +412,7 @@ const PreferencesPage = () => {
|
|||||||
onChange={e => {
|
onChange={e => {
|
||||||
const split = e.target.value.match(/[\p{L}\S]{1}/u);
|
const split = e.target.value.match(/[\p{L}\S]{1}/u);
|
||||||
console.debug(e.target.value, split);
|
console.debug(e.target.value, split);
|
||||||
updatePreferences(login, {
|
updatePreferences(id, {
|
||||||
...perf,
|
...perf,
|
||||||
reactionEmoji: split?.[0] ?? "",
|
reactionEmoji: split?.[0] ?? "",
|
||||||
});
|
});
|
||||||
@ -433,7 +432,7 @@ const PreferencesPage = () => {
|
|||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={perf.confirmReposts}
|
checked={perf.confirmReposts}
|
||||||
onChange={e => updatePreferences(login, { ...perf, confirmReposts: e.target.checked })}
|
onChange={e => updatePreferences(id, { ...perf, confirmReposts: e.target.checked })}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -450,7 +449,7 @@ const PreferencesPage = () => {
|
|||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={perf.autoShowLatest}
|
checked={perf.autoShowLatest}
|
||||||
onChange={e => updatePreferences(login, { ...perf, autoShowLatest: e.target.checked })}
|
onChange={e => updatePreferences(id, { ...perf, autoShowLatest: e.target.checked })}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -464,7 +463,7 @@ const PreferencesPage = () => {
|
|||||||
<select
|
<select
|
||||||
value={perf.fileUploader}
|
value={perf.fileUploader}
|
||||||
onChange={e =>
|
onChange={e =>
|
||||||
updatePreferences(login, {
|
updatePreferences(id, {
|
||||||
...perf,
|
...perf,
|
||||||
fileUploader: e.target.value,
|
fileUploader: e.target.value,
|
||||||
} as UserPreferences)
|
} as UserPreferences)
|
||||||
@ -489,7 +488,7 @@ const PreferencesPage = () => {
|
|||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={perf.showDebugMenus}
|
checked={perf.showDebugMenus}
|
||||||
onChange={e => updatePreferences(login, { ...perf, showDebugMenus: e.target.checked })}
|
onChange={e => updatePreferences(id, { ...perf, showDebugMenus: e.target.checked })}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -62,7 +62,7 @@ export interface UploadProgress {
|
|||||||
export type UploadStage = "starting" | "hashing" | "uploading" | "done" | undefined;
|
export type UploadStage = "starting" | "hashing" | "uploading" | "done" | undefined;
|
||||||
|
|
||||||
export default function useFileUpload(): Uploader {
|
export default function useFileUpload(): Uploader {
|
||||||
const fileUploader = useLogin().preferences.fileUploader;
|
const fileUploader = useLogin(s => s.appData.item.preferences.fileUploader);
|
||||||
const { publisher } = useEventPublisher();
|
const { publisher } = useEventPublisher();
|
||||||
const [progress, setProgress] = useState<Array<UploadProgress>>([]);
|
const [progress, setProgress] = useState<Array<UploadProgress>>([]);
|
||||||
const [stage, setStage] = useState<UploadStage>();
|
const [stage, setStage] = useState<UploadStage>();
|
||||||
|
@ -160,7 +160,7 @@ async function initSite() {
|
|||||||
|
|
||||||
// inject analytics script
|
// inject analytics script
|
||||||
// <script defer data-domain="snort.social" src="http://analytics.v0l.io/js/script.js"></script>
|
// <script defer data-domain="snort.social" src="http://analytics.v0l.io/js/script.js"></script>
|
||||||
if (CONFIG.features.analytics && (login.preferences.telemetry ?? true)) {
|
if (CONFIG.features.analytics && (login.appData.item.preferences.telemetry ?? true)) {
|
||||||
const sc = document.createElement("script");
|
const sc = document.createElement("script");
|
||||||
sc.src = "https://analytics.v0l.io/js/script.js";
|
sc.src = "https://analytics.v0l.io/js/script.js";
|
||||||
sc.defer = true;
|
sc.defer = true;
|
||||||
|
@ -330,6 +330,13 @@ export class EventPublisher {
|
|||||||
return await this.#sign(eb);
|
return await this.#sign(eb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async appData(data: object, id: string) {
|
||||||
|
const eb = this.#eb(EventKind.AppData);
|
||||||
|
eb.content(await this.nip4Encrypt(JSON.stringify(data), this.#pubKey));
|
||||||
|
eb.tag(["d", id]);
|
||||||
|
return await this.#sign(eb);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* NIP-59 Gift Wrap event with ephemeral key
|
* NIP-59 Gift Wrap event with ephemeral key
|
||||||
*/
|
*/
|
||||||
|
@ -139,21 +139,28 @@ export interface MessageEncryptor {
|
|||||||
decryptData(payload: MessageEncryptorPayload, sharedSecet: Uint8Array): Promise<string> | string;
|
decryptData(payload: MessageEncryptorPayload, sharedSecet: Uint8Array): Promise<string> | string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function decodeEncryptionPayload(p: string) {
|
export function decodeEncryptionPayload(p: string): MessageEncryptorPayload {
|
||||||
if (p.startsWith("{") && p.endsWith("}")) {
|
if (p.startsWith("{") && p.endsWith("}")) {
|
||||||
const pj = JSON.parse(p) as { v: number; nonce: string; ciphertext: string };
|
const pj = JSON.parse(p) as { v: number; nonce: string; ciphertext: string };
|
||||||
return {
|
return {
|
||||||
v: pj.v,
|
v: pj.v,
|
||||||
nonce: base64.decode(pj.nonce),
|
nonce: base64.decode(pj.nonce),
|
||||||
ciphertext: base64.decode(pj.ciphertext),
|
ciphertext: base64.decode(pj.ciphertext),
|
||||||
} as MessageEncryptorPayload;
|
};
|
||||||
|
} else if (p.includes("?iv=")) {
|
||||||
|
const [ciphertext, nonce] = p.split("?iv=");
|
||||||
|
return {
|
||||||
|
v: MessageEncryptorVersion.Nip4,
|
||||||
|
nonce: base64.decode(nonce),
|
||||||
|
ciphertext: base64.decode(ciphertext),
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
const buf = base64.decode(p);
|
const buf = base64.decode(p);
|
||||||
return {
|
return {
|
||||||
v: buf[0],
|
v: buf[0],
|
||||||
nonce: buf.subarray(1, 25),
|
nonce: buf.subarray(1, 25),
|
||||||
ciphertext: buf.subarray(25),
|
ciphertext: buf.subarray(25),
|
||||||
} as MessageEncryptorPayload;
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user