diff --git a/.prettierrc b/.prettierrc index 3cae53df..0060a5d8 100644 --- a/.prettierrc +++ b/.prettierrc @@ -8,16 +8,7 @@ "endOfLine": "lf", "bracketSpacing": true, "bracketSameLine": true, - "importOrder": [ - "^@layouts/(.*)$", - "^@pages/(.*)$", - "^@components/(.*)$", - "^@utils/(.*)$", - "^@stores/(.*)$", - "^@assets/(.*)$", - "", - "^[./]" - ], + "importOrder": ["^@layouts/(.*)$", "^@pages/(.*)$", "^@components/(.*)$", "^@utils/(.*)$", "^@stores/(.*)$", "", "^[./]"], "importOrderSeparation": true, "importOrderSortSpecifiers": true, "plugins": ["@trivago/prettier-plugin-sort-imports", "prettier-plugin-tailwindcss"], diff --git a/next.config.js b/next.config.js index 07e914ab..3bd71dbc 100644 --- a/next.config.js +++ b/next.config.js @@ -9,6 +9,9 @@ module.exports = removeImports({ typescript: { ignoreBuildErrors: true, }, + experimental: { + scrollRestoration: true, + }, webpack: (config) => { config.experiments = { ...config.experiments, topLevelAwait: true }; return config; diff --git a/package.json b/package.json index 39897477..bc3f8403 100644 --- a/package.json +++ b/package.json @@ -12,11 +12,10 @@ "**/*": "prettier --write --ignore-unknown" }, "dependencies": { - "@nanostores/persistent": "^0.7.0", - "@nanostores/react": "^0.4.1", "@radix-ui/react-dialog": "^1.0.2", "@radix-ui/react-dropdown-menu": "^2.0.3", "@radix-ui/react-icons": "^1.2.0", + "@rehooks/local-storage": "^2.4.4", "@tauri-apps/api": "^1.2.0", "@uiw/react-markdown-preview": "^4.1.9", "@uiw/react-md-editor": "^3.20.5", @@ -27,8 +26,8 @@ "nanostores": "^0.7.4", "next": "^13.2.1", "next-remove-imports": "^1.0.10", - "nostr-react": "^0.6.4", - "nostr-tools": "^1.6.0", + "nostr-relaypool": "^0.5.3", + "nostr-tools": "^1.7.1", "qrcode.react": "^3.1.0", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -37,25 +36,26 @@ "react-player": "^2.11.2", "react-virtuoso": "^4.1.0", "tauri-plugin-sql-api": "github:tauri-apps/tauri-plugin-sql", - "unique-names-generator": "^4.7.1" + "unique-names-generator": "^4.7.1", + "ws": "^8.12.1" }, "devDependencies": { "@tailwindcss/typography": "^0.5.9", "@tauri-apps/cli": "^1.2.3", - "@trivago/prettier-plugin-sort-imports": "^4.1.0", - "@types/node": "^18.14.1", + "@trivago/prettier-plugin-sort-imports": "^4.1.1", + "@types/node": "^18.14.2", "@types/react": "^18.0.28", "@types/react-dom": "^18.0.11", - "@typescript-eslint/eslint-plugin": "^5.53.0", - "@typescript-eslint/parser": "^5.53.0", + "@typescript-eslint/eslint-plugin": "^5.54.0", + "@typescript-eslint/parser": "^5.54.0", "autoprefixer": "^10.4.13", "csstype": "^3.1.1", - "eslint": "^8.34.0", + "eslint": "^8.35.0", "eslint-config-next": "^13.2.1", "eslint-config-prettier": "^8.6.0", "eslint-plugin-react": "^7.32.2", "eslint-plugin-react-hooks": "^4.6.0", - "husky": "^8.0.0", + "husky": "^8.0.3", "lint-staged": "^13.1.2", "postcss": "^8.4.21", "prettier": "^2.8.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fc513b5c..c59c06fa 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,40 +1,39 @@ lockfileVersion: 5.4 specifiers: - '@nanostores/persistent': ^0.7.0 - '@nanostores/react': ^0.4.1 '@radix-ui/react-dialog': ^1.0.2 '@radix-ui/react-dropdown-menu': ^2.0.3 '@radix-ui/react-icons': ^1.2.0 + '@rehooks/local-storage': ^2.4.4 '@tailwindcss/typography': ^0.5.9 '@tauri-apps/api': ^1.2.0 '@tauri-apps/cli': ^1.2.3 - '@trivago/prettier-plugin-sort-imports': ^4.1.0 - '@types/node': ^18.14.1 + '@trivago/prettier-plugin-sort-imports': ^4.1.1 + '@types/node': ^18.14.2 '@types/react': ^18.0.28 '@types/react-dom': ^18.0.11 - '@typescript-eslint/eslint-plugin': ^5.53.0 - '@typescript-eslint/parser': ^5.53.0 + '@typescript-eslint/eslint-plugin': ^5.54.0 + '@typescript-eslint/parser': ^5.54.0 '@uiw/react-markdown-preview': ^4.1.9 '@uiw/react-md-editor': ^3.20.5 autoprefixer: ^10.4.13 bitcoin-address-validation: ^2.2.1 boring-avatars: ^1.7.0 csstype: ^3.1.1 - eslint: ^8.34.0 + eslint: ^8.35.0 eslint-config-next: ^13.2.1 eslint-config-prettier: ^8.6.0 eslint-plugin-react: ^7.32.2 eslint-plugin-react-hooks: ^4.6.0 framer-motion: ^9.1.7 - husky: ^8.0.0 + husky: ^8.0.3 lint-staged: ^13.1.2 moment: ^2.29.4 nanostores: ^0.7.4 next: ^13.2.1 next-remove-imports: ^1.0.10 - nostr-react: ^0.6.4 - nostr-tools: ^1.6.0 + nostr-relaypool: ^0.5.3 + nostr-tools: ^1.7.1 postcss: ^8.4.21 prettier: ^2.8.4 prettier-plugin-tailwindcss: ^0.2.3 @@ -50,13 +49,13 @@ specifiers: tauri-plugin-sql-api: github:tauri-apps/tauri-plugin-sql typescript: ^4.9.5 unique-names-generator: ^4.7.1 + ws: ^8.12.1 dependencies: - '@nanostores/persistent': 0.7.0_nanostores@0.7.4 - '@nanostores/react': 0.4.1_nkfnbc2tpc77iht7asm3uqwau4 '@radix-ui/react-dialog': 1.0.2_zula6vjvt3wdocc4mwcxqa6nzi '@radix-ui/react-dropdown-menu': 2.0.3_zula6vjvt3wdocc4mwcxqa6nzi '@radix-ui/react-icons': 1.2.0_react@18.2.0 + '@rehooks/local-storage': 2.4.4_react@18.2.0 '@tauri-apps/api': 1.2.0 '@uiw/react-markdown-preview': 4.1.9_zula6vjvt3wdocc4mwcxqa6nzi '@uiw/react-md-editor': 3.20.5_zula6vjvt3wdocc4mwcxqa6nzi @@ -67,8 +66,8 @@ dependencies: nanostores: 0.7.4 next: 13.2.1_biqbaboplfbrettd7655fr4n2y next-remove-imports: 1.0.10 - nostr-react: 0.6.4_react@18.2.0 - nostr-tools: 1.6.0 + nostr-relaypool: 0.5.3_ws@8.12.1 + nostr-tools: 1.7.1 qrcode.react: 3.1.0_react@18.2.0 react: 18.2.0 react-dom: 18.2.0_react@18.2.0 @@ -78,28 +77,29 @@ dependencies: react-virtuoso: 4.1.0_biqbaboplfbrettd7655fr4n2y tauri-plugin-sql-api: github.com/tauri-apps/tauri-plugin-sql/abd8759ef49e1ba441540a2260b453d43d86c7ee unique-names-generator: 4.7.1 + ws: 8.12.1 devDependencies: '@tailwindcss/typography': 0.5.9_tailwindcss@3.2.7 '@tauri-apps/cli': 1.2.3 - '@trivago/prettier-plugin-sort-imports': 4.1.0_prettier@2.8.4 - '@types/node': 18.14.1 + '@trivago/prettier-plugin-sort-imports': 4.1.1_prettier@2.8.4 + '@types/node': 18.14.2 '@types/react': 18.0.28 '@types/react-dom': 18.0.11 - '@typescript-eslint/eslint-plugin': 5.53.0_ny4s7qc6yg74faf3d6xty2ofzy - '@typescript-eslint/parser': 5.53.0_7kw3g6rralp5ps6mg3uyzz6azm + '@typescript-eslint/eslint-plugin': 5.54.0_6mj2wypvdnknez7kws2nfdgupi + '@typescript-eslint/parser': 5.54.0_ycpbpc6yetojsgtrx3mwntkhsu autoprefixer: 10.4.13_postcss@8.4.21 csstype: 3.1.1 - eslint: 8.34.0 - eslint-config-next: 13.2.1_7kw3g6rralp5ps6mg3uyzz6azm - eslint-config-prettier: 8.6.0_eslint@8.34.0 - eslint-plugin-react: 7.32.2_eslint@8.34.0 - eslint-plugin-react-hooks: 4.6.0_eslint@8.34.0 + eslint: 8.35.0 + eslint-config-next: 13.2.1_ycpbpc6yetojsgtrx3mwntkhsu + eslint-config-prettier: 8.6.0_eslint@8.35.0 + eslint-plugin-react: 7.32.2_eslint@8.35.0 + eslint-plugin-react-hooks: 4.6.0_eslint@8.35.0 husky: 8.0.3 lint-staged: 13.1.2 postcss: 8.4.21 prettier: 2.8.4 - prettier-plugin-tailwindcss: 0.2.3_3p4xqifn6m4d44r76wgcnqfi3i + prettier-plugin-tailwindcss: 0.2.3_zmkqdpv3ldc45e6wei6qtrbrca prop-types: 15.8.1 tailwindcss: 3.2.7_postcss@8.4.21 typescript: 4.9.5 @@ -350,8 +350,8 @@ packages: dev: false optional: true - /@eslint/eslintrc/1.4.1: - resolution: { integrity: sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA== } + /@eslint/eslintrc/2.0.0: + resolution: { integrity: sha512-fluIaaV+GyV24CCu/ggiHdV+j4RNh85yQnAYS/G2mZODZgGmmlrgCydjUcV3YvxCm9x8nMAfThsqTni4KiXT4A== } engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } dependencies: ajv: 6.12.6 @@ -367,6 +367,11 @@ packages: - supports-color dev: true + /@eslint/js/8.35.0: + resolution: { integrity: sha512-JXdzbRiWclLVoD8sNUjR443VVlYqiYmDVT6rGUEIEHU5YJW0gaVZwV2xgM7D4arkvASqD0IlLUVjHiFuxaftRw== } + engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + dev: true + /@floating-ui/core/0.7.3: resolution: { integrity: sha512-buc8BXHmG9l82+OQXOFU3Kr2XQx9ys01U/Q9HMIrZ300iLc8HLMgh7dcCqgYzAzf4BkoQvDcXf5Y+CuEZ5JBYg== } dev: false @@ -411,6 +416,15 @@ packages: resolution: { integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== } dev: true + /@jest/source-map/29.4.3: + resolution: { integrity: sha512-qyt/mb6rLyd9j1jUts4EQncvS6Yy3PM9HghnNv86QBlV+zdL2inCdK1tuVlL+J+lpiw2BI67qXOrX3UurBqQ1w== } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + dependencies: + '@jridgewell/trace-mapping': 0.3.17 + callsites: 3.1.0 + graceful-fs: 4.2.10 + dev: false + /@jridgewell/gen-mapping/0.1.1: resolution: { integrity: sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w== } engines: { node: '>=6.0.0' } @@ -449,27 +463,6 @@ packages: '@jridgewell/sourcemap-codec': 1.4.14 dev: false - /@nanostores/persistent/0.7.0_nanostores@0.7.4: - resolution: { integrity: sha512-4PAInL/T1hbftZUJ0cmgdFHBMalUoq7BUXFBy7QfyMv/8X3LPTYNh/yxspL7+J+XM3UNvVI7IFRMMs6FBasjhQ== } - engines: { node: ^14.0.0 || ^16.0.0 || >=18.0.0 } - peerDependencies: - nanostores: ^0.7.0 - dependencies: - nanostores: 0.7.4 - dev: false - - /@nanostores/react/0.4.1_nkfnbc2tpc77iht7asm3uqwau4: - resolution: { integrity: sha512-lsv0CYrMxczbXtoV/mxFVEoL/uVjEjseoP89srO/5yNAOkJka+dSFS7LYyWEbuvCPO7EgbtkvRpO5V+OztKQOw== } - engines: { node: ^14.0.0 || ^16.0.0 || >=18.0.0 } - peerDependencies: - nanostores: ^0.7.0 - react: '>=18.0.0' - dependencies: - nanostores: 0.7.4 - react: 18.2.0 - use-sync-external-store: 1.2.0_react@18.2.0 - dev: false - /@next/env/13.2.1: resolution: { integrity: sha512-Hq+6QZ6kgmloCg8Kgrix+4F0HtvLqVK3FZAnlAoS0eonaDemHe1Km4kwjSWRE3JNpJNcKxFHF+jsZrYo0SxWoQ== } dev: false @@ -991,6 +984,14 @@ packages: '@babel/runtime': 7.21.0 dev: false + /@rehooks/local-storage/2.4.4_react@18.2.0: + resolution: { integrity: sha512-zE+kfOkG59n/1UTxdmbwktIosclr67Nlbf2MzUJ9mNtCSypVscNHeD1qT6JCSo5Pjj8DO893IKWNLJqKKzDL/Q== } + peerDependencies: + react: '>=16.8.0' + dependencies: + react: 18.2.0 + dev: false + /@rushstack/eslint-patch/1.2.0: resolution: { integrity: sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg== } dev: true @@ -1134,8 +1135,8 @@ packages: '@tauri-apps/cli-win32-x64-msvc': 1.2.3 dev: true - /@trivago/prettier-plugin-sort-imports/4.1.0_prettier@2.8.4: - resolution: { integrity: sha512-aTr6QPFaPAAzPRFn9yWB/9yKi3ZAFqfGpxIGLPWuQfYJFGUed+W3KKwxntsoCiNvNE2iuKOg6haMo5KG8WXltg== } + /@trivago/prettier-plugin-sort-imports/4.1.1_prettier@2.8.4: + resolution: { integrity: sha512-dQ2r2uzNr1x6pJsuh/8x0IRA3CBUB+pWEW3J/7N98axqt7SQSm+2fy0FLNXvXGg77xEDC7KHxJlHfLYyi7PDcw== } peerDependencies: '@vue/compiler-sfc': 3.x prettier: 2.x @@ -1183,8 +1184,8 @@ packages: resolution: { integrity: sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA== } dev: false - /@types/node/18.14.1: - resolution: { integrity: sha512-QH+37Qds3E0eDlReeboBxfHbX9omAcBCXEzswCu6jySP642jiM3cYSIkU/REqwhCUqXdonHFuBfJDiAJxMNhaQ== } + /@types/node/18.14.2: + resolution: { integrity: sha512-1uEQxww3DaghA0RxqHx0O0ppVlo43pJhepY51OxuQIKHpjbnYLA7vcdwioNPzIqmC2u3I/dmylcqjlh0e7AyUA== } dev: true /@types/parse5/6.0.3: @@ -1222,8 +1223,8 @@ packages: resolution: { integrity: sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ== } dev: false - /@typescript-eslint/eslint-plugin/5.53.0_ny4s7qc6yg74faf3d6xty2ofzy: - resolution: { integrity: sha512-alFpFWNucPLdUOySmXCJpzr6HKC3bu7XooShWM+3w/EL6J2HIoB2PFxpLnq4JauWVk6DiVeNKzQlFEaE+X9sGw== } + /@typescript-eslint/eslint-plugin/5.54.0_6mj2wypvdnknez7kws2nfdgupi: + resolution: { integrity: sha512-+hSN9BdSr629RF02d7mMtXhAJvDTyCbprNYJKrXETlul/Aml6YZwd90XioVbjejQeHbb3R8Dg0CkRgoJDxo8aw== } engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } peerDependencies: '@typescript-eslint/parser': ^5.0.0 @@ -1233,12 +1234,12 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/parser': 5.53.0_7kw3g6rralp5ps6mg3uyzz6azm - '@typescript-eslint/scope-manager': 5.53.0 - '@typescript-eslint/type-utils': 5.53.0_7kw3g6rralp5ps6mg3uyzz6azm - '@typescript-eslint/utils': 5.53.0_7kw3g6rralp5ps6mg3uyzz6azm + '@typescript-eslint/parser': 5.54.0_ycpbpc6yetojsgtrx3mwntkhsu + '@typescript-eslint/scope-manager': 5.54.0 + '@typescript-eslint/type-utils': 5.54.0_ycpbpc6yetojsgtrx3mwntkhsu + '@typescript-eslint/utils': 5.54.0_ycpbpc6yetojsgtrx3mwntkhsu debug: 4.3.4 - eslint: 8.34.0 + eslint: 8.35.0 grapheme-splitter: 1.0.4 ignore: 5.2.4 natural-compare-lite: 1.4.0 @@ -1250,8 +1251,8 @@ packages: - supports-color dev: true - /@typescript-eslint/parser/5.53.0_7kw3g6rralp5ps6mg3uyzz6azm: - resolution: { integrity: sha512-MKBw9i0DLYlmdOb3Oq/526+al20AJZpANdT6Ct9ffxcV8nKCHz63t/S0IhlTFNsBIHJv+GY5SFJ0XfqVeydQrQ== } + /@typescript-eslint/parser/5.54.0_ycpbpc6yetojsgtrx3mwntkhsu: + resolution: { integrity: sha512-aAVL3Mu2qTi+h/r04WI/5PfNWvO6pdhpeMRWk9R7rEV4mwJNzoWf5CCU5vDKBsPIFQFjEq1xg7XBI2rjiMXQbQ== } engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -1260,26 +1261,26 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/scope-manager': 5.53.0 - '@typescript-eslint/types': 5.53.0 - '@typescript-eslint/typescript-estree': 5.53.0_typescript@4.9.5 + '@typescript-eslint/scope-manager': 5.54.0 + '@typescript-eslint/types': 5.54.0 + '@typescript-eslint/typescript-estree': 5.54.0_typescript@4.9.5 debug: 4.3.4 - eslint: 8.34.0 + eslint: 8.35.0 typescript: 4.9.5 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/scope-manager/5.53.0: - resolution: { integrity: sha512-Opy3dqNsp/9kBBeCPhkCNR7fmdSQqA+47r21hr9a14Bx0xnkElEQmhoHga+VoaoQ6uDHjDKmQPIYcUcKJifS7w== } + /@typescript-eslint/scope-manager/5.54.0: + resolution: { integrity: sha512-VTPYNZ7vaWtYna9M4oD42zENOBrb+ZYyCNdFs949GcN8Miwn37b8b7eMj+EZaq7VK9fx0Jd+JhmkhjFhvnovhg== } engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } dependencies: - '@typescript-eslint/types': 5.53.0 - '@typescript-eslint/visitor-keys': 5.53.0 + '@typescript-eslint/types': 5.54.0 + '@typescript-eslint/visitor-keys': 5.54.0 dev: true - /@typescript-eslint/type-utils/5.53.0_7kw3g6rralp5ps6mg3uyzz6azm: - resolution: { integrity: sha512-HO2hh0fmtqNLzTAme/KnND5uFNwbsdYhCZghK2SoxGp3Ifn2emv+hi0PBUjzzSh0dstUIFqOj3bp0AwQlK4OWw== } + /@typescript-eslint/type-utils/5.54.0_ycpbpc6yetojsgtrx3mwntkhsu: + resolution: { integrity: sha512-WI+WMJ8+oS+LyflqsD4nlXMsVdzTMYTxl16myXPaCXnSgc7LWwMsjxQFZCK/rVmTZ3FN71Ct78ehO9bRC7erYQ== } engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } peerDependencies: eslint: '*' @@ -1288,23 +1289,23 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/typescript-estree': 5.53.0_typescript@4.9.5 - '@typescript-eslint/utils': 5.53.0_7kw3g6rralp5ps6mg3uyzz6azm + '@typescript-eslint/typescript-estree': 5.54.0_typescript@4.9.5 + '@typescript-eslint/utils': 5.54.0_ycpbpc6yetojsgtrx3mwntkhsu debug: 4.3.4 - eslint: 8.34.0 + eslint: 8.35.0 tsutils: 3.21.0_typescript@4.9.5 typescript: 4.9.5 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/types/5.53.0: - resolution: { integrity: sha512-5kcDL9ZUIP756K6+QOAfPkigJmCPHcLN7Zjdz76lQWWDdzfOhZDTj1irs6gPBKiXx5/6O3L0+AvupAut3z7D2A== } + /@typescript-eslint/types/5.54.0: + resolution: { integrity: sha512-nExy+fDCBEgqblasfeE3aQ3NuafBUxZxgxXcYfzYRZFHdVvk5q60KhCSkG0noHgHRo/xQ/BOzURLZAafFpTkmQ== } engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } dev: true - /@typescript-eslint/typescript-estree/5.53.0_typescript@4.9.5: - resolution: { integrity: sha512-eKmipH7QyScpHSkhbptBBYh9v8FxtngLquq292YTEQ1pxVs39yFBlLC1xeIZcPPz1RWGqb7YgERJRGkjw8ZV7w== } + /@typescript-eslint/typescript-estree/5.54.0_typescript@4.9.5: + resolution: { integrity: sha512-X2rJG97Wj/VRo5YxJ8Qx26Zqf0RRKsVHd4sav8NElhbZzhpBI8jU54i6hfo9eheumj4oO4dcRN1B/zIVEqR/MQ== } engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } peerDependencies: typescript: '*' @@ -1312,8 +1313,8 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/types': 5.53.0 - '@typescript-eslint/visitor-keys': 5.53.0 + '@typescript-eslint/types': 5.54.0 + '@typescript-eslint/visitor-keys': 5.54.0 debug: 4.3.4 globby: 11.1.0 is-glob: 4.0.3 @@ -1324,31 +1325,31 @@ packages: - supports-color dev: true - /@typescript-eslint/utils/5.53.0_7kw3g6rralp5ps6mg3uyzz6azm: - resolution: { integrity: sha512-VUOOtPv27UNWLxFwQK/8+7kvxVC+hPHNsJjzlJyotlaHjLSIgOCKj9I0DBUjwOOA64qjBwx5afAPjksqOxMO0g== } + /@typescript-eslint/utils/5.54.0_ycpbpc6yetojsgtrx3mwntkhsu: + resolution: { integrity: sha512-cuwm8D/Z/7AuyAeJ+T0r4WZmlnlxQ8wt7C7fLpFlKMR+dY6QO79Cq1WpJhvZbMA4ZeZGHiRWnht7ZJ8qkdAunw== } engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: '@types/json-schema': 7.0.11 '@types/semver': 7.3.13 - '@typescript-eslint/scope-manager': 5.53.0 - '@typescript-eslint/types': 5.53.0 - '@typescript-eslint/typescript-estree': 5.53.0_typescript@4.9.5 - eslint: 8.34.0 + '@typescript-eslint/scope-manager': 5.54.0 + '@typescript-eslint/types': 5.54.0 + '@typescript-eslint/typescript-estree': 5.54.0_typescript@4.9.5 + eslint: 8.35.0 eslint-scope: 5.1.1 - eslint-utils: 3.0.0_eslint@8.34.0 + eslint-utils: 3.0.0_eslint@8.35.0 semver: 7.3.8 transitivePeerDependencies: - supports-color - typescript dev: true - /@typescript-eslint/visitor-keys/5.53.0: - resolution: { integrity: sha512-JqNLnX3leaHFZEN0gCh81sIvgrp/2GOACZNgO4+Tkf64u51kTpAyWFOY8XHx8XuXr3N2C9zgPPHtcpMg6z1g0w== } + /@typescript-eslint/visitor-keys/5.54.0: + resolution: { integrity: sha512-xu4wT7aRCakGINTLGeyGqDn+78BwFlggwBjnHa1ar/KaGagnmwLYmlrXIrgAaQ3AE1Vd6nLfKASm7LrFHNbKGA== } engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } dependencies: - '@typescript-eslint/types': 5.53.0 + '@typescript-eslint/types': 5.54.0 eslint-visitor-keys: 3.3.0 dev: true @@ -1609,7 +1610,7 @@ packages: postcss: ^8.1.0 dependencies: browserslist: 4.21.5 - caniuse-lite: 1.0.30001457 + caniuse-lite: 1.0.30001458 fraction.js: 4.2.0 normalize-range: 0.1.2 picocolors: 1.0.0 @@ -1714,8 +1715,8 @@ packages: engines: { node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7 } hasBin: true dependencies: - caniuse-lite: 1.0.30001457 - electron-to-chromium: 1.4.310 + caniuse-lite: 1.0.30001458 + electron-to-chromium: 1.4.313 node-releases: 2.0.10 update-browserslist-db: 1.0.10_browserslist@4.21.5 @@ -1729,15 +1730,14 @@ packages: /callsites/3.1.0: resolution: { integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== } engines: { node: '>=6' } - dev: true /camelcase-css/2.0.1: resolution: { integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== } engines: { node: '>= 6' } dev: true - /caniuse-lite/1.0.30001457: - resolution: { integrity: sha512-SDIV6bgE1aVbK6XyxdURbUE89zY7+k1BBBaOwYwkNCglXlel/E7mELiHC64HQ+W0xSKlqWhV9Wh7iHxUjMs4fA== } + /caniuse-lite/1.0.30001458: + resolution: { integrity: sha512-lQ1VlUUq5q9ro9X+5gOEyH7i3vm+AYVT1WDCVB69XOZ17KZRhnZ9J0Sqz7wTHQaLBJccNCHq8/Ww5LlOIZbB0w== } /ccount/2.0.1: resolution: { integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg== } @@ -2030,8 +2030,8 @@ packages: resolution: { integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== } dev: true - /electron-to-chromium/1.4.310: - resolution: { integrity: sha512-/xlATgfwkm5uDDwLw5nt/MNEf7c1oazLURMZLy39vOioGYyYzLWIDT8fZMJak6qTiAJ7udFTy7JG7ziyjNutiA== } + /electron-to-chromium/1.4.313: + resolution: { integrity: sha512-QckB9OVqr2oybjIrbMI99uF+b9+iTja5weFe0ePbqLb5BHqXOJUO1SG6kDj/1WtWPRIBr51N153AEq8m7HuIaA== } /emoji-regex/8.0.0: resolution: { integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== } @@ -2144,7 +2144,7 @@ packages: engines: { node: '>=12' } dev: false - /eslint-config-next/13.2.1_7kw3g6rralp5ps6mg3uyzz6azm: + /eslint-config-next/13.2.1_ycpbpc6yetojsgtrx3mwntkhsu: resolution: { integrity: sha512-2GAx7EjSiCzJN6H2L/v1kbYrNiwQxzkyjy6eWSjuhAKt+P6d3nVNHGy9mON8ZcYd72w/M8kyMjm4UB9cvijgrw== } peerDependencies: eslint: ^7.23.0 || ^8.0.0 @@ -2155,27 +2155,27 @@ packages: dependencies: '@next/eslint-plugin-next': 13.2.1 '@rushstack/eslint-patch': 1.2.0 - '@typescript-eslint/parser': 5.53.0_7kw3g6rralp5ps6mg3uyzz6azm - eslint: 8.34.0 + '@typescript-eslint/parser': 5.54.0_ycpbpc6yetojsgtrx3mwntkhsu + eslint: 8.35.0 eslint-import-resolver-node: 0.3.7 - eslint-import-resolver-typescript: 3.5.3_mvgyw3chnqkp6sgfmmtihyjpnm - eslint-plugin-import: 2.27.5_2hqppaeqs2axgzqg6vttejknky - eslint-plugin-jsx-a11y: 6.7.1_eslint@8.34.0 - eslint-plugin-react: 7.32.2_eslint@8.34.0 - eslint-plugin-react-hooks: 4.6.0_eslint@8.34.0 + eslint-import-resolver-typescript: 3.5.3_yckic57kx266ph64dhq6ozvb54 + eslint-plugin-import: 2.27.5_tqrcrxlenpngfto46ddarus52y + eslint-plugin-jsx-a11y: 6.7.1_eslint@8.35.0 + eslint-plugin-react: 7.32.2_eslint@8.35.0 + eslint-plugin-react-hooks: 4.6.0_eslint@8.35.0 typescript: 4.9.5 transitivePeerDependencies: - eslint-import-resolver-webpack - supports-color dev: true - /eslint-config-prettier/8.6.0_eslint@8.34.0: + /eslint-config-prettier/8.6.0_eslint@8.35.0: resolution: { integrity: sha512-bAF0eLpLVqP5oEVUFKpMA+NnRFICwn9X8B5jrR9FcqnYBuPbqWEjTEspPWMj5ye6czoSLDweCzSo3Ko7gGrZaA== } hasBin: true peerDependencies: eslint: '>=7.0.0' dependencies: - eslint: 8.34.0 + eslint: 8.35.0 dev: true /eslint-import-resolver-node/0.3.7: @@ -2188,7 +2188,7 @@ packages: - supports-color dev: true - /eslint-import-resolver-typescript/3.5.3_mvgyw3chnqkp6sgfmmtihyjpnm: + /eslint-import-resolver-typescript/3.5.3_yckic57kx266ph64dhq6ozvb54: resolution: { integrity: sha512-njRcKYBc3isE42LaTcJNVANR3R99H9bAxBDMNDr2W7yq5gYPxbU3MkdhsQukxZ/Xg9C2vcyLlDsbKfRDg0QvCQ== } engines: { node: ^14.18.0 || >=16.0.0 } peerDependencies: @@ -2197,8 +2197,8 @@ packages: dependencies: debug: 4.3.4 enhanced-resolve: 5.12.0 - eslint: 8.34.0 - eslint-plugin-import: 2.27.5_2hqppaeqs2axgzqg6vttejknky + eslint: 8.35.0 + eslint-plugin-import: 2.27.5_tqrcrxlenpngfto46ddarus52y get-tsconfig: 4.4.0 globby: 13.1.3 is-core-module: 2.11.0 @@ -2208,7 +2208,7 @@ packages: - supports-color dev: true - /eslint-module-utils/2.7.4_yfzt44nswbaazp63chcrlz6vvq: + /eslint-module-utils/2.7.4_igrub7c6rucg6hjc3uqgumd66y: resolution: { integrity: sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA== } engines: { node: '>=4' } peerDependencies: @@ -2229,16 +2229,16 @@ packages: eslint-import-resolver-webpack: optional: true dependencies: - '@typescript-eslint/parser': 5.53.0_7kw3g6rralp5ps6mg3uyzz6azm + '@typescript-eslint/parser': 5.54.0_ycpbpc6yetojsgtrx3mwntkhsu debug: 3.2.7 - eslint: 8.34.0 + eslint: 8.35.0 eslint-import-resolver-node: 0.3.7 - eslint-import-resolver-typescript: 3.5.3_mvgyw3chnqkp6sgfmmtihyjpnm + eslint-import-resolver-typescript: 3.5.3_yckic57kx266ph64dhq6ozvb54 transitivePeerDependencies: - supports-color dev: true - /eslint-plugin-import/2.27.5_2hqppaeqs2axgzqg6vttejknky: + /eslint-plugin-import/2.27.5_tqrcrxlenpngfto46ddarus52y: resolution: { integrity: sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow== } engines: { node: '>=4' } peerDependencies: @@ -2248,15 +2248,15 @@ packages: '@typescript-eslint/parser': optional: true dependencies: - '@typescript-eslint/parser': 5.53.0_7kw3g6rralp5ps6mg3uyzz6azm + '@typescript-eslint/parser': 5.54.0_ycpbpc6yetojsgtrx3mwntkhsu array-includes: 3.1.6 array.prototype.flat: 1.3.1 array.prototype.flatmap: 1.3.1 debug: 3.2.7 doctrine: 2.1.0 - eslint: 8.34.0 + eslint: 8.35.0 eslint-import-resolver-node: 0.3.7 - eslint-module-utils: 2.7.4_yfzt44nswbaazp63chcrlz6vvq + eslint-module-utils: 2.7.4_igrub7c6rucg6hjc3uqgumd66y has: 1.0.3 is-core-module: 2.11.0 is-glob: 4.0.3 @@ -2264,14 +2264,14 @@ packages: object.values: 1.1.6 resolve: 1.22.1 semver: 6.3.0 - tsconfig-paths: 3.14.1 + tsconfig-paths: 3.14.2 transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color dev: true - /eslint-plugin-jsx-a11y/6.7.1_eslint@8.34.0: + /eslint-plugin-jsx-a11y/6.7.1_eslint@8.35.0: resolution: { integrity: sha512-63Bog4iIethyo8smBklORknVjB0T2dwB8Mr/hIC+fBS0uyHdYYpzM/Ed+YC8VxTjlXHEWFOdmgwcDn1U2L9VCA== } engines: { node: '>=4.0' } peerDependencies: @@ -2286,7 +2286,7 @@ packages: axobject-query: 3.1.1 damerau-levenshtein: 1.0.8 emoji-regex: 9.2.2 - eslint: 8.34.0 + eslint: 8.35.0 has: 1.0.3 jsx-ast-utils: 3.3.3 language-tags: 1.0.5 @@ -2296,16 +2296,16 @@ packages: semver: 6.3.0 dev: true - /eslint-plugin-react-hooks/4.6.0_eslint@8.34.0: + /eslint-plugin-react-hooks/4.6.0_eslint@8.35.0: resolution: { integrity: sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g== } engines: { node: '>=10' } peerDependencies: eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 dependencies: - eslint: 8.34.0 + eslint: 8.35.0 dev: true - /eslint-plugin-react/7.32.2_eslint@8.34.0: + /eslint-plugin-react/7.32.2_eslint@8.35.0: resolution: { integrity: sha512-t2fBMa+XzonrrNkyVirzKlvn5RXzzPwRHtMvLAtVZrt8oxgnTQaYbU6SXTOO1mwQgp1y5+toMSKInnzGr0Knqg== } engines: { node: '>=4' } peerDependencies: @@ -2315,7 +2315,7 @@ packages: array.prototype.flatmap: 1.3.1 array.prototype.tosorted: 1.1.1 doctrine: 2.1.0 - eslint: 8.34.0 + eslint: 8.35.0 estraverse: 5.3.0 jsx-ast-utils: 3.3.3 minimatch: 3.1.2 @@ -2345,13 +2345,13 @@ packages: estraverse: 5.3.0 dev: true - /eslint-utils/3.0.0_eslint@8.34.0: + /eslint-utils/3.0.0_eslint@8.35.0: resolution: { integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA== } engines: { node: ^10.0.0 || ^12.0.0 || >= 14.0.0 } peerDependencies: eslint: '>=5' dependencies: - eslint: 8.34.0 + eslint: 8.35.0 eslint-visitor-keys: 2.1.0 dev: true @@ -2365,12 +2365,13 @@ packages: engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } dev: true - /eslint/8.34.0: - resolution: { integrity: sha512-1Z8iFsucw+7kSqXNZVslXS8Ioa4u2KM7GPwuKtkTFAqZ/cHMcEaR+1+Br0wLlot49cNxIiZk5wp8EAbPcYZxTg== } + /eslint/8.35.0: + resolution: { integrity: sha512-BxAf1fVL7w+JLRQhWl2pzGeSiGqbWumV4WNvc9Rhp6tiCtm4oHnyPBSEtMGZwrQgudFQ+otqzWoPB7x+hxoWsw== } engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } hasBin: true dependencies: - '@eslint/eslintrc': 1.4.1 + '@eslint/eslintrc': 2.0.0 + '@eslint/js': 8.35.0 '@humanwhocodes/config-array': 0.11.8 '@humanwhocodes/module-importer': 1.0.1 '@nodelib/fs.walk': 1.2.8 @@ -2381,7 +2382,7 @@ packages: doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.1.1 - eslint-utils: 3.0.0_eslint@8.34.0 + eslint-utils: 3.0.0_eslint@8.35.0 eslint-visitor-keys: 3.3.0 espree: 9.4.1 esquery: 1.4.2 @@ -2734,7 +2735,6 @@ packages: /graceful-fs/4.2.10: resolution: { integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== } - dev: true /grapheme-splitter/1.0.4: resolution: { integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ== } @@ -3187,53 +3187,18 @@ packages: resolution: { integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== } dev: true + /isomorphic-ws/5.0.0_ws@8.12.1: + resolution: { integrity: sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw== } + peerDependencies: + ws: '*' + dependencies: + ws: 8.12.1 + dev: false + /javascript-natural-sort/0.7.1: resolution: { integrity: sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw== } dev: true - /jotai/1.13.1_react@18.2.0: - resolution: { integrity: sha512-RUmH1S4vLsG3V6fbGlKzGJnLrDcC/HNb5gH2AeA9DzuJknoVxSGvvg8OBB7lke+gDc4oXmdVsaKn/xDUhWZ0vw== } - engines: { node: '>=12.20.0' } - peerDependencies: - '@babel/core': '*' - '@babel/template': '*' - jotai-devtools: '*' - jotai-immer: '*' - jotai-optics: '*' - jotai-redux: '*' - jotai-tanstack-query: '*' - jotai-urql: '*' - jotai-valtio: '*' - jotai-xstate: '*' - jotai-zustand: '*' - react: '>=16.8' - peerDependenciesMeta: - '@babel/core': - optional: true - '@babel/template': - optional: true - jotai-devtools: - optional: true - jotai-immer: - optional: true - jotai-optics: - optional: true - jotai-redux: - optional: true - jotai-tanstack-query: - optional: true - jotai-urql: - optional: true - jotai-valtio: - optional: true - jotai-xstate: - optional: true - jotai-zustand: - optional: true - dependencies: - react: 18.2.0 - dev: false - /js-sdsl/4.3.0: resolution: { integrity: sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ== } dev: true @@ -3447,7 +3412,7 @@ packages: dependencies: '@types/mdast': 3.0.10 escape-string-regexp: 5.0.0 - unist-util-is: 5.2.0 + unist-util-is: 5.2.1 unist-util-visit-parents: 5.1.3 dev: false @@ -3530,7 +3495,7 @@ packages: resolution: { integrity: sha512-WmI1gTXUBJo4/ZmSk79Wcb2HcjPJBzM1nlI/OUWA8yk2X9ik3ffNbBGsU+09BFmXaL1IBb9fiuvq6/KMiNycSg== } dependencies: '@types/mdast': 3.0.10 - unist-util-is: 5.2.0 + unist-util-is: 5.2.1 dev: false /mdast-util-to-hast/12.3.0: @@ -3923,7 +3888,7 @@ packages: dependencies: '@next/env': 13.2.1 '@swc/helpers': 0.4.14 - caniuse-lite: 1.0.30001457 + caniuse-lite: 1.0.30001458 postcss: 8.4.14 react: 18.2.0 react-dom: 18.2.0_react@18.2.0 @@ -3960,31 +3925,19 @@ packages: engines: { node: '>=0.10.0' } dev: true - /nostr-react/0.6.4_react@18.2.0: - resolution: { integrity: sha512-esRgmhTP5kPQ8ufs8cFAQxxJtMmzuba/k2QfXevG/ejHP3IMa41pb82qi8V0aPzY3KJ0Nr54x0OSa39d2InKzA== } - engines: { node: '>=12' } - peerDependencies: - react: '>=16' + /nostr-relaypool/0.5.3_ws@8.12.1: + resolution: { integrity: sha512-1INGKleOTuUTFUs3RnnZrew4+G/idLUewh44WBtmTTJ9g+kRiQtMMaBGTVUpf9621nBNleEVOB8p3XSNcaX3FQ== } dependencies: - jotai: 1.13.1_react@18.2.0 - nostr-tools: 1.6.0 - react: 18.2.0 + '@jest/source-map': 29.4.3 + isomorphic-ws: 5.0.0_ws@8.12.1 + nostr-tools: 1.7.1 + safe-stable-stringify: 2.4.2 transitivePeerDependencies: - - '@babel/core' - - '@babel/template' - - jotai-devtools - - jotai-immer - - jotai-optics - - jotai-redux - - jotai-tanstack-query - - jotai-urql - - jotai-valtio - - jotai-xstate - - jotai-zustand + - ws dev: false - /nostr-tools/1.6.0: - resolution: { integrity: sha512-qjjJQ7YxJUMzgS24eVlxkZ87PKJtU6dlH04OzVuK6w+GSPL+VdUZkMe2lfSpnb7OkCrDIzmbFbtx+Q4LXdU2xw== } + /nostr-tools/1.7.1: + resolution: { integrity: sha512-r72KpbLVz6Gaqei6LIj6m+cyp24eF3myiIMlmv93WCgDFCI5w72u+OrZzjSrJaeE94vYoEJfOF16/2Rl5o5z5w== } dependencies: '@noble/hashes': 1.0.0 '@noble/secp256k1': 1.7.1 @@ -4336,7 +4289,7 @@ packages: engines: { node: '>= 0.8.0' } dev: true - /prettier-plugin-tailwindcss/0.2.3_3p4xqifn6m4d44r76wgcnqfi3i: + /prettier-plugin-tailwindcss/0.2.3_zmkqdpv3ldc45e6wei6qtrbrca: resolution: { integrity: sha512-s2N5Dh7Ao5KTV1mao5ZBnn8EKtUcDPJEkGViZIjI0Ij9TTI5zgTz4IHOxW33jOdjHKa8CSjM88scelUiC5TNRQ== } engines: { node: '>=12.17.0' } peerDependencies: @@ -4388,7 +4341,7 @@ packages: prettier-plugin-twig-melody: optional: true dependencies: - '@trivago/prettier-plugin-sort-imports': 4.1.0_prettier@2.8.4 + '@trivago/prettier-plugin-sort-imports': 4.1.1_prettier@2.8.4 prettier: 2.8.4 dev: true @@ -4824,6 +4777,11 @@ packages: is-regex: 1.1.4 dev: true + /safe-stable-stringify/2.4.2: + resolution: { integrity: sha512-gMxvPJYhP0O9n2pvcfYfIuYgbledAOJFcqRThtPRmjscaipiwcwPPKLytpVzMkG2HAN87Qmo2d4PtGiri1dSLA== } + engines: { node: '>=10' } + dev: false + /scheduler/0.23.0: resolution: { integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw== } dependencies: @@ -5146,8 +5104,8 @@ packages: resolution: { integrity: sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g== } dev: false - /tsconfig-paths/3.14.1: - resolution: { integrity: sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ== } + /tsconfig-paths/3.14.2: + resolution: { integrity: sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g== } dependencies: '@types/json5': 0.0.29 json5: 1.0.2 @@ -5233,7 +5191,7 @@ packages: resolution: { integrity: sha512-RynicUM/vbOSTSiUK+BnaK9XMfmQUh6gyi7L6taNgc7FIf84GukXVV3ucGzEN/PhUUkdP5hb1MmXc+3cvPUm5Q== } dependencies: '@types/unist': 2.0.6 - unist-util-is: 5.2.0 + unist-util-is: 5.2.1 unist-util-visit-parents: 5.1.3 dev: false @@ -5241,8 +5199,10 @@ packages: resolution: { integrity: sha512-qF72kLmPxAw0oN2fwpWIqbXAVyEqUzDHMsbtPvOudIlUzXYFIeQIuxXQCRCFh22B7cixvU0MG7m3MW8FTq/S+A== } dev: false - /unist-util-is/5.2.0: - resolution: { integrity: sha512-Glt17jWwZeyqrFqOK0pF1Ded5U3yzJnFr8CG1GMjCWTp9zDo2p+cmD6pWbZU8AgM5WU3IzRv6+rBwhzsGh6hBQ== } + /unist-util-is/5.2.1: + resolution: { integrity: sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw== } + dependencies: + '@types/unist': 2.0.6 dev: false /unist-util-position/4.0.4: @@ -5261,14 +5221,14 @@ packages: resolution: { integrity: sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg== } dependencies: '@types/unist': 2.0.6 - unist-util-is: 5.2.0 + unist-util-is: 5.2.1 dev: false /unist-util-visit/4.1.2: resolution: { integrity: sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg== } dependencies: '@types/unist': 2.0.6 - unist-util-is: 5.2.0 + unist-util-is: 5.2.1 unist-util-visit-parents: 5.1.3 dev: false @@ -5331,14 +5291,6 @@ packages: tslib: 2.5.0 dev: false - /use-sync-external-store/1.2.0_react@18.2.0: - resolution: { integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA== } - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - dependencies: - react: 18.2.0 - dev: false - /util-deprecate/1.0.2: resolution: { integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== } dev: true @@ -5447,6 +5399,19 @@ packages: resolution: { integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== } dev: true + /ws/8.12.1: + resolution: { integrity: sha512-1qo+M9Ba+xNhPB+YTWUlK6M17brTut5EXbcBaMRN5pH5dFrXz7lzz1ChFSUq3bOUl8yEvSenhHmYUNJxFzdJew== } + engines: { node: '>=10.0.0' } + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + dev: false + /xtend/4.0.2: resolution: { integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== } engines: { node: '>=0.4' } diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 46d8f26f..91cbe2c4 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -465,36 +465,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" -[[package]] -name = "curl" -version = "0.4.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "509bd11746c7ac09ebd19f0b17782eae80aadee26237658a6b4808afb5c11a22" -dependencies = [ - "curl-sys", - "libc", - "openssl-probe", - "openssl-sys", - "schannel", - "socket2", - "winapi", -] - -[[package]] -name = "curl-sys" -version = "0.4.59+curl-7.86.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cfce34829f448b08f55b7db6d0009e23e2e86a34e8c2b366269bf5799b4a407" -dependencies = [ - "cc", - "libc", - "libz-sys", - "openssl-sys", - "pkg-config", - "vcpkg", - "winapi", -] - [[package]] name = "darling" version = "0.13.4" @@ -1393,6 +1363,30 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "libappindicator" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2d3cb96d092b4824cb306c9e544c856a4cb6210c1081945187f7f1924b47e8" +dependencies = [ + "glib", + "gtk", + "gtk-sys", + "libappindicator-sys", + "log", +] + +[[package]] +name = "libappindicator-sys" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1b3b6681973cea8cc3bce7391e6d7d5502720b80a581c9a95c9cbaf592826aa" +dependencies = [ + "gtk-sys", + "libloading", + "once_cell", +] + [[package]] name = "libc" version = "0.2.139" @@ -1408,6 +1402,16 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + [[package]] name = "libsqlite3-sys" version = "0.24.2" @@ -1419,18 +1423,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "libz-sys" -version = "1.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "line-wrap" version = "0.1.1" @@ -1485,7 +1477,6 @@ dependencies = [ "tauri", "tauri-build", "tauri-plugin-sql", - "webpage", ] [[package]] @@ -1504,7 +1495,7 @@ dependencies = [ "dirs-next", "objc-foundation", "objc_id", - "time 0.3.17", + "time", ] [[package]] @@ -1530,18 +1521,6 @@ dependencies = [ "tendril", ] -[[package]] -name = "markup5ever_rcdom" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f015da43bcd8d4f144559a3423f4591d69b8ce0652c905374da7205df336ae2b" -dependencies = [ - "html5ever", - "markup5ever", - "tendril", - "xml5ever", -] - [[package]] name = "matchers" version = "0.1.0" @@ -1795,25 +1774,6 @@ dependencies = [ "windows-sys 0.42.0", ] -[[package]] -name = "openssl-probe" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" - -[[package]] -name = "openssl-sys" -version = "0.9.80" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23bbbf7854cd45b83958ebe919f0e8e516793727652e27fda10a8384cfc790b7" -dependencies = [ - "autocfg", - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "overload" version = "0.1.1" @@ -2068,7 +2028,7 @@ dependencies = [ "line-wrap", "quick-xml 0.26.0", "serde", - "time 0.3.17", + "time", ] [[package]] @@ -2421,15 +2381,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "schannel" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" -dependencies = [ - "windows-sys 0.42.0", -] - [[package]] name = "scoped-tls" version = "1.0.1" @@ -2917,6 +2868,7 @@ dependencies = [ "core-foundation", "core-graphics", "crossbeam-channel", + "dirs-next", "dispatch", "gdk", "gdk-pixbuf", @@ -2930,6 +2882,7 @@ dependencies = [ "instant", "jni", "lazy_static", + "libappindicator", "libc", "log", "ndk", @@ -3047,7 +3000,7 @@ dependencies = [ "sha2", "tauri-utils", "thiserror", - "time 0.3.17", + "time", "uuid 1.3.0", "walkdir", ] @@ -3069,7 +3022,7 @@ dependencies = [ [[package]] name = "tauri-plugin-sql" version = "0.1.0" -source = "git+https://github.com/tauri-apps/plugins-workspace?branch=fix/sql-types#f1a7136b1e0b145ea5c5fb9d336cac0cb1dc6265" +source = "git+https://github.com/tauri-apps/plugins-workspace?branch=dev#8f34eb83e4f9a8c72fd3823a066c94f861f2d021" dependencies = [ "futures", "log", @@ -3220,17 +3173,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "time" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" -dependencies = [ - "libc", - "wasi 0.10.0+wasi-snapshot-preview1", - "winapi", -] - [[package]] name = "time" version = "0.3.17" @@ -3540,12 +3482,6 @@ version = "0.9.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" -[[package]] -name = "wasi" -version = "0.10.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -3675,18 +3611,6 @@ dependencies = [ "system-deps 6.0.3", ] -[[package]] -name = "webpage" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d261bbae112cb48a95d3cc9e8873a4e40933bc54ae8eddc1eef70e952dd3b232" -dependencies = [ - "curl", - "html5ever", - "markup5ever_rcdom", - "serde_json", -] - [[package]] name = "webpki" version = "0.22.0" @@ -4051,15 +3975,3 @@ checksum = "6d1526bbe5aaeb5eb06885f4d987bcdfa5e23187055de9b83fe00156a821fabc" dependencies = [ "libc", ] - -[[package]] -name = "xml5ever" -version = "0.16.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9234163818fd8e2418fcde330655e757900d4236acd8cc70fef345ef91f6d865" -dependencies = [ - "log", - "mac", - "markup5ever", - "time 0.1.45", -] diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 785e2ee2..7818d44b 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -16,12 +16,11 @@ tauri-build = { version = "1.2", features = [] } [dependencies] serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } -tauri = { version = "1.2", features = ["clipboard-all", "notification-all", "shell-open", "window-start-dragging"] } -webpage = "1.5.0" +tauri = { version = "1.2", features = ["clipboard-all", "notification-all", "shell-open", "system-tray", "window-start-dragging"] } [dependencies.tauri-plugin-sql] git = "https://github.com/tauri-apps/plugins-workspace" -branch = "fix/sql-types" +branch = "dev" features = ["sqlite"] [target.'cfg(target_os = "macos")'.dependencies] diff --git a/src-tauri/icons/icon.png b/src-tauri/icons/icon.png index a23cdeaf..ac86aaae 100644 Binary files a/src-tauri/icons/icon.png and b/src-tauri/icons/icon.png differ diff --git a/src-tauri/migrations/20230226004139_create_tables.sql b/src-tauri/migrations/20230226004139_create_tables.sql new file mode 100644 index 00000000..7e923494 --- /dev/null +++ b/src-tauri/migrations/20230226004139_create_tables.sql @@ -0,0 +1,70 @@ +-- Add migration script here +-- create relays +CREATE TABLE + relays ( + id INTEGER PRIMARY KEY, + relay_url TEXT NOT NULL, + relay_status INTEGER NOT NULL DEFAULT 1, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP + ); + +INSERT INTO + relays (relay_url, relay_status) +VALUES + ("wss://relay.damus.io", "1"), + ("wss://relay.uselume.xyz", "0"), + ("wss://nostr-pub.wellorder.net", "1"), + ("wss://nostr.bongbong.com", "1"), + ("wss://nostr.zebedee.cloud", "1"), + ("wss://nostr.fmt.wiz.biz", "1"), + ("wss://nostr.walletofsatoshi.com", "1"), + ("wss://relay.snort.social", "1"), + ("wss://offchain.pub", "1"), + ("wss://nos.lol", "1"); + +-- create accounts +CREATE TABLE + accounts ( + id TEXT PRIMARY KEY, + privkey TEXT NOT NULL, + npub TEXT NOT NULL, + nsec TEXT NOT NULL, + is_active INTEGER NOT NULL DEFAULT 0, + metadata JSON + ); + +-- create follows +CREATE TABLE + follows ( + id INTEGER PRIMARY KEY, + pubkey TEXT NOT NULL, + account TEXT NOT NULL, + kind INTEGER NOT NULL DEFAULT 0, + metadata JSON + ); + +-- create index for pubkey in follows +CREATE UNIQUE INDEX index_pubkey ON follows (pubkey); + +-- create cache profiles +CREATE TABLE + cache_profiles ( + id TEXT PRIMARY KEY, + metadata JSON, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP + ); + +-- create cache notes +CREATE TABLE + cache_notes ( + id TEXT PRIMARY KEY, + pubkey TEXT NOT NULL, + created_at TEXT, + kind INTEGER NOT NULL DEFAULT 1, + tags TEXT NOT NULL, + content TEXT NOT NULL, + relay TEXT, + is_multi BOOLEAN DEFAULT 0 + ); \ No newline at end of file diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 38c406f3..4b0c9868 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -7,48 +7,15 @@ #[macro_use] extern crate objc; -use std::time::Duration; - -use tauri::{Manager, WindowEvent}; -use webpage::{Webpage, WebpageOptions}; +use tauri::{Manager, SystemTray, WindowEvent}; +use tauri_plugin_sql::{Migration, MigrationKind}; use window_ext::WindowExt; mod window_ext; -#[derive(serde::Serialize)] -struct OpenGraphResponse { - title: String, - description: String, - url: String, - image: String, -} - -async fn fetch_opengraph(url: String) -> OpenGraphResponse { - let options = WebpageOptions { - allow_insecure: false, - max_redirections: 3, - timeout: Duration::from_secs(30), - useragent: "lume - desktop app".to_string(), - ..Default::default() - }; - let result = Webpage::from_url(&url, options).expect("Could not read from URL"); - let html = result.html; - - return OpenGraphResponse { - title: html.opengraph.properties["title"].to_string(), - description: html.opengraph.properties["description"].to_string(), - url: html.opengraph.properties["url"].to_string(), - image: html.opengraph.images[0].url.to_string(), - }; -} - -#[tauri::command] -async fn opengraph(url: String) -> OpenGraphResponse { - let result = fetch_opengraph(url).await; - return result; -} - fn main() { + let tray = SystemTray::new(); + tauri::Builder::default() .setup(|app| { let main_window = app.get_window("main").unwrap(); @@ -57,8 +24,20 @@ fn main() { Ok(()) }) - .invoke_handler(tauri::generate_handler![opengraph]) - .plugin(tauri_plugin_sql::Builder::default().build()) + .system_tray(tray) + .plugin( + tauri_plugin_sql::Builder::default() + .add_migrations( + "sqlite:lume.db", + vec![Migration { + version: 1, + description: "create default tables", + sql: include_str!("../migrations/20230226004139_create_tables.sql"), + kind: MigrationKind::Up, + }], + ) + .build(), + ) .on_window_event(|e| { let apply_offset = || { let win = e.window(); diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 5a489f2f..96b62eb9 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -37,13 +37,7 @@ "depends": [] }, "externalBin": [], - "icon": [ - "icons/32x32.png", - "icons/128x128.png", - "icons/128x128@2x.png", - "icons/icon.icns", - "icons/icon.ico" - ], + "icon": ["icons/32x32.png", "icons/128x128.png", "icons/128x128@2x.png", "icons/icon.icns", "icons/icon.ico"], "identifier": "com.uselume.xyz", "longDescription": "", "macOS": { @@ -68,6 +62,10 @@ "updater": { "active": false }, + "systemTray": { + "iconPath": "icons/icon.png", + "iconAsTemplate": true + }, "windows": [ { "theme": "Dark", diff --git a/src/components/accountBar/account.tsx b/src/components/accountBar/account.tsx index c5c13690..4f0c7357 100644 --- a/src/components/accountBar/account.tsx +++ b/src/components/accountBar/account.tsx @@ -16,12 +16,7 @@ export const Account = memo(function Account({ user, current }: { user: any; cur current === user.pubkey ? 'ring-1 ring-fuchsia-500 ring-offset-4 ring-offset-black' : '' }`}> {userData?.picture !== undefined ? ( - user's avatar + user's avatar ) : (
)} diff --git a/src/components/accountBar/index.tsx b/src/components/accountBar/index.tsx index d32cc447..515a2af4 100644 --- a/src/components/accountBar/index.tsx +++ b/src/components/accountBar/index.tsx @@ -1,19 +1,16 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { Account } from '@components/accountBar/account'; -import { currentUser } from '@stores/currentUser'; - import LumeSymbol from '@assets/icons/Lume'; - -import { useStore } from '@nanostores/react'; import { PlusIcon } from '@radix-ui/react-icons'; +import { useLocalStorage } from '@rehooks/local-storage'; import Link from 'next/link'; import { useCallback, useEffect, useState } from 'react'; import Database from 'tauri-plugin-sql-api'; export default function AccountBar() { const [users, setUsers] = useState([]); - const $currentUser: any = useStore(currentUser); + const [currentUser]: any = useLocalStorage('current-user'); const getAccounts = useCallback(async () => { const db = await Database.load('sqlite:lume.db'); @@ -30,7 +27,7 @@ export default function AccountBar() {
{users.map((user, index) => ( - + ))} { + const getRelays = async () => { + const arr = []; + const result: any[] = await db.select('SELECT relay_url FROM relays WHERE relay_status = "1"'); + + result.forEach((item: { relay_url: string }) => { + arr.push(item.relay_url); + }); + + writeStorage('relays', arr); + }; + + const getAccount = async () => { + const result = await db.select(`SELECT * FROM accounts LIMIT 1`); + writeStorage('current-user', result[0]); + + return result[0]; + }; + + const getFollows = async (id: string) => { + const arr = []; + const result: any[] = await db.select(`SELECT pubkey FROM follows WHERE account = "${id}"`); + + result.forEach((item: { pubkey: string }) => { + arr.push(item.pubkey); + }); + + writeStorage('follows', arr); + }; + + getRelays().catch(console.error); + getAccount() + .then((res) => { + if (res) { + getFollows(res.id).catch(console.error); + } + setDone(true); + }) + .catch(console.error); + }, []); + + if (done === true) { + return {children}; + } +} diff --git a/src/components/contexts/relay.tsx b/src/components/contexts/relay.tsx new file mode 100644 index 00000000..41b30048 --- /dev/null +++ b/src/components/contexts/relay.tsx @@ -0,0 +1,11 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { RelayPool } from 'nostr-relaypool'; +import { createContext, useMemo } from 'react'; + +export const RelayContext = createContext({}); + +export default function RelayProvider({ relays, children }: { relays: any; children: React.ReactNode }) { + const value = useMemo(() => new RelayPool(relays, { useEventCache: true }), [relays]); + + return {children}; +} diff --git a/src/components/navigatorBar/createPost.tsx b/src/components/navigatorBar/createPost.tsx index 2ce53f11..b813b287 100644 --- a/src/components/navigatorBar/createPost.tsx +++ b/src/components/navigatorBar/createPost.tsx @@ -1,25 +1,28 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { currentUser } from '@stores/currentUser'; +import { RelayContext } from '@components/contexts/relay'; + +import { dateToUnix } from '@utils/getDate'; -import { useStore } from '@nanostores/react'; import * as Dialog from '@radix-ui/react-dialog'; +import { useLocalStorage } from '@rehooks/local-storage'; import * as commands from '@uiw/react-md-editor/lib/commands'; import dynamic from 'next/dynamic'; -import { dateToUnix, useNostr } from 'nostr-react'; import { getEventHash, signEvent } from 'nostr-tools'; -import { useState } from 'react'; +import { useContext, useState } from 'react'; const MDEditor = dynamic(() => import('@uiw/react-md-editor').then((mod) => mod.default), { ssr: false, }); export default function CreatePost() { - const { publish } = useNostr(); + const relayPool: any = useContext(RelayContext); + const [relays]: any = useLocalStorage('relays'); + const [value, setValue] = useState(''); - const $currentUser: any = useStore(currentUser); - const pubkey = $currentUser.pubkey; - const privkey = $currentUser.privkey; + const [currentUser]: any = useLocalStorage('current-user'); + const pubkey = currentUser.pubkey; + const privkey = currentUser.privkey; const postButton = { name: 'post', @@ -27,9 +30,7 @@ export default function CreatePost() { buttonProps: { className: 'cta-btn', 'aria-label': 'Post a message' }, icon: (
- - Post - + Post
), @@ -44,11 +45,10 @@ export default function CreatePost() { pubkey: pubkey, tags: [], }; - event.id = getEventHash(event); event.sig = signEvent(event, privkey); - publish(event); + relayPool.publish(event, relays); setValue(''); } }, diff --git a/src/components/navigatorBar/index.tsx b/src/components/navigatorBar/index.tsx index b11d920e..83bbe7b7 100644 --- a/src/components/navigatorBar/index.tsx +++ b/src/components/navigatorBar/index.tsx @@ -3,30 +3,22 @@ import ActiveLink from '@components/activeLink'; import CreatePost from '@components/navigatorBar/createPost'; import { ProfileMenu } from '@components/navigatorBar/profileMenu'; -import { currentUser } from '@stores/currentUser'; - -import { useStore } from '@nanostores/react'; import { PlusIcon } from '@radix-ui/react-icons'; +import { useLocalStorage } from '@rehooks/local-storage'; export default function NavigatorBar() { - const $currentUser: any = useStore(currentUser); - const profile = - $currentUser.metadata !== undefined - ? JSON.parse($currentUser.metadata) - : { display_name: null, username: null }; + const [currentUser]: any = useLocalStorage('current-user'); + const profile = JSON.parse(currentUser.metadata); return (
- {/* main */}
{/* Create post */}
-
- {profile.display_name || ''} -
- +
{profile.display_name || ''}
+
@{profile.username || ''}
@@ -38,9 +30,7 @@ export default function NavigatorBar() {

Newsfeed

-
@@ -65,9 +55,7 @@ export default function NavigatorBar() {

Direct Messages

-
@@ -77,17 +65,3 @@ export default function NavigatorBar() {
); } - -/* Channels -
-
-

Channels

- -
-
-
-*/ diff --git a/src/components/note/atoms/reaction.tsx b/src/components/note/atoms/reaction.tsx index 8665fc78..ac6e49f3 100644 --- a/src/components/note/atoms/reaction.tsx +++ b/src/components/note/atoms/reaction.tsx @@ -1,46 +1,46 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { currentUser } from '@stores/currentUser'; +import { RelayContext } from '@components/contexts/relay'; + +import { dateToUnix } from '@utils/getDate'; -import { useStore } from '@nanostores/react'; import { HeartFilledIcon, HeartIcon } from '@radix-ui/react-icons'; -import { dateToUnix, useNostr, useNostrEvents } from 'nostr-react'; +import { useLocalStorage } from '@rehooks/local-storage'; import { getEventHash, signEvent } from 'nostr-tools'; -import { useState } from 'react'; +import { useContext, useState } from 'react'; + +export default function Reaction({ eventID, eventPubkey }: { eventID: string; eventPubkey: string }) { + const relayPool: any = useContext(RelayContext); + const [relays]: any = useLocalStorage('relays'); -export default function Reaction({ - eventID, - eventPubkey, -}: { - eventID: string; - eventPubkey: string; -}) { - const { publish } = useNostr(); const [reaction, setReaction] = useState(0); const [isReact, setIsReact] = useState(false); - const $currentUser: any = useStore(currentUser); - const pubkey = $currentUser.pubkey; - const privkey = $currentUser.privkey; + const [currentUser]: any = useLocalStorage('current-user'); + const pubkey = currentUser.pubkey; + const privkey = currentUser.privkey; - const { onEvent } = useNostrEvents({ - filter: { - '#e': [eventID], - since: 0, - kinds: [7], - limit: 20, - }, - }); - - onEvent((rawMetadata) => { - try { - const content = rawMetadata.content; - if (content === '🤙' || content === '+') { + /* + relayPool.subscribe( + [ + { + '#e': [eventID], + since: 0, + kinds: [7], + limit: 10, + }, + ], + relays, + (event: any) => { + if (event.content === '🤙' || event.content === '+') { setReaction(reaction + 1); } - } catch (err) { - console.error(err, rawMetadata); + }, + undefined, + (events: any, relayURL: any) => { + console.log(events, relayURL); } - }); + ); + */ const handleReaction = (e: any) => { e.stopPropagation(); @@ -58,22 +58,16 @@ export default function Reaction({ event.id = getEventHash(event); event.sig = signEvent(event, privkey); - publish(event); + relayPool.publish(event, relays); setIsReact(true); setReaction(reaction + 1); }; return ( - diff --git a/src/components/note/atoms/reply.tsx b/src/components/note/atoms/reply.tsx index 67a3a5d9..cde060c0 100644 --- a/src/components/note/atoms/reply.tsx +++ b/src/components/note/atoms/reply.tsx @@ -1,22 +1,16 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import { ChatBubbleIcon } from '@radix-ui/react-icons'; -import { useNostrEvents } from 'nostr-react'; +import { useState } from 'react'; -export default function Reply({ eventID }: { eventID: string }) { - const { events } = useNostrEvents({ - filter: { - '#e': [eventID], - since: 0, - kinds: [1], - limit: 10, - }, - }); +export default function Reply() { + const [count] = useState(0); return ( ); } diff --git a/src/components/note/atoms/user.tsx b/src/components/note/atoms/user.tsx index 632fe39d..20e73bf0 100644 --- a/src/components/note/atoms/user.tsx +++ b/src/components/note/atoms/user.tsx @@ -1,88 +1,65 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ +import { DatabaseContext } from '@components/contexts/database'; import { ImageWithFallback } from '@components/imageWithFallback'; import { truncate } from '@utils/truncate'; import { DotsHorizontalIcon } from '@radix-ui/react-icons'; import Avatar from 'boring-avatars'; -import { useNostrEvents } from 'nostr-react'; -import { memo, useEffect, useState } from 'react'; +import { memo, useCallback, useContext, useEffect, useState } from 'react'; import Moment from 'react-moment'; -import Database from 'tauri-plugin-sql-api'; - -const db = typeof window !== 'undefined' ? await Database.load('sqlite:lume.db') : null; export const User = memo(function User({ pubkey, time }: { pubkey: string; time: any }) { + const { db }: any = useContext(DatabaseContext); const [profile, setProfile] = useState({ picture: null, name: null, username: null }); - const { onEvent } = useNostrEvents({ - filter: { - authors: [pubkey], - kinds: [0], - }, - }); + const insertCacheProfile = useCallback( + async (event) => { + const metadata: any = JSON.parse(event.content); - onEvent(async (rawMetadata) => { - try { - const metadata: any = JSON.parse(rawMetadata.content); - if (profile.picture === null || profile.name === null) { - setProfile(metadata); - await db.execute( - `INSERT OR IGNORE INTO cache_profiles (pubkey, metadata) VALUES ("${pubkey}", '${JSON.stringify( - metadata - )}')` - ); - } else { - return; - } - } catch (err) { - console.error(err, rawMetadata); - } - }); + await db.execute(`INSERT OR IGNORE INTO cache_profiles (id, metadata) VALUES ("${pubkey}", '${JSON.stringify(metadata)}')`); + setProfile(metadata); + }, + [db, pubkey] + ); + + const getCacheProfile = useCallback(async () => { + const result: any = await db.select(`SELECT metadata FROM cache_profiles WHERE id = "${pubkey}"`); + return result; + }, [db, pubkey]); useEffect(() => { - const initialProfile = async () => { - const result: any = await db.select( - `SELECT metadata FROM cache_profiles WHERE pubkey = "${pubkey}"` - ); - db.close; - return result; - }; - - initialProfile() + getCacheProfile() .then((res) => { if (res[0] !== undefined) { setProfile(JSON.parse(res[0].metadata)); + } else { + fetch(`https://rbr.bio/${pubkey}/metadata.json`).then((res) => + res.json().then((res) => { + // update state + setProfile(JSON.parse(res.content)); + // save profile to database + insertCacheProfile(res); + }) + ); } }) .catch(console.error); - }, [pubkey]); + }, [getCacheProfile, insertCacheProfile, pubkey]); return (
{profile.picture ? ( - + ) : ( - + )}
- - {profile.name ? profile.name : truncate(pubkey, 16, ' .... ')} - + {profile.name ? profile.name : truncate(pubkey, 16, ' .... ')} · {time} diff --git a/src/components/note/atoms/userRepost.tsx b/src/components/note/atoms/userRepost.tsx index 1e49009d..702e6f57 100644 --- a/src/components/note/atoms/userRepost.tsx +++ b/src/components/note/atoms/userRepost.tsx @@ -1,30 +1,19 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { truncate } from '@utils/truncate'; -import { useNostrEvents } from 'nostr-react'; -import { memo, useState } from 'react'; +import { memo, useEffect, useState } from 'react'; export const UserRepost = memo(function UserRepost({ pubkey }: { pubkey: string }) { const [profile, setProfile] = useState({ picture: null, name: null }); - const { onEvent } = useNostrEvents({ - filter: { - authors: [pubkey], - kinds: [0], - }, - }); - - // #TODO: save response to DB - onEvent((rawMetadata) => { - try { - const metadata: any = JSON.parse(rawMetadata.content); - if (metadata) { - setProfile(metadata); - } - } catch (err) { - console.error(err, rawMetadata); - } - }); + useEffect(() => { + fetch(`https://rbr.bio/${pubkey}/metadata.json`).then((res) => + res.json().then((res) => { + // update state + setProfile(JSON.parse(res.content)); + }) + ); + }, [pubkey]); return (
diff --git a/src/components/note/atoms/userWithUsername.tsx b/src/components/note/atoms/userWithUsername.tsx index abc6b50f..1dd2fd96 100644 --- a/src/components/note/atoms/userWithUsername.tsx +++ b/src/components/note/atoms/userWithUsername.tsx @@ -5,86 +5,34 @@ import { truncate } from '@utils/truncate'; import { DotsHorizontalIcon } from '@radix-ui/react-icons'; import Avatar from 'boring-avatars'; -import { useNostrEvents } from 'nostr-react'; import { memo, useEffect, useState } from 'react'; -import Database from 'tauri-plugin-sql-api'; - -const db = typeof window !== 'undefined' ? await Database.load('sqlite:lume.db') : null; export const UserWithUsername = memo(function UserWithUsername({ pubkey }: { pubkey: string }) { const [profile, setProfile] = useState({ picture: null, name: null, username: null }); - const { onEvent } = useNostrEvents({ - filter: { - authors: [pubkey], - kinds: [0], - }, - }); - - onEvent(async (rawMetadata) => { - try { - const metadata: any = JSON.parse(rawMetadata.content); - if (profile.picture === null || profile.name === null) { - setProfile(metadata); - await db.execute( - `INSERT OR IGNORE INTO cache_profiles (pubkey, metadata) VALUES ("${pubkey}", '${JSON.stringify( - metadata - )}')` - ); - } else { - return; - } - } catch (err) { - console.error(err, rawMetadata); - } - }); - useEffect(() => { - const initialProfile = async () => { - const result: any = await db.select( - `SELECT metadata FROM cache_profiles WHERE pubkey = "${pubkey}"` - ); - db.close; - return result; - }; - - initialProfile() - .then((res) => { - if (res[0] !== undefined) { - setProfile(JSON.parse(res[0].metadata)); - } + fetch(`https://rbr.bio/${pubkey}/metadata.json`).then((res) => + res.json().then((res) => { + // update state + setProfile(JSON.parse(res.content)); }) - .catch(console.error); + ); }, [pubkey]); return (
{profile.picture ? ( - + ) : ( - + )}
- - {profile.name ? profile.name : truncate(pubkey, 16, ' .... ')} - - - {profile.username ? profile.username : truncate(pubkey, 16, ' .... ')} - + {profile.name ? profile.name : truncate(pubkey, 16, ' .... ')} + {profile.username ? profile.username : truncate(pubkey, 16, ' .... ')}
diff --git a/src/components/note/connector.tsx b/src/components/note/connector.tsx new file mode 100644 index 00000000..f27da0d4 --- /dev/null +++ b/src/components/note/connector.tsx @@ -0,0 +1,102 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { DatabaseContext } from '@components/contexts/database'; +import { RelayContext } from '@components/contexts/relay'; + +import { dateToUnix, hoursAgo } from '@utils/getDate'; + +import { ReloadIcon } from '@radix-ui/react-icons'; +import { useLocalStorage } from '@rehooks/local-storage'; +import { memo, useCallback, useContext, useEffect, useRef, useState } from 'react'; + +export const NoteConnector = memo(function NoteConnector({ + setParentReload, + setHasNewNote, + currentDate, +}: { + setParentReload: any; + setHasNewNote: any; + currentDate: any; +}) { + const { db }: any = useContext(DatabaseContext); + const relayPool: any = useContext(RelayContext); + + const [follows]: any = useLocalStorage('follows'); + const [relays]: any = useLocalStorage('relays'); + + const [reload, setReload] = useState(false); + const timeout = useRef(null); + + const reloadNewsfeed = () => { + setParentReload(true); + setReload(true); + timeout.current = setTimeout(() => { + setReload(false); + }, 2000); + }; + + const insertDB = useCallback( + async (event: any) => { + await db.execute( + `INSERT OR IGNORE INTO + cache_notes + (id, pubkey, created_at, kind, tags, content) VALUES + ("${event.id}", "${event.pubkey}", "${event.created_at}", "${event.kind}", '${JSON.stringify(event.tags)}', "${event.content}");` + ); + }, + [db] + ); + + const fetchEvent = useCallback(() => { + relayPool.subscribe( + [ + { + kinds: [1], + authors: follows, + since: dateToUnix(hoursAgo(12, currentDate)), + }, + ], + relays, + (event: any) => { + // show trigger update newer event + if (event.created_at > dateToUnix(currentDate)) { + setHasNewNote(true); + } + // insert event to local database + insertDB(event).catch(console.error); + }, + undefined, + (events: any, relayURL: any) => { + console.log(events, relayURL); + } + ); + }, [relayPool, follows, currentDate, relays, insertDB, setHasNewNote]); + + useEffect(() => { + fetchEvent(); + + return () => { + clearTimeout(timeout.current); + }; + }, [fetchEvent]); + + return ( +
+
+

# following

+
+
+ +
+ {/* #TODO: get user network status */} + + + + +

Online

+
+
+
+ ); +}); diff --git a/src/components/note/modal/index.tsx b/src/components/note/modal/index.tsx index 5665c7cb..91824bd0 100644 --- a/src/components/note/modal/index.tsx +++ b/src/components/note/modal/index.tsx @@ -1,18 +1,33 @@ +import { RelayContext } from '@components/contexts/relay'; import { Content } from '@components/note/content'; import NoteReply from '@components/note/modal/noteReply'; -import { useNostrEvents } from 'nostr-react'; -import { memo } from 'react'; +import useLocalStorage from '@rehooks/local-storage'; +import { memo, useContext, useState } from 'react'; /* eslint-disable @typescript-eslint/no-explicit-any */ const Modal = ({ event }: { event: any }) => { - const { events } = useNostrEvents({ - filter: { - '#e': [event.id], - since: event.created_at, - kinds: [1], + const relayPool: any = useContext(RelayContext); + const [relays]: any = useLocalStorage('relays'); + const [events, setEvents] = useState([]); + + relayPool.subscribe( + [ + { + '#e': [event.id], + since: event.created_at, + kinds: [1], + }, + ], + relays, + (event: any) => { + setEvents((events) => [event, ...events]); }, - }); + undefined, + (events: any, relayURL: any) => { + console.log(events, relayURL); + } + ); return (
diff --git a/src/components/note/repost.tsx b/src/components/note/repost.tsx index 7d6c257b..4bc3dcea 100644 --- a/src/components/note/repost.tsx +++ b/src/components/note/repost.tsx @@ -1,49 +1,47 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ +import { RelayContext } from '@components/contexts/relay'; import { UserRepost } from '@components/note/atoms/userRepost'; import { Content } from '@components/note/content'; import { Placeholder } from '@components/note/placeholder'; -import * as Dialog from '@radix-ui/react-dialog'; import { LoopIcon } from '@radix-ui/react-icons'; -import dynamic from 'next/dynamic'; -import { useNostrEvents } from 'nostr-react'; -import { memo } from 'react'; - -const Modal = dynamic(() => import('@components/note/modal'), { - ssr: false, - loading: () => <>, -}); +import useLocalStorage from '@rehooks/local-storage'; +import { memo, useContext, useState } from 'react'; export const Repost = memo(function Repost({ root, user }: { root: any; user: string }) { - const { events } = useNostrEvents({ - filter: { - ids: [root[0][1]], - since: 0, - kinds: [1], + const relayPool: any = useContext(RelayContext); + const [relays]: any = useLocalStorage('relays'); + const [events, setEvents] = useState([]); + + relayPool.subscribe( + [ + { + ids: [root[0][1]], + since: 0, + kinds: [1], + }, + ], + relays, + (event: any) => { + setEvents((events) => [event, ...events]); }, - }); + undefined, + (events: any, relayURL: any) => { + console.log(events, relayURL); + } + ); if (events !== null && Object.keys(events).length > 0) { return ( - - -
-
- -
- -
-
- {events[0].content && } +
+
+ +
+
- - - - {events[0].content && } - - - - +
+ {events[0].content && } +
); } else { return ( diff --git a/src/components/note/single.tsx b/src/components/note/single.tsx index e6d56b59..926bf63b 100644 --- a/src/components/note/single.tsx +++ b/src/components/note/single.tsx @@ -1,30 +1,12 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { Content } from '@components/note/content'; -import * as Dialog from '@radix-ui/react-dialog'; -import dynamic from 'next/dynamic'; import { memo } from 'react'; -const Modal = dynamic(() => import('@components/note/modal'), { - ssr: false, - loading: () => <>, -}); - -// eslint-disable-next-line @typescript-eslint/no-explicit-any export const Single = memo(function Single({ event }: { event: any }) { return ( - - -
- -
-
- - - - - - -
+
+ +
); }); diff --git a/src/components/thread.tsx b/src/components/thread.tsx deleted file mode 100644 index 86d3a4ce..00000000 --- a/src/components/thread.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { Placeholder } from '@components/note/placeholder'; -import { Repost } from '@components/note/repost'; -import { Single } from '@components/note/single'; - -import { useCallback } from 'react'; -import { Virtuoso } from 'react-virtuoso'; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export function Thread({ data }: { data: any }) { - const ItemContent = useCallback( - (index: string | number) => { - const event = data[index]; - - if (event.content.includes('#[0]') && event.tags[0][0] == 'e') { - // type: repost - return ; - } else { - // type: default - return ; - } - }, - [data] - ); - - const computeItemKey = useCallback( - (index) => { - return data[index].id; - }, - [data] - ); - - return ( - , - ScrollSeekPlaceholder: () => , - }} - computeItemKey={computeItemKey} - scrollSeekConfiguration={{ - enter: (velocity) => Math.abs(velocity) > 800, - exit: (velocity) => Math.abs(velocity) < 500, - }} - overscan={800} - increaseViewportBy={1000} - className="scrollbar-hide relative h-full w-full rounded-lg" - style={{ - contain: 'strict', - }} - /> - ); -} diff --git a/src/layouts/userLayout.tsx b/src/layouts/userLayout.tsx index b04a159b..814472e1 100644 --- a/src/layouts/userLayout.tsx +++ b/src/layouts/userLayout.tsx @@ -2,12 +2,10 @@ import AccountBar from '@components/accountBar'; import ActiveLink from '@components/activeLink'; -import { currentUser } from '@stores/currentUser'; - -import { useStore } from '@nanostores/react'; +import { useLocalStorage } from '@rehooks/local-storage'; export default function UserLayout({ children }: { children: React.ReactNode }) { - const $currentUser: any = useStore(currentUser); + const [currentUser]: any = useLocalStorage('current-user'); return (
@@ -27,13 +25,13 @@ export default function UserLayout({ children }: { children: React.ReactNode })
Personal Page Update Profile diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 7f2d2930..b72c3c50 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -1,11 +1,11 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { relays } from '@stores/relays'; +import DatabaseProvider from '@components/contexts/database'; +import RelayProvider from '@components/contexts/relay'; -import { useStore } from '@nanostores/react'; +import { useLocalStorage } from '@rehooks/local-storage'; import type { NextPage } from 'next'; import type { AppProps } from 'next/app'; -import { NostrProvider } from 'nostr-react'; -import type { ReactElement, ReactNode } from 'react'; +import { ReactElement, ReactNode } from 'react'; import '../App.css'; @@ -21,12 +21,12 @@ type AppPropsWithLayout = AppProps & { export default function MyApp({ Component, pageProps }: AppPropsWithLayout) { // Use the layout defined at the page level, if available const getLayout = Component.getLayout ?? ((page) => page); - // Get relays - const $relays = useStore(relays); + // Get relays from localstorage + const [relays] = useLocalStorage('relays'); return ( - - {getLayout()} - + + {getLayout()} + ); } diff --git a/src/pages/feed/following.tsx b/src/pages/feed/following.tsx index 00766a4a..a47e7518 100644 --- a/src/pages/feed/following.tsx +++ b/src/pages/feed/following.tsx @@ -1,54 +1,113 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import BaseLayout from '@layouts/baseLayout'; import NewsFeedLayout from '@layouts/newsfeedLayout'; +import { DatabaseContext } from '@components/contexts/database'; +import { NoteConnector } from '@components/note/connector'; import { Placeholder } from '@components/note/placeholder'; -import { Thread } from '@components/thread'; +import { Repost } from '@components/note/repost'; +import { Single } from '@components/note/single'; -import { hoursAgo } from '@utils/getDate'; +import { dateToUnix } from '@utils/getDate'; -import { follows } from '@stores/follows'; - -import { useStore } from '@nanostores/react'; -import { dateToUnix, useNostrEvents } from 'nostr-react'; -import { - JSXElementConstructor, - ReactElement, - ReactFragment, - ReactPortal, - Suspense, - useRef, -} from 'react'; +import { writeStorage } from '@rehooks/local-storage'; +import { useCallback, useState } from 'react'; +import { JSXElementConstructor, ReactElement, ReactFragment, ReactPortal, useContext, useEffect, useRef } from 'react'; +import { Virtuoso } from 'react-virtuoso'; export default function Page() { - const $follows = useStore(follows); - const now = useRef(new Date()); + const { db }: any = useContext(DatabaseContext); - const { events } = useNostrEvents({ - filter: { - authors: $follows, - since: dateToUnix(hoursAgo(6, now.current)), - kinds: [1], - limit: 100, + const [data, setData] = useState([]); + const [parentReload, setParentReload] = useState(false); + const [hasNewNote, setHasNewNote] = useState(false); + + const now = useRef(new Date()); + const limit = useRef(30); + const offset = useRef(0); + + const loadMore = useCallback(async () => { + offset.current += limit.current; + // next query + const result = await db.select( + `SELECT * FROM + cache_notes + WHERE created_at <= ${dateToUnix(now.current)} + ORDER BY created_at DESC + LIMIT ${limit.current} OFFSET ${offset.current}` + ); + setData((data) => [...data, ...result]); + }, [db]); + + const ItemContent = useCallback( + (index: string | number) => { + const event = data[index]; + if (event.content.includes('#[0]') && event.tags[0][0] == 'e') { + // type: repost + return ; + } else { + // type: default + return ; + } }, - }); + [data] + ); + + useEffect(() => { + const getData = async () => { + const result = await db.select( + `SELECT * FROM cache_notes WHERE created_at <= ${dateToUnix(now.current)} ORDER BY created_at DESC LIMIT ${limit.current}` + ); + if (result) { + setData(result); + writeStorage('settings', new Date()); + } + }; + + getData().catch(console.error); + }, [db, parentReload]); + + const computeItemKey = useCallback( + (index: string | number) => { + return data[index].id; + }, + [data] + ); return ( -
- }> - - +
+ + {hasNewNote && ( +
+ +
+ )} + , + ScrollSeekPlaceholder: () => , + }} + computeItemKey={computeItemKey} + scrollSeekConfiguration={{ + enter: (velocity) => Math.abs(velocity) > 800, + exit: (velocity) => Math.abs(velocity) < 500, + }} + endReached={loadMore} + overscan={800} + increaseViewportBy={1000} + className="relative h-full w-full" + style={{ + contain: 'strict', + }} + />
); } Page.getLayout = function getLayout( - page: - | string - | number - | boolean - | ReactElement> - | ReactFragment - | ReactPortal + page: string | number | boolean | ReactElement> | ReactFragment | ReactPortal ) { return ( diff --git a/src/pages/feed/global.tsx b/src/pages/feed/global.tsx index c8ce0a63..204bd2fc 100644 --- a/src/pages/feed/global.tsx +++ b/src/pages/feed/global.tsx @@ -1,50 +1,18 @@ import BaseLayout from '@layouts/baseLayout'; import NewsFeedLayout from '@layouts/newsfeedLayout'; -import { Placeholder } from '@components/note/placeholder'; -import { Thread } from '@components/thread'; - -import { hoursAgo } from '@utils/getDate'; - -import { dateToUnix, useNostrEvents } from 'nostr-react'; -import { - JSXElementConstructor, - ReactElement, - ReactFragment, - ReactPortal, - Suspense, - useRef, -} from 'react'; +import { JSXElementConstructor, ReactElement, ReactFragment, ReactPortal } from 'react'; export default function Page() { - const now = useRef(new Date()); - - const { events } = useNostrEvents({ - filter: { - until: dateToUnix(now.current), - since: dateToUnix(hoursAgo(1, now.current)), - kinds: [1], - limit: 10, - }, - }); - return (
- }> - - +

Global

); } Page.getLayout = function getLayout( - page: - | string - | number - | boolean - | ReactElement> - | ReactFragment - | ReactPortal + page: string | number | boolean | ReactElement> | ReactFragment | ReactPortal ) { return ( diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 49db3ffb..544b4c4f 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -2,108 +2,31 @@ import BaseLayout from '@layouts/baseLayout'; import FullLayout from '@layouts/fullLayout'; -import { currentUser } from '@stores/currentUser'; -import { follows } from '@stores/follows'; - import LumeSymbol from '@assets/icons/Lume'; - -import { isPermissionGranted, requestPermission, sendNotification } from '@tauri-apps/api/notification'; +import { useLocalStorage } from '@rehooks/local-storage'; import { motion } from 'framer-motion'; import { useRouter } from 'next/router'; -import { JSXElementConstructor, ReactElement, ReactFragment, ReactPortal, useCallback, useEffect, useState } from 'react'; -import Database from 'tauri-plugin-sql-api'; - -const db = typeof window !== 'undefined' ? await Database.load('sqlite:lume.db') : null; +import { JSXElementConstructor, ReactElement, ReactFragment, ReactPortal, useEffect, useState } from 'react'; export default function Page() { const router = useRouter(); + + const [currentUser]: any = useLocalStorage('current-user'); const [loading, setLoading] = useState(true); - const initDB = useCallback(async () => { - if (db) { - await db.execute( - 'CREATE TABLE IF NOT EXISTS accounts (id INTEGER PRIMARY KEY, privkey TEXT NOT NULL, pubkey TEXT NOT NULL, npub TEXT, nsec TEXT, current INTEGER DEFAULT "0" NOT NULL, metadata JSON, UNIQUE(privkey));' - ); - await db.execute('CREATE TABLE IF NOT EXISTS follows (id INTEGER PRIMARY KEY, pubkey TEXT NOT NULL, account TEXT, UNIQUE(pubkey));'); - await db.execute( - 'CREATE TABLE IF NOT EXISTS note_reactions (id INTEGER PRIMARY KEY, reaction_id TEXT NOT NULL, e TEXT, p TEXT, UNIQUE(reaction_id));' - ); - await db.execute('CREATE TABLE IF NOT EXISTS note_replies (id INTEGER PRIMARY KEY, reply_id TEXT NOT NULL, e TEXT, p TEXT, UNIQUE(reply_id));'); - await db.execute('CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, event_id TEXT, event JSON, UNIQUE(event_id));'); - await db.execute('CREATE TABLE IF NOT EXISTS cache_profiles (id INTEGER PRIMARY KEY, pubkey TEXT, metadata JSON, UNIQUE(pubkey));'); - await db.execute('CREATE TABLE IF NOT EXISTS block_pubkeys (id INTEGER PRIMARY KEY, pubkey TEXT, UNIQUE(pubkey));'); - await db.close(); - } - }, []); - - const notification = useCallback(async () => { - // NOTE: notification don't work in dev mode (only affect MacOS) - // ref: https://github.com/tauri-apps/tauri/issues/4965 - let permissionGranted = await isPermissionGranted(); - if (!permissionGranted) { - const permission = await requestPermission(); - permissionGranted = permission === 'granted'; - } - if (permissionGranted) { - sendNotification({ title: 'Lume', body: 'Nostr is awesome' }); - } - }, []); - - const getAccount = useCallback(async () => { - const db = await Database.load('sqlite:lume.db'); - const result = await db.select(`SELECT * FROM accounts WHERE current = "1" ORDER BY id ASC LIMIT 1`); - - return result; - }, []); - - const getFollows = useCallback(async (account) => { - const arr = []; - const db = await Database.load('sqlite:lume.db'); - const result: any = await db.select(`SELECT pubkey FROM follows WHERE account = "${account.pubkey}"`); - - result.forEach((item: { pubkey: string }) => { - arr.push(item.pubkey); - }); - - return arr; - }, []); - - // Explain: - // Step 1: check DB tables, if not exist then create new table. #TODO: move this function to Rust code - // Step 2: request allow notification from system - // Step 3: get first account. #TODO: get last used account instead (part of multi account feature) - // Step 4: get follows by account useEffect(() => { - initDB() - .then(() => notification()) - .then(() => { - getAccount() - .then((res: any) => { - if (res.length === 0) { - setTimeout(() => { - setLoading(false); - router.push('/onboarding'); - }, 1500); - } else { - // store current user in localstorage - currentUser.set(res[0]); - getFollows(res[0]) - .then(async (res) => { - // store follows in localstorage - follows.set(res); - // redirect to newsfeed - setTimeout(() => { - setLoading(false); - router.push('/feed/following'); - }, 1500); - }) - .catch(console.error); - } - }) - .catch(console.error); - }) - .catch(console.error); - }, [getAccount, getFollows, initDB, notification, router]); + if (!currentUser) { + setTimeout(() => { + setLoading(false); + router.push('/onboarding'); + }, 1500); + } else { + setTimeout(() => { + setLoading(false); + router.push('/feed/following'); + }, 1500); + } + }, [currentUser, router]); return (
diff --git a/src/pages/onboarding/create.tsx b/src/pages/onboarding/create/index.tsx similarity index 69% rename from src/pages/onboarding/create.tsx rename to src/pages/onboarding/create/index.tsx index 2034edce..7958e985 100644 --- a/src/pages/onboarding/create.tsx +++ b/src/pages/onboarding/create/index.tsx @@ -2,107 +2,41 @@ import BaseLayout from '@layouts/baseLayout'; import OnboardingLayout from '@layouts/onboardingLayout'; -import { currentUser } from '@stores/currentUser'; +import { DatabaseContext } from '@components/contexts/database'; +import { RelayContext } from '@components/contexts/relay'; import { EyeClosedIcon, EyeOpenIcon } from '@radix-ui/react-icons'; +import { useLocalStorage, writeStorage } from '@rehooks/local-storage'; import { motion } from 'framer-motion'; import Image from 'next/image'; import { useRouter } from 'next/router'; -import { dateToUnix, useNostr } from 'nostr-react'; import { generatePrivateKey, getEventHash, getPublicKey, nip19, signEvent } from 'nostr-tools'; -import { JSXElementConstructor, ReactElement, ReactFragment, ReactPortal, useState } from 'react'; -import Database from 'tauri-plugin-sql-api'; +import { JSXElementConstructor, ReactElement, ReactFragment, ReactPortal, useCallback, useContext, useMemo, useState } from 'react'; import { Config, names, uniqueNamesGenerator } from 'unique-names-generator'; const config: Config = { dictionaries: [names], }; -const defaultAvatars = [ - 'https://bafybeidfsbrzqbvontmucteomoz2rkrxugu462l5hyhh6uioslkfzzs4oq.ipfs.w3s.link/avatar-11.png', - 'https://bafybeid7mrvznbnd6r2ju2iu7lsxkcikufys6z6ssy5ldxrxq5qh3yqf4u.ipfs.w3s.link/avatar-12.png', - 'https://bafybeih5gpwu53ohui6p7scekjpxjk2d4lusq2jqohqhjsvhfkeu56ea4e.ipfs.w3s.link/avatar-13.png', - 'https://bafybeibpbvrpuphkerjygdbnh26av5brqggzunbbbmfl3ozlvcn2mj6zxa.ipfs.w3s.link/avatar-14.png', - 'https://bafybeia4ue4loinuflu7y5q3xu6hcvt653mzw5yorw25oarf2wqksig4ma.ipfs.w3s.link/avatar-15.png', - 'https://bafybeib3gzl6n2bebiru2cpkdljmlzbtqfsl6xcnqtabxt6jrpj7l7ltm4.ipfs.w3s.link/avatar-16.png', -]; - -const defaultBanners = [ - 'https://bafybeiacwit7hjmdefqggxqtgh6ht5dhth7ndptwn2msl5kpkodudsr7py.ipfs.w3s.link/banner-1.jpg', - 'https://bafybeiderllqadxsikh3envikobmyka3uwgojriwh6epctqartq2loswyi.ipfs.w3s.link/banner-2.jpg', - 'https://bafybeiba4tifde2kczvd26vxhbb5jpqi3wmgvccpkcrle4hse2cqrwlwiy.ipfs.w3s.link/banner-3.jpg', - 'https://bafybeifqpny2eom7ccvmaguxxxfajutmn5h3fotaasga7gce2xfx37p6oy.ipfs.w3s.link/banner-4.jpg', -]; - export default function Page() { const router = useRouter(); - const { publish } = useNostr(); + + const { db }: any = useContext(DatabaseContext); + const relayPool: any = useContext(RelayContext); + + const [relays] = useLocalStorage('relays'); const [type, setType] = useState('password'); const [loading, setLoading] = useState(false); const [privKey] = useState(() => generatePrivateKey()); const [name] = useState(() => uniqueNamesGenerator(config).toString()); - const [avatar] = useState( - () => defaultAvatars[Math.floor(Math.random() * defaultAvatars.length)] - ); - const [banner] = useState( - () => defaultBanners[Math.floor(Math.random() * defaultBanners.length)] - ); const pubKey = getPublicKey(privKey); const npub = nip19.npubEncode(pubKey); const nsec = nip19.nsecEncode(privKey); - // auto-generated profile - const data = { - display_name: name, - name: name, - username: name.toLowerCase(), - picture: avatar, - banner: banner, - }; - - const createAccount = async () => { - setLoading(true); - - // publish account to relays - const event: any = { - content: JSON.stringify(data), - created_at: dateToUnix(), - kind: 0, - pubkey: pubKey, - tags: [], - }; - event.id = getEventHash(event); - event.sig = signEvent(event, privKey); - publish(event); - - // save account to database - const db = await Database.load('sqlite:lume.db'); - await db.execute( - `INSERT INTO accounts (privkey, pubkey, npub, nsec, current, metadata) VALUES ("${privKey}", "${pubKey}", "${npub}", "${nsec}", "1", '${JSON.stringify( - data - )}')` - ); - await db.close(); - - // set currentUser in global state - currentUser.set({ - metadata: JSON.stringify(data), - npub: npub, - privkey: privKey, - pubkey: pubKey, - }); - - // redirect to pre-follow - setTimeout(() => { - setLoading(false); - router.push('/onboarding/following'); - }, 1500); - }; - - const showNsec = () => { + const showPrivateKey = () => { if (type === 'password') { setType('text'); } else { @@ -110,19 +44,69 @@ export default function Page() { } }; + // auto-generated profile + const data = useMemo( + () => ({ + display_name: name, + name: name, + username: name.toLowerCase(), + picture: 'https://bafybeidfsbrzqbvontmucteomoz2rkrxugu462l5hyhh6uioslkfzzs4oq.ipfs.w3s.link/avatar-11.png', + banner: 'https://bafybeiacwit7hjmdefqggxqtgh6ht5dhth7ndptwn2msl5kpkodudsr7py.ipfs.w3s.link/banner-1.jpg', + }), + [name] + ); + + const insertDB = useCallback(async () => { + await db.execute( + `INSERT INTO accounts (id, privkey, npub, nsec, metadata) VALUES ("${pubKey}", "${privKey}", "${npub}", "${nsec}", '${JSON.stringify(data)}')` + ); + }, [data, db, npub, nsec, privKey, pubKey]); + + const createAccount = async () => { + setLoading(true); + + // build event + const event: any = { + content: JSON.stringify(data), + created_at: Math.floor(Date.now() / 1000), + kind: 0, + pubkey: pubKey, + tags: [], + }; + event.id = getEventHash(event); + event.sig = signEvent(event, privKey); + + insertDB() + .then(() => { + // publish to relays + relayPool.publish(event, relays); + // set currentUser in global state + writeStorage('current-user', { + metadata: JSON.stringify(data), + npub: npub, + privkey: privKey, + pubkey: pubKey, + }); + // redirect to pre-follow + setTimeout(() => { + setLoading(false); + router.push('/onboarding/create/pre-follows'); + }, 1500); + }) + .catch(console.error); + }; + return (
{/* spacer */}
- + Create new key - Lume will generate key with default profile for you, you can edit it later, and please - store your key safely so you can restore your account or use other client + Lume will generate key with default profile for you, you can edit it later, and please store your key safely so you can restore your + account or use other client
@@ -146,7 +130,7 @@ export default function Page() { className="relative w-full rounded-lg border border-black/5 px-3.5 py-2 shadow-input shadow-black/5 !outline-none placeholder:text-zinc-400 dark:bg-zinc-800 dark:text-zinc-200 dark:shadow-black/10 dark:placeholder:text-zinc-600" />
- +
- +
@@ -193,18 +170,8 @@ export default function Page() {
{loading === true ? ( - - + + > - | ReactFragment - | ReactPortal + page: string | number | boolean | ReactElement> | ReactFragment | ReactPortal ) { return ( diff --git a/src/pages/onboarding/following.tsx b/src/pages/onboarding/create/pre-follows.tsx similarity index 68% rename from src/pages/onboarding/following.tsx rename to src/pages/onboarding/create/pre-follows.tsx index e9a60993..762a70f3 100644 --- a/src/pages/onboarding/following.tsx +++ b/src/pages/onboarding/create/pre-follows.tsx @@ -2,29 +2,29 @@ import BaseLayout from '@layouts/baseLayout'; import OnboardingLayout from '@layouts/onboardingLayout'; +import { DatabaseContext } from '@components/contexts/database'; + import { truncate } from '@utils/truncate'; -import { currentUser } from '@stores/currentUser'; - import data from '@assets/directory.json'; - -import { useStore } from '@nanostores/react'; import { CheckCircledIcon } from '@radix-ui/react-icons'; +import { useLocalStorage } from '@rehooks/local-storage'; import { motion } from 'framer-motion'; import Image from 'next/image'; import { useRouter } from 'next/router'; import { nip19 } from 'nostr-tools'; -import { JSXElementConstructor, ReactElement, ReactFragment, ReactPortal, useState } from 'react'; -import Database from 'tauri-plugin-sql-api'; +import { JSXElementConstructor, ReactElement, ReactFragment, ReactPortal, useContext, useState } from 'react'; + +const shuffle = (arr: { name: string; avatar: string; npub: string }[]) => [...arr].sort(() => Math.random() - 0.5); export default function Page() { + const db: any = useContext(DatabaseContext); const router = useRouter(); - const shuffle = (arr) => [...arr].sort(() => Math.random() - 0.5); const [follow, setFollow] = useState([]); const [loading, setLoading] = useState(false); const [list] = useState(shuffle(data)); - const $currentUser: any = useStore(currentUser); + const [currentUser]: any = useLocalStorage('current-user'); const followUser = (e) => { const npub = e.currentTarget.getAttribute('data-npub'); @@ -32,15 +32,12 @@ export default function Page() { }; const insertDB = async () => { - const db = await Database.load('sqlite:lume.db'); - await db.execute( - `INSERT INTO follows (pubkey, account) VALUES ("${$currentUser.pubkey}", "${$currentUser.pubkey}")` - ); + // self follow + await db.execute(`INSERT INTO follows (pubkey, account, kind) VALUES ("${currentUser.pubkey}", "${currentUser.pubkey}", "0")`); + // follow selected follow.forEach(async (npub) => { const { data } = nip19.decode(npub); - await db.execute( - `INSERT INTO follows (pubkey, account) VALUES ("${data}", "${$currentUser.pubkey}")` - ); + await db.execute(`INSERT INTO follows (pubkey, account, kind) VALUES ("${data}", "${currentUser.pubkey}", "0")`); }); }; @@ -60,14 +57,11 @@ export default function Page() {
{/* spacer */}
- + Choose 10 people you want to following - For better experiences, you should follow the people you care about to personalize your - newsfeed, otherwise you will be very bored + For better experiences, you should follow the people you care about to personalize your newsfeed, otherwise you will be very bored
@@ -81,27 +75,14 @@ export default function Page() { follow.includes(item.npub) ? 'bg-zinc-800' : '' }`}>
- {item.name} + {item.name}

{item.name}

-

- {truncate(item.npub, 16, ' .... ')} -

-
-
- {follow.includes(item.npub) ? ( - - ) : ( - <> - )} +

{truncate(item.npub, 16, ' .... ')}

+
{follow.includes(item.npub) ? : <>}
))} @@ -111,18 +92,8 @@ export default function Page() {
{loading === true ? ( - - + + > - | ReactFragment - | ReactPortal + page: string | number | boolean | ReactElement> | ReactFragment | ReactPortal ) { return ( diff --git a/src/pages/onboarding/fetch-follows.tsx b/src/pages/onboarding/fetch-follows.tsx deleted file mode 100644 index 2088f7f2..00000000 --- a/src/pages/onboarding/fetch-follows.tsx +++ /dev/null @@ -1,125 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import BaseLayout from '@layouts/baseLayout'; -import OnboardingLayout from '@layouts/onboardingLayout'; - -import { motion } from 'framer-motion'; -import { useRouter } from 'next/router'; -import { useNostrEvents } from 'nostr-react'; -import { - JSXElementConstructor, - ReactElement, - ReactFragment, - ReactPortal, - useEffect, - useState, -} from 'react'; -import Database from 'tauri-plugin-sql-api'; - -export default function Page() { - const [follows, setFollows] = useState([null]); - const [loading, setLoading] = useState(false); - - const router = useRouter(); - const { pubkey }: any = router.query; - - const { onEvent } = useNostrEvents({ - filter: { - authors: [pubkey], - kinds: [3], - }, - }); - - onEvent((rawMetadata) => { - try { - setFollows(rawMetadata.tags); - } catch (err) { - console.error(err, rawMetadata); - } - }); - - useEffect(() => { - setLoading(true); - - const insertDB = async () => { - const db = await Database.load('sqlite:lume.db'); - follows.forEach(async (item) => { - if (item) { - await db.execute( - `INSERT OR IGNORE INTO follows (pubkey, account) VALUES ("${item[1]}", "${pubkey}")` - ); - } - }); - }; - - if (follows !== null && follows.length > 0) { - insertDB() - .then(() => { - setTimeout(() => { - setLoading(false); - router.push('/'); - }, 1500); - }) - .catch(console.error); - } - }, [follows, pubkey, router]); - - return ( -
-
{/* spacer */}
- -
- - Fetching your follows... - - - Not only profile, every nostr client can sync your follows list when you move to a new - client, so please keep your key safely (again) - -
-
- -
- {loading === true ? ( - - - - - ) : ( - <> - )} -
-
-
- ); -} - -Page.getLayout = function getLayout( - page: - | string - | number - | boolean - | ReactElement> - | ReactFragment - | ReactPortal -) { - return ( - - {page} - - ); -}; diff --git a/src/pages/onboarding/fetch-profile.tsx b/src/pages/onboarding/fetch-profile.tsx deleted file mode 100644 index fc3c5d54..00000000 --- a/src/pages/onboarding/fetch-profile.tsx +++ /dev/null @@ -1,134 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import BaseLayout from '@layouts/baseLayout'; -import OnboardingLayout from '@layouts/onboardingLayout'; - -import { motion } from 'framer-motion'; -import { useRouter } from 'next/router'; -import { useNostrEvents } from 'nostr-react'; -import { getPublicKey, nip19 } from 'nostr-tools'; -import { - JSXElementConstructor, - ReactElement, - ReactFragment, - ReactPortal, - useEffect, - useState, -} from 'react'; -import Database from 'tauri-plugin-sql-api'; - -export default function Page() { - const router = useRouter(); - const { privkey }: any = router.query; - - const [account, setAccount] = useState(null); - const [loading, setLoading] = useState(false); - - const pubkey = privkey ? getPublicKey(privkey) : null; - const npub = privkey ? nip19.npubEncode(pubkey) : null; - const nsec = privkey ? nip19.nsecEncode(privkey) : null; - - const { onEvent } = useNostrEvents({ - filter: { - authors: [pubkey], - kinds: [0], - }, - }); - - onEvent((rawMetadata) => { - try { - const metadata: any = JSON.parse(rawMetadata.content); - setAccount(metadata); - } catch (err) { - console.error(err, rawMetadata); - } - }); - - useEffect(() => { - setLoading(true); - - const insertDB = async () => { - // save account to database - const db = await Database.load('sqlite:lume.db'); - await db.execute( - `INSERT INTO accounts (privkey, pubkey, npub, nsec, current, metadata) VALUES ("${privkey}", "${pubkey}", "${npub}", "${nsec}", "1", '${JSON.stringify( - account - )}')` - ); - await db.close(); - }; - - if (account !== null) { - insertDB() - .then(() => { - setTimeout(() => { - setLoading(false); - router.push({ - pathname: '/onboarding/fetch-follows', - query: { pubkey: pubkey }, - }); - }, 1500); - }) - .catch(console.error); - } - }, [account, npub, nsec, privkey, pubkey, router]); - - return ( -
-
{/* spacer */}
- -
- - Fetching your profile... - - - As long as you have private key, you alway can sync your profile on every nostr client, - so please keep your key safely - -
-
- -
- {loading === true ? ( - - - - - ) : ( - <> - )} -
-
-
- ); -} - -Page.getLayout = function getLayout( - page: - | string - | number - | boolean - | ReactElement> - | ReactFragment - | ReactPortal -) { - return ( - - {page} - - ); -}; diff --git a/src/pages/onboarding/index.tsx b/src/pages/onboarding/index.tsx index 0075558d..718357f0 100644 --- a/src/pages/onboarding/index.tsx +++ b/src/pages/onboarding/index.tsx @@ -10,9 +10,7 @@ export default function Page() {
{/* spacer */}
- + Other social network require email/password
nostr use{' '} @@ -21,8 +19,8 @@ export default function Page() {
- If you have used nostr before, you can import your own private key. Otherwise, you can - create a new key or use auto-generated account created by system. + If you have used nostr before, you can import your own private key. Otherwise, you can create a new key or use auto-generated account + created by system. @@ -32,7 +30,7 @@ export default function Page() { Create new key Login with private key @@ -44,13 +42,7 @@ export default function Page() { } Page.getLayout = function getLayout( - page: - | string - | number - | boolean - | ReactElement> - | ReactFragment - | ReactPortal + page: string | number | boolean | ReactElement> | ReactFragment | ReactPortal ) { return ( diff --git a/src/pages/onboarding/login/fetch.tsx b/src/pages/onboarding/login/fetch.tsx new file mode 100644 index 00000000..97daf505 --- /dev/null +++ b/src/pages/onboarding/login/fetch.tsx @@ -0,0 +1,122 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import BaseLayout from '@layouts/baseLayout'; +import OnboardingLayout from '@layouts/onboardingLayout'; + +import { DatabaseContext } from '@components/contexts/database'; +import { RelayContext } from '@components/contexts/relay'; + +import { useLocalStorage } from '@rehooks/local-storage'; +import { motion } from 'framer-motion'; +import Link from 'next/link'; +import { useRouter } from 'next/router'; +import { getPublicKey, nip19 } from 'nostr-tools'; +import { JSXElementConstructor, ReactElement, ReactFragment, ReactPortal, useCallback, useContext, useMemo, useState } from 'react'; + +export default function Page() { + const { db }: any = useContext(DatabaseContext); + const relayPool: any = useContext(RelayContext); + + const [loading, setLoading] = useState(false); + const [relays] = useLocalStorage('relays'); + + const router = useRouter(); + const { privkey }: any = router.query; + + const pubkey = useMemo(() => (privkey ? getPublicKey(privkey) : null), [privkey]); + + // save account to database + const insertAccount = useCallback( + async (metadata) => { + if (loading === false) { + const npub = privkey ? nip19.npubEncode(pubkey) : null; + const nsec = privkey ? nip19.nsecEncode(privkey) : null; + await db.execute( + `INSERT OR IGNORE INTO accounts (id, privkey, npub, nsec, metadata) VALUES ("${pubkey}", "${privkey}", "${npub}", "${nsec}", '${metadata}')` + ); + setLoading(true); + } + }, + [db, privkey, pubkey, loading] + ); + + // save follows to database + const insertFollows = useCallback( + async (follows) => { + follows.forEach(async (item) => { + if (item) { + await db.execute(`INSERT OR IGNORE INTO follows (pubkey, account, kind) VALUES ("${item[1]}", "${pubkey}", "0")`); + } + }); + }, + [db, pubkey] + ); + + relayPool.subscribe( + [ + { + authors: [pubkey], + kinds: [0, 3], + since: 0, + }, + ], + relays, + (event: any) => { + if (event.kind === 0) { + insertAccount(event.content); + } else { + if (event.tags.length > 0) { + insertFollows(event.tags); + } + } + }, + undefined, + (events: any, relayURL: any) => { + console.log(events, relayURL); + } + ); + + return ( +
+
{/* spacer */}
+ +
+ + Fetching your profile... + + + As long as you have private key, you alway can sync your profile and follows list on every nostr client, so please keep your key safely + +
+
+ +
+ {loading === true ? ( + + + + + ) : ( + + Finish + + )} +
+
+
+ ); +} + +Page.getLayout = function getLayout( + page: string | number | boolean | ReactElement> | ReactFragment | ReactPortal +) { + return ( + + {page} + + ); +}; diff --git a/src/pages/onboarding/import.tsx b/src/pages/onboarding/login/index.tsx similarity index 81% rename from src/pages/onboarding/import.tsx rename to src/pages/onboarding/login/index.tsx index a3e9604b..3f883224 100644 --- a/src/pages/onboarding/import.tsx +++ b/src/pages/onboarding/login/index.tsx @@ -44,7 +44,7 @@ export default function Page() { try { router.push({ - pathname: '/onboarding/fetch-profile', + pathname: '/onboarding/login/fetch', query: { privkey: privkey }, }); } catch (error) { @@ -60,14 +60,12 @@ export default function Page() {
{/* spacer */}
- + Import your private key - You can import private key format as hex string or nsec. If you have installed Nostr - Connect compality wallet in your mobile, you can connect by scan QR Code below + You can import private key format as hex string or nsec. If you have installed Nostr Connect compality wallet in your mobile, you can + connect by scan QR Code below
@@ -85,18 +83,8 @@ export default function Page() {
{isSubmitting ? ( - - + + > - | ReactFragment - | ReactPortal + page: string | number | boolean | ReactElement> | ReactFragment | ReactPortal ) { return ( diff --git a/src/pages/profile/update.tsx b/src/pages/profile/update.tsx index fa0996bd..dc7e6bda 100644 --- a/src/pages/profile/update.tsx +++ b/src/pages/profile/update.tsx @@ -2,13 +2,14 @@ import BaseLayout from '@layouts/baseLayout'; import UserLayout from '@layouts/userLayout'; -import { currentUser } from '@stores/currentUser'; +import { RelayContext } from '@components/contexts/relay'; -import { useStore } from '@nanostores/react'; +import { dateToUnix } from '@utils/getDate'; + +import { useLocalStorage } from '@rehooks/local-storage'; import { useRouter } from 'next/router'; -import { dateToUnix, useNostr } from 'nostr-react'; import { getEventHash, signEvent } from 'nostr-tools'; -import { JSXElementConstructor, ReactElement, ReactFragment, ReactPortal, useState } from 'react'; +import { JSXElementConstructor, ReactElement, ReactFragment, ReactPortal, useContext, useState } from 'react'; import { useForm } from 'react-hook-form'; import Database from 'tauri-plugin-sql-api'; @@ -24,15 +25,14 @@ type FormValues = { // TODO: update the design export default function Page() { + const relayPool: any = useContext(RelayContext); + const [relays]: any = useLocalStorage('relays'); + const router = useRouter(); - const { publish } = useNostr(); const [loading, setLoading] = useState(false); - const $currentUser: any = useStore(currentUser); - const profile = - $currentUser.metadata !== undefined - ? JSON.parse($currentUser.metadata) - : { display_name: null, username: null }; + const [currentUser]: any = useLocalStorage('current-user'); + const profile = currentUser.metadata !== undefined ? JSON.parse(currentUser.metadata) : { display_name: null, username: null }; const { register, @@ -48,28 +48,25 @@ export default function Page() { content: JSON.stringify(data), created_at: dateToUnix(), kind: 0, - pubkey: $currentUser.pubkey, + pubkey: currentUser.pubkey, tags: [], }; + event.id = getEventHash(event); - event.sig = signEvent(event, $currentUser.privkey); - publish(event); + event.sig = signEvent(event, currentUser.privkey); + + relayPool.publish(event, relays); // save account to database const db = await Database.load('sqlite:lume.db'); - await db.execute( - `UPDATE accounts SET metadata = '${JSON.stringify(data)}' WHERE pubkey = "${ - $currentUser.pubkey - }"` - ); - await db.close(); + await db.execute(`UPDATE accounts SET metadata = '${JSON.stringify(data)}' WHERE pubkey = "${currentUser.pubkey}"`); // set currentUser in global state currentUser.set({ metadata: JSON.stringify(data), - npub: $currentUser.npub, - privkey: $currentUser.privkey, - pubkey: $currentUser.pubkey, + npub: currentUser.npub, + privkey: currentUser.privkey, + pubkey: currentUser.pubkey, }); // redirect to newsfeed @@ -80,16 +77,11 @@ export default function Page() { }; return ( -
+
-

- Update profile -

+

Update profile

- Your profile will be published to all relays, as long as you have the private key, you - always can recover your profile in any client + Your profile will be published to all relays, as long as you have the private key, you always can recover your profile in any client

@@ -105,9 +97,7 @@ export default function Page() { className="relative w-full rounded-lg border border-black/5 px-3.5 py-2 shadow-input shadow-black/5 !outline-none placeholder:text-zinc-400 dark:bg-zinc-800 dark:text-zinc-200 dark:shadow-black/10 dark:placeholder:text-zinc-500" />
- - {errors.display_name &&

{errors.display_name.message}

} -
+ {errors.display_name &&

{errors.display_name.message}

}
@@ -122,9 +112,7 @@ export default function Page() { className="relative w-full rounded-lg border border-black/5 px-3.5 py-2 shadow-input shadow-black/5 !outline-none placeholder:text-zinc-400 dark:bg-zinc-800 dark:text-zinc-200 dark:shadow-black/10 dark:placeholder:text-zinc-500" />
- - {errors.name &&

{errors.name.message}

} -
+ {errors.name &&

{errors.name.message}

}
@@ -139,9 +127,7 @@ export default function Page() { className="relative w-full rounded-lg border border-black/5 px-3.5 py-2 shadow-input shadow-black/5 !outline-none placeholder:text-zinc-400 dark:bg-zinc-800 dark:text-zinc-200 dark:shadow-black/10 dark:placeholder:text-zinc-500" />
- - {errors.username &&

{errors.username.message}

} -
+ {errors.username &&

{errors.username.message}

}
@@ -156,9 +142,7 @@ export default function Page() { className="relative w-full rounded-lg border border-black/5 px-3.5 py-2 shadow-input shadow-black/5 !outline-none placeholder:text-zinc-400 dark:bg-zinc-800 dark:text-zinc-200 dark:shadow-black/10 dark:placeholder:text-zinc-500" />
- - {errors.picture &&

{errors.picture.message}

} -
+ {errors.picture &&

{errors.picture.message}

}
@@ -173,9 +157,7 @@ export default function Page() { className="relative w-full rounded-lg border border-black/5 px-3.5 py-2 shadow-input shadow-black/5 !outline-none placeholder:text-zinc-400 dark:bg-zinc-800 dark:text-zinc-200 dark:shadow-black/10 dark:placeholder:text-zinc-500" />
- - {errors.banner &&

{errors.banner.message}

} -
+ {errors.banner &&

{errors.banner.message}

}
@@ -190,27 +172,15 @@ export default function Page() { className="relative h-24 w-full resize-none rounded-lg border border-black/5 px-3.5 py-2 shadow-input shadow-black/5 !outline-none placeholder:text-zinc-400 dark:bg-zinc-800 dark:text-zinc-200 dark:shadow-black/10 dark:placeholder:text-zinc-500" />
- - {errors.about &&

{errors.about.message}

} -
+ {errors.about &&

{errors.about.message}

}
{loading === true ? ( - - + + > - | ReactFragment - | ReactPortal + page: string | number | boolean | ReactElement> | ReactFragment | ReactPortal ) { return ( diff --git a/src/stores/currentUser.tsx b/src/stores/currentUser.tsx deleted file mode 100644 index ddfc4912..00000000 --- a/src/stores/currentUser.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { persistentAtom } from '@nanostores/persistent'; - -export const currentUser = persistentAtom( - 'currentUser', - {}, - { - encode: JSON.stringify, - decode: JSON.parse, - } -); diff --git a/src/stores/follows.tsx b/src/stores/follows.tsx deleted file mode 100644 index 8016660d..00000000 --- a/src/stores/follows.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { persistentAtom } from '@nanostores/persistent'; - -export const follows = persistentAtom('follows', [], { - encode(value) { - return JSON.stringify(value); - }, - decode(value) { - return JSON.parse(value); - }, -}); diff --git a/src/stores/relays.tsx b/src/stores/relays.tsx deleted file mode 100644 index 27344318..00000000 --- a/src/stores/relays.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { persistentAtom } from '@nanostores/persistent'; - -export const relays = persistentAtom( - 'relays', - [ - 'wss://relay.uselume.xyz', - 'wss://nostr-pub.wellorder.net', - 'wss://nostr.bongbong.com', - 'wss://nostr.zebedee.cloud', - 'wss://nostr.fmt.wiz.biz', - 'wss://nostr.walletofsatoshi.com', - 'wss://relay.snort.social', - 'wss://offchain.pub', - 'wss://nos.lol', - 'wss://relay.damus.io', - ], - { - encode(value) { - return JSON.stringify(value); - }, - decode(value) { - return JSON.parse(value); - }, - } -); diff --git a/src/utils/getDate.tsx b/src/utils/getDate.tsx index 416c6845..66a165e9 100644 --- a/src/utils/getDate.tsx +++ b/src/utils/getDate.tsx @@ -11,3 +11,9 @@ export const hoursAgo = (numOfHours, date = new Date()) => { return hoursAgo; }; + +export const dateToUnix = (_date?: Date) => { + const date = _date || new Date(); + + return Math.floor(date.getTime() / 1000); +}; diff --git a/tsconfig.json b/tsconfig.json index 99c5f7e8..db64c2f8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,7 +6,6 @@ "@layouts/*": ["src/layouts/*"], "@components/*": ["src/components/*"], "@utils/*": ["src/utils/*"], - "@stores/*": ["src/stores/*"], "@assets/*": ["src/assets/*"] }, "target": "es2017",