Compare commits

...

16 Commits

Author SHA1 Message Date
cf7af1ba64 fix ci again again 2023-11-20 08:47:16 +07:00
933ca758ee fix ci again 2023-11-20 08:27:37 +07:00
f537209b92 fix ci 2023-11-20 08:16:39 +07:00
6777610b07 update dependencies 2023-11-20 08:02:09 +07:00
88803cd3cd bump version 2023-11-19 19:51:00 +07:00
Ren Amamiya
6adf5933b0 Merge pull request #113 from luminous-devs/hotfix/themes
Add support dark mode for toaster
2023-11-19 19:49:59 +07:00
9521a49fff support dark mode for toaster 2023-11-19 19:49:26 +07:00
Ren Amamiya
5789a105f5 Merge pull request #112 from luminous-devs/hotfix/settings
Fix settings screen
2023-11-19 15:15:11 +07:00
b7a18bea34 respect user settings 2023-11-19 14:50:59 +07:00
7117ed05a9 update settings screen 2023-11-19 08:48:01 +07:00
c53bdb68e5 add change theme function 2023-11-18 21:17:37 +07:00
6725dca807 fix all dms bugs 2023-11-17 16:03:12 +07:00
Ren Amamiya
077712cf43 Merge pull request #105 from luminous-devs/feat/v2.1.0
v2.1.0
2023-11-17 10:06:58 +07:00
2794c78ee1 add new private key screen 2023-11-17 09:24:21 +07:00
21574023db update dark mode 2023-11-17 08:16:25 +07:00
954b729dc9 wip: settings screen 2023-11-16 17:48:29 +07:00
48 changed files with 1599 additions and 616 deletions

View File

@@ -2,7 +2,7 @@
"name": "lume",
"description": "the communication app",
"private": true,
"version": "2.1.0",
"version": "2.1.1",
"scripts": {
"dev": "vite",
"build": "vite build",
@@ -19,7 +19,7 @@
},
"dependencies": {
"@evilmartians/harmony": "^1.1.0",
"@getalby/sdk": "^2.5.0",
"@getalby/sdk": "^2.6.0",
"@nostr-dev-kit/ndk": "^2.0.5",
"@nostr-fetch/adapter-ndk": "^0.13.1",
"@radix-ui/react-alert-dialog": "^1.0.5",
@@ -29,11 +29,13 @@
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-hover-card": "^1.0.7",
"@radix-ui/react-popover": "^1.0.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.1",
"@tanstack/react-query": "^5.8.4",
"@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",
"@tauri-apps/plugin-clipboard-manager": "2.0.0-alpha.3",
"@tauri-apps/plugin-dialog": "2.0.0-alpha.3",
"@tauri-apps/plugin-fs": "2.0.0-alpha.3",
@@ -57,13 +59,13 @@
"@tiptap/starter-kit": "^2.1.12",
"@tiptap/suggestion": "^2.1.12",
"dayjs": "^1.11.10",
"framer-motion": "^10.16.4",
"framer-motion": "^10.16.5",
"html-to-text": "^9.0.5",
"idb-keyval": "^6.2.1",
"light-bolt11-decoder": "^3.0.0",
"lru-cache": "^10.0.2",
"lru-cache": "^10.0.3",
"markdown-to-jsx": "^7.3.2",
"media-chrome": "^1.5.2",
"media-chrome": "^1.5.3",
"minidenticons": "^4.2.0",
"nanoid": "^5.0.3",
"nostr-fetch": "^0.13.1",
@@ -75,7 +77,7 @@
"react-dom": "^18.2.0",
"react-hook-form": "^7.48.2",
"react-hotkeys-hook": "^4.4.1",
"react-router-dom": "^6.18.0",
"react-router-dom": "^6.19.0",
"react-string-replace": "^1.1.1",
"reactflow": "^11.10.1",
"sonner": "^1.2.0",
@@ -83,34 +85,34 @@
"tauri-controls": "github:reyamir/tauri-controls",
"tippy.js": "^6.3.7",
"tiptap-markdown": "^0.8.4",
"virtua": "^0.16.2",
"virtua": "^0.16.4",
"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.0",
"@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.10.0",
"@typescript-eslint/parser": "^6.10.0",
"@vitejs/plugin-react-swc": "^3.4.1",
"@typescript-eslint/eslint-plugin": "^6.11.0",
"@typescript-eslint/parser": "^6.11.0",
"@vitejs/plugin-react-swc": "^3.5.0",
"autoprefixer": "^10.4.16",
"clsx": "^2.0.0",
"cross-env": "^7.0.3",
"csstype": "^3.1.2",
"encoding": "^0.1.13",
"eslint": "^8.53.0",
"eslint": "^8.54.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-jsx-a11y": "^6.8.0",
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-simple-import-sort": "^10.0.0",
"husky": "^8.0.3",
"lint-staged": "^15.0.2",
"lint-staged": "^15.1.0",
"postcss": "^8.4.31",
"prettier": "^3.0.3",
"prettier": "^3.1.0",
"prettier-plugin-tailwindcss": "^0.5.7",
"prop-types": "^15.8.1",
"tailwind-merge": "^1.14.0",

395
pnpm-lock.yaml generated
View File

@@ -9,8 +9,8 @@ dependencies:
specifier: ^1.1.0
version: 1.1.0
'@getalby/sdk':
specifier: ^2.5.0
version: 2.5.0
specifier: ^2.6.0
version: 2.6.0
'@nostr-dev-kit/ndk':
specifier: ^2.0.5
version: 2.0.5(typescript@5.2.2)
@@ -38,6 +38,9 @@ dependencies:
'@radix-ui/react-popover':
specifier: ^1.0.7
version: 1.0.7(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-switch':
specifier: ^1.0.3
version: 1.0.3(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-toolbar':
specifier: ^1.0.4
version: 1.0.4(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0)
@@ -45,14 +48,17 @@ dependencies:
specifier: ^1.0.7
version: 1.0.7(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0)
'@tanstack/react-query':
specifier: ^5.8.1
version: 5.8.1(react-dom@18.2.0)(react@18.2.0)
specifier: ^5.8.4
version: 5.8.4(react-dom@18.2.0)(react@18.2.0)
'@tauri-apps/api':
specifier: 2.0.0-alpha.11
version: 2.0.0-alpha.11
'@tauri-apps/cli':
specifier: 2.0.0-alpha.17
version: 2.0.0-alpha.17
'@tauri-apps/plugin-autostart':
specifier: 2.0.0-alpha.3
version: 2.0.0-alpha.3
'@tauri-apps/plugin-clipboard-manager':
specifier: 2.0.0-alpha.3
version: 2.0.0-alpha.3
@@ -123,8 +129,8 @@ dependencies:
specifier: ^1.11.10
version: 1.11.10
framer-motion:
specifier: ^10.16.4
version: 10.16.4(react-dom@18.2.0)(react@18.2.0)
specifier: ^10.16.5
version: 10.16.5(react-dom@18.2.0)(react@18.2.0)
html-to-text:
specifier: ^9.0.5
version: 9.0.5
@@ -135,14 +141,14 @@ dependencies:
specifier: ^3.0.0
version: 3.0.0
lru-cache:
specifier: ^10.0.2
version: 10.0.2
specifier: ^10.0.3
version: 10.0.3
markdown-to-jsx:
specifier: ^7.3.2
version: 7.3.2(react@18.2.0)
media-chrome:
specifier: ^1.5.2
version: 1.5.2
specifier: ^1.5.3
version: 1.5.3
minidenticons:
specifier: ^4.2.0
version: 4.2.0
@@ -177,8 +183,8 @@ dependencies:
specifier: ^4.4.1
version: 4.4.1(react-dom@18.2.0)(react@18.2.0)
react-router-dom:
specifier: ^6.18.0
version: 6.18.0(react-dom@18.2.0)(react@18.2.0)
specifier: ^6.19.0
version: 6.19.0(react-dom@18.2.0)(react@18.2.0)
react-string-replace:
specifier: ^1.1.1
version: 1.1.1
@@ -201,8 +207,8 @@ dependencies:
specifier: ^0.8.4
version: 0.8.4(@tiptap/core@2.1.12)
virtua:
specifier: ^0.16.2
version: 0.16.2(react-dom@18.2.0)(react@18.2.0)
specifier: ^0.16.4
version: 0.16.4(react-dom@18.2.0)(react@18.2.0)
zustand:
specifier: ^4.4.6
version: 4.4.6(@types/react@18.2.37)(react@18.2.0)
@@ -213,13 +219,13 @@ devDependencies:
version: 0.5.10(tailwindcss@3.3.5)
'@trivago/prettier-plugin-sort-imports':
specifier: ^4.3.0
version: 4.3.0(prettier@3.0.3)
version: 4.3.0(prettier@3.1.0)
'@types/html-to-text':
specifier: ^9.0.4
version: 9.0.4
'@types/node':
specifier: ^20.9.0
version: 20.9.0
specifier: ^20.9.2
version: 20.9.2
'@types/react':
specifier: ^18.2.37
version: 18.2.37
@@ -230,14 +236,14 @@ devDependencies:
specifier: ^5.5.10
version: 5.5.10
'@typescript-eslint/eslint-plugin':
specifier: ^6.10.0
version: 6.10.0(@typescript-eslint/parser@6.10.0)(eslint@8.53.0)(typescript@5.2.2)
specifier: ^6.11.0
version: 6.11.0(@typescript-eslint/parser@6.11.0)(eslint@8.54.0)(typescript@5.2.2)
'@typescript-eslint/parser':
specifier: ^6.10.0
version: 6.10.0(eslint@8.53.0)(typescript@5.2.2)
specifier: ^6.11.0
version: 6.11.0(eslint@8.54.0)(typescript@5.2.2)
'@vitejs/plugin-react-swc':
specifier: ^3.4.1
version: 3.4.1(vite@4.5.0)
specifier: ^3.5.0
version: 3.5.0(vite@4.5.0)
autoprefixer:
specifier: ^10.4.16
version: 10.4.16(postcss@8.4.31)
@@ -254,35 +260,35 @@ devDependencies:
specifier: ^0.1.13
version: 0.1.13
eslint:
specifier: ^8.53.0
version: 8.53.0
specifier: ^8.54.0
version: 8.54.0
eslint-config-prettier:
specifier: ^9.0.0
version: 9.0.0(eslint@8.53.0)
version: 9.0.0(eslint@8.54.0)
eslint-plugin-jsx-a11y:
specifier: ^6.8.0
version: 6.8.0(eslint@8.53.0)
version: 6.8.0(eslint@8.54.0)
eslint-plugin-react:
specifier: ^7.33.2
version: 7.33.2(eslint@8.53.0)
version: 7.33.2(eslint@8.54.0)
eslint-plugin-simple-import-sort:
specifier: ^10.0.0
version: 10.0.0(eslint@8.53.0)
version: 10.0.0(eslint@8.54.0)
husky:
specifier: ^8.0.3
version: 8.0.3
lint-staged:
specifier: ^15.0.2
version: 15.0.2
specifier: ^15.1.0
version: 15.1.0
postcss:
specifier: ^8.4.31
version: 8.4.31
prettier:
specifier: ^3.0.3
version: 3.0.3
specifier: ^3.1.0
version: 3.1.0
prettier-plugin-tailwindcss:
specifier: ^0.5.7
version: 0.5.7(@trivago/prettier-plugin-sort-imports@4.3.0)(prettier@3.0.3)
version: 0.5.7(@trivago/prettier-plugin-sort-imports@4.3.0)(prettier@3.1.0)
prop-types:
specifier: ^15.8.1
version: 15.8.1
@@ -297,7 +303,7 @@ devDependencies:
version: 5.2.2
vite:
specifier: ^4.5.0
version: 4.5.0(@types/node@20.9.0)
version: 4.5.0(@types/node@20.9.2)
vite-tsconfig-paths:
specifier: ^4.2.1
version: 4.2.1(typescript@5.2.2)(vite@4.5.0)
@@ -656,13 +662,13 @@ packages:
dev: true
optional: true
/@eslint-community/eslint-utils@4.4.0(eslint@8.53.0):
/@eslint-community/eslint-utils@4.4.0(eslint@8.54.0):
resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
dependencies:
eslint: 8.53.0
eslint: 8.54.0
eslint-visitor-keys: 3.4.3
dev: true
@@ -679,7 +685,7 @@ packages:
debug: 4.3.4
espree: 9.6.1
globals: 13.23.0
ignore: 5.2.4
ignore: 5.3.0
import-fresh: 3.3.0
js-yaml: 4.1.0
minimatch: 3.1.2
@@ -688,8 +694,8 @@ packages:
- supports-color
dev: true
/@eslint/js@8.53.0:
resolution: {integrity: sha512-Kn7K8dx/5U6+cT1yEhpX1w4PCSg0M+XyRILPgvwcEBjerFWCwQj5sbr3/VmxqV0JGHCBCzyd6LxypEuehypY1w==}
/@eslint/js@8.54.0:
resolution: {integrity: sha512-ut5V+D+fOoWPgGGNj83GGjnntO39xDy6DWxO0wb7Jp3DcMX0TfIqdzHF85VTQkerdyGmuuMD9AKAo5KiNlf/AQ==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dev: true
@@ -725,8 +731,8 @@ packages:
resolution: {integrity: sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A==}
dev: false
/@getalby/sdk@2.5.0:
resolution: {integrity: sha512-MRLgI6WxCCLgrar+qDqm/UhKs+V6yXzNm4y1bJRAuN72nkKT+TjTJHCmk9GjTngR3FrOfLbeMsPwBxCmbvfrLQ==}
/@getalby/sdk@2.6.0:
resolution: {integrity: sha512-klD1FrpGY39QldPf00KtzIveWj0dXtgGdZH6nLWdJ0Ness7FAqy2RkgSF7JQfKJeJSFek7Ip3AL/KCT5/lZgww==}
engines: {node: '>=14'}
dependencies:
crypto-js: 4.2.0
@@ -1438,6 +1444,33 @@ packages:
react: 18.2.0
dev: false
/@radix-ui/react-switch@1.0.3(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-mxm87F88HyHztsI7N+ZUmEoARGkC22YVW5CaC+Byc+HRpuvCrOBPTAnXgf+tZ/7i0Sg/eOePGdMhUKhPaQEqow==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0
react-dom: ^16.8 || ^17.0 || ^18.0
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
dependencies:
'@babel/runtime': 7.23.2
'@radix-ui/primitive': 1.0.1
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.37)(react@18.2.0)
'@radix-ui/react-context': 1.0.1(@types/react@18.2.37)(react@18.2.0)
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.37)(react@18.2.0)
'@radix-ui/react-use-previous': 1.0.1(@types/react@18.2.37)(react@18.2.0)
'@radix-ui/react-use-size': 1.0.1(@types/react@18.2.37)(react@18.2.0)
'@types/react': 18.2.37
'@types/react-dom': 18.2.15
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
/@radix-ui/react-toggle-group@1.0.4(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-Uaj/M/cMyiyT9Bx6fOZO0SAG4Cls0GptBWiBmBxofmDbNVnYYoyRWj/2M/6VCi/7qcXFWnHhRUfdfZFvvkuu8A==}
peerDependencies:
@@ -1605,6 +1638,20 @@ packages:
react: 18.2.0
dev: false
/@radix-ui/react-use-previous@1.0.1(@types/react@18.2.37)(react@18.2.0):
resolution: {integrity: sha512-cV5La9DPwiQ7S0gf/0qiD6YgNqM5Fk97Kdrlc5yBcrF3jyEZQwm7vYFqMo4IfeHgJXsRaMvLABFtd0OVEmZhDw==}
peerDependencies:
'@types/react': '*'
react: ^16.8 || ^17.0 || ^18.0
peerDependenciesMeta:
'@types/react':
optional: true
dependencies:
'@babel/runtime': 7.23.2
'@types/react': 18.2.37
react: 18.2.0
dev: false
/@radix-ui/react-use-rect@1.0.1(@types/react@18.2.37)(react@18.2.0):
resolution: {integrity: sha512-Cq5DLuSiuYVKNU8orzJMbl15TXilTnJKUCltMVQg53BQOF1/C5toAaGrowkgksdBQ9H+SRL23g0HDmg9tvmxXw==}
peerDependencies:
@@ -1798,8 +1845,8 @@ packages:
type-fest: 2.19.0
dev: false
/@remix-run/router@1.11.0:
resolution: {integrity: sha512-BHdhcWgeiudl91HvVa2wxqZjSHbheSgIiDvxrF1VjFzBzpTtuDPkOdOi3Iqvc08kXtFkLjhbS+ML9aM8mJS+wQ==}
/@remix-run/router@1.12.0:
resolution: {integrity: sha512-2hXv036Bux90e1GXTWSMfNzfDDK8LA8JYEWfyHxzvwdp6GyoWEovKc9cotb3KCKmkdwsIBuFGX7ScTWyiHv7Eg==}
engines: {node: '>=14.0.0'}
dev: false
@@ -1968,12 +2015,12 @@ packages:
tailwindcss: 3.3.5
dev: true
/@tanstack/query-core@5.8.1:
resolution: {integrity: sha512-Y0enatz2zQXBAsd7XmajlCs+WaitdR7dIFkqz9Xd7HL4KV04JOigWVreYseTmNH7YFSBSC/BJ9uuNp1MAf+GfA==}
/@tanstack/query-core@5.8.3:
resolution: {integrity: sha512-SWFMFtcHfttLYif6pevnnMYnBvxKf3C+MHMH7bevyYfpXpTMsLB9O6nNGBdWSoPwnZRXFNyNeVZOw25Wmdasow==}
dev: false
/@tanstack/react-query@5.8.1(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-YMagxS8iNPOLg0pK6WOjdSDlAvWKOf69udLOwQrBVmkC2SRLNLko7elo5Ro3ptlJkXvTVHidxC/h5KGi5bH1XQ==}
/@tanstack/react-query@5.8.4(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-CD+AkXzg8J72JrE6ocmuBEJfGzEzu/bzkD6sFXFDDB5yji9N20JofXZlN6n0+CaPJuIi+e4YLCbGsyPFKkfNQA==}
peerDependencies:
react: ^18.0.0
react-dom: ^18.0.0
@@ -1984,7 +2031,7 @@ packages:
react-native:
optional: true
dependencies:
'@tanstack/query-core': 5.8.1
'@tanstack/query-core': 5.8.3
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
@@ -2101,6 +2148,12 @@ packages:
'@tauri-apps/cli-win32-x64-msvc': 2.0.0-alpha.17
dev: false
/@tauri-apps/plugin-autostart@2.0.0-alpha.3:
resolution: {integrity: sha512-FWXMun68YPs+czGj063B/R2ItK0lFAHz08GCY8Ez1v5qGfq48MqBVF6EB5AHqQ73Wyq3+RGgDTyxuRFzBnXr6A==}
dependencies:
'@tauri-apps/api': 2.0.0-alpha.11
dev: false
/@tauri-apps/plugin-clipboard-manager@2.0.0-alpha.3:
resolution: {integrity: sha512-Lo30EM8VRo9bYMeRHhZT65OUgajbxaK1A9UhD7/9VZIFoWGbzKU/jrP78mcJ77lc+RrcCcSJvkAgOaLtuOlhxw==}
dependencies:
@@ -2400,7 +2453,7 @@ packages:
prosemirror-dropcursor: 1.8.1
prosemirror-gapcursor: 1.3.2
prosemirror-history: 1.3.2
prosemirror-inputrules: 1.2.1
prosemirror-inputrules: 1.3.0
prosemirror-keymap: 1.2.2
prosemirror-markdown: 1.11.2
prosemirror-menu: 1.2.4
@@ -2466,7 +2519,7 @@ packages:
'@tiptap/pm': 2.1.12
dev: false
/@trivago/prettier-plugin-sort-imports@4.3.0(prettier@3.0.3):
/@trivago/prettier-plugin-sort-imports@4.3.0(prettier@3.1.0):
resolution: {integrity: sha512-r3n0onD3BTOVUNPhR4lhVK4/pABGpbA7bW3eumZnYdKaHkf1qEC+Mag6DPbGNuuh0eG8AaYj+YqmVHSiGslaTQ==}
peerDependencies:
'@vue/compiler-sfc': 3.x
@@ -2481,7 +2534,7 @@ packages:
'@babel/types': 7.17.0
javascript-natural-sort: 0.7.1
lodash: 4.17.21
prettier: 3.0.3
prettier: 3.1.0
transitivePeerDependencies:
- supports-color
dev: true
@@ -2553,8 +2606,8 @@ packages:
resolution: {integrity: sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==}
dev: false
/@types/d3-geo@3.0.8:
resolution: {integrity: sha512-eDivwKKB4ELHw93QNxigleCxZczcyrFgtKyHK5HoCQoeUD0i5feT8i5tH6RxXvt5hPPhpruvKWDSwvfsW448zQ==}
/@types/d3-geo@3.1.0:
resolution: {integrity: sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==}
dependencies:
'@types/geojson': 7946.0.13
dev: false
@@ -2647,7 +2700,7 @@ packages:
'@types/d3-fetch': 3.0.7
'@types/d3-force': 3.0.9
'@types/d3-format': 3.0.4
'@types/d3-geo': 3.0.8
'@types/d3-geo': 3.1.0
'@types/d3-hierarchy': 3.1.6
'@types/d3-interpolate': 3.0.4
'@types/d3-path': 3.0.2
@@ -2692,8 +2745,8 @@ packages:
resolution: {integrity: sha512-6L6VymKTzYSrEf4Nev4Xa1LCHKrlTlYCBMTlQKFuddo1CvQcE52I0mwfOJayueUC7MJuXOeHTcIU683lzd0cUA==}
dev: false
/@types/node@20.9.0:
resolution: {integrity: sha512-nekiGu2NDb1BcVofVcEKMIwzlx4NjHlcjhoxxKBNLtz15Y1z7MYf549DFvkHSId02Ax6kGwWntIBPC3l/JZcmw==}
/@types/node@20.9.2:
resolution: {integrity: sha512-WHZXKFCEyIUJzAwh3NyyTHYSR35SevJ6mZ1nWwJafKtiQbqRTIKSRcw3Ma3acqgsent3RRDqeVwpHntMk+9irg==}
dependencies:
undici-types: 5.26.5
dev: true
@@ -2736,8 +2789,8 @@ packages:
resolution: {integrity: sha512-A45rtI9nc4k/a1xdrSxE4tYO9MbGJg4esuCrTXNaB3v/ZqpeBQrD1OQwmKwZA9TMdg8VR8DPJY37pFOEBSuWIg==}
dev: true
/@typescript-eslint/eslint-plugin@6.10.0(@typescript-eslint/parser@6.10.0)(eslint@8.53.0)(typescript@5.2.2):
resolution: {integrity: sha512-uoLj4g2OTL8rfUQVx2AFO1hp/zja1wABJq77P6IclQs6I/m9GLrm7jCdgzZkvWdDCQf1uEvoa8s8CupsgWQgVg==}
/@typescript-eslint/eslint-plugin@6.11.0(@typescript-eslint/parser@6.11.0)(eslint@8.54.0)(typescript@5.2.2):
resolution: {integrity: sha512-uXnpZDc4VRjY4iuypDBKzW1rz9T5YBBK0snMn8MaTSNd2kMlj50LnLBABELjJiOL5YHk7ZD8hbSpI9ubzqYI0w==}
engines: {node: ^16.0.0 || >=18.0.0}
peerDependencies:
'@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha
@@ -2748,15 +2801,15 @@ packages:
optional: true
dependencies:
'@eslint-community/regexpp': 4.10.0
'@typescript-eslint/parser': 6.10.0(eslint@8.53.0)(typescript@5.2.2)
'@typescript-eslint/scope-manager': 6.10.0
'@typescript-eslint/type-utils': 6.10.0(eslint@8.53.0)(typescript@5.2.2)
'@typescript-eslint/utils': 6.10.0(eslint@8.53.0)(typescript@5.2.2)
'@typescript-eslint/visitor-keys': 6.10.0
'@typescript-eslint/parser': 6.11.0(eslint@8.54.0)(typescript@5.2.2)
'@typescript-eslint/scope-manager': 6.11.0
'@typescript-eslint/type-utils': 6.11.0(eslint@8.54.0)(typescript@5.2.2)
'@typescript-eslint/utils': 6.11.0(eslint@8.54.0)(typescript@5.2.2)
'@typescript-eslint/visitor-keys': 6.11.0
debug: 4.3.4
eslint: 8.53.0
eslint: 8.54.0
graphemer: 1.4.0
ignore: 5.2.4
ignore: 5.3.0
natural-compare: 1.4.0
semver: 7.5.4
ts-api-utils: 1.0.3(typescript@5.2.2)
@@ -2765,8 +2818,8 @@ packages:
- supports-color
dev: true
/@typescript-eslint/parser@6.10.0(eslint@8.53.0)(typescript@5.2.2):
resolution: {integrity: sha512-+sZwIj+s+io9ozSxIWbNB5873OSdfeBEH/FR0re14WLI6BaKuSOnnwCJ2foUiu8uXf4dRp1UqHP0vrZ1zXGrog==}
/@typescript-eslint/parser@6.11.0(eslint@8.54.0)(typescript@5.2.2):
resolution: {integrity: sha512-+whEdjk+d5do5nxfxx73oanLL9ghKO3EwM9kBCkUtWMRwWuPaFv9ScuqlYfQ6pAD6ZiJhky7TZ2ZYhrMsfMxVQ==}
engines: {node: ^16.0.0 || >=18.0.0}
peerDependencies:
eslint: ^7.0.0 || ^8.0.0
@@ -2775,27 +2828,27 @@ packages:
typescript:
optional: true
dependencies:
'@typescript-eslint/scope-manager': 6.10.0
'@typescript-eslint/types': 6.10.0
'@typescript-eslint/typescript-estree': 6.10.0(typescript@5.2.2)
'@typescript-eslint/visitor-keys': 6.10.0
'@typescript-eslint/scope-manager': 6.11.0
'@typescript-eslint/types': 6.11.0
'@typescript-eslint/typescript-estree': 6.11.0(typescript@5.2.2)
'@typescript-eslint/visitor-keys': 6.11.0
debug: 4.3.4
eslint: 8.53.0
eslint: 8.54.0
typescript: 5.2.2
transitivePeerDependencies:
- supports-color
dev: true
/@typescript-eslint/scope-manager@6.10.0:
resolution: {integrity: sha512-TN/plV7dzqqC2iPNf1KrxozDgZs53Gfgg5ZHyw8erd6jd5Ta/JIEcdCheXFt9b1NYb93a1wmIIVW/2gLkombDg==}
/@typescript-eslint/scope-manager@6.11.0:
resolution: {integrity: sha512-0A8KoVvIURG4uhxAdjSaxy8RdRE//HztaZdG8KiHLP8WOXSk0vlF7Pvogv+vlJA5Rnjj/wDcFENvDaHb+gKd1A==}
engines: {node: ^16.0.0 || >=18.0.0}
dependencies:
'@typescript-eslint/types': 6.10.0
'@typescript-eslint/visitor-keys': 6.10.0
'@typescript-eslint/types': 6.11.0
'@typescript-eslint/visitor-keys': 6.11.0
dev: true
/@typescript-eslint/type-utils@6.10.0(eslint@8.53.0)(typescript@5.2.2):
resolution: {integrity: sha512-wYpPs3hgTFblMYwbYWPT3eZtaDOjbLyIYuqpwuLBBqhLiuvJ+9sEp2gNRJEtR5N/c9G1uTtQQL5AhV0fEPJYcg==}
/@typescript-eslint/type-utils@6.11.0(eslint@8.54.0)(typescript@5.2.2):
resolution: {integrity: sha512-nA4IOXwZtqBjIoYrJcYxLRO+F9ri+leVGoJcMW1uqr4r1Hq7vW5cyWrA43lFbpRvQ9XgNrnfLpIkO3i1emDBIA==}
engines: {node: ^16.0.0 || >=18.0.0}
peerDependencies:
eslint: ^7.0.0 || ^8.0.0
@@ -2804,23 +2857,23 @@ packages:
typescript:
optional: true
dependencies:
'@typescript-eslint/typescript-estree': 6.10.0(typescript@5.2.2)
'@typescript-eslint/utils': 6.10.0(eslint@8.53.0)(typescript@5.2.2)
'@typescript-eslint/typescript-estree': 6.11.0(typescript@5.2.2)
'@typescript-eslint/utils': 6.11.0(eslint@8.54.0)(typescript@5.2.2)
debug: 4.3.4
eslint: 8.53.0
eslint: 8.54.0
ts-api-utils: 1.0.3(typescript@5.2.2)
typescript: 5.2.2
transitivePeerDependencies:
- supports-color
dev: true
/@typescript-eslint/types@6.10.0:
resolution: {integrity: sha512-36Fq1PWh9dusgo3vH7qmQAj5/AZqARky1Wi6WpINxB6SkQdY5vQoT2/7rW7uBIsPDcvvGCLi4r10p0OJ7ITAeg==}
/@typescript-eslint/types@6.11.0:
resolution: {integrity: sha512-ZbEzuD4DwEJxwPqhv3QULlRj8KYTAnNsXxmfuUXFCxZmO6CF2gM/y+ugBSAQhrqaJL3M+oe4owdWunaHM6beqA==}
engines: {node: ^16.0.0 || >=18.0.0}
dev: true
/@typescript-eslint/typescript-estree@6.10.0(typescript@5.2.2):
resolution: {integrity: sha512-ek0Eyuy6P15LJVeghbWhSrBCj/vJpPXXR+EpaRZqou7achUWL8IdYnMSC5WHAeTWswYQuP2hAZgij/bC9fanBg==}
/@typescript-eslint/typescript-estree@6.11.0(typescript@5.2.2):
resolution: {integrity: sha512-Aezzv1o2tWJwvZhedzvD5Yv7+Lpu1by/U1LZ5gLc4tCx8jUmuSCMioPFRjliN/6SJIvY6HpTtJIWubKuYYYesQ==}
engines: {node: ^16.0.0 || >=18.0.0}
peerDependencies:
typescript: '*'
@@ -2828,8 +2881,8 @@ packages:
typescript:
optional: true
dependencies:
'@typescript-eslint/types': 6.10.0
'@typescript-eslint/visitor-keys': 6.10.0
'@typescript-eslint/types': 6.11.0
'@typescript-eslint/visitor-keys': 6.11.0
debug: 4.3.4
globby: 11.1.0
is-glob: 4.0.3
@@ -2840,30 +2893,30 @@ packages:
- supports-color
dev: true
/@typescript-eslint/utils@6.10.0(eslint@8.53.0)(typescript@5.2.2):
resolution: {integrity: sha512-v+pJ1/RcVyRc0o4wAGux9x42RHmAjIGzPRo538Z8M1tVx6HOnoQBCX/NoadHQlZeC+QO2yr4nNSFWOoraZCAyg==}
/@typescript-eslint/utils@6.11.0(eslint@8.54.0)(typescript@5.2.2):
resolution: {integrity: sha512-p23ibf68fxoZy605dc0dQAEoUsoiNoP3MD9WQGiHLDuTSOuqoTsa4oAy+h3KDkTcxbbfOtUjb9h3Ta0gT4ug2g==}
engines: {node: ^16.0.0 || >=18.0.0}
peerDependencies:
eslint: ^7.0.0 || ^8.0.0
dependencies:
'@eslint-community/eslint-utils': 4.4.0(eslint@8.53.0)
'@eslint-community/eslint-utils': 4.4.0(eslint@8.54.0)
'@types/json-schema': 7.0.15
'@types/semver': 7.5.5
'@typescript-eslint/scope-manager': 6.10.0
'@typescript-eslint/types': 6.10.0
'@typescript-eslint/typescript-estree': 6.10.0(typescript@5.2.2)
eslint: 8.53.0
'@typescript-eslint/scope-manager': 6.11.0
'@typescript-eslint/types': 6.11.0
'@typescript-eslint/typescript-estree': 6.11.0(typescript@5.2.2)
eslint: 8.54.0
semver: 7.5.4
transitivePeerDependencies:
- supports-color
- typescript
dev: true
/@typescript-eslint/visitor-keys@6.10.0:
resolution: {integrity: sha512-xMGluxQIEtOM7bqFCo+rCMh5fqI+ZxV5RUUOa29iVPz1OgCZrtc7rFnz5cLUazlkPKYqX+75iuDq7m0HQ48nCg==}
/@typescript-eslint/visitor-keys@6.11.0:
resolution: {integrity: sha512-+SUN/W7WjBr05uRxPggJPSzyB8zUpaYo2hByKasWbqr3PM8AXfZt8UHdNpBS1v9SA62qnSSMF3380SwDqqprgQ==}
engines: {node: ^16.0.0 || >=18.0.0}
dependencies:
'@typescript-eslint/types': 6.10.0
'@typescript-eslint/types': 6.11.0
eslint-visitor-keys: 3.4.3
dev: true
@@ -2871,13 +2924,13 @@ packages:
resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==}
dev: true
/@vitejs/plugin-react-swc@3.4.1(vite@4.5.0):
resolution: {integrity: sha512-7YQOQcVV5x1luD8nkbCDdyYygFvn1hjqJk68UvNAzY2QG4o4N5EwAhLLFNOcd1HrdMwDl0VElP8VutoWf9IvJg==}
/@vitejs/plugin-react-swc@3.5.0(vite@4.5.0):
resolution: {integrity: sha512-1PrOvAaDpqlCV+Up8RkAh9qaiUjoDUcjtttyhXDKw53XA6Ve16SOp6cCOpRs8Dj8DqUQs6eTW5YkLcLJjrXAig==}
peerDependencies:
vite: ^4
vite: ^4 || ^5
dependencies:
'@swc/core': 1.3.96
vite: 4.5.0(@types/node@20.9.0)
vite: 4.5.0(@types/node@20.9.2)
transitivePeerDependencies:
- '@swc/helpers'
dev: true
@@ -3054,7 +3107,7 @@ packages:
postcss: ^8.1.0
dependencies:
browserslist: 4.22.1
caniuse-lite: 1.0.30001561
caniuse-lite: 1.0.30001563
fraction.js: 4.3.7
normalize-range: 0.1.2
picocolors: 1.0.0
@@ -3102,8 +3155,8 @@ packages:
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
hasBin: true
dependencies:
caniuse-lite: 1.0.30001561
electron-to-chromium: 1.4.581
caniuse-lite: 1.0.30001563
electron-to-chromium: 1.4.588
node-releases: 2.0.13
update-browserslist-db: 1.0.13(browserslist@4.22.1)
dev: true
@@ -3113,7 +3166,7 @@ packages:
engines: {node: '>=6.14.2'}
requiresBuild: true
dependencies:
node-gyp-build: 4.6.1
node-gyp-build: 4.7.0
dev: false
/call-bind@1.0.5:
@@ -3133,8 +3186,8 @@ packages:
resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==}
engines: {node: '>= 6'}
/caniuse-lite@1.0.30001561:
resolution: {integrity: sha512-NTt0DNoKe958Q0BE0j0c1V9jbUzhBxHIEJy7asmGrpE0yG63KTV7PLHPnK2E1O9RsQrQ081I3NLuXGS6zht3cw==}
/caniuse-lite@1.0.30001563:
resolution: {integrity: sha512-na2WUmOxnwIZtwnFI2CZ/3er0wdNzU7hN+cPYz/z2ajHThnkWjNBOpEPP4n+4r2WPM847JaMotaJE3bnfzjyKw==}
dev: true
/case-anything@2.1.13:
@@ -3476,8 +3529,8 @@ packages:
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
dev: true
/electron-to-chromium@1.4.581:
resolution: {integrity: sha512-6uhqWBIapTJUxgPTCHH9sqdbxIMPt7oXl0VcAL1kOtlU6aECdcMncCrX5Z7sHQ/invtrC9jUQUef7+HhO8vVFw==}
/electron-to-chromium@1.4.588:
resolution: {integrity: sha512-soytjxwbgcCu7nh5Pf4S2/4wa6UIu+A3p03U2yVr53qGxi1/VTR3ENI+p50v+UxqqZAfl48j3z55ud7VHIOr9w==}
dev: true
/emoji-regex@9.2.2:
@@ -3657,16 +3710,16 @@ packages:
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
engines: {node: '>=10'}
/eslint-config-prettier@9.0.0(eslint@8.53.0):
/eslint-config-prettier@9.0.0(eslint@8.54.0):
resolution: {integrity: sha512-IcJsTkJae2S35pRsRAwoCE+925rJJStOdkKnLVgtE+tEpqU0EVVM7OqrwxqgptKdX29NUwC82I5pXsGFIgSevw==}
hasBin: true
peerDependencies:
eslint: '>=7.0.0'
dependencies:
eslint: 8.53.0
eslint: 8.54.0
dev: true
/eslint-plugin-jsx-a11y@6.8.0(eslint@8.53.0):
/eslint-plugin-jsx-a11y@6.8.0(eslint@8.54.0):
resolution: {integrity: sha512-Hdh937BS3KdwwbBaKd5+PLCOmYY6U4f2h9Z2ktwtNKvIdIEu137rjYbcb9ApSbVJfWxANNuiKTD/9tOKjK9qOA==}
engines: {node: '>=4.0'}
peerDependencies:
@@ -3682,7 +3735,7 @@ packages:
damerau-levenshtein: 1.0.8
emoji-regex: 9.2.2
es-iterator-helpers: 1.0.15
eslint: 8.53.0
eslint: 8.54.0
hasown: 2.0.0
jsx-ast-utils: 3.3.5
language-tags: 1.0.9
@@ -3691,7 +3744,7 @@ packages:
object.fromentries: 2.0.7
dev: true
/eslint-plugin-react@7.33.2(eslint@8.53.0):
/eslint-plugin-react@7.33.2(eslint@8.54.0):
resolution: {integrity: sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw==}
engines: {node: '>=4'}
peerDependencies:
@@ -3702,7 +3755,7 @@ packages:
array.prototype.tosorted: 1.1.2
doctrine: 2.1.0
es-iterator-helpers: 1.0.15
eslint: 8.53.0
eslint: 8.54.0
estraverse: 5.3.0
jsx-ast-utils: 3.3.5
minimatch: 3.1.2
@@ -3716,12 +3769,12 @@ packages:
string.prototype.matchall: 4.0.10
dev: true
/eslint-plugin-simple-import-sort@10.0.0(eslint@8.53.0):
/eslint-plugin-simple-import-sort@10.0.0(eslint@8.54.0):
resolution: {integrity: sha512-AeTvO9UCMSNzIHRkg8S6c3RPy5YEwKWSQPx3DYghLedo2ZQxowPFLGDN1AZ2evfg6r6mjBSZSLxLFsWSu3acsw==}
peerDependencies:
eslint: '>=5.0.0'
dependencies:
eslint: 8.53.0
eslint: 8.54.0
dev: true
/eslint-scope@7.2.2:
@@ -3737,15 +3790,15 @@ packages:
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dev: true
/eslint@8.53.0:
resolution: {integrity: sha512-N4VuiPjXDUa4xVeV/GC/RV3hQW9Nw+Y463lkWaKKXKYMvmRiRDAtfpuPFLN+E1/6ZhyR8J2ig+eVREnYgUsiag==}
/eslint@8.54.0:
resolution: {integrity: sha512-NY0DfAkM8BIZDVl6PgSa1ttZbx3xHgJzSNJKYcQglem6CppHyMhRIQkBVSSMaSRnLhig3jsDbEzOjwCVt4AmmA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
hasBin: true
dependencies:
'@eslint-community/eslint-utils': 4.4.0(eslint@8.53.0)
'@eslint-community/eslint-utils': 4.4.0(eslint@8.54.0)
'@eslint-community/regexpp': 4.10.0
'@eslint/eslintrc': 2.1.3
'@eslint/js': 8.53.0
'@eslint/js': 8.54.0
'@humanwhocodes/config-array': 0.11.13
'@humanwhocodes/module-importer': 1.0.1
'@nodelib/fs.walk': 1.2.8
@@ -3767,7 +3820,7 @@ packages:
glob-parent: 6.0.2
globals: 13.23.0
graphemer: 1.4.0
ignore: 5.2.4
ignore: 5.3.0
imurmurhash: 0.1.4
is-glob: 4.0.3
is-path-inside: 3.0.3
@@ -3884,7 +3937,7 @@ packages:
resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==}
engines: {node: ^10.12.0 || >=12.0.0}
dependencies:
flat-cache: 3.1.1
flat-cache: 3.2.0
dev: true
/fill-range@7.0.1:
@@ -3901,9 +3954,9 @@ packages:
path-exists: 4.0.0
dev: true
/flat-cache@3.1.1:
resolution: {integrity: sha512-/qM2b3LUIaIgviBQovTLvijfyOQXPtSRnRK26ksj2J7rzPIecePUIpJsZ4T02Qg+xiAEKIs5K8dsHEd+VaKa/Q==}
engines: {node: '>=12.0.0'}
/flat-cache@3.2.0:
resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==}
engines: {node: ^10.12.0 || >=12.0.0}
dependencies:
flatted: 3.2.9
keyv: 4.5.4
@@ -3931,8 +3984,8 @@ packages:
resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
dev: true
/framer-motion@10.16.4(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-p9V9nGomS3m6/CALXqv6nFGMuFOxbWsmaOrdmhyQimMIlLl3LC7h7l86wge/Js/8cRu5ktutS/zlzgR7eBOtFA==}
/framer-motion@10.16.5(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-GEzVjOYP2MIpV9bT/GbhcsBNoImG3/2X3O/xVNWmktkv9MdJ7P/44zELm/7Fjb+O3v39SmKFnoDQB32giThzpg==}
peerDependencies:
react: ^18.0.0
react-dom: ^18.0.0
@@ -4062,7 +4115,7 @@ packages:
array-union: 2.1.0
dir-glob: 3.0.1
fast-glob: 3.3.2
ignore: 5.2.4
ignore: 5.3.0
merge2: 1.4.1
slash: 3.0.0
dev: true
@@ -4166,8 +4219,8 @@ packages:
resolution: {integrity: sha512-8Sb3veuYCyrZL+VBt9LJfZjLUPWVvqn8tG28VqYNFCo43KHcKuq+b4EiXGeuaLAQWL2YmyDgMp2aSpH9JHsEQg==}
dev: false
/ignore@5.2.4:
resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==}
/ignore@5.3.0:
resolution: {integrity: sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==}
engines: {node: '>= 4'}
dev: true
@@ -4500,6 +4553,10 @@ packages:
resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==}
engines: {node: '>=10'}
/lilconfig@3.0.0:
resolution: {integrity: sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==}
engines: {node: '>=14'}
/lines-and-columns@1.2.4:
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
@@ -4509,8 +4566,8 @@ packages:
uc.micro: 1.0.6
dev: false
/lint-staged@15.0.2:
resolution: {integrity: sha512-vnEy7pFTHyVuDmCAIFKR5QDO8XLVlPFQQyujQ/STOxe40ICWqJ6knS2wSJ/ffX/Lw0rz83luRDh+ET7toN+rOw==}
/lint-staged@15.1.0:
resolution: {integrity: sha512-ZPKXWHVlL7uwVpy8OZ7YQjYDAuO5X4kMh0XgZvPNxLcCCngd0PO5jKQyy3+s4TL2EnHoIXIzP1422f/l3nZKMw==}
engines: {node: '>=18.12.0'}
hasBin: true
dependencies:
@@ -4523,7 +4580,7 @@ packages:
micromatch: 4.0.5
pidtree: 0.6.0
string-argv: 0.3.2
yaml: 2.3.3
yaml: 2.3.4
transitivePeerDependencies:
- supports-color
dev: true
@@ -4580,11 +4637,9 @@ packages:
dependencies:
js-tokens: 4.0.0
/lru-cache@10.0.2:
resolution: {integrity: sha512-Yj9mA8fPiVgOUpByoTZO5pNrcl5Yk37FcSHsUINpAsaBIEZIuqcCclDZJCVxqQShDsmYX8QG63svJiTbOATZwg==}
/lru-cache@10.0.3:
resolution: {integrity: sha512-B7gr+F6MkqB3uzINHXNctGieGsRTMwIBgxkp0yq/5BwcuDzD4A8wQpHQW6vDAm1uKSLQghmRdD9sKqf2vJ1cEg==}
engines: {node: 14 || >=16.14}
dependencies:
semver: 7.5.4
dev: false
/lru-cache@6.0.0:
@@ -4592,6 +4647,7 @@ packages:
engines: {node: '>=10'}
dependencies:
yallist: 4.0.0
dev: true
/make-error@1.3.6:
resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==}
@@ -4625,8 +4681,8 @@ packages:
resolution: {integrity: sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==}
dev: false
/media-chrome@1.5.2:
resolution: {integrity: sha512-wMWYZXjRqXhv2gYR2arQAMphDXLVVPVmyLQFeI0NjBhKQ36oZujqGiiNBhP3leE4ol+t8oXoziPamt2qAeGpxw==}
/media-chrome@1.5.3:
resolution: {integrity: sha512-XiE29yNa9jyCMdshDLvSgXb0e/RJauMaTXT9kL9dTJmmZXz0RK+Ol/TsE2z4uO68x+rqZauoGEANUbDEyN8NLg==}
dev: false
/merge-stream@2.0.0:
@@ -4711,8 +4767,8 @@ packages:
formdata-polyfill: 4.0.10
dev: false
/node-gyp-build@4.6.1:
resolution: {integrity: sha512-24vnklJmyRS8ViBNI8KbtK/r/DmXQMRiOMXTNz2nrTnAYUwjmEEbnnpB/+kt+yWRv73bPsSPRFddrcIbAxSiMQ==}
/node-gyp-build@4.7.0:
resolution: {integrity: sha512-PbZERfeFdrHQOOXiAKOY0VPbykZy90ndPKk0d+CFDegTKmWp1VgOTz2xACVbr1BjCWxrQp68CXtvNsveFhqDJg==}
hasBin: true
dev: false
@@ -4979,8 +5035,8 @@ packages:
camelcase-css: 2.0.1
postcss: 8.4.31
/postcss-load-config@4.0.1(postcss@8.4.31):
resolution: {integrity: sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==}
/postcss-load-config@4.0.2(postcss@8.4.31):
resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==}
engines: {node: '>= 14'}
peerDependencies:
postcss: '>=8.0.9'
@@ -4991,7 +5047,7 @@ packages:
ts-node:
optional: true
dependencies:
lilconfig: 2.1.0
lilconfig: 3.0.0
postcss: 8.4.31
yaml: 2.3.4
@@ -5035,7 +5091,7 @@ packages:
engines: {node: '>= 0.8.0'}
dev: true
/prettier-plugin-tailwindcss@0.5.7(@trivago/prettier-plugin-sort-imports@4.3.0)(prettier@3.0.3):
/prettier-plugin-tailwindcss@0.5.7(@trivago/prettier-plugin-sort-imports@4.3.0)(prettier@3.1.0):
resolution: {integrity: sha512-4v6uESAgwCni6YF6DwJlRaDjg9Z+al5zM4JfngcazMy4WEf/XkPS5TEQjbD+DZ5iNuG6RrKQLa/HuX2SYzC3kQ==}
engines: {node: '>=14.21.3'}
peerDependencies:
@@ -5087,12 +5143,12 @@ packages:
prettier-plugin-twig-melody:
optional: true
dependencies:
'@trivago/prettier-plugin-sort-imports': 4.3.0(prettier@3.0.3)
prettier: 3.0.3
'@trivago/prettier-plugin-sort-imports': 4.3.0(prettier@3.1.0)
prettier: 3.1.0
dev: true
/prettier@3.0.3:
resolution: {integrity: sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==}
/prettier@3.1.0:
resolution: {integrity: sha512-TQLvXjq5IAibjh8EpBIkNKxO749UEWABoiIZehEPiY4GNpVdhaFKqSTu+QrlU6D2dPAfubRmtJTi4K4YkQ5eXw==}
engines: {node: '>=14'}
hasBin: true
dev: true
@@ -5151,8 +5207,8 @@ packages:
rope-sequence: 1.3.4
dev: false
/prosemirror-inputrules@1.2.1:
resolution: {integrity: sha512-3LrWJX1+ULRh5SZvbIQlwZafOXqp1XuV21MGBu/i5xsztd+9VD15x6OtN6mdqSFI7/8Y77gYUbQ6vwwJ4mr6QQ==}
/prosemirror-inputrules@1.3.0:
resolution: {integrity: sha512-z1GRP2vhh5CihYMQYsJSa1cOwXb3SYxALXOIfAkX8nZserARtl9LiL+CEl+T+OFIsXc3mJIHKhbsmRzC0HDAXA==}
dependencies:
prosemirror-state: 1.4.3
prosemirror-transform: 1.8.0
@@ -5350,26 +5406,26 @@ packages:
use-sidecar: 1.1.2(@types/react@18.2.37)(react@18.2.0)
dev: false
/react-router-dom@6.18.0(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-Ubrue4+Ercc/BoDkFQfc6og5zRQ4A8YxSO3Knsne+eRbZ+IepAsK249XBH/XaFuOYOYr3L3r13CXTLvYt5JDjw==}
/react-router-dom@6.19.0(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-N6dWlcgL2w0U5HZUUqU2wlmOrSb3ighJmtQ438SWbhB1yuLTXQ8yyTBMK3BSvVjp7gBtKurT554nCtMOgxCZmQ==}
engines: {node: '>=14.0.0'}
peerDependencies:
react: '>=16.8'
react-dom: '>=16.8'
dependencies:
'@remix-run/router': 1.11.0
'@remix-run/router': 1.12.0
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
react-router: 6.18.0(react@18.2.0)
react-router: 6.19.0(react@18.2.0)
dev: false
/react-router@6.18.0(react@18.2.0):
resolution: {integrity: sha512-vk2y7Dsy8wI02eRRaRmOs9g2o+aE72YCx5q9VasT1N9v+lrdB79tIqrjMfByHiY5+6aYkH2rUa5X839nwWGPDg==}
/react-router@6.19.0(react@18.2.0):
resolution: {integrity: sha512-0W63PKCZ7+OuQd7Tm+RbkI8kCLmn4GPjDbX61tWljPxWgqTKlEpeQUwPkT1DRjYhF8KSihK0hQpmhU4uxVMcdw==}
engines: {node: '>=14.0.0'}
peerDependencies:
react: '>=16.8'
dependencies:
'@remix-run/router': 1.11.0
'@remix-run/router': 1.12.0
react: 18.2.0
dev: false
@@ -5563,6 +5619,7 @@ packages:
hasBin: true
dependencies:
lru-cache: 6.0.0
dev: true
/set-function-length@1.1.1:
resolution: {integrity: sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==}
@@ -5786,7 +5843,7 @@ packages:
postcss: 8.4.31
postcss-import: 15.1.0(postcss@8.4.31)
postcss-js: 4.0.1(postcss@8.4.31)
postcss-load-config: 4.0.1(postcss@8.4.31)
postcss-load-config: 4.0.2(postcss@8.4.31)
postcss-nested: 6.0.1(postcss@8.4.31)
postcss-selector-parser: 6.0.13
resolve: 1.22.8
@@ -6037,7 +6094,7 @@ packages:
engines: {node: '>=6.14.2'}
requiresBuild: true
dependencies:
node-gyp-build: 4.6.1
node-gyp-build: 4.7.0
dev: false
/utf8-buffer@1.0.0:
@@ -6048,8 +6105,8 @@ packages:
/util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
/virtua@0.16.2(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-NZSYUBjTsoiZcPgjHBnXtx334ATMMDgxxEc5254gVX89rvzaZqVz4ksNX8+PkM2+ZFEWcldLzjPK5yLHBxZVgA==}
/virtua@0.16.4(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-Ilp+G7fJg09IGM3DkSKI0Q+TliLo3vw+1c8A7gmxQ9eaKMBHUJD/C8PrFsjSu2lZgaBPMxHG7x3MenYS7aBy+Q==}
peerDependencies:
react: '>=16.14.0'
react-dom: '>=16.14.0'
@@ -6069,13 +6126,13 @@ packages:
debug: 4.3.4
globrex: 0.1.2
tsconfck: 2.1.2(typescript@5.2.2)
vite: 4.5.0(@types/node@20.9.0)
vite: 4.5.0(@types/node@20.9.2)
transitivePeerDependencies:
- supports-color
- typescript
dev: true
/vite@4.5.0(@types/node@20.9.0):
/vite@4.5.0(@types/node@20.9.2):
resolution: {integrity: sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==}
engines: {node: ^14.18.0 || >=16.0.0}
hasBin: true
@@ -6103,7 +6160,7 @@ packages:
terser:
optional: true
dependencies:
'@types/node': 20.9.0
'@types/node': 20.9.2
esbuild: 0.18.20
postcss: 8.4.31
rollup: 3.29.4
@@ -6218,10 +6275,6 @@ packages:
/yallist@4.0.0:
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
/yaml@2.3.3:
resolution: {integrity: sha512-zw0VAJxgeZ6+++/su5AFoqBbZbrEakwu+X0M5HmcwUiBL7AzcuPKjj5we4xfQLp78LkEMpD0cOnUhmgOVy3KdQ==}
engines: {node: '>= 14'}
dev: true
/yaml@2.3.4:

566
src-tauri/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[package]
name = "lume"
version = "2.1.0"
version = "2.1.1"
description = "the communication app"
authors = ["Ren Amamiya"]
license = "GPL-3.0"
@@ -33,6 +33,7 @@ tauri-plugin-autostart = { git = "https://github.com/tauri-apps/plugins-workspac
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-sql = { git = "hhttps://github.com/tauri-apps/plugins-workspace", branch = "v2", features = [
"sqlite",
] }

View File

@@ -7,6 +7,7 @@ use keyring::Entry;
use std::time::Duration;
use tauri_plugin_autostart::MacosLauncher;
use tauri_plugin_sql::{Migration, MigrationKind};
use tauri_plugin_theme::ThemePlugin;
use webpage::{Webpage, WebpageOptions};
#[derive(Clone, serde::Serialize)]
@@ -105,6 +106,7 @@ fn secure_remove(key: String) -> Result<(), ()> {
}
fn main() {
let mut ctx = tauri::generate_context!();
tauri::Builder::default()
.setup(|app| {
#[cfg(desktop)]
@@ -113,6 +115,7 @@ fn main() {
.plugin(tauri_plugin_updater::Builder::new().build())?;
Ok(())
})
.plugin(ThemePlugin::init(ctx.config_mut()))
.plugin(
tauri_plugin_sql::Builder::default()
.add_migrations(
@@ -134,10 +137,6 @@ fn main() {
)
.build(),
)
.plugin(tauri_plugin_autostart::init(
MacosLauncher::LaunchAgent,
Some(vec!["--flag1", "--flag2"]),
))
.plugin(tauri_plugin_clipboard_manager::init())
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_fs::init())
@@ -148,12 +147,16 @@ fn main() {
.plugin(tauri_plugin_shell::init())
.plugin(tauri_plugin_upload::init())
.plugin(tauri_plugin_window_state::Builder::default().build())
.plugin(tauri_plugin_autostart::init(
MacosLauncher::LaunchAgent,
Some(vec![]),
))
.invoke_handler(tauri::generate_handler![
opengraph,
secure_save,
secure_load,
secure_remove
])
.run(tauri::generate_context!())
.run(ctx)
.expect("error while running tauri application");
}

View File

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

View File

@@ -7,13 +7,13 @@ import { OnboardingScreen } from '@app/auth/onboarding';
import { ChatsScreen } from '@app/chats';
import { ErrorScreen } from '@app/error';
import { ExploreScreen } from '@app/explore';
import { NewScreen } from '@app/new';
import { useStorage } from '@libs/storage/provider';
import { LoaderIcon } from '@shared/icons';
import { AppLayout } from '@shared/layouts/app';
import { AuthLayout } from '@shared/layouts/auth';
import { NewLayout } from '@shared/layouts/new';
import { NoteLayout } from '@shared/layouts/note';
import { SettingsLayout } from '@shared/layouts/settings';
@@ -115,7 +115,7 @@ export default function App() {
},
{
path: '/new',
element: <NewScreen />,
element: <NewLayout />,
errorElement: <ErrorScreen />,
children: [
{
@@ -139,6 +139,13 @@ export default function App() {
return { Component: NewFileScreen };
},
},
{
path: 'privkey',
async lazy() {
const { NewPrivkeyScreen } = await import('@app/new/privkey');
return { Component: NewPrivkeyScreen };
},
},
],
},
{

View File

@@ -45,6 +45,8 @@ 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

@@ -47,7 +47,6 @@ export function OnboardEnrichScreen() {
setLoading(true);
const tags = arrayToNIP02(follows);
const event = new NDKEvent(ndk);
event.content = '';
event.kind = NDKKind.Contacts;

View File

@@ -1,5 +1,5 @@
import { NDKEvent, NDKSubscription } from '@nostr-dev-kit/ndk';
import { useQuery } from '@tanstack/react-query';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { useCallback, useEffect, useRef } from 'react';
import { useParams } from 'react-router-dom';
import { VList, VListHandle } from 'virtua';
@@ -16,8 +16,6 @@ import { User } from '@shared/user';
import { useNostr } from '@utils/hooks/useNostr';
export function ChatScreen() {
const listRef = useRef<VListHandle>(null);
const { db } = useStorage();
const { ndk } = useNDK();
const { pubkey } = useParams();
@@ -30,10 +28,39 @@ export function ChatScreen() {
refetchOnWindowFocus: false,
});
const queryClient = useQueryClient();
const listRef = useRef<VListHandle>(null);
const newMessage = useMutation({
mutationFn: async (event: NDKEvent) => {
// Cancel any outgoing refetches
await queryClient.cancelQueries({ queryKey: ['nip04-dm', pubkey] });
// Snapshot the previous value
const prevMessages = queryClient.getQueryData(['nip04-dm', pubkey]);
// Optimistically update to the new value
queryClient.setQueryData(['nip04-dm', pubkey], (prev: NDKEvent[]) => [
...prev,
event,
]);
// Return a context object with the snapshotted value
return { prevMessages };
},
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ['nip04-dm', pubkey] });
},
});
const renderItem = useCallback(
(message: NDKEvent) => {
return (
<ChatMessage message={message} self={message.pubkey === db.account.pubkey} />
<ChatMessage
key={message.id}
message={message}
isSelf={message.pubkey === db.account.pubkey}
/>
);
},
[data]
@@ -57,7 +84,7 @@ export function ChatScreen() {
);
sub.addListener('event', (event) => {
console.log(event);
newMessage.mutate(event);
});
return () => {
@@ -96,11 +123,7 @@ export function ChatScreen() {
)}
</div>
<div className="shrink-0 rounded-b-lg border-t border-neutral-300 bg-neutral-200 p-3 dark:border-neutral-700 dark:bg-neutral-800">
<ChatForm
receiverPubkey={pubkey}
userPubkey={db.account.pubkey}
userPrivkey={''}
/>
<ChatForm receiverPubkey={pubkey} />
</div>
</div>
</div>

View File

@@ -1,6 +1,6 @@
import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk';
import { nip04 } from 'nostr-tools';
import { useCallback, useState } from 'react';
import { NDKEvent, NDKKind, NDKUser } from '@nostr-dev-kit/ndk';
import { useState } from 'react';
import { toast } from 'sonner';
import { MediaUploader } from '@app/chats/components/mediaUploader';
@@ -8,34 +8,26 @@ import { useNDK } from '@libs/ndk/provider';
import { EnterIcon } from '@shared/icons';
export function ChatForm({
receiverPubkey,
userPrivkey,
}: {
receiverPubkey: string;
userPubkey: string;
userPrivkey: string;
}) {
export function ChatForm({ receiverPubkey }: { receiverPubkey: string }) {
const { ndk } = useNDK();
const [value, setValue] = useState('');
const encryptMessage = useCallback(async () => {
return await nip04.encrypt(userPrivkey, receiverPubkey, value);
}, [receiverPubkey, value]);
const submit = async () => {
const message = await encryptMessage();
const tags = [['p', receiverPubkey]];
try {
const recipient = new NDKUser({ pubkey: receiverPubkey });
const message = await ndk.signer.encrypt(recipient, value);
const event = new NDKEvent(ndk);
event.content = message;
event.kind = NDKKind.EncryptedDirectMessage;
event.tags = tags;
const event = new NDKEvent(ndk);
event.content = message;
event.kind = NDKKind.EncryptedDirectMessage;
event.tag(recipient);
await event.publish();
const publish = await event.publish();
// reset state
setValue('');
if (publish) setValue('');
} catch (e) {
toast.error(e);
}
};
const handleEnterPress = (e: {
@@ -61,7 +53,7 @@ export function ChatForm({
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
placeholder="Message"
placeholder="Message..."
className="h-10 flex-1 resize-none bg-transparent px-3 text-neutral-900 placeholder:text-neutral-600 focus:outline-none dark:text-neutral-100 dark:placeholder:text-neutral-300"
/>
<button

View File

@@ -3,14 +3,14 @@ import { twMerge } from 'tailwind-merge';
import { useDecryptMessage } from '@app/chats/hooks/useDecryptMessage';
export function ChatMessage({ message, self }: { message: NDKEvent; self: boolean }) {
export function ChatMessage({ message, isSelf }: { message: NDKEvent; isSelf: boolean }) {
const decryptedContent = useDecryptMessage(message);
return (
<div
className={twMerge(
'my-2 w-max max-w-[400px] rounded-t-xl px-3 py-3',
self
isSelf
? 'ml-auto rounded-l-xl bg-blue-500 text-white'
: 'rounded-r-xl bg-neutral-200 text-neutral-900 dark:bg-neutral-800 dark:text-neutral-100'
)}
@@ -18,9 +18,7 @@ export function ChatMessage({ message, self }: { message: NDKEvent; self: boolea
{!decryptedContent ? (
<p>Decrypting...</p>
) : (
<div>
<p className="select-text whitespace-pre-line">{decryptedContent}</p>
</div>
<p className="select-text whitespace-pre-line break-all">{decryptedContent}</p>
)}
</div>
);

View File

@@ -1,15 +1,20 @@
import { NDKEvent } from '@nostr-dev-kit/ndk';
import { useQuery } from '@tanstack/react-query';
import { useCallback } from 'react';
import { Outlet } from 'react-router-dom';
import { useCallback, useEffect } from 'react';
import { Outlet, useNavigate } from 'react-router-dom';
import { ChatListItem } from '@app/chats/components/chatListItem';
import { useNDK } from '@libs/ndk/provider';
import { LoaderIcon } from '@shared/icons';
import { useNostr } from '@utils/hooks/useNostr';
export function ChatsScreen() {
const navigate = useNavigate();
const { ndk } = useNDK();
const { getAllNIP04Chats } = useNostr();
const { status, data } = useQuery({
queryKey: ['nip04-chats'],
@@ -29,6 +34,10 @@ export function ChatsScreen() {
[data]
);
useEffect(() => {
if (!ndk.signer) navigate('/new/privkey');
}, []);
return (
<div className="grid h-full w-full grid-cols-3">
<div className="col-span-1 h-full overflow-y-auto border-r border-neutral-200 scrollbar-none dark:border-neutral-800">

View File

@@ -5,6 +5,7 @@ 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 { useNavigate } from 'react-router-dom';
import { toast } from 'sonner';
import { twMerge } from 'tailwind-merge';
import { Markdown } from 'tiptap-markdown';
@@ -31,6 +32,7 @@ export function NewArticleScreen() {
const [summary, setSummary] = useState({ open: false, content: '' });
const [cover, setCover] = useState('');
const navigate = useNavigate();
const ident = useMemo(() => String(Date.now()), []);
const editor = useEditor({
extensions: [
@@ -65,6 +67,8 @@ export function NewArticleScreen() {
const submit = async () => {
try {
if (!ndk.signer) return navigate('/new/privkey');
setLoading(true);
// get markdown content

View File

@@ -2,6 +2,7 @@ import { NDKEvent } from '@nostr-dev-kit/ndk';
import { message, open } from '@tauri-apps/plugin-dialog';
import { readBinaryFile } from '@tauri-apps/plugin-fs';
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { toast } from 'sonner';
import { useNDK } from '@libs/ndk/provider';
@@ -10,6 +11,7 @@ import { LoaderIcon } from '@shared/icons';
export function NewFileScreen() {
const { ndk } = useNDK();
const navigate = useNavigate();
const [loading, setLoading] = useState(false);
const [isPublish, setIsPublish] = useState(false);
@@ -84,6 +86,8 @@ export function NewFileScreen() {
const submit = async () => {
try {
if (!ndk.signer) return navigate('/new/privkey');
setIsPublish(true);
const event = new NDKEvent(ndk);

View File

@@ -1,77 +0,0 @@
import { Link, NavLink, Outlet } from 'react-router-dom';
import { twMerge } from 'tailwind-merge';
import { WindowTitlebar } from 'tauri-controls';
import { useStorage } from '@libs/storage/provider';
import { ArrowLeftIcon } from '@shared/icons';
export function NewScreen() {
const { db } = useStorage();
return (
<div className="flex h-screen w-screen flex-col bg-neutral-50 dark:bg-neutral-950">
{db.platform !== 'macos' ? (
<WindowTitlebar />
) : (
<div data-tauri-drag-region className="h-9" />
)}
<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">
<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>
</div>
<div className="h-full min-h-0 w-full">
<Outlet />
</div>
</div>
<div className="col-span-1" />
</div>
</div>
</div>
);
}

View File

@@ -6,7 +6,7 @@ import { EditorContent, useEditor } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
import { convert } from 'html-to-text';
import { useEffect, useState } from 'react';
import { useSearchParams } from 'react-router-dom';
import { useNavigate, useSearchParams } from 'react-router-dom';
import { toast } from 'sonner';
import { MediaUploader, MentionPopup } from '@app/new/components';
@@ -27,6 +27,7 @@ export function NewPostScreen() {
const [loading, setLoading] = useState(false);
const [searchParams, setSearchParams] = useSearchParams();
const navigate = useNavigate();
const editor = useEditor({
extensions: [
StarterKit.configure(),
@@ -54,6 +55,8 @@ export function NewPostScreen() {
const submit = async () => {
try {
if (!ndk.signer) return navigate('/new/privkey');
setLoading(true);
// get plaintext content

86
src/app/new/privkey.tsx Normal file
View File

@@ -0,0 +1,86 @@
import { NDKPrivateKeySigner } from '@nostr-dev-kit/ndk';
import { getPublicKey, nip19 } from 'nostr-tools';
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { toast } from 'sonner';
import { useNDK } from '@libs/ndk/provider';
import { useStorage } from '@libs/storage/provider';
export function NewPrivkeyScreen() {
const { db } = useStorage();
const { ndk } = useNDK();
const [nsec, setNsec] = useState('');
const navigate = useNavigate();
const save = async (content: string) => {
return await db.secureSave(db.account.pubkey, content);
};
const submit = async (isSave?: boolean) => {
try {
if (!nsec.startsWith('nsec1'))
return toast.info('You must enter a private key starts with nsec');
const decoded = nip19.decode(nsec);
if (decoded.type !== 'nsec') return toast.info('You must enter a valid nsec');
const privkey = decoded.data;
const pubkey = getPublicKey(privkey);
if (pubkey !== db.account.pubkey)
return toast.info(
'Your nsec is not match your current public key, please make sure you enter right nsec'
);
const signer = new NDKPrivateKeySigner(privkey);
ndk.signer = signer;
if (isSave) await save(privkey);
navigate(-1);
} catch (e) {
toast.error(e);
}
};
return (
<div className="flex h-full w-full items-center justify-center">
<div className="mb-16 flex flex-col gap-3">
<h1 className="text-center font-semibold text-neutral-900 dark:text-neutral-100">
You need to provide private key to sign nostr event.
</h1>
<input
name="privkey"
placeholder="nsec..."
type="password"
value={nsec}
onChange={(e) => setNsec(e.target.value)}
spellCheck={false}
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
className="h-11 w-full rounded-lg bg-neutral-100 px-3 py-2 placeholder:text-neutral-500 dark:bg-neutral-900 dark:placeholder:text-neutral-400"
/>
<div className="mt-2 flex flex-col gap-2">
<button
type="button"
onClick={() => submit()}
className="inline-flex h-9 w-full shrink-0 items-center justify-center rounded-lg bg-blue-500 font-semibold text-white hover:bg-blue-600"
>
Submit
</button>
<button
type="button"
onClick={() => submit(true)}
className="inline-flex h-9 w-full shrink-0 items-center justify-center rounded-lg bg-neutral-100 font-medium text-neutral-900 hover:bg-neutral-200 dark:bg-neutral-900 dark:text-neutral-100 dark:hover:bg-neutral-800"
>
Submit and Save
</button>
</div>
</div>
</div>
);
}

View File

@@ -1,3 +1,42 @@
import { getVersion } from '@tauri-apps/api/app';
import { useEffect, useState } from 'react';
import { Link } from 'react-router-dom';
export function AboutScreen() {
return <div></div>;
const [version, setVersion] = useState('');
useEffect(() => {
async function loadVersion() {
const appVersion = await getVersion();
setVersion(appVersion);
}
loadVersion();
}, []);
return (
<div className="mx-auto w-full max-w-lg">
<div className="flex items-center justify-center gap-2">
<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>
</div>
</div>
<div className="mx-auto mt-4 flex w-full max-w-xs flex-col gap-2">
<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"
>
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"
>
Report a issue
</Link>
</div>
</div>
);
}

View File

@@ -1,3 +1,29 @@
import { useStorage } from '@libs/storage/provider';
export function AdvancedSettingScreen() {
return <div></div>;
const { db } = useStorage();
const clearCache = async () => {
await db.clearCache();
};
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">Caches</div>
<div className="text-sm">Use for boost up NDK</div>
</div>
<button
type="button"
onClick={() => clearCache()}
className="h-8 w-max rounded-lg bg-blue-500 px-3 text-sm font-medium text-white hover:bg-blue-600"
>
Clear
</button>
</div>
</div>
</div>
);
}

View File

@@ -1,3 +1,64 @@
import { nip19 } from 'nostr-tools';
import { useEffect, useState } from 'react';
import { useStorage } from '@libs/storage/provider';
import { EyeOffIcon } from '@shared/icons';
export function BackupSettingScreen() {
return <div></div>;
const { db } = useStorage();
const [privkey, setPrivkey] = useState(null);
const [showPassword, setShowPassword] = useState(false);
const removePrivkey = async () => {
await db.secureRemove(db.account.pubkey);
};
useEffect(() => {
async function loadPrivkey() {
const key = await db.secureLoad(db.account.pubkey);
if (key) setPrivkey(key);
}
loadPrivkey();
}, []);
return (
<div className="mx-auto w-full max-w-lg">
<div className="mb-2 text-sm font-semibold">Private key</div>
<div>
{!privkey ? (
<div className="inline-flex h-24 w-full items-center justify-center rounded-lg bg-neutral-100 dark:bg-neutral-900">
You&apos;ve stored private key on Lume
</div>
) : (
<>
<div className="relative">
<input
readOnly
type={showPassword ? 'text' : 'password'}
value={nip19.nsecEncode(privkey)}
className="relative h-11 w-full resize-none rounded-lg bg-neutral-200 py-1 pl-3 pr-11 text-neutral-900 !outline-none placeholder:text-neutral-600 dark:bg-neutral-800 dark:text-neutral-100 dark:placeholder:text-neutral-400"
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-1.5 top-1/2 inline-flex h-8 w-8 -translate-y-1/2 transform items-center justify-center rounded-lg bg-neutral-50 dark:bg-neutral-950"
>
<EyeOffIcon className="h-4 w-4" />
</button>
</div>
<button
type="button"
onClick={() => removePrivkey()}
className="mt-2 inline-flex h-9 w-full items-center justify-center gap-2 rounded-lg bg-red-200 px-6 font-medium text-red-500 hover:bg-red-500 hover:text-white focus:outline-none dark:hover:text-white"
>
Remove private key
</button>
</>
)}
</div>
</div>
);
}

View File

@@ -3,6 +3,7 @@ import { useQueryClient } from '@tanstack/react-query';
import { message } from '@tauri-apps/plugin-dialog';
import { useState } from 'react';
import { useForm } from 'react-hook-form';
import { useNavigate } from 'react-router-dom';
import { useNDK } from '@libs/ndk/provider';
import { useStorage } from '@libs/storage/provider';
@@ -13,6 +14,7 @@ import { useNostr } from '@utils/hooks/useNostr';
export function EditProfileScreen() {
const queryClient = useQueryClient();
const navigate = useNavigate();
const [loading, setLoading] = useState(false);
const [picture, setPicture] = useState('');
@@ -46,10 +48,11 @@ export function EditProfileScreen() {
const uploadAvatar = async () => {
try {
if (!ndk.signer) return navigate('/new/privkey');
setLoading(true);
const image = await upload();
if (image) {
setPicture(image);
setLoading(false);

View File

@@ -1,3 +1,250 @@
import * as Switch from '@radix-ui/react-switch';
import { invoke } from '@tauri-apps/api/primitives';
import { getCurrent } from '@tauri-apps/api/window';
import { disable, enable, isEnabled } from '@tauri-apps/plugin-autostart';
import { isPermissionGranted, requestPermission } from '@tauri-apps/plugin-notification';
import { useEffect, useState } from 'react';
import { twMerge } from 'tailwind-merge';
import { useStorage } from '@libs/storage/provider';
import { DarkIcon, LightIcon, SystemModeIcon } from '@shared/icons';
export function GeneralSettingScreen() {
return <div></div>;
const { db } = useStorage();
const [settings, setSettings] = useState({
autolaunch: false,
outbox: false,
media: true,
hashtag: true,
notification: true,
appearance: 'system',
});
const changeTheme = async (theme: 'light' | 'dark' | 'auto') => {
await invoke('plugin:theme|set_theme', { theme });
// update state
setSettings((prev) => ({ ...prev, appearance: theme }));
};
const toggleAutolaunch = async () => {
if (!settings.autolaunch) {
await enable();
// update state
setSettings((prev) => ({ ...prev, autolaunch: true }));
} else {
await disable();
// update state
setSettings((prev) => ({ ...prev, autolaunch: false }));
}
};
const toggleOutbox = async () => {
await db.createSetting('outbox', String(+!settings.outbox));
// update state
setSettings((prev) => ({ ...prev, outbox: !settings.outbox }));
};
const toggleMedia = async () => {
await db.createSetting('media', String(+!settings.media));
db.settings.media = !settings.media;
// update state
setSettings((prev) => ({ ...prev, media: !settings.media }));
};
const toggleHashtag = async () => {
await db.createSetting('hashtag', String(+!settings.hashtag));
db.settings.hashtag = !settings.hashtag;
// update state
setSettings((prev) => ({ ...prev, hashtag: !settings.hashtag }));
};
const toggleNofitication = async () => {
if (settings.notification) return;
await requestPermission();
// update state
setSettings((prev) => ({ ...prev, notification: !settings.notification }));
};
useEffect(() => {
async function loadSettings() {
const theme = await getCurrent().theme();
setSettings((prev) => ({ ...prev, appearance: theme }));
const autostart = await isEnabled();
setSettings((prev) => ({ ...prev, autolaunch: autostart }));
const permissionGranted = await isPermissionGranted();
setSettings((prev) => ({ ...prev, notification: permissionGranted }));
const data = await db.getAllSettings();
if (!data) return;
data.forEach((item) => {
if (item.key === 'outbox')
setSettings((prev) => ({
...prev,
outbox: !!parseInt(item.value),
}));
if (item.key === 'media')
setSettings((prev) => ({
...prev,
media: !!parseInt(item.value),
}));
if (item.key === 'hashtag')
setSettings((prev) => ({
...prev,
hashtag: !!parseInt(item.value),
}));
if (item.key === 'notification')
setSettings((prev) => ({
...prev,
notification: !!parseInt(item.value),
}));
});
}
loadSettings();
}, []);
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">Startup</div>
<div className="text-sm">Launch Lume at Login</div>
</div>
<Switch.Root
checked={settings.autolaunch}
onClick={() => toggleAutolaunch()}
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">Gossip</div>
<div className="text-sm">Use Outbox model</div>
</div>
<Switch.Root
checked={settings.outbox}
onClick={() => toggleOutbox()}
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">Media</div>
<div className="text-sm">Automatically load media</div>
</div>
<Switch.Root
checked={settings.media}
onClick={() => toggleMedia()}
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">Hashtag</div>
<div className="text-sm">Hide all hashtags in content</div>
</div>
<Switch.Root
checked={settings.hashtag}
onClick={() => toggleHashtag()}
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">
Notification
</div>
<div className="text-sm">Automatically send notification</div>
</div>
<Switch.Root
checked={settings.notification}
disabled={settings.notification}
onClick={() => toggleNofitication()}
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-start gap-8">
<div className="w-24 shrink-0 text-end text-sm font-semibold">Appearance</div>
<div className="flex flex-1 gap-6">
<button
type="button"
onClick={() => changeTheme('light')}
className="flex flex-col items-center justify-center gap-0.5"
>
<div
className={twMerge(
'inline-flex h-11 w-11 items-center justify-center rounded-lg',
settings.appearance === 'light'
? 'bg-blue-500 text-white'
: 'bg-neutral-100 dark:bg-neutral-900'
)}
>
<LightIcon className="h-5 w-5" />
</div>
<p className="text-sm font-medium text-neutral-700 dark:text-neutral-300">
Light
</p>
</button>
<button
type="button"
onClick={() => changeTheme('dark')}
className="flex flex-col items-center justify-center gap-0.5"
>
<div
className={twMerge(
'inline-flex h-11 w-11 items-center justify-center rounded-lg',
settings.appearance === 'dark'
? 'bg-blue-500 text-white'
: 'bg-neutral-100 dark:bg-neutral-900'
)}
>
<DarkIcon className="h-5 w-5" />
</div>
<p className="text-sm font-medium text-neutral-700 dark:text-neutral-300">
Dark
</p>
</button>
<button
type="button"
onClick={() => changeTheme('auto')}
className="flex flex-col items-center justify-center gap-0.5"
>
<div
className={twMerge(
'inline-flex h-11 w-11 items-center justify-center rounded-lg',
settings.appearance === 'auto'
? 'bg-blue-500 text-white'
: 'bg-neutral-100 dark:bg-neutral-900'
)}
>
<SystemModeIcon className="h-5 w-5" />
</div>
<p className="text-sm font-medium text-neutral-700 dark:text-neutral-300">
System
</p>
</button>
</div>
</div>
</div>
</div>
);
}

View File

@@ -2,7 +2,7 @@ import { NDKEvent, NDKKind, NDKUser } from '@nostr-dev-kit/ndk';
import * as Avatar from '@radix-ui/react-avatar';
import { minidenticon } from 'minidenticons';
import { useEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import { Link, useNavigate } from 'react-router-dom';
import { toast } from 'sonner';
import { UserStats } from '@app/users/components/stats';
@@ -21,6 +21,7 @@ export function UserProfile({ pubkey }: { pubkey: string }) {
const { user } = useProfile(pubkey);
const [followed, setFollowed] = useState(false);
const navigate = useNavigate();
const svgURI =
'data:image/svg+xml;utf8,' + encodeURIComponent(minidenticon(pubkey, 90, 50));
@@ -43,6 +44,8 @@ export function UserProfile({ pubkey }: { pubkey: string }) {
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 }));

View File

@@ -20,11 +20,13 @@ export class LumeStorage {
public db: Database;
public account: Account | null;
public platform: Platform | null;
public settings: { 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 };
}
public async secureSave(key: string, value: string) {
@@ -429,10 +431,28 @@ export class LumeStorage {
}
public async createSetting(key: string, value: string) {
return await this.db.execute(
'INSERT OR IGNORE INTO settings (key, value) VALUES ($1, $2);',
[key, value]
const currentSetting = await this.getSettingValue(key);
if (!currentSetting)
return await this.db.execute(
'INSERT OR IGNORE INTO settings (key, value) VALUES ($1, $2);',
[key, value]
);
const currentValue = !!parseInt(currentSetting);
return await this.db.execute('UPDATE settings SET value = $1 WHERE key = $2;', [
+!currentValue,
key,
]);
}
public async getAllSettings() {
const results: { key: string; value: string }[] = await this.db.select(
'SELECT * FROM settings ORDER BY id DESC;'
);
if (results.length < 1) return null;
return results;
}
public async getSettingValue(key: string) {
@@ -444,6 +464,12 @@ export class LumeStorage {
return results[0].value;
}
public async clearCache() {
await this.db.execute('DELETE FROM ndk_events;');
await this.db.execute('DELETE FROM ndk_eventtags;');
await this.db.execute('DELETE FROM ndk_users;');
}
public async accountLogout() {
// update current account status
await this.db.execute("UPDATE accounts SET is_active = '0' WHERE id = $1;", [

View File

@@ -1,4 +1,3 @@
import { appConfigDir } from '@tauri-apps/api/path';
import { message } from '@tauri-apps/plugin-dialog';
import { platform } from '@tauri-apps/plugin-os';
import { relaunch } from '@tauri-apps/plugin-process';
@@ -29,11 +28,22 @@ const StorageProvider = ({ children }: PropsWithChildren<object>) => {
try {
const sqlite = await Database.load('sqlite:lume_v2.db');
const platformName = await platform();
const dir = await appConfigDir();
const lumeStorage = new LumeStorage(sqlite, platformName);
if (!lumeStorage.account) await lumeStorage.getActiveAccount();
const settings = await lumeStorage.getAllSettings();
if (settings) {
settings.forEach((item) => {
if (item.key === 'outbox') lumeStorage.settings.outbox = !!parseInt(item.value);
if (item.key === 'media') lumeStorage.settings.media = !!parseInt(item.value);
if (item.key === 'hashtag')
lumeStorage.settings.hashtag = !!parseInt(item.value);
});
}
// check update
const update = await check();
if (update) {
@@ -44,7 +54,6 @@ const StorageProvider = ({ children }: PropsWithChildren<object>) => {
}
setDB(lumeStorage);
console.info(dir);
} catch (e) {
await message(`Cannot initialize database: ${e}`, {
title: 'Lume',

View File

@@ -20,7 +20,7 @@ const root = createRoot(container);
root.render(
<QueryClientProvider client={queryClient}>
<Toaster position="top-center" closeButton />
<Toaster position="top-center" closeButton theme="system" />
<StorageProvider>
<NDKProvider>
<App />

View File

@@ -0,0 +1,24 @@
import { SVGProps } from 'react';
export function AdvancedSettingsIcon(
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
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.5"
d="M13.75 7h-10m10 0a3.25 3.25 0 116.5 0 3.25 3.25 0 11-6.5 0zm6.5 10h-8m0 0a3.25 3.25 0 11-6.5 0m6.5 0a3.25 3.25 0 10-6.5 0m0 0h-2"
></path>
</svg>
);
}

22
src/shared/icons/dark.tsx Normal file
View File

@@ -0,0 +1,22 @@
import { SVGProps } from 'react';
export function DarkIcon(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
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.5"
d="M21.248 11.811a6.5 6.5 0 01-9.06-9.06 9.25 9.25 0 109.06 9.06z"
></path>
</svg>
);
}

View File

@@ -78,3 +78,9 @@ export * from './heading2';
export * from './heading3';
export * from './bold';
export * from './italic';
export * from './user';
export * from './advancedSettings';
export * from './info';
export * from './light';
export * from './dark';
export * from './system';

32
src/shared/icons/info.tsx Normal file
View File

@@ -0,0 +1,32 @@
import { SVGProps } from 'react';
export function InfoIcon(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
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.5"
d="M10.75 11H12v5.25M21.25 12a9.25 9.25 0 11-18.5 0 9.25 9.25 0 0118.5 0z"
></path>
<rect
width="1.25"
height="1.25"
x="11.375"
y="7.375"
fill="currentColor"
stroke="currentColor"
strokeWidth="0.25"
rx="0.625"
></rect>
</svg>
);
}

View File

@@ -0,0 +1,22 @@
import { SVGProps } from 'react';
export function LightIcon(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
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.5"
d="M11.998 3.29V1.769M5.84 18.158l-1.077 1.078m7.235 2.997v-1.524m7.235-15.944l-1.077 1.077M20.707 12h1.523m-4.074 6.159l1.077 1.077M1.766 12h1.523m1.474-7.235L5.84 5.842m9.87 2.446a5.25 5.25 0 11-7.424 7.424 5.25 5.25 0 017.424-7.424z"
></path>
</svg>
);
}

View File

@@ -0,0 +1,22 @@
import { SVGProps } from 'react';
export function SystemModeIcon(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
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.5"
d="M3.75 12.25V12a8.25 8.25 0 1116.5 0v.25m-18.5 4h20.5m-15.5 4h10.5"
></path>
</svg>
);
}

21
src/shared/icons/user.tsx Normal file
View File

@@ -0,0 +1,21 @@
import { SVGProps } from 'react';
export function UserIcon(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
stroke="currentColor"
strokeLinejoin="round"
strokeWidth="1.5"
d="M5.857 18.916C7.171 16.996 9.332 15.75 12 15.75c2.668 0 4.83 1.247 6.143 3.166m-12.286 0A9.215 9.215 0 0012 21.25c2.358 0 4.51-.882 6.143-2.334m-12.286 0a9.25 9.25 0 1112.286 0M15.25 10a3.25 3.25 0 11-6.5 0 3.25 3.25 0 016.5 0z"
></path>
</svg>
);
}

View File

@@ -0,0 +1,80 @@
import { Link, NavLink, Outlet, useLocation } from 'react-router-dom';
import { twMerge } from 'tailwind-merge';
import { WindowTitlebar } from 'tauri-controls';
import { useStorage } from '@libs/storage/provider';
import { ArrowLeftIcon } from '@shared/icons';
export function NewLayout() {
const { db } = useStorage();
const location = useLocation();
return (
<div className="flex h-screen w-screen flex-col bg-neutral-50 dark:bg-neutral-950">
{db.platform !== 'macos' ? (
<WindowTitlebar />
) : (
<div data-tauri-drag-region className="h-9" />
)}
<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>
</div>
</div>
);
}

View File

@@ -1,10 +1,17 @@
import { NavLink, Outlet, ScrollRestoration } from 'react-router-dom';
import { Link, NavLink, Outlet, ScrollRestoration } from 'react-router-dom';
import { twMerge } from 'tailwind-merge';
import { WindowTitlebar } from 'tauri-controls';
import { useStorage } from '@libs/storage/provider';
import { SecureIcon, SettingsIcon } from '@shared/icons';
import {
AdvancedSettingsIcon,
ArrowLeftIcon,
InfoIcon,
SecureIcon,
SettingsIcon,
UserIcon,
} from '@shared/icons';
export function SettingsLayout() {
const { db } = useStorage();
@@ -17,77 +24,89 @@ export function SettingsLayout() {
<div data-tauri-drag-region className="h-9" />
)}
<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-end justify-center gap-0.5 border-b border-neutral-200 pb-2 dark:border-neutral-800">
<NavLink
to="/settings/"
className={({ isActive }) =>
twMerge(
'flex w-20 shrink-0 flex-col items-center justify-center rounded-lg px-2 py-2 hover:bg-neutral-100 dark:hover:bg-neutral-900',
isActive
? 'bg-neutral-100 text-blue-500 hover:bg-neutral-200 dark:bg-neutral-900 dark:hover:bg-neutral-800'
: ''
)
}
>
<SettingsIcon className="h-6 w-6" />
<p className="text-sm font-medium">User</p>
</NavLink>
<NavLink
to="/settings/general"
className={({ isActive }) =>
twMerge(
'flex w-20 shrink-0 flex-col items-center justify-center rounded-lg px-2 py-2 text-neutral-700 hover:bg-neutral-100 dark:text-neutral-300 dark:hover:bg-neutral-900',
isActive
? 'bg-neutral-100 text-blue-500 hover:bg-neutral-200 dark:bg-neutral-900 dark:hover:bg-neutral-800'
: ''
)
}
>
<SettingsIcon className="h-6 w-6" />
<p className="text-sm font-medium">General</p>
</NavLink>
<NavLink
to="/settings/backup"
className={({ isActive }) =>
twMerge(
'flex w-20 shrink-0 flex-col items-center justify-center rounded-lg px-2 py-2 text-neutral-700 hover:bg-neutral-100 dark:text-neutral-300 dark:hover:bg-neutral-900',
isActive
? 'bg-neutral-100 text-blue-500 hover:bg-neutral-200 dark:bg-neutral-900 dark:hover:bg-neutral-800'
: ''
)
}
>
<SecureIcon className="h-6 w-6" />
<p className="text-sm font-medium">Backup</p>
</NavLink>
<NavLink
to="/settings/advanced"
className={({ isActive }) =>
twMerge(
'flex w-20 shrink-0 flex-col items-center justify-center rounded-lg px-2 py-2 text-neutral-700 hover:bg-neutral-100 dark:text-neutral-300 dark:hover:bg-neutral-900',
isActive
? 'bg-neutral-100 text-blue-500 hover:bg-neutral-200 dark:bg-neutral-900 dark:hover:bg-neutral-800'
: ''
)
}
>
<SettingsIcon className="h-6 w-6" />
<p className="text-sm font-medium">Advanced</p>
</NavLink>
<NavLink
to="/settings/about"
className={({ isActive }) =>
twMerge(
'flex w-20 shrink-0 flex-col items-center justify-center rounded-lg px-2 py-2 text-neutral-700 hover:bg-neutral-100 dark:text-neutral-300 dark:hover:bg-neutral-900',
isActive
? 'bg-neutral-100 text-blue-500 hover:bg-neutral-200 dark:bg-neutral-900 dark:hover:bg-neutral-800'
: ''
)
}
>
<SettingsIcon className="h-6 w-6" />
<p className="text-sm font-medium">About</p>
</NavLink>
<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="/"
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>
</div>
<div className="flex items-center gap-0.5">
<NavLink
to="/settings"
end
className={({ isActive }) =>
twMerge(
'flex w-20 shrink-0 flex-col items-center justify-center rounded-lg px-2 py-2 text-neutral-700 hover:bg-neutral-100 dark:text-neutral-300 dark:hover:bg-neutral-900',
isActive
? 'bg-neutral-100 text-blue-500 hover:bg-neutral-200 dark:bg-neutral-900 dark:hover:bg-neutral-800'
: ''
)
}
>
<UserIcon className="h-6 w-6" />
<p className="text-sm font-medium">User</p>
</NavLink>
<NavLink
to="/settings/general"
className={({ isActive }) =>
twMerge(
'flex w-20 shrink-0 flex-col items-center justify-center rounded-lg px-2 py-2 text-neutral-700 hover:bg-neutral-100 dark:text-neutral-300 dark:hover:bg-neutral-900',
isActive
? 'bg-neutral-100 text-blue-500 hover:bg-neutral-200 dark:bg-neutral-900 dark:hover:bg-neutral-800'
: ''
)
}
>
<SettingsIcon className="h-6 w-6" />
<p className="text-sm font-medium">General</p>
</NavLink>
<NavLink
to="/settings/backup"
className={({ isActive }) =>
twMerge(
'flex w-20 shrink-0 flex-col items-center justify-center rounded-lg px-2 py-2 text-neutral-700 hover:bg-neutral-100 dark:text-neutral-300 dark:hover:bg-neutral-900',
isActive
? 'bg-neutral-100 text-blue-500 hover:bg-neutral-200 dark:bg-neutral-900 dark:hover:bg-neutral-800'
: ''
)
}
>
<SecureIcon className="h-6 w-6" />
<p className="text-sm font-medium">Backup</p>
</NavLink>
<NavLink
to="/settings/advanced"
className={({ isActive }) =>
twMerge(
'flex w-20 shrink-0 flex-col items-center justify-center rounded-lg px-2 py-2 text-neutral-700 hover:bg-neutral-100 dark:text-neutral-300 dark:hover:bg-neutral-900',
isActive
? 'bg-neutral-100 text-blue-500 hover:bg-neutral-200 dark:bg-neutral-900 dark:hover:bg-neutral-800'
: ''
)
}
>
<AdvancedSettingsIcon className="h-6 w-6" />
<p className="text-sm font-medium">Advanced</p>
</NavLink>
<NavLink
to="/settings/about"
className={({ isActive }) =>
twMerge(
'flex w-20 shrink-0 flex-col items-center justify-center rounded-lg px-2 py-2 text-neutral-700 hover:bg-neutral-100 dark:text-neutral-300 dark:hover:bg-neutral-900',
isActive
? 'bg-neutral-100 text-blue-500 hover:bg-neutral-200 dark:bg-neutral-900 dark:hover:bg-neutral-800'
: ''
)
}
>
<InfoIcon className="h-6 w-6" />
<p className="text-sm font-medium">About</p>
</NavLink>
</div>
<div />
</div>
<Outlet />
<ScrollRestoration />

View File

@@ -1,8 +1,11 @@
import { NDKEvent } from '@nostr-dev-kit/ndk';
import * as Popover from '@radix-ui/react-popover';
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { toast } from 'sonner';
import { useNDK } from '@libs/ndk/provider';
import { ReactionIcon } from '@shared/icons';
const REACTIONS = [
@@ -32,6 +35,9 @@ export function NoteReaction({ event }: { event: NDKEvent }) {
const [open, setOpen] = useState(false);
const [reaction, setReaction] = useState<string | null>(null);
const { ndk } = useNDK();
const navigate = useNavigate();
const getReactionImage = (content: string) => {
const reaction: { img: string } = REACTIONS.find((el) => el.content === content);
return reaction.img;
@@ -39,6 +45,8 @@ export function NoteReaction({ event }: { event: NDKEvent }) {
const react = async (content: string) => {
try {
if (!ndk.signer) return navigate('/new/privkey');
setReaction(content);
// react

View File

@@ -2,9 +2,12 @@ import { NDKEvent } from '@nostr-dev-kit/ndk';
import * as AlertDialog from '@radix-ui/react-alert-dialog';
import * as Tooltip from '@radix-ui/react-tooltip';
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { toast } from 'sonner';
import { twMerge } from 'tailwind-merge';
import { useNDK } from '@libs/ndk/provider';
import { LoaderIcon, RepostIcon } from '@shared/icons';
export function NoteRepost({ event }: { event: NDKEvent }) {
@@ -12,8 +15,13 @@ export function NoteRepost({ event }: { event: NDKEvent }) {
const [isLoading, setIsLoading] = useState(false);
const [isRepost, setIsRepost] = useState(false);
const { ndk } = useNDK();
const navigate = useNavigate();
const submit = async () => {
try {
if (!ndk.signer) return navigate('/new/privkey');
setIsLoading(true);
// repsot

View File

@@ -7,6 +7,9 @@ import { message } from '@tauri-apps/plugin-dialog';
import { QRCodeSVG } from 'qrcode.react';
import { useEffect, useRef, useState } from 'react';
import CurrencyInput from 'react-currency-input-field';
import { useNavigate } from 'react-router-dom';
import { useNDK } from '@libs/ndk/provider';
import { CancelIcon, ZapIcon } from '@shared/icons';
@@ -16,6 +19,9 @@ import { compactNumber } from '@utils/number';
export function NoteZap({ event }: { event: NDKEvent }) {
const nwc = useRef(null);
const navigate = useNavigate();
const { ndk } = useNDK();
const { user } = useProfile(event.pubkey);
const [walletConnectURL, setWalletConnectURL] = useState<string>(null);
@@ -28,6 +34,8 @@ export function NoteZap({ event }: { event: NDKEvent }) {
const createZapRequest = async () => {
try {
if (!ndk.signer) return navigate('/new/privkey');
const zapAmount = parseInt(amount) * 1000;
const res = await event.zap(zapAmount, zapMessage);

View File

@@ -1,5 +1,6 @@
import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk';
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { toast } from 'sonner';
import { useNDK } from '@libs/ndk/provider';
@@ -9,12 +10,15 @@ import { ReplyMediaUploader } from '@shared/notes';
export function NoteReplyForm({ rootEvent }: { rootEvent: NDKEvent }) {
const { ndk } = useNDK();
const navigate = useNavigate();
const [value, setValue] = useState('');
const [loading, setLoading] = useState(false);
const submit = async () => {
try {
if (!ndk.signer) return navigate('/new/privkey');
setLoading(true);
const event = new NDKEvent(ndk);

View File

@@ -541,7 +541,7 @@ export const User = memo(function User({
</Avatar.Fallback>
</Avatar.Root>
<div className="flex flex-1 flex-col gap-2">
<div className="inline-flex flex-col gap-1">
<div className="inline-flex flex-col">
<h5 className="text-sm font-semibold">
{user?.name ||
user?.display_name ||

View File

@@ -1,6 +1,6 @@
import { NDKEvent, NDKKind, NDKUser } from '@nostr-dev-kit/ndk';
import { useEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import { Link, useNavigate } from 'react-router-dom';
import { toast } from 'sonner';
import { useNDK } from '@libs/ndk/provider';
@@ -17,6 +17,7 @@ export function UserProfile({ pubkey }: { pubkey: string }) {
const { user } = useProfile(pubkey);
const [followed, setFollowed] = useState(false);
const navigate = useNavigate();
const follow = async (pubkey: string) => {
try {
@@ -36,6 +37,8 @@ export function UserProfile({ pubkey }: { pubkey: string }) {
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 }));

View File

@@ -44,9 +44,9 @@ export function AddGroupFeeds({ currentWidgetId }: { currentWidgetId: string })
return (
<Dialog.Root>
<div className="inline-flex h-14 w-full items-center justify-between rounded-lg bg-white px-3 hover:shadow-md hover:shadow-neutral-200/50 dark:hover:shadow-neutral-800/50">
<div className="inline-flex h-14 w-full items-center justify-between rounded-lg bg-neutral-50 px-3 hover:shadow-md hover:shadow-neutral-200/50 dark:bg-neutral-950 dark:hover:shadow-neutral-800/50">
<div className="inline-flex items-center gap-2.5">
<div className="inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-md bg-neutral-100">
<div className="inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-md bg-neutral-100 dark:bg-neutral-900">
<GroupFeedsIcon className="h-4 w-4" />
</div>
<p className="font-medium">Group feeds</p>

View File

@@ -55,9 +55,9 @@ export function AddHashtagFeeds({ currentWidgetId }: { currentWidgetId: string }
return (
<Dialog.Root>
<div className="inline-flex h-14 w-full items-center justify-between rounded-lg bg-white px-3 hover:shadow-md hover:shadow-neutral-200/50 dark:hover:shadow-neutral-800/50">
<div className="inline-flex h-14 w-full items-center justify-between rounded-lg bg-neutral-50 px-3 hover:shadow-md hover:shadow-neutral-200/50 dark:bg-neutral-950 dark:hover:shadow-neutral-800/50">
<div className="inline-flex items-center gap-2.5">
<div className="inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-md bg-neutral-100">
<div className="inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-md bg-neutral-100 dark:bg-neutral-900">
<GroupFeedsIcon className="h-4 w-4" />
</div>
<p className="font-medium">Hashtag</p>

View File

@@ -1,5 +1,6 @@
import { NDKEvent, NDKKind, NDKUser } from '@nostr-dev-kit/ndk';
import { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { toast } from 'sonner';
import { useNDK } from '@libs/ndk/provider';
@@ -22,6 +23,7 @@ export function NostrBandUserProfile({ data }: { data: Profile }) {
const { ndk } = useNDK();
const [followed, setFollowed] = useState(false);
const navigate = useNavigate();
const follow = async (pubkey: string) => {
try {
@@ -41,6 +43,8 @@ export function NostrBandUserProfile({ data }: { data: Profile }) {
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 }));

View File

@@ -16,7 +16,7 @@ export function ToggleWidgetList() {
onClick={() =>
addWidget.mutate({ kind: WIDGET_KIND.list, title: '', content: '' })
}
className="inline-flex h-14 w-14 items-center justify-center rounded-full bg-neutral-100 text-neutral-900 hover:bg-neutral-200 dark:bg-neutral-900 dark:hover:bg-neutral-800"
className="inline-flex h-14 w-14 items-center justify-center rounded-full bg-neutral-100 text-neutral-900 hover:bg-neutral-200 dark:bg-neutral-900 dark:text-neutral-100 dark:hover:bg-neutral-800"
>
<PlusIcon className="h-5 w-5" />
</button>

View File

@@ -65,9 +65,9 @@ export function WidgetList({ widget }: { widget: Widget }) {
<div className="flex flex-col gap-3">
<AddGroupFeeds currentWidgetId={widget.id} />
<AddHashtagFeeds currentWidgetId={widget.id} />
<div className="inline-flex h-14 w-full items-center justify-between rounded-lg bg-white px-3 hover:shadow-md hover:shadow-neutral-200/50 dark:hover:shadow-neutral-800/50">
<div className="inline-flex h-14 w-full items-center justify-between rounded-lg bg-neutral-50 px-3 hover:shadow-md hover:shadow-neutral-200/50 dark:bg-neutral-950 dark:hover:shadow-neutral-800/50">
<div className="inline-flex items-center gap-2.5">
<div className="inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-md bg-neutral-100">
<div className="inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-md bg-neutral-100 dark:bg-neutral-900">
<ArticleIcon className="h-4 w-4" />
</div>
<p className="font-medium">Articles</p>
@@ -90,9 +90,9 @@ export function WidgetList({ widget }: { widget: Widget }) {
Add
</button>
</div>
<div className="inline-flex h-14 w-full items-center justify-between rounded-lg bg-white px-3 hover:shadow-md hover:shadow-neutral-200/50 dark:hover:shadow-neutral-800/50">
<div className="inline-flex h-14 w-full items-center justify-between rounded-lg bg-neutral-50 px-3 hover:shadow-md hover:shadow-neutral-200/50 dark:bg-neutral-950 dark:hover:shadow-neutral-800/50">
<div className="inline-flex items-center gap-2.5">
<div className="inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-md bg-neutral-100">
<div className="inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-md bg-neutral-100 dark:bg-neutral-900">
<MediaIcon className="h-4 w-4" />
</div>
<p className="font-medium">Media</p>

View File

@@ -4,6 +4,8 @@ import { ReactNode } from 'react';
import { Link } from 'react-router-dom';
import reactStringReplace from 'react-string-replace';
import { useStorage } from '@libs/storage/provider';
import {
Hashtag,
ImagePreview,
@@ -44,6 +46,8 @@ const VIDEOS = [
];
export function useRichContent(content: string, textmode: boolean = false) {
const { db } = useStorage();
let parsedContent: string | ReactNode[] = content.replace(/\n+/g, '\n');
let linkPreview: string;
let images: string[] = [];
@@ -54,8 +58,10 @@ export function useRichContent(content: string, textmode: boolean = false) {
const words = text.split(/( |\n)/);
if (!textmode) {
images = words.filter((word) => IMAGES.some((el) => word.endsWith(el)));
videos = words.filter((word) => VIDEOS.some((el) => word.endsWith(el)));
if (db.settings.media) {
images = words.filter((word) => IMAGES.some((el) => word.endsWith(el)));
videos = words.filter((word) => VIDEOS.some((el) => word.endsWith(el)));
}
events = words.filter((word) => NOSTR_EVENTS.some((el) => word.startsWith(el)));
}
@@ -83,9 +89,10 @@ export function useRichContent(content: string, textmode: boolean = false) {
if (hashtags.length) {
hashtags.forEach((hashtag) => {
parsedContent = reactStringReplace(parsedContent, hashtag, (match, i) => (
<Hashtag key={match + i} tag={hashtag} />
));
parsedContent = reactStringReplace(parsedContent, hashtag, (match, i) => {
if (db.settings.hashtag) return <Hashtag key={match + i} tag={hashtag} />;
return null;
});
});
}