Compare commits

...

29 Commits

Author SHA1 Message Date
Ren Amamiya
d989d6ffad Merge pull request #123 from luminous-devs/feat/v2.1.6
Feat/v2.1.6
2023-11-27 12:01:25 +07:00
5229458746 bump version 2023-11-27 09:56:43 +07:00
2bfa1db816 update 2023-11-27 09:48:51 +07:00
8439428ce1 fix crash on settings screen 2023-11-26 15:01:13 +07:00
34dceef4a3 fix mention popup 2023-11-26 07:48:28 +07:00
Ren Amamiya
619bfb8dff Merge pull request #122 from luminous-devs/v2.1.4
v2.1.4
2023-11-26 07:22:13 +07:00
7759851541 clean up 2023-11-26 07:21:24 +07:00
9112c1c24a improve connection 2023-11-25 17:56:45 +07:00
24b21a9451 update 2023-11-25 16:03:05 +07:00
31a53b9c48 add @ suggestion popup 2023-11-25 15:41:18 +07:00
dc229f40cb fix new article layout 2023-11-25 11:07:31 +07:00
54ad1e6e1d fix new post layout 2023-11-25 09:22:15 +07:00
Ren Amamiya
065ccbbea4 Merge pull request #121 from luminous-devs/fix/nsecbunker
Fix stuck issue for connect with nsecbunker
2023-11-24 13:53:26 +07:00
74738c36cd disable blockUntilReady 2023-11-23 15:12:46 +07:00
Ren Amamiya
2fdf437789 Merge pull request #120 from luminous-devs/fix/logout
Fix logout function and other issues
2023-11-23 08:54:24 +07:00
731c72535c bump version 2023-11-23 08:52:47 +07:00
628102087e fix total account count function 2023-11-23 08:52:04 +07:00
536ea30ed2 fix logout function 2023-11-23 08:49:05 +07:00
8ee38cdb42 temp disable single-instance plugin 2023-11-22 17:27:09 +07:00
Ren Amamiya
a896300f23 Merge pull request #118 from luminous-devs/v2.1.2
v2.1.2
2023-11-22 16:18:02 +07:00
d3cf1200ba bump version 2023-11-22 16:13:06 +07:00
b5ac3df090 fix package 2023-11-22 16:10:20 +07:00
3b40dd6903 update dependencies 2023-11-22 15:27:19 +07:00
Ren Amamiya
efba6b20ea Merge pull request #117 from luminous-devs/feat/optional-updater
Make auto update is optional
2023-11-22 10:34:49 +07:00
Ren Amamiya
05fb56e5fc Merge pull request #116 from vivganes/patch-1
Little grammar corrections
2023-11-22 10:32:47 +07:00
Vivek Ganesan
59d9646e9f Little grammar corrections 2023-11-22 08:43:31 +05:30
b73d84fccb update storage provider 2023-11-22 09:05:10 +07:00
1929ceb72d add toast message 2023-11-22 08:31:58 +07:00
a1d22c1daf make auto update is optional 2023-11-22 08:30:43 +07:00
39 changed files with 1431 additions and 1150 deletions

View File

@@ -2,7 +2,7 @@
"name": "lume",
"description": "the communication app",
"private": true,
"version": "2.1.1",
"version": "2.1.6",
"scripts": {
"dev": "vite",
"build": "vite build",
@@ -19,8 +19,8 @@
},
"dependencies": {
"@evilmartians/harmony": "^1.1.0",
"@getalby/sdk": "^2.6.0",
"@nostr-dev-kit/ndk": "^2.0.5",
"@getalby/sdk": "^2.7.0",
"@nostr-dev-kit/ndk": "^2.1.1",
"@nostr-fetch/adapter-ndk": "^0.13.1",
"@radix-ui/react-alert-dialog": "^1.0.5",
"@radix-ui/react-avatar": "^1.0.4",
@@ -32,7 +32,7 @@
"@radix-ui/react-switch": "^1.0.3",
"@radix-ui/react-toolbar": "^1.0.4",
"@radix-ui/react-tooltip": "^1.0.7",
"@tanstack/react-query": "^5.8.4",
"@tanstack/react-query": "^5.8.7",
"@tauri-apps/api": "2.0.0-alpha.11",
"@tauri-apps/cli": "2.0.0-alpha.17",
"@tauri-apps/plugin-autostart": "2.0.0-alpha.3",
@@ -63,7 +63,7 @@
"html-to-text": "^9.0.5",
"idb-keyval": "^6.2.1",
"light-bolt11-decoder": "^3.0.0",
"lru-cache": "^10.0.3",
"lru-cache": "^10.1.0",
"markdown-to-jsx": "^7.3.2",
"media-chrome": "^1.5.3",
"minidenticons": "^4.2.0",
@@ -77,27 +77,27 @@
"react-dom": "^18.2.0",
"react-hook-form": "^7.48.2",
"react-hotkeys-hook": "^4.4.1",
"react-router-dom": "^6.19.0",
"react-router-dom": "^6.20.0",
"react-string-replace": "^1.1.1",
"reactflow": "^11.10.1",
"sonner": "^1.2.0",
"sonner": "^1.2.3",
"tailwind-scrollbar": "^3.0.5",
"tauri-controls": "github:reyamir/tauri-controls",
"tippy.js": "^6.3.7",
"tiptap-markdown": "^0.8.4",
"virtua": "^0.16.4",
"virtua": "^0.16.7",
"zustand": "^4.4.6"
},
"devDependencies": {
"@tailwindcss/typography": "^0.5.10",
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
"@types/html-to-text": "^9.0.4",
"@types/node": "^20.9.2",
"@types/react": "^18.2.37",
"@types/react-dom": "^18.2.15",
"@types/youtube-player": "^5.5.10",
"@typescript-eslint/eslint-plugin": "^6.11.0",
"@typescript-eslint/parser": "^6.11.0",
"@types/node": "^20.10.0",
"@types/react": "^18.2.38",
"@types/react-dom": "^18.2.17",
"@types/youtube-player": "^5.5.11",
"@typescript-eslint/eslint-plugin": "^6.12.0",
"@typescript-eslint/parser": "^6.12.0",
"@vitejs/plugin-react-swc": "^3.5.0",
"autoprefixer": "^10.4.16",
"clsx": "^2.0.0",
@@ -117,7 +117,7 @@
"prop-types": "^15.8.1",
"tailwind-merge": "^1.14.0",
"tailwindcss": "^3.3.5",
"typescript": "^5.2.2",
"typescript": "^5.3.2",
"vite": "^4.5.0",
"vite-tsconfig-paths": "^4.2.1"
}

1131
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

299
src-tauri/Cargo.lock generated
View File

@@ -154,9 +154,9 @@ checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
[[package]]
name = "arboard"
version = "3.2.1"
version = "3.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac57f2b058a76363e357c056e4f74f1945bf734d37b8b3ef49066c4787dde0fc"
checksum = "aafb29b107435aa276664c1db8954ac27a6e105cdad3c88287a199eb0e313c08"
dependencies = [
"clipboard-win",
"core-graphics 0.22.3",
@@ -183,12 +183,12 @@ dependencies = [
[[package]]
name = "async-channel"
version = "2.1.0"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d37875bd9915b7d67c2f117ea2c30a0989874d0b2cb694fe25403c85763c0c9e"
checksum = "1ca33f4bc4ed1babef42cad36cc1f51fa88be00420404e5b1e80ab1b18f7678c"
dependencies = [
"concurrent-queue",
"event-listener 3.1.0",
"event-listener 4.0.0",
"event-listener-strategy",
"futures-core",
"pin-project-lite",
@@ -196,11 +196,11 @@ dependencies = [
[[package]]
name = "async-executor"
version = "1.7.2"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc5ea910c42e5ab19012bab31f53cb4d63d54c3a27730f9a833a88efcf4bb52d"
checksum = "17ae5ebefcc48e7452b4987947920dac9450be1110cadf34d1b8c116bdbaf97c"
dependencies = [
"async-lock 3.1.1",
"async-lock 3.1.2",
"async-task",
"concurrent-queue",
"fastrand 2.0.1",
@@ -242,22 +242,21 @@ dependencies = [
[[package]]
name = "async-io"
version = "2.2.0"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41ed9d5715c2d329bf1b4da8d60455b99b187f27ba726df2883799af9af60997"
checksum = "d6d3b15875ba253d1110c740755e246537483f152fa334f91abd7fe84c88b3ff"
dependencies = [
"async-lock 3.1.1",
"async-lock 3.1.2",
"cfg-if",
"concurrent-queue",
"futures-io",
"futures-lite 2.0.1",
"parking",
"polling 3.3.0",
"polling 3.3.1",
"rustix 0.38.25",
"slab",
"tracing",
"waker-fn",
"windows-sys 0.48.0",
"windows-sys 0.52.0",
]
[[package]]
@@ -271,11 +270,11 @@ dependencies = [
[[package]]
name = "async-lock"
version = "3.1.1"
version = "3.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "655b9c7fe787d3b25cc0f804a1a8401790f0c5bc395beb5a64dc77d8de079105"
checksum = "dea8b3453dd7cc96711834b75400d671b73e3656975fa68d9f277163b7f7e316"
dependencies = [
"event-listener 3.1.0",
"event-listener 4.0.0",
"event-listener-strategy",
"pin-project-lite",
]
@@ -314,7 +313,7 @@ version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e47d90f65a225c4527103a8d747001fc56e375203592b25ad103e1ca13124c5"
dependencies = [
"async-io 2.2.0",
"async-io 2.2.1",
"async-lock 2.8.0",
"atomic-waker",
"cfg-if",
@@ -381,6 +380,16 @@ version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
[[package]]
name = "atomic-write-file"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c232177ba50b16fe7a4588495bd474a62a9e45a8e4ca6fd7d0b7ac29d164631e"
dependencies = [
"nix 0.26.4",
"rand 0.8.5",
]
[[package]]
name = "auto-launch"
version = "0.5.0"
@@ -501,7 +510,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a37913e8dc4ddcc604f0c6d3bf2887c995153af3611de9e23c352b44c1b9118"
dependencies = [
"async-channel",
"async-lock 3.1.1",
"async-lock 3.1.2",
"async-task",
"fastrand 2.0.1",
"futures-io",
@@ -637,9 +646,9 @@ dependencies = [
[[package]]
name = "cargo_toml"
version = "0.16.3"
version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3f9629bc6c4388ea699781dc988c2b99766d7679b151c81990b4fa1208fafd3"
checksum = "4d1ece59890e746567b467253aea0adbe8a21784d0b025d8a306f66c391c2957"
dependencies = [
"serde",
"toml 0.8.8",
@@ -1108,9 +1117,9 @@ dependencies = [
[[package]]
name = "data-url"
version = "0.3.0"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41b319d1b62ffbd002e057f36bebd1f42b9f97927c9577461d855f3513c4289f"
checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a"
[[package]]
name = "der"
@@ -1368,12 +1377,23 @@ dependencies = [
]
[[package]]
name = "event-listener-strategy"
version = "0.3.0"
name = "event-listener"
version = "4.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d96b852f1345da36d551b9473fa1e2b1eb5c5195585c6c018118bc92a8d91160"
checksum = "770d968249b5d99410d61f5bf89057f3199a077a04d087092f58e7d10692baae"
dependencies = [
"event-listener 3.1.0",
"concurrent-queue",
"parking",
"pin-project-lite",
]
[[package]]
name = "event-listener-strategy"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3"
dependencies = [
"event-listener 4.0.0",
"pin-project-lite",
]
@@ -1511,9 +1531,9 @@ checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b"
[[package]]
name = "form_urlencoded"
version = "1.2.0"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652"
checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
dependencies = [
"percent-encoding",
]
@@ -1776,9 +1796,9 @@ dependencies = [
[[package]]
name = "gethostname"
version = "0.2.3"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1ebd34e35c46e00bb73e81363248d627782724609fe1b6396f553f68fe3862e"
checksum = "bb65d4ba3173c56a500b555b532f72c42e8d1fe64962b518897f8959fae2c177"
dependencies = [
"libc",
"winapi",
@@ -1818,9 +1838,9 @@ dependencies = [
[[package]]
name = "gimli"
version = "0.28.0"
version = "0.28.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0"
checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
[[package]]
name = "gio"
@@ -2259,9 +2279,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "idna"
version = "0.4.0"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c"
checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
dependencies = [
"unicode-bidi",
"unicode-normalization",
@@ -2575,9 +2595,9 @@ dependencies = [
[[package]]
name = "libsqlite3-sys"
version = "0.26.0"
version = "0.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "afc22eff61b133b115c6e8c74e818c628d6d5e7a502afea6f64dee076dd94326"
checksum = "cf4e226dcd58b4be396f7bd3c20da8fdee2911400705297ba7d2d7cc2c30f716"
dependencies = [
"cc",
"pkg-config",
@@ -2660,7 +2680,7 @@ dependencies = [
[[package]]
name = "lume"
version = "2.1.1"
version = "2.1.6"
dependencies = [
"keyring",
"serde",
@@ -2678,7 +2698,6 @@ dependencies = [
"tauri-plugin-os",
"tauri-plugin-process",
"tauri-plugin-shell",
"tauri-plugin-single-instance",
"tauri-plugin-sql",
"tauri-plugin-store",
"tauri-plugin-theme",
@@ -2841,9 +2860,9 @@ dependencies = [
[[package]]
name = "muda"
version = "0.10.0"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9767ce3b12d2928f17ff4f91b29e7e872a8594033d82bf00e56017cc23bb8410"
checksum = "b564d551449738387fb4541aef5fbfceaa81b2b732f2534c1c7c89dc7d673eaa"
dependencies = [
"cocoa 0.25.0",
"crossbeam-channel",
@@ -2852,8 +2871,9 @@ dependencies = [
"objc",
"once_cell",
"png",
"serde",
"thiserror",
"windows-sys 0.48.0",
"windows-sys 0.52.0",
]
[[package]]
@@ -2931,18 +2951,6 @@ dependencies = [
"memoffset 0.6.5",
]
[[package]]
name = "nix"
version = "0.24.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069"
dependencies = [
"bitflags 1.3.2",
"cfg-if",
"libc",
"memoffset 0.6.5",
]
[[package]]
name = "nix"
version = "0.26.4"
@@ -2953,6 +2961,7 @@ dependencies = [
"cfg-if",
"libc",
"memoffset 0.7.1",
"pin-utils",
]
[[package]]
@@ -3192,9 +3201,9 @@ dependencies = [
[[package]]
name = "openssl"
version = "0.10.59"
version = "0.10.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a257ad03cd8fb16ad4172fedf8094451e1af1c4b70097636ef2eac9a5f0cc33"
checksum = "79a4c6c3a2b158f7f8f2a2fc5a969fa3a068df6fc9dbb4a43845436e3af7c800"
dependencies = [
"bitflags 2.4.1",
"cfg-if",
@@ -3233,9 +3242,9 @@ dependencies = [
[[package]]
name = "openssl-sys"
version = "0.9.95"
version = "0.9.96"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40a4130519a360279579c2053038317e40eff64d13fd3f004f9e1b72b8a6aaf9"
checksum = "3812c071ba60da8b5677cc12bcb1d42989a65553772897a7e0355545a819838f"
dependencies = [
"cc",
"libc",
@@ -3381,9 +3390,9 @@ dependencies = [
[[package]]
name = "percent-encoding"
version = "2.3.0"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]]
name = "phf"
@@ -3614,16 +3623,16 @@ dependencies = [
[[package]]
name = "polling"
version = "3.3.0"
version = "3.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e53b6af1f60f36f8c2ac2aad5459d75a5a9b4be1e8cdd40264f315d78193e531"
checksum = "cf63fa624ab313c11656b4cda960bfc46c410187ad493c41f6ba2d8c1e991c9e"
dependencies = [
"cfg-if",
"concurrent-queue",
"pin-project-lite",
"rustix 0.38.25",
"tracing",
"windows-sys 0.48.0",
"windows-sys 0.52.0",
]
[[package]]
@@ -3984,9 +3993,9 @@ dependencies = [
[[package]]
name = "rsa"
version = "0.9.3"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86ef35bf3e7fe15a53c4ab08a998e42271eab13eb0db224126bc7bc4c4bad96d"
checksum = "6a3211b01eea83d80687da9eef70e39d65144a3894866a5153a2723e425a157f"
dependencies = [
"const-oid",
"digest",
@@ -4189,18 +4198,18 @@ dependencies = [
[[package]]
name = "serde"
version = "1.0.192"
version = "1.0.193"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001"
checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.192"
version = "1.0.193"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1"
checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
dependencies = [
"proc-macro2",
"quote",
@@ -4482,9 +4491,9 @@ dependencies = [
[[package]]
name = "sqlx"
version = "0.7.2"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e50c216e3624ec8e7ecd14c6a6a6370aad6ee5d8cfc3ab30b5162eeeef2ed33"
checksum = "dba03c279da73694ef99763320dea58b51095dfe87d001b1d4b5fe78ba8763cf"
dependencies = [
"sqlx-core",
"sqlx-macros",
@@ -4495,9 +4504,9 @@ dependencies = [
[[package]]
name = "sqlx-cli"
version = "0.7.2"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e80bc07dfc7f258db72ae5d72d793aa87943690fc1b2afc87b4cabf87035bac0"
checksum = "1b941ddc37071bb01d001ec479885a493021f1ca39142d754a05a780a77fff99"
dependencies = [
"anyhow",
"async-trait",
@@ -4520,9 +4529,9 @@ dependencies = [
[[package]]
name = "sqlx-core"
version = "0.7.2"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d6753e460c998bbd4cd8c6f0ed9a64346fcca0723d6e75e52fdc351c5d2169d"
checksum = "d84b0a3c3739e220d94b3239fd69fb1f74bc36e16643423bd99de3b43c21bfbd"
dependencies = [
"ahash",
"atoi",
@@ -4561,9 +4570,9 @@ dependencies = [
[[package]]
name = "sqlx-macros"
version = "0.7.2"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a793bb3ba331ec8359c1853bd39eed32cdd7baaf22c35ccf5c92a7e8d1189ec"
checksum = "89961c00dc4d7dffb7aee214964b065072bff69e36ddb9e2c107541f75e4f2a5"
dependencies = [
"proc-macro2",
"quote",
@@ -4574,10 +4583,11 @@ dependencies = [
[[package]]
name = "sqlx-macros-core"
version = "0.7.2"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a4ee1e104e00dedb6aa5ffdd1343107b0a4702e862a84320ee7cc74782d96fc"
checksum = "d0bd4519486723648186a08785143599760f7cc81c52334a55d6a83ea1e20841"
dependencies = [
"atomic-write-file",
"dotenvy",
"either",
"heck",
@@ -4600,9 +4610,9 @@ dependencies = [
[[package]]
name = "sqlx-mysql"
version = "0.7.2"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "864b869fdf56263f4c95c45483191ea0af340f9f3e3e7b4d57a61c7c87a970db"
checksum = "e37195395df71fd068f6e2082247891bc11e3289624bbc776a0cdfa1ca7f1ea4"
dependencies = [
"atoi",
"base64",
@@ -4643,9 +4653,9 @@ dependencies = [
[[package]]
name = "sqlx-postgres"
version = "0.7.2"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb7ae0e6a97fb3ba33b23ac2671a5ce6e3cabe003f451abd5a56e7951d975624"
checksum = "d6ac0ac3b7ccd10cc96c7ab29791a7dd236bd94021f31eec7ba3d46a74aa1c24"
dependencies = [
"atoi",
"base64",
@@ -4683,9 +4693,9 @@ dependencies = [
[[package]]
name = "sqlx-sqlite"
version = "0.7.2"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d59dc83cf45d89c555a577694534fcd1b55c545a816c816ce51f20bbe56a4f3f"
checksum = "210976b7d948c7ba9fced8ca835b11cbb2d677c59c79de41ac0d397e14547490"
dependencies = [
"atoi",
"flume",
@@ -4702,6 +4712,7 @@ dependencies = [
"time",
"tracing",
"url",
"urlencoding",
]
[[package]]
@@ -4935,9 +4946,9 @@ checksum = "14c39fd04924ca3a864207c66fc2cd7d22d7c016007f9ce846cbb9326331930a"
[[package]]
name = "tauri"
version = "2.0.0-alpha.17"
version = "2.0.0-alpha.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "892f45e7f10c9481488633f506496eeb3c69034c17fc71d5562d1e87b5442f78"
checksum = "6dc6ec8c246fa16092a3e650de2f10f3af3362915b7caab1aeed364a60829fcc"
dependencies = [
"anyhow",
"bytes",
@@ -4983,9 +4994,9 @@ dependencies = [
[[package]]
name = "tauri-build"
version = "2.0.0-alpha.11"
version = "2.0.0-alpha.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d5f06f1cbb5507f4de803f8e1fab01c71cb9515297a90d3adba4fa6c06a194c"
checksum = "f0997a36aa2a1431500ef6ef92e7076521ae3258a8f73914b49ba876361ba2fe"
dependencies = [
"anyhow",
"cargo_toml",
@@ -5004,9 +5015,9 @@ dependencies = [
[[package]]
name = "tauri-codegen"
version = "2.0.0-alpha.10"
version = "2.0.0-alpha.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28094b389b0981aba46eeba7dcda6216c23fdf7288b2e0414e69fbc55aa55676"
checksum = "71c36db748f557c1f89f075e37ab22ad77e8798fb9432367cf4013ea364811de"
dependencies = [
"base64",
"brotli",
@@ -5030,14 +5041,14 @@ dependencies = [
[[package]]
name = "tauri-macros"
version = "2.0.0-alpha.10"
version = "2.0.0-alpha.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1df582259285a81324d4d38846433bfd3c0440c7a268f730acb7c29350f25cf"
checksum = "83c895e684477cfb07aeeb0fdb11076bee98219806b68d1f3ddf99d893038a93"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 1.0.109",
"syn 2.0.39",
"tauri-codegen",
"tauri-utils",
]
@@ -5045,7 +5056,7 @@ dependencies = [
[[package]]
name = "tauri-plugin-autostart"
version = "2.0.0-alpha.4"
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#5a8bbe296704f679063ef8b820d582d7f728400b"
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#f49b391f10515db4465da32d839c5cc43ebdb3d3"
dependencies = [
"auto-launch",
"log",
@@ -5058,7 +5069,7 @@ dependencies = [
[[package]]
name = "tauri-plugin-cli"
version = "2.0.0-alpha.4"
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#5a8bbe296704f679063ef8b820d582d7f728400b"
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#f49b391f10515db4465da32d839c5cc43ebdb3d3"
dependencies = [
"clap",
"log",
@@ -5071,7 +5082,7 @@ dependencies = [
[[package]]
name = "tauri-plugin-clipboard-manager"
version = "2.0.0-alpha.4"
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#5a8bbe296704f679063ef8b820d582d7f728400b"
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#f49b391f10515db4465da32d839c5cc43ebdb3d3"
dependencies = [
"arboard",
"log",
@@ -5085,7 +5096,7 @@ dependencies = [
[[package]]
name = "tauri-plugin-dialog"
version = "2.0.0-alpha.4"
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#5a8bbe296704f679063ef8b820d582d7f728400b"
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#f49b391f10515db4465da32d839c5cc43ebdb3d3"
dependencies = [
"glib 0.16.9",
"log",
@@ -5102,7 +5113,7 @@ dependencies = [
[[package]]
name = "tauri-plugin-fs"
version = "2.0.0-alpha.4"
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#5a8bbe296704f679063ef8b820d582d7f728400b"
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#f49b391f10515db4465da32d839c5cc43ebdb3d3"
dependencies = [
"anyhow",
"glob",
@@ -5115,7 +5126,7 @@ dependencies = [
[[package]]
name = "tauri-plugin-http"
version = "2.0.0-alpha.5"
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#5a8bbe296704f679063ef8b820d582d7f728400b"
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#f49b391f10515db4465da32d839c5cc43ebdb3d3"
dependencies = [
"data-url",
"glob",
@@ -5132,7 +5143,7 @@ dependencies = [
[[package]]
name = "tauri-plugin-notification"
version = "2.0.0-alpha.5"
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#5a8bbe296704f679063ef8b820d582d7f728400b"
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#f49b391f10515db4465da32d839c5cc43ebdb3d3"
dependencies = [
"log",
"notify-rust",
@@ -5150,7 +5161,7 @@ dependencies = [
[[package]]
name = "tauri-plugin-os"
version = "2.0.0-alpha.4"
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#5a8bbe296704f679063ef8b820d582d7f728400b"
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#f49b391f10515db4465da32d839c5cc43ebdb3d3"
dependencies = [
"gethostname 0.4.3",
"log",
@@ -5166,7 +5177,7 @@ dependencies = [
[[package]]
name = "tauri-plugin-process"
version = "2.0.0-alpha.4"
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#5a8bbe296704f679063ef8b820d582d7f728400b"
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#f49b391f10515db4465da32d839c5cc43ebdb3d3"
dependencies = [
"tauri",
]
@@ -5174,7 +5185,7 @@ dependencies = [
[[package]]
name = "tauri-plugin-shell"
version = "2.0.0-alpha.4"
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#5a8bbe296704f679063ef8b820d582d7f728400b"
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#f49b391f10515db4465da32d839c5cc43ebdb3d3"
dependencies = [
"encoding_rs",
"log",
@@ -5188,24 +5199,10 @@ dependencies = [
"thiserror",
]
[[package]]
name = "tauri-plugin-single-instance"
version = "2.0.0-alpha.4"
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#5a8bbe296704f679063ef8b820d582d7f728400b"
dependencies = [
"log",
"serde",
"serde_json",
"tauri",
"thiserror",
"windows-sys 0.48.0",
"zbus",
]
[[package]]
name = "tauri-plugin-sql"
version = "2.0.0-alpha.4"
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#5a8bbe296704f679063ef8b820d582d7f728400b"
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#f49b391f10515db4465da32d839c5cc43ebdb3d3"
dependencies = [
"futures-core",
"log",
@@ -5221,7 +5218,7 @@ dependencies = [
[[package]]
name = "tauri-plugin-store"
version = "2.0.0-alpha.4"
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#5a8bbe296704f679063ef8b820d582d7f728400b"
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#f49b391f10515db4465da32d839c5cc43ebdb3d3"
dependencies = [
"log",
"serde",
@@ -5232,11 +5229,12 @@ dependencies = [
[[package]]
name = "tauri-plugin-theme"
version = "0.2.0"
source = "git+https://github.com/reyamir/tauri-plugin-theme?branch=tauri-v2#1b8bab206915329f40e0382eb752bcbdbc2e43f0"
version = "0.3.0"
source = "git+https://github.com/wyhaya/tauri-plugin-theme#cccc9b3fbc308a475ef8720f3535ae657ce1924b"
dependencies = [
"cocoa 0.25.0",
"futures-lite 1.13.0",
"dirs-next",
"futures-lite 2.0.1",
"gtk",
"once_cell",
"serde",
@@ -5248,7 +5246,7 @@ dependencies = [
[[package]]
name = "tauri-plugin-updater"
version = "2.0.0-alpha.4"
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#5a8bbe296704f679063ef8b820d582d7f728400b"
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#f49b391f10515db4465da32d839c5cc43ebdb3d3"
dependencies = [
"base64",
"dirs-next",
@@ -5274,7 +5272,7 @@ dependencies = [
[[package]]
name = "tauri-plugin-upload"
version = "2.0.0-alpha.4"
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#5a8bbe296704f679063ef8b820d582d7f728400b"
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#f49b391f10515db4465da32d839c5cc43ebdb3d3"
dependencies = [
"futures-util",
"log",
@@ -5291,7 +5289,7 @@ dependencies = [
[[package]]
name = "tauri-plugin-window-state"
version = "2.0.0-alpha.4"
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#5a8bbe296704f679063ef8b820d582d7f728400b"
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v2#f49b391f10515db4465da32d839c5cc43ebdb3d3"
dependencies = [
"bincode",
"bitflags 2.4.1",
@@ -5304,9 +5302,9 @@ dependencies = [
[[package]]
name = "tauri-runtime"
version = "1.0.0-alpha.4"
version = "1.0.0-alpha.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f46889d91efc090ac4031c7c423c30c5280d6984cb071b729b748d643aa3df40"
checksum = "16cc441e5bcb3332a0af069b7580083104aacf09b66e27938b47517790d7b384"
dependencies = [
"gtk",
"http",
@@ -5322,9 +5320,9 @@ dependencies = [
[[package]]
name = "tauri-runtime-wry"
version = "1.0.0-alpha.5"
version = "1.0.0-alpha.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b083797e147019c318db883a51868014751b578ba0d1ef3aadd34199a3bbcf61"
checksum = "7c9229a7caf9c63eeaf4389789e1c983757135f4ece3355d0ae647c492682f61"
dependencies = [
"cocoa 0.24.1",
"gtk",
@@ -5342,9 +5340,9 @@ dependencies = [
[[package]]
name = "tauri-utils"
version = "2.0.0-alpha.10"
version = "2.0.0-alpha.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68e138783ca404416a4afe34f22f804d533c28fb73f87870f365c6ecdcee6c23"
checksum = "ce0dbf67341adad8d48255d605b45b25bf1c7445116355e61ed6219d204e94e0"
dependencies = [
"brotli",
"ctor",
@@ -5367,7 +5365,7 @@ dependencies = [
"thiserror",
"url",
"walkdir",
"windows 0.51.1",
"windows-version",
]
[[package]]
@@ -5718,9 +5716,9 @@ dependencies = [
[[package]]
name = "tray-icon"
version = "0.10.0"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a7dce8a4b6d7a566ca78fa43f37efc7a3ec427480828d35fe3474c4f0c75396"
checksum = "5375d350db4ccd3c783a4c683be535e70df5c62b07a824e7bcd6d43ef6d74181"
dependencies = [
"cocoa 0.25.0",
"core-graphics 0.23.1",
@@ -5731,8 +5729,9 @@ dependencies = [
"objc",
"once_cell",
"png",
"serde",
"thiserror",
"windows-sys 0.48.0",
"windows-sys 0.52.0",
]
[[package]]
@@ -5807,9 +5806,9 @@ checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
[[package]]
name = "url"
version = "2.4.1"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5"
checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633"
dependencies = [
"form_urlencoded",
"idna",
@@ -5817,6 +5816,12 @@ dependencies = [
"serde",
]
[[package]]
name = "urlencoding"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
[[package]]
name = "utf-8"
version = "0.7.6"
@@ -5831,9 +5836,9 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]]
name = "uuid"
version = "1.6.0"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c58fe91d841bc04822c9801002db4ea904b9e4b8e6bbad25127b46eff8dc516b"
checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560"
dependencies = [
"getrandom 0.2.11",
]
@@ -6522,12 +6527,12 @@ dependencies = [
[[package]]
name = "x11rb"
version = "0.10.1"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "592b4883219f345e712b3209c62654ebda0bb50887f330cbd018d0f654bfd507"
checksum = "b1641b26d4dec61337c35a1b1aaf9e3cba8f46f0b43636c609ab0291a648040a"
dependencies = [
"gethostname 0.2.3",
"nix 0.24.3",
"gethostname 0.3.0",
"nix 0.26.4",
"winapi",
"winapi-wsapoll",
"x11rb-protocol",
@@ -6535,11 +6540,11 @@ dependencies = [
[[package]]
name = "x11rb-protocol"
version = "0.10.0"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56b245751c0ac9db0e006dc812031482784e434630205a93c73cfefcaabeac67"
checksum = "82d6c3f9a0fb6701fab8f6cea9b0c0bd5d6876f1f89f7fada07e558077c344bc"
dependencies = [
"nix 0.24.3",
"nix 0.26.4",
]
[[package]]

View File

@@ -1,6 +1,6 @@
[package]
name = "lume"
version = "2.1.1"
version = "2.1.6"
description = "the communication app"
authors = ["Ren Amamiya"]
license = "GPL-3.0"
@@ -28,12 +28,11 @@ tauri-plugin-os = { git = "https://github.com/tauri-apps/plugins-workspace", bra
tauri-plugin-process = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
tauri-plugin-shell = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
tauri-plugin-updater = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
tauri-plugin-single-instance = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
tauri-plugin-autostart = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
tauri-plugin-store = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
tauri-plugin-upload = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
tauri-plugin-window-state = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v2" }
tauri-plugin-theme = { git = "https://github.com/reyamir/tauri-plugin-theme", branch = "tauri-v2" }
tauri-plugin-theme = { git = "https://github.com/wyhaya/tauri-plugin-theme" }
tauri-plugin-sql = { git = "hhttps://github.com/tauri-apps/plugins-workspace", branch = "v2", features = [
"sqlite",
] }

View File

@@ -9,7 +9,7 @@
},
"package": {
"productName": "Lume",
"version": "2.1.1"
"version": "2.1.6"
},
"plugins": {
"fs": {

View File

@@ -21,7 +21,7 @@ export function OutboxModel() {
<div className="rounded-xl bg-neutral-100 p-3 text-neutral-800 dark:bg-neutral-900 dark:text-neutral-200">
<div className="flex items-start justify-between gap-2">
<div>
<h5 className="font-semibold">Enable Outbox (experiment)</h5>
<h5 className="font-semibold">Enable Outbox</h5>
<p className="text-sm">
When you request information about a user, Lume will automatically query the
user&apos;s outbox relays and subsequent queries will favour using those

View File

@@ -13,21 +13,13 @@ export function FollowList() {
queryKey: ['follows'],
queryFn: async () => {
const user = ndk.getUser({ pubkey: db.account.pubkey });
const follows = await user.follows();
const followsAsArr = [];
follows.forEach((user) => {
followsAsArr.push(user.pubkey);
});
const follows = [...(await user.follows())].map((user) => user.pubkey);
// update db
await db.updateAccount('follows', JSON.stringify(followsAsArr));
await db.updateAccount('circles', JSON.stringify(followsAsArr));
await db.updateAccount('follows', JSON.stringify(follows));
db.account.follows = follows;
db.account.follows = followsAsArr;
db.account.circles = followsAsArr;
return followsAsArr;
return follows;
},
refetchOnWindowFocus: false,
});

View File

@@ -45,8 +45,6 @@ export function CreateAccountScreen() {
const onSubmit = async (data: { name: string; about: string }) => {
try {
if (!ndk.signer) return navigate('/new/privkey');
setLoading(true);
const profile = {

View File

@@ -44,10 +44,11 @@ export function ImportAccountScreen() {
try {
const pubkey = nip19.decode(npub.split('#')[0]).data as string;
const localSigner = NDKPrivateKeySigner.generate();
await db.secureSave(pubkey + '-bunker', localSigner.privateKey);
await db.createSetting('nsecbunker', '1');
await db.secureSave(pubkey + '-nsecbunker', localSigner.privateKey);
const remoteSigner = new NDKNip46Signer(ndk, npub, localSigner);
await remoteSigner.blockUntilReady();
// await remoteSigner.blockUntilReady();
ndk.signer = remoteSigner;
@@ -259,8 +260,8 @@ export function ImportAccountScreen() {
{db.platform === 'macos'
? 'Apple Keychain (macOS)'
: db.platform === 'windows'
? 'Credential Manager (Windows)'
: 'Secret Service (Linux)'}
? 'Credential Manager (Windows)'
: 'Secret Service (Linux)'}
</b>
, it will be secured by your OS
</p>

View File

@@ -2,7 +2,6 @@ import { useState } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
import { AllowNotification } from '@app/auth/components/features/allowNotification';
import { Circle } from '@app/auth/components/features/enableCircle';
import { OutboxModel } from '@app/auth/components/features/enableOutbox';
import { FavoriteHashtag } from '@app/auth/components/features/favoriteHashtag';
import { FollowList } from '@app/auth/components/features/followList';
@@ -41,7 +40,6 @@ export function OnboardingListScreen() {
<div className="flex flex-col gap-3">
{newuser ? <SuggestFollow /> : <FollowList />}
<FavoriteHashtag />
<Circle />
<OutboxModel />
<AllowNotification />
<button

View File

@@ -57,7 +57,7 @@ export function ErrorScreen() {
Sorry, an unexpected error has occurred.
</h1>
<h3 className="text-3xl font-semibold leading-snug text-white">
Don&apos;t be panic, your account is safe.
Don&apos;t panic, your account is safe.
<br />
Here are what things you can do:
</h3>
@@ -65,7 +65,7 @@ export function ErrorScreen() {
<div className="flex w-full flex-col gap-3">
<div className="flex items-center justify-between rounded-xl bg-blue-700 px-3 py-4">
<div className="text-xl font-semibold text-white">
1. Try close and re-open app
1. Try to close and re-open the app
</div>
<button
type="button"
@@ -112,12 +112,12 @@ export function ErrorScreen() {
<div className="rounded-xl bg-blue-700 px-3 py-4">
<div className="flex w-full flex-col gap-1.5">
<div className="text-xl font-semibold text-white">
4. Use other Nostr client
4. Use another Nostr client
</div>
<div className="select-text text-lg font-medium text-blue-300">
<p>
While waiting Lume&apos;s Devs release the bug fixes, you always can use
other Nostr client with your account:
While waiting for Lume&apos;s Devs to release the bug fixes, you always can use
other Nostr clients with your account:
</p>
<div className="mt-2 flex flex-col gap-1 text-white">
<a href="https://snort.social" className="hover:!underline">

View File

@@ -4,7 +4,7 @@ import Image from '@tiptap/extension-image';
import Placeholder from '@tiptap/extension-placeholder';
import { EditorContent, FloatingMenu, useEditor } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
import { useMemo, useState } from 'react';
import { useLayoutEffect, useMemo, useRef, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { toast } from 'sonner';
import { twMerge } from 'tailwind-merge';
@@ -27,12 +27,14 @@ import {
export function NewArticleScreen() {
const { ndk } = useNDK();
const [height, setHeight] = useState(0);
const [loading, setLoading] = useState(false);
const [title, setTitle] = useState('');
const [summary, setSummary] = useState({ open: false, content: '' });
const [cover, setCover] = useState('');
const navigate = useNavigate();
const containerRef = useRef(null);
const ident = useMemo(() => String(Date.now()), []);
const editor = useEditor({
extensions: [
@@ -113,123 +115,133 @@ export function NewArticleScreen() {
}
};
useLayoutEffect(() => {
setHeight(containerRef.current.clientHeight);
}, []);
return (
<div className="flex h-full flex-col justify-between">
<div className="flex flex-col gap-4">
{cover ? (
<img
src={cover}
alt="post cover"
className="h-72 w-full rounded-lg object-cover"
/>
) : null}
<div className="group flex justify-between gap-2">
<input
name="title"
className="h-9 flex-1 border-none bg-transparent text-2xl font-semibold text-neutral-900 shadow-none outline-none placeholder:text-neutral-400 dark:text-neutral-100 dark:placeholder:text-neutral-600"
placeholder="Untitled"
value={title}
onChange={(e) => setTitle(e.target.value)}
/>
<div
className={twMerge(
'inline-flex shrink-0 gap-2 group-hover:inline-flex',
title.length > 0 ? '' : 'hidden'
)}
>
<ArticleCoverUploader setCover={setCover} />
<button
type="button"
onClick={() => setSummary((prev) => ({ ...prev, open: !prev.open }))}
className="inline-flex h-9 w-max items-center gap-2 rounded-lg bg-neutral-100 px-2.5 text-sm font-medium hover:bg-neutral-200 dark:bg-neutral-800 dark:hover:bg-neutral-800"
<div className="flex flex-1 flex-col justify-between">
<div className="flex-1 overflow-y-auto">
<div
className="flex flex-col gap-4"
ref={containerRef}
style={{ height: `${height}px` }}
>
{cover ? (
<img
src={cover}
alt="post cover"
className="h-72 w-full rounded-lg object-cover"
/>
) : null}
<div className="group flex justify-between gap-2">
<input
name="title"
className="h-9 flex-1 border-none bg-transparent text-2xl font-semibold text-neutral-900 shadow-none outline-none placeholder:text-neutral-400 dark:text-neutral-100 dark:placeholder:text-neutral-600"
placeholder="Untitled"
value={title}
onChange={(e) => setTitle(e.target.value)}
/>
<div
className={twMerge(
'inline-flex shrink-0 gap-2 group-hover:inline-flex',
title.length > 0 ? '' : 'hidden'
)}
>
<ThreadsIcon className="h-4 w-4" />
Add summary
</button>
</div>
</div>
{summary.open ? (
<div className="flex gap-3">
<div className="h-16 w-1 shrink-0 rounded-full bg-neutral-200 dark:bg-neutral-800" />
<div className="flex-1">
<textarea
className="h-16 w-full border-none bg-transparent px-1 py-1 text-neutral-900 shadow-none outline-none placeholder:text-neutral-400 dark:text-neutral-100 dark:placeholder:text-neutral-600"
placeholder="A brief summary of your article"
value={summary.content}
onChange={(e) =>
setSummary((prev) => ({ ...prev, content: e.target.value }))
}
/>
<ArticleCoverUploader setCover={setCover} />
<button
type="button"
onClick={() => setSummary((prev) => ({ ...prev, open: !prev.open }))}
className="inline-flex h-9 w-max items-center gap-2 rounded-lg bg-neutral-100 px-2.5 text-sm font-medium hover:bg-neutral-200 dark:bg-neutral-800 dark:hover:bg-neutral-800"
>
<ThreadsIcon className="h-4 w-4" />
Add summary
</button>
</div>
</div>
) : null}
<div>
{editor && (
<FloatingMenu
{summary.open ? (
<div className="flex gap-3">
<div className="h-16 w-1 shrink-0 rounded-full bg-neutral-200 dark:bg-neutral-800" />
<div className="flex-1">
<textarea
className="h-16 w-full border-none bg-transparent px-1 py-1 text-neutral-900 shadow-none outline-none placeholder:text-neutral-400 dark:text-neutral-100 dark:placeholder:text-neutral-600"
placeholder="A brief summary of your article"
value={summary.content}
onChange={(e) =>
setSummary((prev) => ({ ...prev, content: e.target.value }))
}
/>
</div>
</div>
) : null}
<div>
{editor && (
<FloatingMenu
editor={editor}
tippyOptions={{ duration: 100 }}
className="ml-36 inline-flex h-10 items-center gap-1 rounded-lg border border-neutral-200 bg-neutral-100 px-px dark:border-neutral-800 dark:bg-neutral-900"
>
<button
onClick={() => editor.chain().focus().toggleHeading({ level: 1 }).run()}
className={twMerge(
'inline-flex h-9 w-9 items-center justify-center rounded-md text-neutral-900 hover:bg-neutral-50 dark:text-neutral-100 dark:hover:bg-neutral-950',
editor.isActive('heading', { level: 1 })
? 'bg-white shadow dark:bg-black'
: ''
)}
>
<Heading1Icon className="h-5 w-5" />
</button>
<button
onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()}
className={twMerge(
'inline-flex h-9 w-9 items-center justify-center rounded-md text-neutral-900 hover:bg-neutral-50 dark:text-neutral-100 dark:hover:bg-neutral-950',
editor.isActive('heading', { level: 2 })
? 'bg-white shadow dark:bg-black'
: ''
)}
>
<Heading2Icon className="h-5 w-5" />
</button>
<button
onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()}
className={twMerge(
'inline-flex h-9 w-9 items-center justify-center rounded-md text-neutral-900 hover:bg-neutral-50 dark:text-neutral-100 dark:hover:bg-neutral-950',
editor.isActive('heading', { level: 3 })
? 'bg-white shadow dark:bg-black'
: ''
)}
>
<Heading3Icon className="h-5 w-5" />
</button>
<button
onClick={() => editor.chain().focus().toggleBold().run()}
className={twMerge(
'inline-flex h-9 w-9 items-center justify-center rounded-md text-neutral-900 hover:bg-neutral-50 dark:text-neutral-100 dark:hover:bg-neutral-950',
editor.isActive('bold') ? 'bg-white shadow dark:bg-black' : ''
)}
>
<BoldIcon className="h-5 w-5" />
</button>
<button
onClick={() => editor.chain().focus().toggleItalic().run()}
className={twMerge(
'inline-flex h-9 w-9 items-center justify-center rounded-md text-neutral-900 hover:bg-neutral-50 dark:text-neutral-100 dark:hover:bg-neutral-950',
editor.isActive('italic') ? 'bg-white shadow dark:bg-black' : ''
)}
>
<ItalicIcon className="h-5 w-5" />
</button>
</FloatingMenu>
)}
<EditorContent
editor={editor}
tippyOptions={{ duration: 100 }}
className="ml-36 inline-flex h-10 items-center gap-1 rounded-lg border border-neutral-200 bg-neutral-100 px-px dark:border-neutral-800 dark:bg-neutral-900"
>
<button
onClick={() => editor.chain().focus().toggleHeading({ level: 1 }).run()}
className={twMerge(
'inline-flex h-9 w-9 items-center justify-center rounded-md text-neutral-900 hover:bg-neutral-50 dark:text-neutral-100 dark:hover:bg-neutral-950',
editor.isActive('heading', { level: 1 })
? 'bg-white shadow dark:bg-black'
: ''
)}
>
<Heading1Icon className="h-5 w-5" />
</button>
<button
onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()}
className={twMerge(
'inline-flex h-9 w-9 items-center justify-center rounded-md text-neutral-900 hover:bg-neutral-50 dark:text-neutral-100 dark:hover:bg-neutral-950',
editor.isActive('heading', { level: 2 })
? 'bg-white shadow dark:bg-black'
: ''
)}
>
<Heading2Icon className="h-5 w-5" />
</button>
<button
onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()}
className={twMerge(
'inline-flex h-9 w-9 items-center justify-center rounded-md text-neutral-900 hover:bg-neutral-50 dark:text-neutral-100 dark:hover:bg-neutral-950',
editor.isActive('heading', { level: 3 })
? 'bg-white shadow dark:bg-black'
: ''
)}
>
<Heading3Icon className="h-5 w-5" />
</button>
<button
onClick={() => editor.chain().focus().toggleBold().run()}
className={twMerge(
'inline-flex h-9 w-9 items-center justify-center rounded-md text-neutral-900 hover:bg-neutral-50 dark:text-neutral-100 dark:hover:bg-neutral-950',
editor.isActive('bold') ? 'bg-white shadow dark:bg-black' : ''
)}
>
<BoldIcon className="h-5 w-5" />
</button>
<button
onClick={() => editor.chain().focus().toggleItalic().run()}
className={twMerge(
'inline-flex h-9 w-9 items-center justify-center rounded-md text-neutral-900 hover:bg-neutral-50 dark:text-neutral-100 dark:hover:bg-neutral-950',
editor.isActive('italic') ? 'bg-white shadow dark:bg-black' : ''
)}
>
<ItalicIcon className="h-5 w-5" />
</button>
</FloatingMenu>
)}
<EditorContent
editor={editor}
spellCheck="false"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
/>
spellCheck="false"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
/>
</div>
</div>
</div>
<div>

View File

@@ -2,3 +2,4 @@ export * from './articleCoverUploader';
export * from './mediaUploader';
export * from './mentionPopup';
export * from './mentionPopupItem';
export * from './mentionList';

View File

@@ -0,0 +1,104 @@
import * as Avatar from '@radix-ui/react-avatar';
import { minidenticon } from 'minidenticons';
import { Ref, forwardRef, useEffect, useImperativeHandle, useState } from 'react';
import { twMerge } from 'tailwind-merge';
import { NDKCacheUserProfile } from '@utils/types';
type MentionListRef = {
onKeyDown: (props: { event: Event }) => boolean;
};
const List = (
props: {
items: NDKCacheUserProfile[];
command: (arg0: { id: string }) => void;
},
ref: Ref<unknown>
) => {
const [selectedIndex, setSelectedIndex] = useState(0);
const selectItem = (index) => {
const item = props.items[index];
if (item) {
props.command({ id: item.pubkey });
}
};
const upHandler = () => {
setSelectedIndex((selectedIndex + props.items.length - 1) % props.items.length);
};
const downHandler = () => {
setSelectedIndex((selectedIndex + 1) % props.items.length);
};
const enterHandler = () => {
selectItem(selectedIndex);
};
useEffect(() => setSelectedIndex(0), [props.items]);
useImperativeHandle(ref, () => ({
onKeyDown: ({ event }) => {
if (event.key === 'ArrowUp') {
upHandler();
return true;
}
if (event.key === 'ArrowDown') {
downHandler();
return true;
}
if (event.key === 'Enter') {
enterHandler();
return true;
}
return false;
},
}));
return (
<div className="flex w-[200px] flex-col overflow-y-auto rounded-lg border border-neutral-200 bg-neutral-50 p-2 shadow-lg shadow-neutral-500/20 dark:border-neutral-800 dark:bg-neutral-950 dark:shadow-neutral-300/50">
{props.items.length ? (
props.items.map((item, index) => (
<button
key={index}
onClick={() => selectItem(index)}
className={twMerge(
'inline-flex h-11 items-center gap-2 rounded-md px-2',
index === selectedIndex ? 'bg-neutral-100 dark:bg-neutral-900' : ''
)}
>
<Avatar.Root className="h-8 w-8 shrink-0">
<Avatar.Image
src={item.image}
alt={item.name}
loading="lazy"
decoding="async"
className="h-8 w-8 rounded-md"
/>
<Avatar.Fallback delayMs={150}>
<img
src={
'data:image/svg+xml;utf8,' +
encodeURIComponent(minidenticon(item.name, 90, 50))
}
alt={item.name}
className="h-8 w-8 rounded-md bg-black dark:bg-white"
/>
</Avatar.Fallback>
</Avatar.Root>
<h5 className="max-w-[150px] truncate text-sm font-medium">{item.name}</h5>
</button>
))
) : (
<div className="text-center text-sm font-medium">No result</div>
)}
</div>
);
};
export const MentionList = forwardRef<MentionListRef>(List);

View File

@@ -1,11 +1,13 @@
import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk';
import CharacterCount from '@tiptap/extension-character-count';
import Image from '@tiptap/extension-image';
import Mention from '@tiptap/extension-mention';
import Placeholder from '@tiptap/extension-placeholder';
import { EditorContent, useEditor } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
import { convert } from 'html-to-text';
import { useEffect, useState } from 'react';
import { nip19 } from 'nostr-tools';
import { useEffect, useLayoutEffect, useRef, useState } from 'react';
import { useNavigate, useSearchParams } from 'react-router-dom';
import { toast } from 'sonner';
@@ -18,16 +20,20 @@ import { MentionNote } from '@shared/notes';
import { WIDGET_KIND } from '@stores/constants';
import { useSuggestion } from '@utils/hooks/useSuggestion';
import { useWidget } from '@utils/hooks/useWidget';
export function NewPostScreen() {
const { ndk } = useNDK();
const { addWidget } = useWidget();
const { suggestion } = useSuggestion();
const [loading, setLoading] = useState(false);
const [height, setHeight] = useState(0);
const [searchParams, setSearchParams] = useSearchParams();
const navigate = useNavigate();
const containerRef = useRef(null);
const editor = useEditor({
extensions: [
StarterKit.configure(),
@@ -39,6 +45,14 @@ export function NewPostScreen() {
},
}),
CharacterCount.configure(),
Mention.configure({
suggestion,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
renderLabel({ options, node }) {
const npub = nip19.npubEncode(node.attrs.id);
return `nostr:${npub}`;
},
}),
],
content: JSON.parse(localStorage.getItem('editor-post') || '{}'),
editorProps: {
@@ -115,34 +129,40 @@ export function NewPostScreen() {
}
};
useLayoutEffect(() => {
setHeight(containerRef.current.clientHeight);
}, []);
useEffect(() => {
if (editor) editor.commands.focus('end');
}, [editor]);
return (
<div className="flex h-full flex-col justify-between">
<div>
<EditorContent
editor={editor}
spellCheck="false"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
/>
{searchParams.get('replyTo') && (
<div className="relative max-w-lg">
<MentionNote id={searchParams.get('replyTo')} editing />
<button
type="button"
onClick={() => setSearchParams({})}
className="absolute right-3 top-3 inline-flex h-6 w-6 items-center justify-center rounded bg-neutral-200 px-2 dark:bg-neutral-800"
>
<CancelIcon className="h-5 w-5" />
</button>
</div>
)}
<div className="flex flex-1 flex-col gap-4">
<div className="flex-1 overflow-y-auto">
<div ref={containerRef} style={{ height: `${height}px` }}>
<EditorContent
editor={editor}
spellCheck="false"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
/>
{searchParams.get('replyTo') && (
<div className="relative max-w-lg">
<MentionNote id={searchParams.get('replyTo')} editing />
<button
type="button"
onClick={() => setSearchParams({})}
className="absolute right-3 top-3 inline-flex h-6 w-6 items-center justify-center rounded bg-neutral-200 px-2 dark:bg-neutral-800"
>
<CancelIcon className="h-5 w-5" />
</button>
</div>
)}
</div>
</div>
<div className="flex h-16 w-full items-center justify-between border-t border-neutral-100 dark:border-neutral-900">
<div className="inline-flex h-16 w-full items-center justify-between border-t border-neutral-100 bg-neutral-50 dark:border-neutral-900 dark:bg-neutral-950">
<span className="text-sm font-medium tabular-nums text-neutral-600 dark:text-neutral-400">
{editor?.storage?.characterCount.characters()} characters
</span>

View File

@@ -1,7 +1,7 @@
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { message } from '@tauri-apps/plugin-dialog';
import { normalizeRelayUrl } from 'nostr-fetch';
import { useNavigate } from 'react-router-dom';
import { toast } from 'sonner';
import { VList } from 'virtua';
import { useStorage } from '@libs/storage/provider';
@@ -37,10 +37,14 @@ export function RelayList() {
const url = normalizeRelayUrl(relayUrl);
const res = await db.createRelay(url);
if (!res) await message("You're aldready connected to this relay");
queryClient.invalidateQueries({
queryKey: ['user-relay'],
});
if (res) {
toast.info('Connected. You need to restart app to take effect');
queryClient.invalidateQueries({
queryKey: ['user-relay'],
});
} else {
toast.warning("You're aldready connected to this relay");
}
};
return (

View File

@@ -1,9 +1,24 @@
import { getVersion } from '@tauri-apps/api/app';
import { relaunch } from '@tauri-apps/plugin-process';
import { Update, check } from '@tauri-apps/plugin-updater';
import { useEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import { toast } from 'sonner';
export function AboutScreen() {
const [version, setVersion] = useState('');
const [newUpdate, setNewUpdate] = useState<Update>(null);
const checkUpdate = async () => {
const update = await check();
if (!update) toast.info('There is no update available');
setNewUpdate(update);
};
const installUpdate = async () => {
await newUpdate.downloadAndInstall();
await relaunch();
};
useEffect(() => {
async function loadVersion() {
@@ -20,19 +35,38 @@ export function AboutScreen() {
<img src="/icon.png" alt="Lume's logo" className="w-16 shrink-0" />
<div>
<h1 className="text-xl font-semibold">Lume</h1>
<p className="text-neutral-700 dark:text-neutral-300">Version {version}</p>
<p className="text-sm font-medium text-neutral-700 dark:text-neutral-300">
Version {version}
</p>
</div>
</div>
<div className="mx-auto mt-4 flex w-full max-w-xs flex-col gap-2">
{!newUpdate ? (
<button
type="button"
onClick={() => checkUpdate()}
className="inline-flex h-9 w-full items-center justify-center rounded-lg bg-blue-500 text-sm font-medium text-white hover:bg-blue-600"
>
Check for update
</button>
) : (
<button
type="button"
onClick={() => installUpdate()}
className="inline-flex h-9 w-full items-center justify-center rounded-lg bg-blue-500 text-sm font-medium text-white hover:bg-blue-600"
>
Install {newUpdate.version}
</button>
)}
<Link
to="https://lume.nu"
className="inline-flex h-9 w-full items-center justify-center rounded-lg bg-neutral-100 hover:bg-neutral-200 dark:bg-neutral-900 dark:hover:bg-neutral-800"
className="inline-flex h-9 w-full items-center justify-center rounded-lg bg-neutral-100 text-sm font-medium hover:bg-neutral-200 dark:bg-neutral-900 dark:hover:bg-neutral-800"
>
Website
</Link>
<Link
to="https://github.com/luminous-devs/lume/issues"
className="inline-flex h-9 w-full items-center justify-center rounded-lg bg-neutral-100 hover:bg-neutral-200 dark:bg-neutral-900 dark:hover:bg-neutral-800"
className="inline-flex h-9 w-full items-center justify-center rounded-lg bg-neutral-100 text-sm font-medium hover:bg-neutral-200 dark:bg-neutral-900 dark:hover:bg-neutral-800"
>
Report a issue
</Link>

View File

@@ -57,7 +57,7 @@ export function ProfileCard() {
{user?.display_name || user?.name}
</h3>
<p className="text-lg text-neutral-700 dark:text-neutral-300">
{user.nip05 || displayNpub(db.account.pubkey, 16)}
{user?.nip05 || displayNpub(db.account.pubkey, 16)}
</p>
</div>
</div>

View File

@@ -184,10 +184,7 @@ export function EditProfileScreen() {
</label>
<input
type={'text'}
{...register('display_name', {
required: true,
minLength: 4,
})}
{...register('display_name')}
spellCheck={false}
className="relative h-11 w-full rounded-lg bg-neutral-100 px-3 py-1 text-neutral-900 !outline-none backdrop-blur-xl placeholder:text-neutral-500 dark:bg-neutral-900 dark:text-neutral-100"
/>
@@ -201,10 +198,7 @@ export function EditProfileScreen() {
</label>
<input
type={'text'}
{...register('name', {
required: true,
minLength: 4,
})}
{...register('name')}
spellCheck={false}
className="relative h-11 w-full rounded-lg bg-neutral-100 px-3 py-1 text-neutral-900 !outline-none backdrop-blur-xl placeholder:text-neutral-500 dark:bg-neutral-900 dark:text-neutral-100"
/>
@@ -218,10 +212,7 @@ export function EditProfileScreen() {
</label>
<div className="relative">
<input
{...register('nip05', {
required: true,
minLength: 4,
})}
{...register('nip05')}
spellCheck={false}
className="relative h-11 w-full rounded-lg bg-neutral-100 px-3 py-1 text-neutral-900 !outline-none backdrop-blur-xl placeholder:text-neutral-500 dark:bg-neutral-900 dark:text-neutral-100"
/>

View File

@@ -13,6 +13,7 @@ import { DarkIcon, LightIcon, SystemModeIcon } from '@shared/icons';
export function GeneralSettingScreen() {
const { db } = useStorage();
const [settings, setSettings] = useState({
autoupdate: false,
autolaunch: false,
outbox: false,
media: true,
@@ -59,6 +60,13 @@ export function GeneralSettingScreen() {
setSettings((prev) => ({ ...prev, hashtag: !settings.hashtag }));
};
const toggleAutoupdate = async () => {
await db.createSetting('autoupdate', String(+!settings.autoupdate));
db.settings.autoupdate = !settings.autoupdate;
// update state
setSettings((prev) => ({ ...prev, autoupdate: !settings.autoupdate }));
};
const toggleNofitication = async () => {
if (settings.notification) return;
@@ -82,6 +90,12 @@ export function GeneralSettingScreen() {
if (!data) return;
data.forEach((item) => {
if (item.key === 'autoupdate')
setSettings((prev) => ({
...prev,
autoupdate: !!parseInt(item.value),
}));
if (item.key === 'outbox')
setSettings((prev) => ({
...prev,
@@ -114,6 +128,19 @@ export function GeneralSettingScreen() {
return (
<div className="mx-auto w-full max-w-lg">
<div className="flex flex-col gap-6">
<div className="flex w-full items-center justify-between">
<div className="flex items-center gap-8">
<div className="w-24 shrink-0 text-end text-sm font-semibold">Updater</div>
<div className="text-sm">Auto download new update at Login</div>
</div>
<Switch.Root
checked={settings.autoupdate}
onClick={() => toggleAutoupdate()}
className="relative h-7 w-12 cursor-default rounded-full bg-neutral-200 outline-none data-[state=checked]:bg-blue-500 dark:bg-neutral-800"
>
<Switch.Thumb className="block h-6 w-6 translate-x-0.5 rounded-full bg-white transition-transform duration-100 will-change-transform data-[state=checked]:translate-x-[19px]" />
</Switch.Root>
</div>
<div className="flex w-full items-center justify-between">
<div className="flex items-center gap-8">
<div className="w-24 shrink-0 text-end text-sm font-semibold">Startup</div>

View File

@@ -21,49 +21,53 @@ export function UserProfile({ pubkey }: { pubkey: string }) {
const { user } = useProfile(pubkey);
const [followed, setFollowed] = useState(false);
const navigate = useNavigate();
const navigate = useNavigate();
const svgURI =
'data:image/svg+xml;utf8,' + encodeURIComponent(minidenticon(pubkey, 90, 50));
const follow = async (pubkey: string) => {
const follow = async () => {
try {
if (!ndk.signer) return navigate('/new/privkey');
setFollowed(true);
const user = ndk.getUser({ pubkey: db.account.pubkey });
const contacts = await user.follows();
const add = await user.follow(new NDKUser({ pubkey: pubkey }), contacts);
if (add) {
setFollowed(true);
} else {
toast('You already follow this user');
if (!add) {
toast.success('You already follow this user');
setFollowed(false);
}
} catch (error) {
console.log(error);
} catch (e) {
toast.error(e);
setFollowed(false);
}
};
const unfollow = async (pubkey: string) => {
const unfollow = async () => {
try {
if (!ndk.signer) return navigate('/new/privkey');
setFollowed(false);
const user = ndk.getUser({ pubkey: db.account.pubkey });
const contacts = await user.follows();
contacts.delete(new NDKUser({ pubkey: pubkey }));
let list: string[][];
contacts.forEach((el) => list.push(['p', el.pubkey, el.relayUrls?.[0] || '', '']));
const list = [...contacts].map((item) => [
'p',
item.pubkey,
item.relayUrls?.[0] || '',
'',
]);
const event = new NDKEvent(ndk);
event.content = '';
event.kind = NDKKind.Contacts;
event.tags = list;
const publishedRelays = await event.publish();
if (publishedRelays) {
setFollowed(false);
}
} catch (error) {
console.log(error);
await event.publish();
} catch (e) {
toast.error(e);
}
};
@@ -78,9 +82,9 @@ export function UserProfile({ pubkey }: { pubkey: string }) {
return (
<>
<div className="h-56 w-full overflow-hidden rounded-tl-lg">
{user.banner ? (
{user?.banner ? (
<img
src={user.banner}
src={user?.banner}
alt="user banner"
className="h-full w-full rounded-tl-lg object-cover"
/>
@@ -112,10 +116,10 @@ export function UserProfile({ pubkey }: { pubkey: string }) {
<h5 className="text-center text-xl font-semibold text-neutral-900 dark:text-neutral-100">
{user.name || user.display_name || user.displayName || 'No name'}
</h5>
{user.nip05 ? (
{user?.nip05 ? (
<NIP05
pubkey={pubkey}
nip05={user?.nip05}
nip05={user.nip05}
className="text-neutral-600 dark:text-neutral-400"
/>
) : (
@@ -125,7 +129,7 @@ export function UserProfile({ pubkey }: { pubkey: string }) {
)}
</div>
<div className="flex flex-col gap-6">
{user.about || user.bio ? (
{user?.about || user?.bio ? (
<p className="mt-2 max-w-[500px] select-text break-words text-center text-neutral-900 dark:text-neutral-100">
{user.about || user.bio}
</p>
@@ -139,23 +143,23 @@ export function UserProfile({ pubkey }: { pubkey: string }) {
{followed ? (
<button
type="button"
onClick={() => unfollow(pubkey)}
className="inline-flex h-10 w-36 items-center justify-center rounded-md bg-neutral-200 text-sm font-medium text-neutral-900 backdrop-blur-xl hover:bg-blue-600 hover:text-neutral-100 dark:bg-neutral-800 dark:text-neutral-100 dark:hover:bg-blue-600 dark:hover:text-neutral-100"
onClick={unfollow}
className="inline-flex h-10 w-36 items-center justify-center rounded-md bg-neutral-200 text-sm font-medium text-neutral-900 backdrop-blur-xl hover:bg-blue-500 hover:text-neutral-100 dark:bg-neutral-800 dark:text-neutral-100 dark:hover:bg-blue-600 dark:hover:text-neutral-100"
>
Unfollow
</button>
) : (
<button
type="button"
onClick={() => follow(pubkey)}
className="inline-flex h-10 w-36 items-center justify-center rounded-md bg-neutral-200 text-sm font-medium text-neutral-900 backdrop-blur-xl hover:bg-blue-600 hover:text-neutral-100 dark:bg-neutral-800 dark:text-neutral-100 dark:hover:bg-blue-600 dark:hover:text-neutral-100"
onClick={follow}
className="inline-flex h-10 w-36 items-center justify-center rounded-md bg-neutral-200 text-sm font-medium text-neutral-900 backdrop-blur-xl hover:bg-blue-500 hover:text-neutral-100 dark:bg-neutral-800 dark:text-neutral-100 dark:hover:bg-blue-600 dark:hover:text-neutral-100"
>
Follow
</button>
)}
<Link
to={`/chats/${pubkey}`}
className="inline-flex h-10 w-36 items-center justify-center rounded-md bg-neutral-200 text-sm font-medium text-neutral-900 backdrop-blur-xl hover:bg-blue-600 hover:text-neutral-100 dark:bg-neutral-800 dark:text-neutral-100 dark:hover:bg-blue-600 dark:hover:text-neutral-100"
className="inline-flex h-10 w-36 items-center justify-center rounded-md bg-neutral-200 text-sm font-medium text-neutral-900 backdrop-blur-xl hover:bg-blue-500 hover:text-neutral-100 dark:bg-neutral-800 dark:text-neutral-100 dark:hover:bg-blue-600 dark:hover:text-neutral-100"
>
Message
</Link>

View File

@@ -1,7 +1,8 @@
import NDK, { NDKNip46Signer, NDKPrivateKeySigner } from '@nostr-dev-kit/ndk';
import { ndkAdapter } from '@nostr-fetch/adapter-ndk';
import { message } from '@tauri-apps/plugin-dialog';
import { ask } from '@tauri-apps/plugin-dialog';
import { fetch } from '@tauri-apps/plugin-http';
import { relaunch } from '@tauri-apps/plugin-process';
import { NostrFetcher } from 'nostr-fetch';
import { useEffect, useMemo, useState } from 'react';
import { toast } from 'sonner';
@@ -61,81 +62,74 @@ export const NDKInstance = () => {
}
}
async function getSigner(instance: NDK) {
if (!db.account) return null;
const localSignerPrivkey = await db.secureLoad(db.account.pubkey + '-bunker');
const userPrivkey = await db.secureLoad(db.account.pubkey);
async function getSigner(nsecbunker?: boolean) {
if (!db.account) return;
// NIP-46 Signer
if (localSignerPrivkey) {
if (nsecbunker) {
const localSignerPrivkey = await db.secureLoad(db.account.pubkey + '-nsecbunker');
const localSigner = new NDKPrivateKeySigner(localSignerPrivkey);
const remoteSigner = new NDKNip46Signer(instance, db.account.id, localSigner);
await remoteSigner.blockUntilReady();
return remoteSigner;
// await remoteSigner.blockUntilReady();
return new NDKNip46Signer(ndk, db.account.id, localSigner);
}
// Privkey Signer
if (userPrivkey) {
return new NDKPrivateKeySigner(userPrivkey);
}
// Private key Signer
const userPrivkey = await db.secureLoad(db.account.pubkey);
return new NDKPrivateKeySigner(userPrivkey);
}
async function initNDK() {
const outboxSetting = await db.getSettingValue('outbox');
const explicitRelayUrls = await getExplicitRelays();
const tauriAdapter = new NDKCacheAdapterTauri(db);
const instance = new NDK({
explicitRelayUrls,
cacheAdapter: tauriAdapter,
outboxRelayUrls: ['wss://purplepag.es'],
enableOutboxModel: outboxSetting === '1',
});
try {
// connect
await instance.connect(2000);
const outboxSetting = await db.getSettingValue('outbox');
const bunkerSetting = await db.getSettingValue('nsecbunker');
const signer = await getSigner(!!parseInt(bunkerSetting));
const explicitRelayUrls = await getExplicitRelays();
// add signer
const signer = await getSigner(instance);
const tauriAdapter = new NDKCacheAdapterTauri(db);
const instance = new NDK({
explicitRelayUrls,
cacheAdapter: tauriAdapter,
outboxRelayUrls: ['wss://purplepag.es'],
blacklistRelayUrls: [],
enableOutboxModel: !!parseInt(outboxSetting),
});
instance.signer = signer;
// connect
await instance.connect();
// update account's metadata
if (db.account) {
const circleSetting = await db.getSettingValue('circles');
const user = instance.getUser({ pubkey: db.account.pubkey });
const follows = await user.follows();
const relayList = await user.relayList();
if (user) {
const follows = [...(await user.follows())].map((user) => user.pubkey);
const relayList = await user.relayList();
const followsAsArr = [];
follows.forEach((user) => {
followsAsArr.push(user.pubkey);
});
// update user's follows
await db.updateAccount('follows', JSON.stringify(follows));
// update user's follows
await db.updateAccount('follows', JSON.stringify(followsAsArr));
if (circleSetting !== '1')
await db.updateAccount('circles', JSON.stringify(followsAsArr));
// update user's relay list
if (relayList) {
for (const relay of relayList.relays) {
await db.createRelay(relay);
}
if (relayList)
// update user's relays
for (const relay of relayList.relays) {
await db.createRelay(relay);
}
}
}
} catch (error) {
await message(`NDK instance init failed: ${error}`, {
title: 'Lume',
type: 'error',
});
}
setNDK(instance);
setRelayUrls(explicitRelayUrls);
setNDK(instance);
setRelayUrls(explicitRelayUrls);
} catch (e) {
const yes = await ask(
`Something wrong, Lume is not working as expected, do you want to relaunch app?`,
{
title: 'Lume',
type: 'error',
okLabel: 'Yes',
}
);
if (yes) relaunch();
}
}
useEffect(() => {

View File

@@ -12,6 +12,7 @@ import type {
NDKCacheEvent,
NDKCacheEventTag,
NDKCacheUser,
NDKCacheUserProfile,
Relays,
Widget,
} from '@utils/types';
@@ -20,13 +21,18 @@ export class LumeStorage {
public db: Database;
public account: Account | null;
public platform: Platform | null;
public settings: { outbox: boolean; media: boolean; hashtag: boolean };
public settings: {
autoupdate: boolean;
outbox: boolean;
media: boolean;
hashtag: boolean;
};
constructor(sqlite: Database, platform: Platform) {
this.db = sqlite;
this.account = null;
this.platform = platform;
this.settings = { outbox: false, media: true, hashtag: true };
this.settings = { autoupdate: false, outbox: false, media: true, hashtag: true };
}
public async secureSave(key: string, value: string) {
@@ -47,6 +53,20 @@ export class LumeStorage {
return await invoke('secure_remove', { key });
}
public async getAllCacheUsers() {
const results: Array<NDKCacheUser> = await this.db.select(
'SELECT * FROM ndk_users ORDER BY createdAt DESC;'
);
if (!results.length) return [];
const users: NDKCacheUserProfile[] = results.map((item) => ({
pubkey: item.pubkey,
...JSON.parse(item.profile as string),
}));
return users;
}
public async getCacheUser(pubkey: string) {
const results: Array<NDKCacheUser> = await this.db.select(
'SELECT * FROM ndk_users WHERE pubkey = $1 ORDER BY pubkey DESC LIMIT 1;',
@@ -158,7 +178,7 @@ export class LumeStorage {
public async checkAccount() {
const result: Array<{ total: string }> = await this.db.select(
'SELECT COUNT(*) AS "total" FROM accounts;'
'SELECT COUNT(*) AS "total" FROM accounts WHERE is_active = "1" ORDER BY id DESC LIMIT 1;'
);
return parseInt(result[0].total);
}
@@ -418,7 +438,7 @@ export class LumeStorage {
[relay, this.account.id]
);
if (existRelays.length > 0) return false;
if (existRelays.length) return;
return await this.db.execute(
'INSERT OR IGNORE INTO relays (account_id, relay, purpose) VALUES ($1, $2, $3);',
@@ -431,7 +451,7 @@ export class LumeStorage {
}
public async createSetting(key: string, value: string) {
const currentSetting = await this.getSettingValue(key);
const currentSetting = await this.checkSettingValue(key);
if (!currentSetting)
return await this.db.execute(
@@ -455,12 +475,21 @@ export class LumeStorage {
return results;
}
public async checkSettingValue(key: string) {
const results: { key: string; value: string }[] = await this.db.select(
'SELECT * FROM settings WHERE key = $1 ORDER BY id DESC LIMIT 1;',
[key]
);
if (!results.length) return false;
return results[0].value;
}
public async getSettingValue(key: string) {
const results: { key: string; value: string }[] = await this.db.select(
'SELECT * FROM settings WHERE key = $1 ORDER BY id DESC LIMIT 1;',
[key]
);
if (results.length < 1) return null;
if (!results.length) return '0';
return results[0].value;
}

View File

@@ -20,7 +20,7 @@ const StorageContext = createContext<StorageContext>({
db: undefined,
});
const StorageProvider = ({ children }: PropsWithChildren<object>) => {
const StorageInstance = () => {
const [db, setDB] = useState<LumeStorage>(undefined);
const [isNewVersion, setIsNewVersion] = useState(false);
@@ -33,6 +33,8 @@ const StorageProvider = ({ children }: PropsWithChildren<object>) => {
if (!lumeStorage.account) await lumeStorage.getActiveAccount();
const settings = await lumeStorage.getAllSettings();
let autoUpdater = false;
if (settings) {
settings.forEach((item) => {
if (item.key === 'outbox') lumeStorage.settings.outbox = !!parseInt(item.value);
@@ -41,16 +43,23 @@ const StorageProvider = ({ children }: PropsWithChildren<object>) => {
if (item.key === 'hashtag')
lumeStorage.settings.hashtag = !!parseInt(item.value);
if (item.key === 'autoupdate') {
if (parseInt(item.value)) autoUpdater = true;
}
});
}
// check update
const update = await check();
if (update) {
setIsNewVersion(true);
if (autoUpdater) {
// check update
const update = await check();
// install new version
if (update) {
setIsNewVersion(true);
await update.downloadAndInstall();
await relaunch();
await update.downloadAndInstall();
await relaunch();
}
}
setDB(lumeStorage);
@@ -66,6 +75,12 @@ const StorageProvider = ({ children }: PropsWithChildren<object>) => {
if (!db) initLumeStorage();
}, []);
return { db, isNewVersion };
};
const StorageProvider = ({ children }: PropsWithChildren<object>) => {
const { db, isNewVersion } = StorageInstance();
if (!db)
return (
<div
@@ -93,7 +108,7 @@ const StorageProvider = ({ children }: PropsWithChildren<object>) => {
<div className="absolute bottom-5 right-5 inline-flex items-center gap-2.5">
<LoaderIcon className="h-6 w-6 animate-spin text-blue-500" />
<p className="font-semibold">
{isNewVersion ? 'Found a new version, updating' : 'Checking for updates...'}
{isNewVersion ? 'Found a new version, updating...' : 'Starting...'}
</p>
</div>
</div>

View File

@@ -15,65 +15,62 @@ export function NewLayout() {
{db.platform !== 'macos' ? (
<WindowTitlebar />
) : (
<div data-tauri-drag-region className="h-9" />
<div data-tauri-drag-region className="h-9 shrink-0" />
)}
<div data-tauri-drag-region className="h-6" />
<div className="flex h-full min-h-0 w-full">
<div className="container mx-auto grid grid-cols-8 px-4">
<div className="col-span-1">
<Link
to="/"
className="inline-flex h-10 w-10 items-center justify-center rounded-lg bg-neutral-100 hover:bg-neutral-200 dark:bg-neutral-900"
>
<ArrowLeftIcon className="h-5 w-5" />
</Link>
</div>
<div className="relative col-span-6 flex flex-col">
<div className="mb-8 flex h-10 shrink-0 items-center gap-3">
{location.pathname !== '/new/privkey' ? (
<div className="flex h-10 items-center gap-2 rounded-lg bg-neutral-100 px-0.5 dark:bg-neutral-800">
<NavLink
to="/new/"
className={({ isActive }) =>
twMerge(
'inline-flex h-9 w-20 items-center justify-center rounded-lg text-sm font-medium',
isActive ? 'bg-white shadow dark:bg-black' : 'bg-transparent'
)
}
>
Post
</NavLink>
<NavLink
to="/new/article"
className={({ isActive }) =>
twMerge(
'inline-flex h-9 w-20 items-center justify-center rounded-lg text-sm font-medium',
isActive ? 'bg-white shadow dark:bg-black' : 'bg-transparent'
)
}
>
Article
</NavLink>
<NavLink
to="/new/file"
className={({ isActive }) =>
twMerge(
'inline-flex h-9 w-28 items-center justify-center rounded-lg text-sm font-medium',
isActive ? 'bg-white shadow dark:bg-black' : 'bg-transparent'
)
}
>
File Sharing
</NavLink>
</div>
) : null}
</div>
<div className="h-full min-h-0 w-full">
<Outlet />
</div>
</div>
<div className="col-span-1" />
<div data-tauri-drag-region className="h-4 shrink-0" />
<div className="container mx-auto grid flex-1 grid-cols-8 px-4">
<div className="col-span-1">
<Link
to="/"
className="inline-flex h-10 w-10 items-center justify-center rounded-lg bg-neutral-100 hover:bg-neutral-200 dark:bg-neutral-900"
>
<ArrowLeftIcon className="h-5 w-5" />
</Link>
</div>
<div className="col-span-6 flex flex-col">
<div className="mb-8 flex h-10 shrink-0 items-center gap-3">
{location.pathname !== '/new/privkey' ? (
<div className="flex h-10 items-center gap-2 rounded-lg bg-neutral-100 px-0.5 dark:bg-neutral-800">
<NavLink
to="/new/"
end
className={({ isActive }) =>
twMerge(
'inline-flex h-9 w-20 items-center justify-center rounded-lg text-sm font-medium',
isActive ? 'bg-white shadow dark:bg-black' : 'bg-transparent'
)
}
>
Post
</NavLink>
<NavLink
to="/new/article"
className={({ isActive }) =>
twMerge(
'inline-flex h-9 w-20 items-center justify-center rounded-lg text-sm font-medium',
isActive ? 'bg-white shadow dark:bg-black' : 'bg-transparent'
)
}
>
Article
</NavLink>
<NavLink
to="/new/file"
className={({ isActive }) =>
twMerge(
'inline-flex h-9 w-28 items-center justify-center rounded-lg text-sm font-medium',
isActive ? 'bg-white shadow dark:bg-black' : 'bg-transparent'
)
}
>
File Sharing
</NavLink>
</div>
) : null}
</div>
<Outlet />
</div>
<div className="col-span-1" />
</div>
</div>
);

View File

@@ -1,4 +1,4 @@
import { Link, NavLink, Outlet, ScrollRestoration } from 'react-router-dom';
import { NavLink, Outlet, ScrollRestoration, useNavigate } from 'react-router-dom';
import { twMerge } from 'tailwind-merge';
import { WindowTitlebar } from 'tauri-controls';
@@ -15,6 +15,7 @@ import {
export function SettingsLayout() {
const { db } = useStorage();
const navigate = useNavigate();
return (
<div className="flex h-screen w-screen flex-col bg-neutral-50 dark:bg-neutral-950">
@@ -26,16 +27,17 @@ export function SettingsLayout() {
<div className="flex h-full min-h-0 w-full flex-col gap-8 overflow-y-auto pb-10">
<div className="flex h-20 w-full items-center justify-between border-b border-neutral-200 px-2 pb-2 dark:border-neutral-900">
<div>
<Link
to="/"
<button
type="button"
onClick={() => navigate(-1)}
className="inline-flex h-12 w-12 items-center justify-center rounded-xl bg-neutral-100 dark:bg-neutral-900"
>
<ArrowLeftIcon className="h-5 w-5" />
</Link>
</button>
</div>
<div className="flex items-center gap-0.5">
<NavLink
to="/settings"
to="/settings/"
end
className={({ isActive }) =>
twMerge(

View File

@@ -1,5 +1,6 @@
import * as AlertDialog from '@radix-ui/react-alert-dialog';
import { useNavigate } from 'react-router-dom';
import { toast } from 'sonner';
import { useNDK } from '@libs/ndk/provider';
import { useStorage } from '@libs/storage/provider';
@@ -11,15 +12,21 @@ export function Logout() {
const navigate = useNavigate();
const logout = async () => {
ndk.signer = null;
try {
ndk.signer = null;
// remove account
await db.accountLogout();
await db.secureRemove(db.account.pubkey);
await db.secureRemove(db.account.pubkey + '-bunker');
// remove private key
await db.secureRemove(db.account.pubkey);
await db.secureRemove(db.account.pubkey + '-bunker');
// redirect to welcome screen
navigate('/auth/welcome');
// logout
await db.accountLogout();
// redirect to welcome screen
navigate('/auth/welcome');
} catch (e) {
toast.error(e);
}
};
return (
@@ -33,7 +40,7 @@ export function Logout() {
</button>
</AlertDialog.Trigger>
<AlertDialog.Portal>
<AlertDialog.Overlay className="fixed inset-0 z-50 bg-black/50 backdrop-blur-2xl dark:bg-white/50" />
<AlertDialog.Overlay className="fixed inset-0 z-50 bg-black/20 backdrop-blur-sm dark:bg-black/20" />
<AlertDialog.Content className="fixed inset-0 z-50 flex min-h-full items-center justify-center">
<div className="relative h-min w-full max-w-md rounded-xl bg-neutral-100 dark:bg-neutral-900">
<div className="flex flex-col gap-1 border-b border-white/5 px-5 py-4">
@@ -54,13 +61,15 @@ export function Logout() {
Cancel
</button>
</AlertDialog.Cancel>
<button
type="button"
onClick={() => logout()}
className="inline-flex h-9 items-center justify-center rounded-lg bg-red-500 px-4 text-sm font-medium text-white outline-none hover:bg-red-600"
>
Logout
</button>
<AlertDialog.Action asChild>
<button
type="button"
onClick={() => logout()}
className="inline-flex h-9 items-center justify-center rounded-lg bg-red-500 px-4 text-sm font-medium text-white outline-none hover:bg-red-600"
>
Logout
</button>
</AlertDialog.Action>
</div>
</div>
</AlertDialog.Content>

View File

@@ -39,7 +39,6 @@ export const NIP05 = memo(function NIP05({
if (!res.ok) throw new Error(`Failed to fetch NIP-05 service: ${nip05}`);
const data: NIP05 = await res.json();
if (data.names) {
if (data.names[localPath] !== pubkey) return false;
return true;

View File

@@ -33,13 +33,13 @@ export function TitleBar({
<div className="col-span-1 flex justify-center">
{id === '9999' ? (
<div className="isolate flex -space-x-2">
{db.account.circles
{db.account.follows
?.slice(0, 8)
.map((item) => <User key={item} pubkey={item} variant="ministacked" />)}
{db.account.circles?.length > 8 ? (
{db.account.follows?.length > 8 ? (
<div className="inline-flex h-6 w-6 items-center justify-center rounded-full bg-neutral-300 text-neutral-900 ring-1 ring-white dark:bg-neutral-700 dark:text-neutral-100 dark:ring-black">
<span className="text-[8px] font-medium">
+{db.account.circles?.length - 8}
+{db.account.follows?.length - 8}
</span>
</div>
) : null}

View File

@@ -222,7 +222,7 @@ export const User = memo(function User({
{user?.name || user?.display_name || user?.displayName}
</h3>
<p className="max-w-[10rem] truncate text-sm text-neutral-900 dark:text-neutral-100/70">
{user?.nip05 || user?.username || displayNpub(pubkey, 16)}
{user?.username || displayNpub(pubkey, 16)}
</p>
</div>
</div>
@@ -551,7 +551,7 @@ export const User = memo(function User({
{user?.nip05 ? (
<NIP05
pubkey={pubkey}
nip05={user?.nip05}
nip05={user.nip05}
className="max-w-[15rem] truncate text-sm text-neutral-500 dark:text-neutral-300"
/>
) : (

View File

@@ -40,7 +40,7 @@ export function ArticleWidget({ widget }: { widget: Widget }) {
} else {
filter = {
kinds: [NDKKind.Article],
authors: db.account.circles,
authors: db.account.follows,
};
}

View File

@@ -40,7 +40,7 @@ export function FileWidget({ widget }: { widget: Widget }) {
} else {
filter = {
kinds: [1063],
authors: db.account.circles,
authors: db.account.follows,
};
}

View File

@@ -39,7 +39,7 @@ export function NewsfeedWidget() {
relayUrls,
{
kinds: [NDKKind.Text, NDKKind.Repost],
authors: db.account.circles,
authors: db.account.follows,
},
FETCH_LIMIT,
{ asOf: pageParam === 0 ? undefined : pageParam, abortSignal: signal }

View File

@@ -96,7 +96,7 @@ export function AddGroupFeeds({ currentWidgetId }: { currentWidgetId: string })
Users
</span>
<div className="flex h-[420px] flex-col overflow-y-auto rounded-xl bg-neutral-100 py-2 dark:bg-neutral-900">
{db.account.circles.map((item: string) => (
{db.account.follows.map((item: string) => (
<button
key={item}
type="button"

View File

@@ -30,12 +30,12 @@ export function LiveUpdater({ status }: { status: QueryStatus }) {
useEffect(() => {
let sub: NDKSubscription = undefined;
if (status === 'success' && db.account && db.account.circles.length > 0) {
if (status === 'success' && db.account && db.account.follows.length > 0) {
queryClient.fetchQuery({ queryKey: ['notification'] });
const filter: NDKFilter = {
kinds: [NDKKind.Text, NDKKind.Repost],
authors: db.account.circles,
authors: db.account.follows,
since: Math.floor(Date.now() / 1000),
};

View File

@@ -1,4 +1,4 @@
import { NDKEvent, NDKKind, NDKUser } from '@nostr-dev-kit/ndk';
import { NDKUser } from '@nostr-dev-kit/ndk';
import { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { toast } from 'sonner';
@@ -6,7 +6,7 @@ import { toast } from 'sonner';
import { useNDK } from '@libs/ndk/provider';
import { useStorage } from '@libs/storage/provider';
import { FollowIcon, UnfollowIcon } from '@shared/icons';
import { FollowIcon } from '@shared/icons';
import { shortenKey } from '@utils/shortenKey';
@@ -16,53 +16,30 @@ export interface Profile {
}
export function NostrBandUserProfile({ data }: { data: Profile }) {
const embedProfile = data.profile ? JSON.parse(data.profile.content) : null;
const profile = embedProfile;
const { db } = useStorage();
const { ndk } = useNDK();
const [followed, setFollowed] = useState(false);
const navigate = useNavigate();
const profile = data.profile ? JSON.parse(data.profile.content) : null;
const follow = async (pubkey: string) => {
try {
if (!ndk.signer) return navigate('/new/privkey');
setFollowed(true);
const user = ndk.getUser({ pubkey: db.account.pubkey });
const contacts = await user.follows();
const add = await user.follow(new NDKUser({ pubkey: pubkey }), contacts);
if (add) {
setFollowed(true);
} else {
toast('You already follow this user');
}
} catch (error) {
console.log(error);
}
};
const unfollow = async (pubkey: string) => {
try {
if (!ndk.signer) return navigate('/new/privkey');
const user = ndk.getUser({ pubkey: db.account.pubkey });
const contacts = await user.follows();
contacts.delete(new NDKUser({ pubkey: pubkey }));
let list: string[][];
contacts.forEach((el) => list.push(['p', el.pubkey, el.relayUrls?.[0] || '', '']));
const event = new NDKEvent(ndk);
event.content = '';
event.kind = NDKKind.Contacts;
event.tags = list;
const publishedRelays = await event.publish();
if (publishedRelays) {
if (!add) {
toast.success('You already follow this user');
setFollowed(false);
}
} catch (error) {
console.log(error);
} catch (e) {
toast.error(e);
setFollowed(false);
}
};
@@ -100,15 +77,7 @@ export function NostrBandUserProfile({ data }: { data: Profile }) {
</div>
</div>
<div className="inline-flex items-center gap-2">
{followed ? (
<button
type="button"
onClick={() => unfollow(data.pubkey)}
className="inline-flex h-8 w-8 items-center justify-center rounded-md bg-neutral-200 text-neutral-900 backdrop-blur-xl hover:bg-blue-600 hover:text-white dark:bg-neutral-800 dark:text-neutral-100 dark:hover:text-white"
>
<UnfollowIcon className="h-4 w-4" />
</button>
) : (
{!followed ? (
<button
type="button"
onClick={() => follow(data.pubkey)}
@@ -116,7 +85,7 @@ export function NostrBandUserProfile({ data }: { data: Profile }) {
>
<FollowIcon className="h-4 w-4" />
</button>
)}
) : null}
</div>
</div>
<div className="mt-2 line-clamp-5 whitespace-pre-line break-all text-neutral-900 dark:text-neutral-100">

View File

@@ -1,6 +1,5 @@
export const FULL_RELAYS = [
'wss://relay.damus.io',
'wss://relayable.org',
'wss://relay.nostr.band/all',
'wss://nostr.mutinywallet.com',
];

View File

@@ -0,0 +1,79 @@
import { MentionOptions } from '@tiptap/extension-mention';
import { ReactRenderer } from '@tiptap/react';
import tippy from 'tippy.js';
import { MentionList } from '@app/new/components';
import { useStorage } from '@libs/storage/provider';
export function useSuggestion() {
const { db } = useStorage();
const suggestion: MentionOptions['suggestion'] = {
items: async ({ query }) => {
const users = await db.getAllCacheUsers();
return users
.filter((item) => {
if (item.name) return item.name.toLowerCase().startsWith(query.toLowerCase());
return item.displayName.toLowerCase().startsWith(query.toLowerCase());
})
.slice(0, 5);
},
render: () => {
let component;
let popup;
return {
onStart: (props) => {
component = new ReactRenderer(MentionList, {
props,
editor: props.editor,
});
if (!props.clientRect) {
return;
}
popup = tippy('body', {
getReferenceClientRect: props.clientRect,
appendTo: () => document.body,
content: component.element,
showOnCreate: true,
interactive: true,
trigger: 'manual',
placement: 'bottom-start',
});
},
onUpdate(props) {
component.updateProps(props);
if (!props.clientRect) {
return;
}
popup[0].setProps({
getReferenceClientRect: props.clientRect,
});
},
onKeyDown(props) {
if (props.event.key === 'Escape') {
popup[0].hide();
return true;
}
return component.ref?.onKeyDown(props);
},
onExit() {
popup[0].destroy();
component.destroy();
},
};
},
};
return { suggestion };
}

View File

@@ -122,6 +122,10 @@ export interface NDKCacheUser {
createdAt: number;
}
export interface NDKCacheUserProfile extends NDKUserProfile {
pubkey: string;
}
export interface NDKCacheEvent {
id: string;
pubkey: string;