fully support nip05

This commit is contained in:
Ren Amamiya 2023-06-30 16:36:03 +07:00
parent 1ba7f823cb
commit 332dbf608d
17 changed files with 250 additions and 245 deletions

View File

@ -15,7 +15,7 @@
"dependencies": {
"@floating-ui/react": "^0.23.1",
"@headlessui/react": "^1.7.15",
"@nostr-dev-kit/ndk": "^0.6.3",
"@nostr-dev-kit/ndk": "0.6.0",
"@radix-ui/react-tooltip": "^1.0.6",
"@tanstack/react-query": "^4.29.19",
"@tanstack/react-virtual": "3.0.0-beta.54",
@ -27,7 +27,7 @@
"get-urls": "^11.0.0",
"immer": "^10.0.2",
"light-bolt11-decoder": "^3.0.0",
"nostr-tools": "^1.12.0",
"nostr-tools": "^1.12.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.45.1",

View File

@ -8,8 +8,8 @@ dependencies:
specifier: ^1.7.15
version: 1.7.15(react-dom@18.2.0)(react@18.2.0)
'@nostr-dev-kit/ndk':
specifier: ^0.6.3
version: 0.6.3(typescript@4.9.5)
specifier: 0.6.0
version: 0.6.0(typescript@4.9.5)
'@radix-ui/react-tooltip':
specifier: ^1.0.6
version: 1.0.6(@types/react-dom@18.2.6)(@types/react@18.2.14)(react-dom@18.2.0)(react@18.2.0)
@ -44,8 +44,8 @@ dependencies:
specifier: ^3.0.0
version: 3.0.0
nostr-tools:
specifier: ^1.12.0
version: 1.12.0
specifier: ^1.12.1
version: 1.12.1
react:
specifier: ^18.2.0
version: 18.2.0
@ -424,8 +424,8 @@ packages:
resolution: {integrity: sha512-Bu+AMaXNjrpjh41znzHqaz3r2Nr8hHuHZT6V2LBKMhyMl0FgKA62PNYbqnfgmzOhoWZj70Zecisbo4H1rotP5g==}
dev: false
/@floating-ui/dom@1.4.2:
resolution: {integrity: sha512-VKmvHVatWnewmGGy+7Mdy4cTJX71Pli6v/Wjb5RQBuq5wjUYx+Ef+kRThi8qggZqDgD8CogCpqhRoVp3+yQk+g==}
/@floating-ui/dom@1.4.3:
resolution: {integrity: sha512-nB/68NyaQlcdY22L+Fgd1HERQ7UGv7XFN+tPxwrEfQL4nKtAP/jIZnZtpUlXbtV+VEGHh6W/63Gy2C5biWI3sA==}
dependencies:
'@floating-ui/core': 1.3.1
dev: false
@ -436,7 +436,7 @@ packages:
react: '>=16.8.0'
react-dom: '>=16.8.0'
dependencies:
'@floating-ui/dom': 1.4.2
'@floating-ui/dom': 1.4.3
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
@ -447,7 +447,7 @@ packages:
react: '>=16.8.0'
react-dom: '>=16.8.0'
dependencies:
'@floating-ui/dom': 1.4.2
'@floating-ui/dom': 1.4.3
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
@ -591,8 +591,8 @@ packages:
'@nodelib/fs.scandir': 2.1.5
fastq: 1.15.0
/@nostr-dev-kit/ndk@0.6.3(typescript@4.9.5):
resolution: {integrity: sha512-lXMahPRepqNmt7rIM/I7pJz/VB5PSCakZScV3+L0AmUpsN4UXHX+fFtkFjyfNpM/zW+YlxFMFbo/WM+jB55j0A==}
/@nostr-dev-kit/ndk@0.6.0(typescript@4.9.5):
resolution: {integrity: sha512-0ptE6OIZhFW+aRRIXAI8PvUIoVU6iQLpiwFtJj48XAUO2EC3WiSuqKLshjg6wj1bbo9qGs1PyFS9AUhUlWWJtg==}
dependencies:
'@noble/hashes': 1.3.1
'@noble/secp256k1': 2.0.0
@ -605,11 +605,11 @@ packages:
eslint: 8.43.0
eslint-config-prettier: 8.8.0(eslint@8.43.0)
eslint-plugin-import: 2.27.5(@typescript-eslint/parser@5.60.1)(eslint@8.43.0)
esm-loader-typescript: 1.0.4
esm-loader-typescript: 1.0.5
eventemitter3: 5.0.1
light-bolt11-decoder: 3.0.0
node-fetch: 3.3.1
nostr-tools: 1.12.0
nostr-tools: 1.12.1
tsd: 0.28.1
utf8-buffer: 1.0.0
websocket-polyfill: 0.0.3
@ -1071,8 +1071,8 @@ packages:
resolution: {integrity: sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==}
dev: false
/@swc/core-darwin-arm64@1.3.66:
resolution: {integrity: sha512-UijJsvuLy73vxeVYEy7urIHksXS+3BdvJ9s9AY+bRMSQW483NO7RLp8g4FdTyJbRaN0BH15SQnY0dcjQBkVuHw==}
/@swc/core-darwin-arm64@1.3.67:
resolution: {integrity: sha512-zCT2mCkOBVNf5uJDcQ3A9KDoO1OEaGdfjsRTZTo7sejDd9AXLfJg+xgyCBBrK2jNS/uWcT21IvSv3LqKp4K8pA==}
engines: {node: '>=10'}
cpu: [arm64]
os: [darwin]
@ -1080,8 +1080,8 @@ packages:
dev: true
optional: true
/@swc/core-darwin-x64@1.3.66:
resolution: {integrity: sha512-xGsHKvViQnwTNLF30Y/5OqWdnN6RsiyUI8awZXfz1sHcXCEaLe+v+WLQ+/E8sgw0YUkYVHzzfV/sAN2CezJK5Q==}
/@swc/core-darwin-x64@1.3.67:
resolution: {integrity: sha512-hXTVsfTatPEec5gFVyjGj3NccKZsYj/OXyHn6XA+l3Q76lZzGm2ISHdku//XNwXu8OmJ0HhS7LPsC4XXwxXQhg==}
engines: {node: '>=10'}
cpu: [x64]
os: [darwin]
@ -1089,8 +1089,8 @@ packages:
dev: true
optional: true
/@swc/core-linux-arm-gnueabihf@1.3.66:
resolution: {integrity: sha512-gNbLcSIV2pq90BkMSpzvK4xPXOl8GEF3YR4NaqF0CYSzQsVXXTTqMuX/r26xNYudBKzH0345S1MpoRk2qricnA==}
/@swc/core-linux-arm-gnueabihf@1.3.67:
resolution: {integrity: sha512-l8AKL0RkDL5FRTeWMmjoz9zvAc37amxC+0rheaNwE+gZya7ObyNjnIYz5FwN+3y+z6JFU7LS2x/5f6iwruv6pg==}
engines: {node: '>=10'}
cpu: [arm]
os: [linux]
@ -1098,8 +1098,8 @@ packages:
dev: true
optional: true
/@swc/core-linux-arm64-gnu@1.3.66:
resolution: {integrity: sha512-cJSQ0oplyWbJqy4rzVcnBYLAi6z1QT3QCcR7iAey0aAmCvfRBZJfXlyjggMjn4iosuadkauwCZR1xYNhBDRn7w==}
/@swc/core-linux-arm64-gnu@1.3.67:
resolution: {integrity: sha512-S8zOB1AXEpb7kmtgMaFNeLAj01VOky4B0RNZ+uJWigdrDiFT67FeZzNHUNmNSOU0QM79G+Lie/xD/beqEw0vDg==}
engines: {node: '>=10'}
cpu: [arm64]
os: [linux]
@ -1107,8 +1107,8 @@ packages:
dev: true
optional: true
/@swc/core-linux-arm64-musl@1.3.66:
resolution: {integrity: sha512-GDQZpcB9aGxG9PTA2shdIkoMZlGK5omJ8NR49uoBTtLBVYiGeXAwV0U1Uaw8kXEZj9i7wZDkvjzjSaNH3evRsg==}
/@swc/core-linux-arm64-musl@1.3.67:
resolution: {integrity: sha512-Fex8J8ASrt13pmOr2xWh41tEeKWwXYGk3sV8L/aGHiYtIJEUi2f+RtMx3jp7LIdOD8pQptor7i5WBlfR9jhp8A==}
engines: {node: '>=10'}
cpu: [arm64]
os: [linux]
@ -1116,8 +1116,8 @@ packages:
dev: true
optional: true
/@swc/core-linux-x64-gnu@1.3.66:
resolution: {integrity: sha512-lg8E4O/Pd9KfK0lajdinVMuGME8dSv7V9arhEpmlfGE2eXSDCWqDn5Htk5QVBstt9lt1lsRhWHJ/YYc2eQY30Q==}
/@swc/core-linux-x64-gnu@1.3.67:
resolution: {integrity: sha512-9bz9/bMphrv5vDg0os/d8ve0QgFpDzJgZgHUaHiGwcmfnlgdOSAaYJLIvWdcGTjZuQeV4L0m+iru357D9TXEzA==}
engines: {node: '>=10'}
cpu: [x64]
os: [linux]
@ -1125,8 +1125,8 @@ packages:
dev: true
optional: true
/@swc/core-linux-x64-musl@1.3.66:
resolution: {integrity: sha512-lo8ZcAO/zL2pZWH+LZIyge8u2MklaeuT6+FpVVpBFktMVdYXbaVtzpvWbgRFBZHvL3SRDF+u8jxjtkXhvGUpTw==}
/@swc/core-linux-x64-musl@1.3.67:
resolution: {integrity: sha512-ED0H6oLvQmhgo9zs8usmEA/lcZPGTu7K9og9K871b7HhHX0h/R+Xg2pb5KD7S/GyUHpfuopxjVROm+h6X1jMUA==}
engines: {node: '>=10'}
cpu: [x64]
os: [linux]
@ -1134,8 +1134,8 @@ packages:
dev: true
optional: true
/@swc/core-win32-arm64-msvc@1.3.66:
resolution: {integrity: sha512-cQoVwBuJY5WkHbfpCOlndNwYr1ZThatRjQQvKy540NUIeAEk9Fa6ozlDBtU75UdaWKtUG6YQ/bWz+KTemheVxw==}
/@swc/core-win32-arm64-msvc@1.3.67:
resolution: {integrity: sha512-J1yFDLgPFeRtA8t5E159OXX+ww1gbkFg70yr4OP7EsOkOD1uMkuTf9yK/woHfsaVJlUYjJHzw7MkUIEgQBucqQ==}
engines: {node: '>=10'}
cpu: [arm64]
os: [win32]
@ -1143,8 +1143,8 @@ packages:
dev: true
optional: true
/@swc/core-win32-ia32-msvc@1.3.66:
resolution: {integrity: sha512-y/FrAIINK4UBeUQQknGlWXEyjo+MBvjF7WkUf2KP7sNr9EHHy8+dXohAGd5Anz0eJrqOM1ZXR/GEjxRp7bGQ1Q==}
/@swc/core-win32-ia32-msvc@1.3.67:
resolution: {integrity: sha512-bK11/KtasewqHxzkjKUBXRE9MSAidbZCxrgJUd49bItG2N/DHxkwMYu8Xkh5VDHdTYWv/2idYtf/VM9Yi+53qw==}
engines: {node: '>=10'}
cpu: [ia32]
os: [win32]
@ -1152,8 +1152,8 @@ packages:
dev: true
optional: true
/@swc/core-win32-x64-msvc@1.3.66:
resolution: {integrity: sha512-yI64ACzS14qFLrfyO12qW+f/UROTotzDeEbuyJAaPD2IZexoT1cICznI3sBmIfrSt33mVuW8eF5m3AG/NUImzw==}
/@swc/core-win32-x64-msvc@1.3.67:
resolution: {integrity: sha512-GxzUU3+NA3cPcYxCxtfSQIS2ySD7Z8IZmKTVaWA9GOUQbKLyCE8H5js31u39+0op/1gNgxOgYFDoj2lUyvLCqw==}
engines: {node: '>=10'}
cpu: [x64]
os: [win32]
@ -1161,8 +1161,8 @@ packages:
dev: true
optional: true
/@swc/core@1.3.66:
resolution: {integrity: sha512-Hpf91kH5ly7fHkWnApwryTQryT+TO4kMMPH3WyciUSQOWLE3UuQz1PtETHQQk7PZ/b1QF0qQurJrgfBr5bSKUA==}
/@swc/core@1.3.67:
resolution: {integrity: sha512-9DROjzfAEt0xt0CDkOYsWpkUPyne8fl5ggWGon049678BOM7p0R0dmaalZGAsKatG5vYP1IWSKWsKhJIubDCsQ==}
engines: {node: '>=10'}
requiresBuild: true
peerDependencies:
@ -1171,16 +1171,16 @@ packages:
'@swc/helpers':
optional: true
optionalDependencies:
'@swc/core-darwin-arm64': 1.3.66
'@swc/core-darwin-x64': 1.3.66
'@swc/core-linux-arm-gnueabihf': 1.3.66
'@swc/core-linux-arm64-gnu': 1.3.66
'@swc/core-linux-arm64-musl': 1.3.66
'@swc/core-linux-x64-gnu': 1.3.66
'@swc/core-linux-x64-musl': 1.3.66
'@swc/core-win32-arm64-msvc': 1.3.66
'@swc/core-win32-ia32-msvc': 1.3.66
'@swc/core-win32-x64-msvc': 1.3.66
'@swc/core-darwin-arm64': 1.3.67
'@swc/core-darwin-x64': 1.3.67
'@swc/core-linux-arm-gnueabihf': 1.3.67
'@swc/core-linux-arm64-gnu': 1.3.67
'@swc/core-linux-arm64-musl': 1.3.67
'@swc/core-linux-x64-gnu': 1.3.67
'@swc/core-linux-x64-musl': 1.3.67
'@swc/core-win32-arm64-msvc': 1.3.67
'@swc/core-win32-ia32-msvc': 1.3.67
'@swc/core-win32-x64-msvc': 1.3.67
dev: true
/@tailwindcss/typography@0.5.9(tailwindcss@3.3.2):
@ -1551,7 +1551,7 @@ packages:
peerDependencies:
vite: ^4
dependencies:
'@swc/core': 1.3.66
'@swc/core': 1.3.67
vite: 4.3.9(@types/node@18.16.18)
transitivePeerDependencies:
- '@swc/helpers'
@ -1796,7 +1796,7 @@ packages:
hasBin: true
dependencies:
caniuse-lite: 1.0.30001509
electron-to-chromium: 1.4.444
electron-to-chromium: 1.4.446
node-releases: 2.0.12
update-browserslist-db: 1.0.11(browserslist@4.21.9)
dev: true
@ -2227,8 +2227,8 @@ packages:
/eastasianwidth@0.2.0:
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
/electron-to-chromium@1.4.444:
resolution: {integrity: sha512-/AjkL4syRvOpowXWy3N4OHmVbFdWQpfSKHh0sCVYipDeEAtMce3rLjMJi/27Ia9jNIbw6P1JuPN32pSWybXXEQ==}
/electron-to-chromium@1.4.446:
resolution: {integrity: sha512-4Gnw7ztEQ/E0eOt5JWfPn9jjeupfUlKoeW5ETKP9nLdWj+4spFoS3Stj19fqlKIaX28UQs0fNX+uKEyoLCBnkw==}
dev: true
/emoji-regex@8.0.0:
@ -2567,8 +2567,8 @@ packages:
- supports-color
dev: false
/esm-loader-typescript@1.0.4:
resolution: {integrity: sha512-ejo2f+NKIt23jaJKVpjcRQC0xTa6cTqt6VA+z8Ef/A/TCjQ5u7opPO1J4fJFeTiiHEo8JqU92EG4I1KwuXYZMg==}
/esm-loader-typescript@1.0.5:
resolution: {integrity: sha512-BeHp2TrYbRL9fUttlyzPQJPTvLDBXXUli09UNoAr87WKi8jedcULlMteNZgl7DtFZ3ZE1Mmv74SwRgwJDWyc0A==}
dependencies:
create-esm-loader: 0.2.3
npm-run-all: 4.1.5
@ -3832,8 +3832,8 @@ packages:
engines: {node: '>=12.20'}
dev: false
/nostr-tools@1.12.0:
resolution: {integrity: sha512-fsIXaNJPKaSrO9MxsCEWbhI4tG4pToQK4D4sgLRD0fRDfZ6ocCg8CLlh9lcNx0o8pVErCMLVASxbJ+w4WNK0MA==}
/nostr-tools@1.12.1:
resolution: {integrity: sha512-ZeoV7g3jBUAlb4mKa3C+6hrc84htPkbebMShfGNgV4vAiz18e/sQukUBFL6vb/+sxZy+dBQFkRwsJIaVFs8Gfw==}
dependencies:
'@noble/curves': 1.0.0
'@noble/hashes': 1.3.0
@ -4455,8 +4455,8 @@ packages:
glob: 7.2.3
dev: false
/rollup@3.25.3:
resolution: {integrity: sha512-ZT279hx8gszBj9uy5FfhoG4bZx8c+0A1sbqtr7Q3KNWIizpTdDEPZbV2xcbvHsnFp4MavCQYZyzApJ+virB8Yw==}
/rollup@3.26.0:
resolution: {integrity: sha512-YzJH0eunH2hr3knvF3i6IkLO/jTjAEwU4HoMUbQl4//Tnl3ou0e7P5SjxdDr8HQJdeUJShlbEHXrrnEHy1l7Yg==}
engines: {node: '>=14.18.0', npm: '>=8.0.0'}
hasBin: true
optionalDependencies:
@ -5159,7 +5159,7 @@ packages:
vite: '>=2.8'
dependencies:
'@rollup/plugin-virtual': 3.0.1
'@swc/core': 1.3.66
'@swc/core': 1.3.67
uuid: 9.0.0
vite: 4.3.9(@types/node@18.16.18)
transitivePeerDependencies:
@ -5212,7 +5212,7 @@ packages:
'@types/node': 18.16.18
esbuild: 0.17.19
postcss: 8.4.24
rollup: 3.25.3
rollup: 3.26.0
optionalDependencies:
fsevents: 2.3.2
dev: true

View File

@ -14,24 +14,6 @@ CREATE TABLE
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- create plebs table
CREATE TABLE
plebs (
id INTEGER NOT NULL PRIMARY KEY,
npub TEXT NOT NULL UNIQUE,
name TEXT,
displayName TEXT,
image TEXT,
banner TEXT,
bio TEXT,
nip05 TEXT,
lud06 TEXT,
lud16 TEXT,
about TEXT,
zapService TEXT,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- create notes table
CREATE TABLE
notes (

View File

@ -20,7 +20,7 @@ export function ChatMessageForm({
const tags = [["p", receiverPubkey]];
// publish message
publish({ content: message, kind: 4, tags });
await publish({ content: message, kind: 4, tags });
// reset state
setValue("");

View File

@ -1,21 +1,17 @@
import { User } from "@app/auth/components/user";
import { Dialog, Transition } from "@headlessui/react";
import { getPlebs } from "@libs/storage";
import { CancelIcon, PlusIcon } from "@shared/icons";
import { DEFAULT_AVATAR } from "@stores/constants";
import { useQuery } from "@tanstack/react-query";
import { nip19 } from "nostr-tools";
import { useAccount } from "@utils/hooks/useAccount";
import { Fragment, useState } from "react";
import { useNavigate } from "react-router-dom";
export function NewMessageModal() {
const navigate = useNavigate();
const { status, data }: any = useQuery(["plebs"], async () => {
return await getPlebs();
});
const [isOpen, setIsOpen] = useState(false);
const { status, account } = useAccount();
const follows = account ? JSON.parse(account.follows) : [];
const closeModal = () => {
setIsOpen(false);
};
@ -24,8 +20,7 @@ export function NewMessageModal() {
setIsOpen(true);
};
const openChat = (npub: string) => {
const pubkey = nip19.decode(npub).data;
const openChat = (pubkey: string) => {
closeModal();
navigate(`/app/chat/${pubkey}`);
};
@ -99,31 +94,16 @@ export function NewMessageModal() {
{status === "loading" ? (
<p>Loading...</p>
) : (
data.map((pleb) => (
follows.map((follow) => (
<div
key={pleb.npub}
key={follow}
className="group flex items-center justify-between px-4 py-3 hover:bg-zinc-800"
>
<div className="flex items-center gap-2">
<img
alt={pleb.npub}
src={pleb.image || DEFAULT_AVATAR}
className="w-9 h-9 shrink-0 object-cover rounded"
/>
<div className="inline-flex flex-col gap-1">
<h3 className="leading-none max-w-[15rem] line-clamp-1 font-medium text-zinc-100">
{pleb.displayName || pleb.name}
</h3>
<span className="leading-none max-w-[10rem] line-clamp-1 text-sm text-zinc-400">
{pleb.nip05 ||
pleb.npub.substring(0, 16).concat("...")}
</span>
</div>
</div>
<User pubkey={follow} />
<div>
<button
type="button"
onClick={() => openChat(pleb.npub)}
onClick={() => openChat(follow)}
className="inline-flex text-sm w-max px-3 py-1.5 rounded border-t border-zinc-600/50 bg-zinc-700 hover:bg-fuchsia-500 transform translate-x-20 group-hover:translate-x-0 transition-transform ease-in-out duration-150"
>
Chat

View File

@ -2,18 +2,11 @@ import { Image } from "@shared/image";
import { DEFAULT_AVATAR } from "@stores/constants";
import { useProfile } from "@utils/hooks/useProfile";
import { shortenKey } from "@utils/shortenKey";
import { nip19 } from "nostr-tools";
import { useNavigate } from "react-router-dom";
import { Link } from "react-router-dom";
export function ChatSidebar({ pubkey }: { pubkey: string }) {
const navigate = useNavigate();
const { user } = useProfile(pubkey);
const viewProfile = () => {
const pubkey = nip19.decode(user.npub).data;
navigate(`/app/user/${pubkey}`);
};
return (
<div className="px-3 py-2">
<div className="flex flex-col gap-3">
@ -36,13 +29,12 @@ export function ChatSidebar({ pubkey }: { pubkey: string }) {
</div>
<div>
<p className="leading-tight">{user?.bio || user?.about}</p>
<button
type="button"
onClick={() => viewProfile()}
<Link
to={`/app/user/${pubkey}`}
className="mt-3 inline-flex w-full h-10 items-center justify-center rounded-md bg-zinc-900 hover:bg-zinc-800 text-sm text-zinc-300 hover:text-zinc-100 font-medium"
>
View full profile
</button>
</Link>
</div>
</div>
</div>

View File

@ -6,6 +6,7 @@ import {
createNote,
getChannels,
getLastLogin,
updateLastLogin,
} from "@libs/storage";
import { NDKFilter } from "@nostr-dev-kit/ndk";
import { LoaderIcon, LumeIcon } from "@shared/icons";
@ -142,6 +143,7 @@ export function Root() {
const chats = await fetchChats();
// const channels = await fetchChannelMessages();
if (chats) {
await updateLastLogin(dateToUnix());
navigate("/app/space", { replace: true });
}
}

View File

@ -11,7 +11,6 @@ import { useVirtualizer } from "@tanstack/react-virtual";
import { useEffect, useRef } from "react";
const ITEM_PER_PAGE = 10;
const TIME = Math.floor(Date.now() / 1000);
export function FeedBlock({ params }: { params: any }) {
const queryClient = useQueryClient();
@ -21,7 +20,6 @@ export function FeedBlock({ params }: { params: any }) {
queryFn: async ({ pageParam = 0 }) => {
return await getNotesByAuthors(
params.content,
TIME,
ITEM_PER_PAGE,
pageParam,
);

View File

@ -9,7 +9,6 @@ import { useVirtualizer } from "@tanstack/react-virtual";
import { useEffect, useRef } from "react";
const ITEM_PER_PAGE = 10;
const TIME = Math.floor(Date.now() / 1000);
export function FollowingBlock({ block }: { block: number }) {
// subscribe for live update
@ -29,7 +28,7 @@ export function FollowingBlock({ block }: { block: number }) {
}: any = useInfiniteQuery({
queryKey: ["newsfeed-circle"],
queryFn: async ({ pageParam = 0 }) => {
return await getNotes(TIME, ITEM_PER_PAGE, pageParam);
return await getNotes(ITEM_PER_PAGE, pageParam);
},
getNextPageParam: (lastPage) => lastPage.nextCursor,
});
@ -64,7 +63,7 @@ export function FollowingBlock({ block }: { block: number }) {
const refreshFirstPage = () => {
// refetch
refetch({ refetchPage: (page, index) => index === 0 });
refetch({ refetchPage: (_, index: number) => index === 0 });
// scroll to top
rowVirtualizer.scrollToIndex(1);
// stop notify

View File

@ -1,4 +1,3 @@
import { createReplyNote } from "./storage";
import NDK, {
NDKConstructorParams,
NDKEvent,
@ -15,21 +14,10 @@ export async function initNDK(relays?: string[]): Promise<NDK> {
const opts: NDKConstructorParams = {};
const defaultRelays = new Set(relays || FULL_RELAYS);
/*
for (const relay of defaultRelays) {
const url = new URL(relay);
url.protocol = url.protocol = url.protocol.replace("wss", "https");
const res = await fetch(url.href, { method: "HEAD", timeout: 5 });
if (!res.ok) {
defaultRelays.delete(relay);
}
}
*/
opts.explicitRelayUrls = [...defaultRelays];
const ndk = new NDK(opts);
await ndk.connect();
await ndk.connect(500);
return ndk;
}
@ -57,15 +45,10 @@ export async function prefetchEvents(
}
export function usePublish() {
const { account } = useAccount();
const ndk = useContext(RelayContext);
const { account } = useAccount();
if (!ndk.signer) {
const signer = new NDKPrivateKeySigner(account?.privkey);
ndk.signer = signer;
}
const publish = ({
const publish = async ({
content,
kind,
tags,
@ -73,8 +56,9 @@ export function usePublish() {
content: string;
kind: NDKKind;
tags: string[][];
}): NDKEvent => {
}): Promise<NDKEvent> => {
const event = new NDKEvent(ndk);
const signer = new NDKPrivateKeySigner(account.privkey);
event.content = content;
event.kind = kind;
@ -82,7 +66,8 @@ export function usePublish() {
event.pubkey = account.pubkey;
event.tags = tags;
event.publish();
await event.sign(signer);
await event.publish();
return event;
};

View File

@ -1,6 +1,4 @@
import { NDKTag, NDKUserProfile } from "@nostr-dev-kit/ndk";
import { getParentID } from "@utils/transform";
import { nip19 } from "nostr-tools";
import Database from "tauri-plugin-sql-api";
let db: null | Database = null;
@ -73,55 +71,6 @@ export async function updateAccount(
);
}
// get all plebs
export async function getPlebs() {
const db = await connect();
return await db.select("SELECT * FROM plebs ORDER BY created_at DESC;");
}
// get pleb by pubkey
export async function getPleb(npub: string) {
const db = await connect();
const result = await db.select(`SELECT * FROM plebs WHERE npub = "${npub}";`);
if (result) {
return result[0];
} else {
return null;
}
}
// create pleb
export async function createPleb(key: string, data: NDKUserProfile) {
const db = await connect();
const now = Math.floor(Date.now() / 1000);
let npub: string;
if (key.substring(0, 4) === "npub") {
npub = key;
} else {
npub = nip19.npubEncode(key);
}
return await db.execute(
"INSERT OR REPLACE INTO plebs (npub, name, displayName, image, banner, bio, nip05, lud06, lud16, about, zapService, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);",
[
npub,
data.name,
data.displayName,
data.image,
data.banner,
data.bio,
data.nip05,
data.lud06,
data.lud16,
data.about,
data.zapService,
now,
],
);
}
// count total notes
export async function countTotalChannels() {
const db = await connect();
@ -139,14 +88,14 @@ export async function countTotalNotes() {
}
// get all notes
export async function getNotes(time: number, limit: number, offset: number) {
export async function getNotes(limit: number, offset: number) {
const db = await connect();
const totalNotes = await countTotalNotes();
const nextCursor = offset + limit;
const notes: any = { data: null, nextCursor: 0 };
const query: any = await db.select(
`SELECT * FROM notes WHERE created_at <= "${time}" AND kind IN (1, 6, 1063) GROUP BY parent_id ORDER BY created_at DESC LIMIT "${limit}" OFFSET "${offset}";`,
`SELECT * FROM notes WHERE kind IN (1, 6, 1063) GROUP BY parent_id ORDER BY created_at DESC LIMIT "${limit}" OFFSET "${offset}";`,
);
notes["data"] = query;
@ -169,7 +118,6 @@ export async function getNotesByPubkey(pubkey: string) {
// get all notes by authors
export async function getNotesByAuthors(
authors: string,
time: number,
limit: number,
offset: number,
) {
@ -181,7 +129,7 @@ export async function getNotesByAuthors(
const notes: any = { data: null, nextCursor: 0 };
const query: any = await db.select(
`SELECT * FROM notes WHERE created_at <= "${time}" AND pubkey IN (${finalArray}) AND kind IN (1, 6, 1063) GROUP BY parent_id ORDER BY created_at DESC LIMIT "${limit}" OFFSET "${offset}";`,
`SELECT * FROM notes WHERE pubkey IN (${finalArray}) AND kind IN (1, 6, 1063) GROUP BY parent_id ORDER BY created_at DESC LIMIT "${limit}" OFFSET "${offset}";`,
);
notes["data"] = query;

View File

@ -97,7 +97,7 @@ export function Post() {
const refID = getRef();
const submit = () => {
const submit = async () => {
let tags: string[][] = [];
let kind: number;
@ -130,7 +130,7 @@ export function Post() {
const serializedContent = serialize(content);
// publish message
publish({ content: serializedContent, kind, tags });
await publish({ content: serializedContent, kind, tags });
// close modal
toggle(false);

View File

@ -1,38 +1,51 @@
import { Dialog, Transition } from "@headlessui/react";
import { usePublish } from "@libs/ndk";
import { getPleb } from "@libs/storage";
import { NDKEvent } from "@nostr-dev-kit/ndk";
import { AvatarUploader } from "@shared/avatarUploader";
import { BannerUploader } from "@shared/bannerUploader";
import { CancelIcon, LoaderIcon } from "@shared/icons";
import {
CancelIcon,
CheckCircleIcon,
LoaderIcon,
UnverifiedIcon,
} from "@shared/icons";
import { Image } from "@shared/image";
import { DEFAULT_AVATAR } from "@stores/constants";
import { useQueryClient } from "@tanstack/react-query";
import { fetch } from "@tauri-apps/api/http";
import { useAccount } from "@utils/hooks/useAccount";
import { Fragment, useState } from "react";
import { Fragment, useEffect, useState } from "react";
import { useForm } from "react-hook-form";
export function EditProfileModal() {
const queryClient = useQueryClient();
const publish = usePublish();
const [isOpen, setIsOpen] = useState(false);
const [loading, setLoading] = useState(false);
const [picture, setPicture] = useState(DEFAULT_AVATAR);
const [banner, setBanner] = useState(null);
const [banner, setBanner] = useState("");
const [nip05, setNIP05] = useState({ verified: false, text: "" });
const { account } = useAccount();
const {
register,
handleSubmit,
reset,
formState: { isValid },
setError,
formState: { isValid, errors },
} = useForm({
defaultValues: async () => {
const res = await getPleb(account.npub);
if (res.picture) {
const res: any = queryClient.getQueryData(["user", account.pubkey]);
if (res.image) {
setPicture(res.image);
}
if (res.banner) {
setBanner(res.banner);
}
if (res.nip05) {
setNIP05((prev) => ({ ...prev, text: res.nip05 }));
}
return res;
},
});
@ -45,24 +58,72 @@ export function EditProfileModal() {
setIsOpen(true);
};
const onSubmit = (data: any) => {
const verifyNIP05 = async (data: string) => {
if (data) {
const url = data.split("@");
const username = url[0];
const service = url[1];
const verifyURL = `https://${service}/.well-known/nostr.json?name=${username}`;
const res: any = await fetch(verifyURL, {
method: "GET",
timeout: 30,
headers: {
"Content-Type": "application/json; charset=utf-8",
},
});
if (!res.ok) return false;
if (res.data.names[username] === account.pubkey) {
setNIP05((prev) => ({ ...prev, verified: true }));
return true;
} else {
return false;
}
}
};
const onSubmit = async (data: any) => {
// start loading
setLoading(true);
// publish
const event = publish({
content: JSON.stringify({
...data,
display_name: data.name,
bio: data.about,
image: data.picture,
}),
kind: 0,
tags: [],
});
let event: NDKEvent;
if (event) {
const content = {
...data,
username: data.name,
display_name: data.name,
bio: data.about,
image: data.picture,
};
if (data.nip05) {
const verify = await verifyNIP05(data.nip05);
if (verify) {
event = await publish({
content: JSON.stringify({ ...content, nip05: data.nip05 }),
kind: 0,
tags: [],
});
} else {
setNIP05((prev) => ({ ...prev, verified: false }));
setError("nip05", {
type: "manual",
message: "Can't verify your Lume ID / NIP-05, please check again",
});
}
} else {
event = await publish({
content: JSON.stringify(content),
kind: 0,
tags: [],
});
}
if (event.id) {
setTimeout(() => {
// invalid cache
queryClient.invalidateQueries(["user", account.pubkey]);
// reset form
reset();
// reset state
@ -71,9 +132,17 @@ export function EditProfileModal() {
setPicture(DEFAULT_AVATAR);
setBanner(null);
}, 1200);
} else {
setLoading(false);
}
};
useEffect(() => {
if (!nip05.verified && /\S+@\S+\.\S+/.test(nip05.text)) {
verifyNIP05(nip05.text);
}
}, [nip05.text]);
return (
<>
<button
@ -179,6 +248,39 @@ export function EditProfileModal() {
className="relative h-10 w-full rounded-lg px-3 py-2 !outline-none bg-zinc-800 text-zinc-100 placeholder:text-zinc-500"
/>
</div>
<div className="flex flex-col gap-1">
<label className="text-sm font-semibold uppercase tracking-wider text-zinc-400">
Lume ID / NIP-05
</label>
<div className="relative">
<input
{...register("nip05", {
required: true,
minLength: 4,
})}
spellCheck={false}
className="relative h-10 w-full rounded-lg px-3 py-2 !outline-none bg-zinc-800 text-zinc-100 placeholder:text-zinc-500"
/>
<div className="absolute top-1/2 right-2 transform -translate-y-1/2">
{nip05.verified ? (
<span className="inline-flex items-center gap-1 rounded h-6 px-2 bg-green-500 text-sm font-medium">
<CheckCircleIcon className="w-4 h-4 text-white" />
Verified
</span>
) : (
<span className="inline-flex items-center gap-1 rounded h-6 px-2 bg-red-500 text-sm font-medium">
<UnverifiedIcon className="w-4 h-4 text-white" />
Unverified
</span>
)}
</div>
{errors.nip05 && (
<p className="mt-1 text-sm text-red-400">
{errors.nip05.message.toString()}
</p>
)}
</div>
</div>
<div className="flex flex-col gap-1">
<label className="text-sm font-semibold uppercase tracking-wider text-zinc-400">
Bio

View File

@ -38,4 +38,5 @@ export * from "./empty";
export * from "./cmd";
export * from "./verticalDots";
export * from "./signal";
export * from "./unverified";
// @endindex

View File

@ -0,0 +1,23 @@
import { SVGProps } from "react";
export function UnverifiedIcon(
props: JSX.IntrinsicAttributes & SVGProps<SVGSVGElement>,
) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="none"
viewBox="0 0 24 24"
{...props}
>
<path
fill="currentColor"
fillRule="evenodd"
d="M2 12C2 6.477 6.477 2 12 2s10 4.477 10 10-4.477 10-10 10S2 17.523 2 12zm7.53-3.53a.75.75 0 00-1.06 1.06L10.94 12l-2.47 2.47a.75.75 0 101.06 1.06L12 13.06l2.47 2.47a.75.75 0 101.06-1.06L13.06 12l2.47-2.47a.75.75 0 00-1.06-1.06L12 10.94 9.53 8.47z"
clipRule="evenodd"
/>
</svg>
);
}

View File

@ -1,38 +1,28 @@
import { createPleb, getPleb } from "@libs/storage";
import { RelayContext } from "@shared/relayProvider";
import { useQuery } from "@tanstack/react-query";
import { nip19 } from "nostr-tools";
import { useContext } from "react";
export function useProfile(id: string) {
export function useProfile(pubkey: string) {
const ndk = useContext(RelayContext);
const {
status,
data: user,
error,
isFetching,
} = useQuery(["user", id], async () => {
let npub: string;
if (id.substring(0, 4) === "npub") {
npub = id;
} else {
npub = nip19.npubEncode(id);
}
const current = Math.floor(Date.now() / 1000);
const result = await getPleb(npub);
if (result && parseInt(result.created_at) + 86400 >= current) {
return result;
} else {
const user = ndk.getUser({ npub });
} = useQuery(
["user", pubkey],
async () => {
const user = ndk.getUser({ hexpubkey: pubkey });
await user.fetchProfile();
await createPleb(id, user.profile);
return user.profile;
}
});
},
{
staleTime: Infinity,
refetchOnWindowFocus: false,
refetchOnReconnect: false,
},
);
return { status, user, error, isFetching };
}

View File

@ -24,6 +24,9 @@ export function useSocial() {
},
{
enabled: account ? true : false,
refetchOnReconnect: false,
refetchOnMount: false,
refetchOnWindowFocus: false,
},
);