diff --git a/packages/app/.eslintrc.cjs b/packages/app/.eslintrc.cjs
deleted file mode 100644
index 3fc75e48..00000000
--- a/packages/app/.eslintrc.cjs
+++ /dev/null
@@ -1,41 +0,0 @@
-module.exports = {
- extends: [
- "eslint:recommended",
- "plugin:@typescript-eslint/recommended",
- "plugin:react/recommended",
- "plugin:react-hooks/recommended",
- ],
- parser: "@typescript-eslint/parser",
- plugins: ["@typescript-eslint", "formatjs", "react-refresh", "simple-import-sort"],
- rules: {
- "formatjs/enforce-id": [
- "error",
- {
- idInterpolationPattern: "[sha512:contenthash:base64:6]",
- },
- ],
- "react/react-in-jsx-scope": "off",
- "react-hooks/exhaustive-deps": "off",
- "react-refresh/only-export-components": "error",
- "simple-import-sort/imports": "error",
- "simple-import-sort/exports": "error",
- "@typescript-eslint/no-unused-vars": "error",
- "max-lines": ["warn", { max: 300, skipBlankLines: true, skipComments: true }],
- },
- overrides: [
- {
- files: ["*.tsx"],
- rules: {
- "max-lines": ["warn", { max: 200, skipBlankLines: true, skipComments: true }],
- },
- },
- ],
- root: true,
- ignorePatterns: ["build/", "*.test.ts", "*.js"],
- env: {
- browser: true,
- worker: true,
- commonjs: true,
- node: false,
- },
-};
diff --git a/packages/app/eslint.config.mjs b/packages/app/eslint.config.mjs
new file mode 100644
index 00000000..23fa161c
--- /dev/null
+++ b/packages/app/eslint.config.mjs
@@ -0,0 +1,44 @@
+/* eslint-disable import/no-anonymous-default-export */
+export default [
+ {
+ extends: [
+ "eslint:recommended",
+ "plugin:@typescript-eslint/recommended",
+ "plugin:react/recommended",
+ "plugin:react-hooks/recommended",
+ ],
+ parser: "@typescript-eslint/parser",
+ plugins: ["@typescript-eslint", "formatjs", "react-refresh", "simple-import-sort"],
+ rules: {
+ "formatjs/enforce-id": [
+ "error",
+ {
+ idInterpolationPattern: "[sha512:contenthash:base64:6]",
+ },
+ ],
+ "react/react-in-jsx-scope": "off",
+ "react-hooks/exhaustive-deps": "off",
+ "react-refresh/only-export-components": "error",
+ "simple-import-sort/imports": "error",
+ "simple-import-sort/exports": "error",
+ "@typescript-eslint/no-unused-vars": "error",
+ "max-lines": ["warn", { max: 300, skipBlankLines: true, skipComments: true }],
+ },
+ overrides: [
+ {
+ files: ["*.tsx"],
+ rules: {
+ "max-lines": ["warn", { max: 200, skipBlankLines: true, skipComments: true }],
+ },
+ },
+ ],
+ root: true,
+ ignores: ["build/", "*.test.ts", "*.js"],
+ env: {
+ browser: true,
+ worker: true,
+ commonjs: true,
+ node: false,
+ },
+ },
+];
diff --git a/packages/app/package.json b/packages/app/package.json
index 296a1199..d298aca5 100644
--- a/packages/app/package.json
+++ b/packages/app/package.json
@@ -106,6 +106,7 @@
"autoprefixer": "^10.4.16",
"config": "^3.3.9",
"eslint": "^8.48.0",
+ "eslint-config-react-app": "^7.0.1",
"eslint-plugin-formatjs": "^4.11.3",
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^4.6.0",
@@ -120,6 +121,7 @@
"tinybench": "^2.5.1",
"typescript": "^5.2.2",
"vite": "^5.2.8",
+ "vite-plugin-eslint": "^1.8.1",
"vite-plugin-pwa": "^0.19.2",
"vite-plugin-version-mark": "^0.0.10",
"vitest": "^0.34.6"
diff --git a/packages/app/src/Components/Embed/MixCloudEmbed.tsx b/packages/app/src/Components/Embed/MixCloudEmbed.tsx
index ffd0359d..b37f60aa 100644
--- a/packages/app/src/Components/Embed/MixCloudEmbed.tsx
+++ b/packages/app/src/Components/Embed/MixCloudEmbed.tsx
@@ -1,10 +1,10 @@
-import useLogin from "@/Hooks/useLogin";
+import usePreferences from "@/Hooks/usePreferences";
import { MixCloudRegex } from "@/Utils/Const";
const MixCloudEmbed = ({ link }: { link: string }) => {
const feedPath = (MixCloudRegex.test(link) && RegExp.$1) + "%2F" + (MixCloudRegex.test(link) && RegExp.$2);
- const theme = useLogin(s => s.appData.json.preferences.theme);
+ const theme = usePreferences(s => s.theme);
const lightParams = theme === "light" ? "light=1" : "light=0";
return (
<>
diff --git a/packages/app/src/Components/Embed/PubkeyList.tsx b/packages/app/src/Components/Embed/PubkeyList.tsx
index 7d11cd39..72d69f1b 100644
--- a/packages/app/src/Components/Embed/PubkeyList.tsx
+++ b/packages/app/src/Components/Embed/PubkeyList.tsx
@@ -8,30 +8,31 @@ import AsyncButton from "@/Components/Button/AsyncButton";
import { Toastore } from "@/Components/Toaster/Toaster";
import FollowListBase from "@/Components/User/FollowListBase";
import useEventPublisher from "@/Hooks/useEventPublisher";
-import useLogin from "@/Hooks/useLogin";
+import usePreferences from "@/Hooks/usePreferences";
import { dedupe, findTag, getDisplayName, hexToBech32 } from "@/Utils";
import { useWallet } from "@/Wallet";
export default function PubkeyList({ ev, className }: { ev: NostrEvent; className?: string }) {
const wallet = useWallet();
- const login = useLogin();
- const { publisher } = useEventPublisher();
+ const defaultZapAmount = usePreferences(s => s.defaultZapAmount);
+ const { publisher, system } = useEventPublisher();
const ids = dedupe(ev.tags.filter(a => a[0] === "p").map(a => a[1]));
async function zapAll() {
for (const pk of ids) {
try {
const profile = await UserCache.get(pk);
- const amtSend = login.appData.json.preferences.defaultZapAmount;
+ const amtSend = defaultZapAmount;
const lnurl = profile?.lud16 || profile?.lud06;
if (lnurl) {
const svc = new LNURL(lnurl);
await svc.load();
+ const relays = await system.requestRouter?.forReplyTo(pk);
const zap = await publisher?.zap(
amtSend * 1000,
pk,
- Object.keys(login.relays.item),
+ relays ?? [],
undefined,
`Zap from ${hexToBech32("note", ev.id)}`,
);
@@ -74,7 +75,7 @@ export default function PubkeyList({ ev, className }: { ev: NostrEvent; classNam
defaultMessage="Zap all {n} sats"
id="IVbtTS"
values={{
- n: ,
+ n: ,
}}
/>
diff --git a/packages/app/src/Components/Event/Create/NoteCreator.tsx b/packages/app/src/Components/Event/Create/NoteCreator.tsx
index e6fc50d2..f6a1cff6 100644
--- a/packages/app/src/Components/Event/Create/NoteCreator.tsx
+++ b/packages/app/src/Components/Event/Create/NoteCreator.tsx
@@ -20,6 +20,8 @@ import { Toastore } from "@/Components/Toaster/Toaster";
import ProfileImage from "@/Components/User/ProfileImage";
import useEventPublisher from "@/Hooks/useEventPublisher";
import useLogin from "@/Hooks/useLogin";
+import usePreferences from "@/Hooks/usePreferences";
+import useRelays from "@/Hooks/useRelays";
import { useNoteCreator } from "@/State/NoteCreator";
import { openFile, trackEvent } from "@/Utils";
import useFileUpload from "@/Utils/Upload";
@@ -56,11 +58,12 @@ const quoteNoteOptions = {
export function NoteCreator() {
const { formatMessage } = useIntl();
const uploader = useFileUpload();
- const login = useLogin(s => ({ relays: s.relays, publicKey: s.publicKey, pow: s.appData.json.preferences.pow }));
+ const publicKey = useLogin(s => s.publicKey);
+ const pow = usePreferences(s => s.pow);
+ const relays = useRelays();
const { system, publisher: pub } = useEventPublisher();
- const publisher = login.pow ? pub?.pow(login.pow, GetPowWorker()) : pub;
+ const publisher = pow ? pub?.pow(pow, GetPowWorker()) : pub;
const note = useNoteCreator();
- const relays = login.relays;
useEffect(() => {
const draft = localStorage.getItem("msgDraft");
@@ -367,8 +370,9 @@ export function NoteCreator() {
function renderRelayCustomisation() {
return (
- {Object.keys(relays.item || {})
- .filter(el => relays.item[el].write)
+ {Object.entries(relays)
+ .filter(el => el[1].write)
+ .map(a => a[0])
.map((r, i, a) => (
{r}
@@ -523,7 +527,7 @@ export function NoteCreator() {
void }) {
const [r, setResult] = useState(rsp);
const { formatMessage } = useIntl();
- const { publisher, system } = useEventPublisher();
+ const { system } = useEventPublisher();
const login = useLogin();
async function removeRelayFromResult(r: OkResponse) {
- if (publisher) {
- removeRelay(login, unwrap(sanitizeRelayUrl(r.relay)));
- await saveRelays(system, publisher, login.relays.item);
- }
+ await login.state.removeRelay(unwrap(sanitizeRelayUrl(r.relay)), true);
close();
}
diff --git a/packages/app/src/Components/Event/HiddenNote.tsx b/packages/app/src/Components/Event/HiddenNote.tsx
index fb7b2e90..264edf0f 100644
--- a/packages/app/src/Components/Event/HiddenNote.tsx
+++ b/packages/app/src/Components/Event/HiddenNote.tsx
@@ -1,10 +1,10 @@
import { useState } from "react";
import { FormattedMessage } from "react-intl";
-import useLogin from "@/Hooks/useLogin";
+import usePreferences from "@/Hooks/usePreferences";
const HiddenNote = ({ children }: { children: React.ReactNode }) => {
- const hideMutedNotes = useLogin(s => s.appData.json.preferences.hideMutedNotes);
+ const hideMutedNotes = usePreferences(s => s.hideMutedNotes);
const [show, setShow] = useState(false);
if (hideMutedNotes) return;
diff --git a/packages/app/src/Components/Event/Note/NoteContextMenu.tsx b/packages/app/src/Components/Event/Note/NoteContextMenu.tsx
index 38d77821..fb773628 100644
--- a/packages/app/src/Components/Event/Note/NoteContextMenu.tsx
+++ b/packages/app/src/Components/Event/Note/NoteContextMenu.tsx
@@ -1,4 +1,4 @@
-import { HexKey, NostrLink, NostrPrefix } from "@snort/system";
+import { EventKind, HexKey, NostrLink, NostrPrefix } from "@snort/system";
import { Menu, MenuItem } from "@szhsin/react-menu";
import { useEffect, useState } from "react";
import { FormattedMessage, useIntl } from "react-intl";
@@ -10,7 +10,7 @@ import SnortApi from "@/External/SnortApi";
import useEventPublisher from "@/Hooks/useEventPublisher";
import useLogin from "@/Hooks/useLogin";
import useModeration from "@/Hooks/useModeration";
-import { setBookmarked, setPinned } from "@/Utils/Login";
+import usePreferences from "@/Hooks/usePreferences";
import { getCurrentSubscription, SubscriptionType } from "@/Utils/Subscription";
import { ReBroadcaster } from "../../ReBroadcaster";
@@ -18,7 +18,8 @@ import { ReBroadcaster } from "../../ReBroadcaster";
export function NoteContextMenu({ ev, ...props }: NoteContextMenuProps) {
const { formatMessage } = useIntl();
const login = useLogin();
- const { mute, block } = useModeration();
+ const autoTranslate = usePreferences(s => s.autoTranslate);
+ const { mute } = useModeration();
const { publisher, system } = useEventPublisher();
const [showBroadcast, setShowBroadcast] = useState(false);
const lang = window.navigator.language;
@@ -26,6 +27,7 @@ export function NoteContextMenu({ ev, ...props }: NoteContextMenuProps) {
type: "language",
});
const isMine = ev.pubkey === login.publicKey;
+ const link = NostrLink.fromEvent(ev);
async function deleteEvent() {
if (window.confirm(formatMessage(messages.ConfirmDeletion, { id: ev.id.substring(0, 8) })) && publisher) {
@@ -78,7 +80,7 @@ export function NoteContextMenu({ ev, ...props }: NoteContextMenuProps) {
useEffect(() => {
const sub = getCurrentSubscription(login.subscriptions);
- if (sub?.type === SubscriptionType.Premium && (login.appData.json.preferences.autoTranslate ?? true)) {
+ if (sub?.type === SubscriptionType.Premium && (autoTranslate ?? true)) {
translate();
}
}, []);
@@ -90,19 +92,13 @@ export function NoteContextMenu({ ev, ...props }: NoteContextMenuProps) {
async function pin(id: HexKey) {
if (publisher) {
- const es = [...login.pinned.item, id];
- const ev = await publisher.pinned(es.map(a => new NostrLink(NostrPrefix.Note, a)));
- system.BroadcastEvent(ev);
- setPinned(login, es, ev.created_at * 1000);
+ //todo: PIN note
}
}
async function bookmark(id: string) {
if (publisher) {
- const es = [...login.bookmarked.item, id];
- const ev = await publisher.bookmarks(es.map(a => new NostrLink(NostrPrefix.Note, a)));
- system.BroadcastEvent(ev);
- setBookmarked(login, es, ev.created_at * 1000);
+ //todo: bookmark note
}
}
@@ -132,13 +128,13 @@ export function NoteContextMenu({ ev, ...props }: NoteContextMenuProps) {
- {!login.pinned.item.includes(ev.id) && !login.readonly && (
+ {!login.state.isOnList(EventKind.PinList, link) && !login.readonly && (
)}
- {!login.bookmarked.item.includes(ev.id) && !login.readonly && (
+ {!login.state.isOnList(EventKind.BookmarksList, link) && !login.readonly && (
- {ev.pubkey !== login.publicKey && !login.readonly && (
-
- )}