Browse Source

refactor: remove electron-related code

imwald
codytseng 1 year ago
parent
commit
2b1e6fe8f5
  1. 9
      .editorconfig
  2. 4
      .eslintignore
  3. 14
      .eslintrc.cjs
  4. 1
      .github/FUNDING.yml
  5. 57
      .github/workflows/release.yml
  6. 23
      .gitignore
  7. 3
      .prettierignore
  8. 3
      .vscode/extensions.json
  9. 39
      .vscode/launch.json
  10. 11
      .vscode/settings.json
  11. 21
      LICENSE
  12. 26
      README.md
  13. BIN
      build/icon.icns
  14. BIN
      build/icon.ico
  15. BIN
      build/icon.png
  16. BIN
      build/icon@2x.png
  17. 16
      components.json
  18. 34
      electron-builder.yml
  19. 21
      electron.vite.config.ts
  20. 30
      eslint.config.js
  21. 27
      index.html
  22. 9325
      package-lock.json
  23. 112
      package.json
  24. 7
      postcss.config.js
  25. 0
      public/favicon-dark.svg
  26. 0
      public/favicon-light.svg
  27. BIN
      resources/icon.png
  28. 1
      resources/icon.svg
  29. 6
      src/App.tsx
  30. 16
      src/PageManager.tsx
  31. 0
      src/assets/Logo.tsx
  32. 59
      src/common/types.ts
  33. 13
      src/components/AboutInfoDialog/index.tsx
  34. 4
      src/components/AccountButton/LoginButton.tsx
  35. 20
      src/components/AccountButton/ProfileButton.tsx
  36. 2
      src/components/AccountButton/index.tsx
  37. 4
      src/components/BackButton/index.tsx
  38. 4
      src/components/Content/index.tsx
  39. 4
      src/components/Embedded/EmbeddedHashtag.tsx
  40. 0
      src/components/Embedded/EmbeddedMention.tsx
  41. 0
      src/components/Embedded/EmbeddedNormalUrl.tsx
  42. 6
      src/components/Embedded/EmbeddedNote.tsx
  43. 4
      src/components/Embedded/EmbeddedWebsocketUrl.tsx
  44. 0
      src/components/Embedded/index.tsx
  45. 0
      src/components/Embedded/types.tsx
  46. 8
      src/components/FollowButton/index.tsx
  47. 0
      src/components/FormattedTimestamp/index.tsx
  48. 4
      src/components/ImageGallery/index.tsx
  49. 6
      src/components/LoginDialog/BunkerLogin.tsx
  50. 32
      src/components/LoginDialog/NsecLogin.tsx
  51. 9
      src/components/LoginDialog/index.tsx
  52. 2
      src/components/Nip05/index.tsx
  53. 4
      src/components/Note/index.tsx
  54. 2
      src/components/NoteCard/RepostNoteCard.tsx
  55. 10
      src/components/NoteCard/ShortTextNoteCard.tsx
  56. 3
      src/components/NoteCard/index.tsx
  57. 29
      src/components/NoteList/index.tsx
  58. 12
      src/components/NoteStats/LikeButton.tsx
  59. 8
      src/components/NoteStats/NoteOptions/RawEventDialog.tsx
  60. 4
      src/components/NoteStats/NoteOptions/index.tsx
  61. 4
      src/components/NoteStats/ReplyButton.tsx
  62. 16
      src/components/NoteStats/RepostButton.tsx
  63. 2
      src/components/NoteStats/index.tsx
  64. 0
      src/components/NoteStats/utils.ts
  65. 6
      src/components/NotificationButton/index.tsx
  66. 29
      src/components/NotificationList/index.tsx
  67. 2
      src/components/NsfwOverlay/index.tsx
  68. 4
      src/components/ParentNotePreview/index.tsx
  69. 4
      src/components/PostButton/index.tsx
  70. 10
      src/components/PostDialog/Metions.tsx
  71. 2
      src/components/PostDialog/Preview.tsx
  72. 10
      src/components/PostDialog/Uploader.tsx
  73. 16
      src/components/PostDialog/index.tsx
  74. 0
      src/components/ProfileAbout/index.tsx
  75. 4
      src/components/ProfileBanner/index.tsx
  76. 6
      src/components/ProfileCard/index.tsx
  77. 4
      src/components/RefreshButton/index.tsx
  78. 8
      src/components/RelaySettings/RelayGroup.tsx
  79. 27
      src/components/RelaySettings/RelayUrl.tsx
  80. 8
      src/components/RelaySettings/TemporaryRelayGroup.tsx
  81. 8
      src/components/RelaySettings/index.tsx
  82. 0
      src/components/RelaySettings/provider.tsx
  83. 0
      src/components/RelaySettings/types.ts
  84. 14
      src/components/RelaySettingsButton/index.tsx
  85. 0
      src/components/ReplyNote/index.tsx
  86. 20
      src/components/ReplyNoteList/index.tsx
  87. 4
      src/components/ScrollToTopButton/index.tsx
  88. 2
      src/components/SearchButton/index.tsx
  89. 30
      src/components/SearchDialog/index.tsx
  90. 25
      src/components/Sidebar/index.tsx
  91. 4
      src/components/ThemeToggle/index.tsx
  92. 23
      src/components/Titlebar/index.tsx
  93. 16
      src/components/UserAvatar/index.tsx
  94. 10
      src/components/UserItem/index.tsx
  95. 12
      src/components/Username/index.tsx
  96. 2
      src/components/VideoPlayer/index.tsx
  97. 4
      src/components/WebPreview/index.tsx
  98. 15
      src/components/ui/avatar.tsx
  99. 29
      src/components/ui/button.tsx
  100. 19
      src/components/ui/card.tsx
  101. Some files were not shown because too many files have changed in this diff Show More

9
.editorconfig

@ -1,9 +0,0 @@ @@ -1,9 +0,0 @@
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

4
.eslintignore

@ -1,4 +0,0 @@ @@ -1,4 +0,0 @@
node_modules
dist
out
.gitignore

14
.eslintrc.cjs

@ -1,14 +0,0 @@ @@ -1,14 +0,0 @@
module.exports = {
extends: [
'eslint:recommended',
'plugin:react/recommended',
'plugin:react/jsx-runtime',
'@electron-toolkit/eslint-config-ts/recommended',
'@electron-toolkit/eslint-config-prettier'
],
rules: {
'@typescript-eslint/explicit-function-return-type': 'off',
'react/prop-types': 'off',
'@typescript-eslint/no-explicit-any': 'off'
}
}

1
.github/FUNDING.yml

@ -1 +0,0 @@ @@ -1 +0,0 @@
github: [CodyTseng]

57
.github/workflows/release.yml

@ -1,57 +0,0 @@ @@ -1,57 +0,0 @@
name: Build/release
on:
push:
tags:
- v*.*.*
permissions:
contents: write
jobs:
release:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-13, windows-latest]
steps:
- name: Check out Git repository
uses: actions/checkout@v4
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: 20
- name: Install Dependencies
run: npm install
- name: build-linux
if: matrix.os == 'ubuntu-latest'
run: npm run build:linux
- name: build-mac
if: matrix.os == 'macos-13'
run: npm run build:mac
- name: build-win
if: matrix.os == 'windows-latest'
run: npm run build:win
- name: release
uses: softprops/action-gh-release@v2
with:
draft: true
files: |
dist/*.exe
dist/*.zip
dist/*.dmg
dist/*.AppImage
dist/*.snap
dist/*.deb
dist/*.rpm
dist/*.tar.gz
env:
GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }}

23
.gitignore vendored

@ -1,5 +1,24 @@ @@ -1,5 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
out
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.log*
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

3
.prettierignore

@ -2,5 +2,4 @@ out @@ -2,5 +2,4 @@ out
dist
pnpm-lock.yaml
LICENSE.md
tsconfig.json
tsconfig.*.json
*.json

3
.vscode/extensions.json vendored

@ -1,3 +0,0 @@ @@ -1,3 +0,0 @@
{
"recommendations": ["dbaeumer.vscode-eslint"]
}

39
.vscode/launch.json vendored

@ -1,39 +0,0 @@ @@ -1,39 +0,0 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug Main Process",
"type": "node",
"request": "launch",
"cwd": "${workspaceRoot}",
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron-vite",
"windows": {
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron-vite.cmd"
},
"runtimeArgs": ["--sourcemap"],
"env": {
"REMOTE_DEBUGGING_PORT": "9222"
}
},
{
"name": "Debug Renderer Process",
"port": 9222,
"request": "attach",
"type": "chrome",
"webRoot": "${workspaceFolder}/src/renderer",
"timeout": 60000,
"presentation": {
"hidden": true
}
}
],
"compounds": [
{
"name": "Debug All",
"configurations": ["Debug Main Process", "Debug Renderer Process"],
"presentation": {
"order": 1
}
}
]
}

11
.vscode/settings.json vendored

@ -1,11 +0,0 @@ @@ -1,11 +0,0 @@
{
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}

21
LICENSE

@ -1,21 +0,0 @@ @@ -1,21 +0,0 @@
MIT License
Copyright (c) 2024 Cody Tseng
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

26
README.md

@ -17,23 +17,8 @@ A beautiful nostr client focused on browsing relay feeds @@ -17,23 +17,8 @@ A beautiful nostr client focused on browsing relay feeds
- **Relay-Friendly Design:** Minimized and simplified requests ensure efficient communication with relays
- **Relay Groups:** Easily manage and switch between relay groups
- **Clean Interface:** Enjoy a minimalist design and intuitive interactions
- **Cross-Platform:** Available on macOS, Windows, Linux, and web browsers
## Web Version
You can use the web version of Jumble at [jumble.social](https://jumble.social).
## Desktop Version
You can download the desktop version from the [release page](https://github.com/CodyTseng/jumble/releases). If you want to use Apple Silicon version, you need to build it from the source code.
Because the app is not signed, you may need to allow it to run in the system settings.
## Build from source
You can also build the app from the source code.
> Note: Node.js >= 20 is required.
## Run Locally
```bash
# Clone this repository
@ -45,15 +30,10 @@ cd jumble @@ -45,15 +30,10 @@ cd jumble
# Install dependencies
npm install
# Build the app
npm run build:mac
# or npm run build:win
# or npm run build:linux
# or npm run build:web
# Run the app
npm run dev
```
The executable file will be in the `dist` folder.
## Donate
If you like this project, you can buy me a coffee :) ⚡ codytseng@getalby.com ⚡

BIN
build/icon.icns

Binary file not shown.

BIN
build/icon.ico

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

BIN
build/icon.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

BIN
build/icon@2x.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

16
components.json

@ -1,17 +1,21 @@ @@ -1,17 +1,21 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"style": "new-york",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "tailwind.config.js",
"css": "src/renderer/src/assets/main.css",
"baseColor": "slate",
"css": "src/index.css",
"baseColor": "zinc",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@renderer/components",
"utils": "@renderer/lib/utils"
}
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"iconLibrary": "lucide"
}

34
electron-builder.yml

@ -1,34 +0,0 @@ @@ -1,34 +0,0 @@
appId: com.jumble.app
productName: jumble
directories:
buildResources: build
files:
- '!**/.vscode/*'
- '!src/*'
- '!electron.vite.config.{js,ts,mjs,cjs}'
- '!{.eslintignore,.eslintrc.cjs,.prettierignore,.prettierrc.yaml,dev-app-update.yml,CHANGELOG.md,README.md}'
- '!{.env,.env.*,.npmrc,pnpm-lock.yaml}'
- '!{tsconfig.json,tsconfig.node.json,tsconfig.web.json}'
asarUnpack:
- resources/**
win:
executableName: jumble
nsis:
artifactName: ${name}-${version}-setup.${ext}
shortcutName: ${productName}
uninstallDisplayName: ${productName}
createDesktopShortcut: always
mac:
notarize: false
dmg:
artifactName: ${name}-${version}.${ext}
linux:
target:
- AppImage
- snap
- deb
maintainer: codytseng
category: Utility
appImage:
artifactName: ${name}-${version}.${ext}
npmRebuild: false

21
electron.vite.config.ts

@ -1,21 +0,0 @@ @@ -1,21 +0,0 @@
import { resolve } from 'path'
import { defineConfig, externalizeDepsPlugin } from 'electron-vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
main: {
plugins: [externalizeDepsPlugin()]
},
preload: {
plugins: [externalizeDepsPlugin()]
},
renderer: {
resolve: {
alias: {
'@renderer': resolve('src/renderer/src'),
'@common': resolve('src/common')
}
},
plugins: [react()]
}
})

30
eslint.config.js

@ -0,0 +1,30 @@ @@ -0,0 +1,30 @@
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import tseslint from 'typescript-eslint'
export default tseslint.config(
{ ignores: ['dist'] },
{
extends: [js.configs.recommended, ...tseslint.configs.recommended],
files: ['**/*.{ts,tsx}'],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser
},
plugins: {
'react-hooks': reactHooks,
'react-refresh': reactRefresh
},
rules: {
...reactHooks.configs.recommended.rules,
'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
'@typescript-eslint/explicit-function-return-type': 'off',
'react/prop-types': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'react-refresh/only-export-components': 'off',
'react-hooks/exhaustive-deps': 'off'
}
}
)

27
src/renderer/index.html → index.html

@ -1,23 +1,34 @@ @@ -1,23 +1,34 @@
<!doctype html>
<html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" href="/src/assets/favicon-light.svg" media="(prefers-color-scheme: light)" type="image/svg+xml" />
<link rel="icon" href="/src/assets/favicon-dark.svg" media="(prefers-color-scheme: dark)" type="image/svg+xml" />
<link
rel="icon"
href="/favicon-light.svg"
media="(prefers-color-scheme: light)"
type="image/svg+xml"
/>
<link
rel="icon"
href="/favicon-dark.svg"
media="(prefers-color-scheme: dark)"
type="image/svg+xml"
/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta property="og:url" content="https://jumble.social" />
<meta property="og:type" content="website" />
<meta property="og:title" content="Jumble" />
<meta property="og:description" content="A beautiful nostr client focused on browsing relay feeds" />
<meta
property="og:description"
content="A beautiful nostr client focused on browsing relay feeds"
/>
<meta
property="og:image"
content="https://github.com/CodyTseng/jumble/blob/master/resources/og-image.png?raw=true"
/>
<title>Jumble</title>
</head>
<title>Jumble</title>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>

9325
package-lock.json generated

File diff suppressed because it is too large Load Diff

112
package.json

@ -2,7 +2,8 @@ @@ -2,7 +2,8 @@
"name": "jumble",
"version": "0.1.0",
"description": "Yet another Nostr desktop client",
"main": "./out/main/index.js",
"private": true,
"type": "module",
"author": "codytseng",
"license": "MIT",
"repository": {
@ -11,80 +12,63 @@ @@ -11,80 +12,63 @@
},
"homepage": "https://github.com/CodyTseng/jumble",
"scripts": {
"dev": "vite --host",
"build": "tsc -b && vite build",
"lint": "eslint .",
"format": "prettier --write .",
"lint": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix",
"typecheck:node": "tsc --noEmit -p tsconfig.node.json --composite false",
"typecheck:web": "tsc --noEmit -p tsconfig.web.json --composite false",
"typecheck": "npm run typecheck:node && npm run typecheck:web",
"start": "electron-vite preview",
"dev": "electron-vite dev",
"dev:web": "vite --config web.vite.config.ts",
"build": "npm run typecheck && electron-vite build",
"postinstall": "electron-builder install-app-deps",
"build:unpack": "npm run build && electron-builder --dir",
"build:win": "npm run build && electron-builder --win -p never",
"build:mac": "electron-vite build && electron-builder --mac -p never",
"build:linux": "electron-vite build && electron-builder --linux -p never",
"build:web": "vite build --config web.vite.config.ts"
"preview": "vite preview"
},
"dependencies": {
"@electron-toolkit/preload": "^3.0.1",
"@electron-toolkit/utils": "^3.0.0",
"@nextui-org/image": "^2.0.32",
"@nextui-org/system": "^2.2.6",
"@nextui-org/theme": "^2.2.11",
"@radix-ui/react-alert-dialog": "^1.1.2",
"@radix-ui/react-avatar": "^1.1.1",
"@radix-ui/react-dialog": "^1.1.2",
"@radix-ui/react-dropdown-menu": "^2.1.2",
"@radix-ui/react-hover-card": "^1.1.2",
"@radix-ui/react-popover": "^1.1.2",
"@radix-ui/react-radio-group": "^1.2.1",
"@radix-ui/react-scroll-area": "^1.2.0",
"@radix-ui/react-select": "^2.1.2",
"@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-switch": "^1.1.1",
"@radix-ui/react-toast": "^1.2.2",
"class-variance-authority": "^0.7.0",
"@nextui-org/image": "^2.2.3",
"@radix-ui/react-avatar": "^1.1.2",
"@radix-ui/react-dialog": "^1.1.4",
"@radix-ui/react-dropdown-menu": "^2.1.4",
"@radix-ui/react-hover-card": "^1.1.4",
"@radix-ui/react-popover": "^1.1.4",
"@radix-ui/react-scroll-area": "^1.2.2",
"@radix-ui/react-separator": "^1.1.1",
"@radix-ui/react-slot": "^1.1.1",
"@radix-ui/react-switch": "^1.1.2",
"@radix-ui/react-toast": "^1.2.4",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.0.0",
"dataloader": "^2.2.2",
"dataloader": "^2.2.3",
"dayjs": "^1.11.13",
"framer-motion": "^11.11.17",
"i18next": "^23.16.5",
"i18next-browser-languagedetector": "^8.0.0",
"lru-cache": "^11.0.1",
"lucide-react": "^0.453.0",
"nostr-tools": "^2.9.1",
"framer-motion": "^11.15.0",
"i18next": "^24.2.0",
"i18next-browser-languagedetector": "^8.0.2",
"lru-cache": "^11.0.2",
"lucide-react": "^0.469.0",
"nostr-tools": "^2.10.4",
"path-to-regexp": "^8.2.0",
"qrcode.react": "^4.1.0",
"react-i18next": "^15.1.1",
"react-resizable-panels": "^2.1.5",
"qrcode.react": "^4.2.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-i18next": "^15.2.0",
"react-resizable-panels": "^2.1.7",
"react-string-replace": "^1.1.1",
"tailwind-merge": "^2.5.4",
"tailwind-merge": "^2.5.5",
"tailwindcss-animate": "^1.0.7",
"yet-another-react-lightbox": "^3.21.6"
"yet-another-react-lightbox": "^3.21.7",
"zod": "^3.24.1"
},
"devDependencies": {
"@electron-toolkit/eslint-config-prettier": "^2.0.0",
"@electron-toolkit/eslint-config-ts": "^2.0.0",
"@electron-toolkit/tsconfig": "^1.0.1",
"@types/node": "^20.14.8",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@vitejs/plugin-react": "^4.3.1",
"@eslint/js": "^9.17.0",
"@types/node": "^22.10.2",
"@types/react": "^18.3.17",
"@types/react-dom": "^18.3.5",
"@vitejs/plugin-react": "^4.3.4",
"autoprefixer": "^10.4.20",
"electron": "^31.0.2",
"electron-builder": "^24.13.3",
"electron-vite": "^2.3.0",
"eslint": "^8.57.0",
"eslint-plugin-react": "^7.34.3",
"prettier": "^3.3.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"tailwindcss": "^3.4.14",
"typescript": "^5.5.2",
"vite": "^5.3.1"
"eslint": "^9.17.0",
"eslint-plugin-react-hooks": "^5.0.0",
"eslint-plugin-react-refresh": "^0.4.16",
"globals": "^15.13.0",
"postcss": "^8.4.49",
"prettier": "3.4.2",
"tailwindcss": "^3.4.17",
"typescript": "~5.6.2",
"typescript-eslint": "^8.18.1",
"vite": "^6.0.3"
}
}

7
postcss.config.js

@ -1,3 +1,6 @@ @@ -1,3 +1,6 @@
module.exports = {
plugins: [require('tailwindcss'), require('autoprefixer')]
export default {
plugins: {
tailwindcss: {},
autoprefixer: {}
}
}

0
src/renderer/src/assets/favicon-dark.svg → public/favicon-dark.svg

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

0
src/renderer/src/assets/favicon-light.svg → public/favicon-light.svg

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
resources/icon.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

1
resources/icon.svg

@ -1 +0,0 @@ @@ -1 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 1080 1228" version="1.1" fill="currentColor" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"><path id="path1" d="M360.047,1225.75c-31.046,-3.901 -75.11,-14.46 -106.756,-25.58c-101.676,-35.727 -175.164,-93.066 -215.387,-168.055c-12.079,-22.521 -30.071,-71.422 -27.297,-74.195c0.736,-0.736 11.648,5.578 24.249,14.031c135.436,90.86 301.047,169.043 465.056,219.547l32.77,10.091l-20.27,7.416c-43.455,15.896 -105.159,22.678 -152.365,16.745Zm166.293,-59.234c-168.523,-50.004 -331.475,-126.514 -481.755,-226.196c-37.737,-25.031 -41.489,-28.372 -43.419,-38.663c-3.585,-19.109 1.498,-83.894 9.798,-124.886c7.343,-36.266 27.664,-106.034 32.278,-110.818c2.023,-2.099 217.924,48.207 221.274,51.557c0.975,0.975 -1.132,11.339 -4.682,23.032c-24.542,80.842 -27.217,127.586 -9.935,173.593c22.507,59.917 114.521,99.888 177.281,77.012c29.23,-10.654 56.593,-41.085 82.629,-91.894c29.288,-57.155 32.348,-64.988 196.483,-503.076c81.138,-216.562 148.499,-394.821 149.692,-396.131c2.1,-2.304 217.949,76.926 223.076,81.884c2.056,1.988 -262.476,712.505 -307.806,826.747c-18.422,46.426 -56.939,123.045 -77.918,154.993c-10.157,15.469 -30.753,40.901 -45.769,56.515c-27.821,28.93 -66.46,58.952 -75.447,58.621c-2.738,-0.106 -23.339,-5.631 -45.78,-12.29Z" style="fill-rule:nonzero;"/></svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

6
src/renderer/src/App.tsx → src/App.tsx

@ -1,8 +1,8 @@ @@ -1,8 +1,8 @@
import 'yet-another-react-lightbox/styles.css'
import './assets/main.css'
import './index.css'
import { Toaster } from '@renderer/components/ui/toaster'
import { ThemeProvider } from '@renderer/providers/ThemeProvider'
import { Toaster } from '@/components/ui/toaster'
import { ThemeProvider } from '@/providers/ThemeProvider'
import { PageManager } from './PageManager'
import NoteListPage from './pages/primary/NoteListPage'
import { FollowListProvider } from './providers/FollowListProvider'

16
src/renderer/src/PageManager.tsx → src/PageManager.tsx

@ -1,11 +1,7 @@ @@ -1,11 +1,7 @@
import Sidebar from '@renderer/components/Sidebar'
import {
ResizableHandle,
ResizablePanel,
ResizablePanelGroup
} from '@renderer/components/ui/resizable'
import { cn } from '@renderer/lib/utils'
import HomePage from '@renderer/pages/secondary/HomePage'
import Sidebar from '@/components/Sidebar'
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from '@/components/ui/resizable'
import { cn } from '@/lib/utils'
import HomePage from '@/pages/secondary/HomePage'
import { cloneElement, createContext, useContext, useEffect, useState } from 'react'
import { useScreenSize } from './providers/ScreenSizeProvider'
import { routes } from './routes'
@ -187,7 +183,9 @@ export function SecondaryPageLink({ @@ -187,7 +183,9 @@ export function SecondaryPageLink({
<span
className={cn('cursor-pointer', className)}
onClick={(e) => {
onClick && onClick(e)
if (onClick) {
onClick(e)
}
push(to)
}}
>

0
src/renderer/src/assets/Logo.tsx → src/assets/Logo.tsx

59
src/common/types.ts

@ -1,59 +0,0 @@ @@ -1,59 +0,0 @@
import { ElectronAPI } from '@electron-toolkit/preload'
import { Event } from 'nostr-tools'
export type TRelayGroup = {
groupName: string
relayUrls: string[]
isActive: boolean
}
export type TConfig = {
relayGroups: TRelayGroup[]
theme: TThemeSetting
}
export type TThemeSetting = 'light' | 'dark' | 'system'
export type TTheme = 'light' | 'dark'
export type TDraftEvent = Pick<Event, 'content' | 'created_at' | 'kind' | 'tags'>
export interface ISigner {
getPublicKey: () => Promise<string | null>
signEvent: (draftEvent: TDraftEvent) => Promise<Event | null>
}
export type TElectronWindow = {
electron: ElectronAPI
api: {
system: {
isEncryptionAvailable: () => Promise<boolean>
getSelectedStorageBackend: () => Promise<string>
}
theme: {
addChangeListener: (listener: (theme: TTheme) => void) => void
removeChangeListener: () => void
current: () => Promise<TTheme>
}
storage: {
getItem: (key: string) => Promise<string>
setItem: (key: string, value: string) => Promise<void>
removeItem: (key: string) => Promise<void>
}
nostr: {
login: (nsec: string) => Promise<{
pubkey?: string
reason?: string
}>
logout: () => Promise<void>
}
}
nostr: ISigner
}
export type TAccount = {
pubkey: string
signerType: 'nsec' | 'browser-nsec' | 'nip-07' | 'bunker'
nsec?: string
bunker?: string
bunkerClientSecretKey?: string
}

13
src/renderer/src/components/AboutInfoDialog/index.tsx → src/components/AboutInfoDialog/index.tsx

@ -5,7 +5,7 @@ import { @@ -5,7 +5,7 @@ import {
DialogHeader,
DialogTitle,
DialogTrigger
} from '@renderer/components/ui/dialog'
} from '@/components/ui/dialog'
import Username from '../Username'
export default function AboutInfoDialog({ children }: { children: React.ReactNode }) {
@ -38,17 +38,6 @@ export default function AboutInfoDialog({ children }: { children: React.ReactNod @@ -38,17 +38,6 @@ export default function AboutInfoDialog({ children }: { children: React.ReactNod
GitHub
</a>
</div>
<div>
Desktop app:{' '}
<a
href="https://github.com/CodyTseng/jumble/releases"
target="_blank"
rel="noreferrer"
className="text-primary hover:underline"
>
Download
</a>
</div>
<div>
If you like this project, you can buy me a coffee <br />
<div className="font-semibold"> codytseng@getalby.com </div>

4
src/renderer/src/components/AccountButton/LoginButton.tsx → src/components/AccountButton/LoginButton.tsx

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
import { Button } from '@renderer/components/ui/button'
import { useNostr } from '@renderer/providers/NostrProvider'
import { Button } from '@/components/ui/button'
import { useNostr } from '@/providers/NostrProvider'
import { LogIn } from 'lucide-react'
export default function LoginButton({

20
src/renderer/src/components/AccountButton/ProfileButton.tsx → src/components/AccountButton/ProfileButton.tsx

@ -1,16 +1,16 @@ @@ -1,16 +1,16 @@
import { Avatar, AvatarFallback, AvatarImage } from '@renderer/components/ui/avatar'
import { Button } from '@renderer/components/ui/button'
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
import { Button } from '@/components/ui/button'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger
} from '@renderer/components/ui/dropdown-menu'
import { useFetchProfile } from '@renderer/hooks'
import { toProfile } from '@renderer/lib/link'
import { formatPubkey, generateImageByPubkey } from '@renderer/lib/pubkey'
import { useSecondaryPage } from '@renderer/PageManager'
import { useNostr } from '@renderer/providers/NostrProvider'
} from '@/components/ui/dropdown-menu'
import { useFetchProfile } from '@/hooks'
import { toProfile } from '@/lib/link'
import { formatPubkey, generateImageByPubkey } from '@/lib/pubkey'
import { useSecondaryPage } from '@/PageManager'
import { useNostr } from '@/providers/NostrProvider'
import { useTranslation } from 'react-i18next'
export default function ProfileButton({
@ -69,9 +69,7 @@ export default function ProfileButton({ @@ -69,9 +69,7 @@ export default function ProfileButton({
return (
<DropdownMenu>
<DropdownMenuTrigger className="non-draggable" asChild>
{triggerComponent}
</DropdownMenuTrigger>
<DropdownMenuTrigger asChild>{triggerComponent}</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem onClick={() => push(toProfile(pubkey))}>{t('Profile')}</DropdownMenuItem>
<DropdownMenuItem className="text-destructive focus:text-destructive" onClick={logout}>

2
src/renderer/src/components/AccountButton/index.tsx → src/components/AccountButton/index.tsx

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
import { useNostr } from '@renderer/providers/NostrProvider'
import { useNostr } from '@/providers/NostrProvider'
import LoginButton from './LoginButton'
import ProfileButton from './ProfileButton'

4
src/renderer/src/components/BackButton/index.tsx → src/components/BackButton/index.tsx

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
import { Button } from '@renderer/components/ui/button'
import { useSecondaryPage } from '@renderer/PageManager'
import { Button } from '@/components/ui/button'
import { useSecondaryPage } from '@/PageManager'
import { ChevronLeft } from 'lucide-react'
import { useTranslation } from 'react-i18next'

4
src/renderer/src/components/Content/index.tsx → src/components/Content/index.tsx

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
import { isNsfwEvent } from '@renderer/lib/event'
import { cn } from '@renderer/lib/utils'
import { isNsfwEvent } from '@/lib/event'
import { cn } from '@/lib/utils'
import { Event } from 'nostr-tools'
import { memo } from 'react'
import {

4
src/renderer/src/components/Embedded/EmbeddedHashtag.tsx → src/components/Embedded/EmbeddedHashtag.tsx

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
import { toNoteList } from '@renderer/lib/link'
import { SecondaryPageLink } from '@renderer/PageManager'
import { toNoteList } from '@/lib/link'
import { SecondaryPageLink } from '@/PageManager'
import { TEmbeddedRenderer } from './types'
export function EmbeddedHashtag({ hashtag }: { hashtag: string }) {

0
src/renderer/src/components/Embedded/EmbeddedMention.tsx → src/components/Embedded/EmbeddedMention.tsx

0
src/renderer/src/components/Embedded/EmbeddedNormalUrl.tsx → src/components/Embedded/EmbeddedNormalUrl.tsx

6
src/renderer/src/components/Embedded/EmbeddedNote.tsx → src/components/Embedded/EmbeddedNote.tsx

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
import { useFetchEvent } from '@renderer/hooks'
import { toNoStrudelArticle, toNoStrudelNote, toNoStrudelStream } from '@renderer/lib/link'
import { cn } from '@renderer/lib/utils'
import { useFetchEvent } from '@/hooks'
import { toNoStrudelArticle, toNoStrudelNote, toNoStrudelStream } from '@/lib/link'
import { cn } from '@/lib/utils'
import { kinds } from 'nostr-tools'
import ShortTextNoteCard from '../NoteCard/ShortTextNoteCard'

4
src/renderer/src/components/Embedded/EmbeddedWebsocketUrl.tsx → src/components/Embedded/EmbeddedWebsocketUrl.tsx

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
import { useSecondaryPage } from '@renderer/PageManager'
import { toNoteList } from '@renderer/lib/link'
import { useSecondaryPage } from '@/PageManager'
import { toNoteList } from '@/lib/link'
import { TEmbeddedRenderer } from './types'
export function EmbeddedWebsocketUrl({ url }: { url: string }) {

0
src/renderer/src/components/Embedded/index.tsx → src/components/Embedded/index.tsx

0
src/renderer/src/components/Embedded/types.tsx → src/components/Embedded/types.tsx

8
src/renderer/src/components/FollowButton/index.tsx → src/components/FollowButton/index.tsx

@ -1,7 +1,7 @@ @@ -1,7 +1,7 @@
import { Button } from '@renderer/components/ui/button'
import { useToast } from '@renderer/hooks'
import { useFollowList } from '@renderer/providers/FollowListProvider'
import { useNostr } from '@renderer/providers/NostrProvider'
import { Button } from '@/components/ui/button'
import { useToast } from '@/hooks'
import { useFollowList } from '@/providers/FollowListProvider'
import { useNostr } from '@/providers/NostrProvider'
import { Loader } from 'lucide-react'
import { useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'

0
src/renderer/src/components/FormattedTimestamp/index.tsx → src/components/FormattedTimestamp/index.tsx

4
src/renderer/src/components/ImageGallery/index.tsx → src/components/ImageGallery/index.tsx

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
import { Image } from '@nextui-org/image'
import { ScrollArea, ScrollBar } from '@renderer/components/ui/scroll-area'
import { cn } from '@renderer/lib/utils'
import { ScrollArea, ScrollBar } from '@/components/ui/scroll-area'
import { cn } from '@/lib/utils'
import { useState } from 'react'
import Lightbox from 'yet-another-react-lightbox'
import Zoom from 'yet-another-react-lightbox/plugins/zoom'

6
src/renderer/src/components/LoginDialog/BunkerLogin.tsx → src/components/LoginDialog/BunkerLogin.tsx

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
import { Button } from '@renderer/components/ui/button'
import { Input } from '@renderer/components/ui/input'
import { useNostr } from '@renderer/providers/NostrProvider'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { useNostr } from '@/providers/NostrProvider'
import { Loader } from 'lucide-react'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'

32
src/renderer/src/components/LoginDialog/NsecLogin.tsx → src/components/LoginDialog/NsecLogin.tsx

@ -1,8 +1,7 @@ @@ -1,8 +1,7 @@
import { Button } from '@renderer/components/ui/button'
import { Input } from '@renderer/components/ui/input'
import { IS_ELECTRON, isElectron } from '@renderer/lib/env'
import { useNostr } from '@renderer/providers/NostrProvider'
import { useEffect, useState } from 'react'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { useNostr } from '@/providers/NostrProvider'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
export default function PrivateKeyLogin({ onLoginSuccess }: { onLoginSuccess: () => void }) {
@ -10,17 +9,6 @@ export default function PrivateKeyLogin({ onLoginSuccess }: { onLoginSuccess: () @@ -10,17 +9,6 @@ export default function PrivateKeyLogin({ onLoginSuccess }: { onLoginSuccess: ()
const { nsecLogin } = useNostr()
const [nsec, setNsec] = useState('')
const [errMsg, setErrMsg] = useState<string | null>(null)
const [storageBackend, setStorageBackend] = useState('unknown')
useEffect(() => {
const init = async () => {
if (!isElectron(window)) return
const backend = await window.api.system.getSelectedStorageBackend()
setStorageBackend(backend)
}
init()
}, [])
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setNsec(e.target.value)
@ -40,15 +28,9 @@ export default function PrivateKeyLogin({ onLoginSuccess }: { onLoginSuccess: () @@ -40,15 +28,9 @@ export default function PrivateKeyLogin({ onLoginSuccess }: { onLoginSuccess: ()
return (
<>
<div className="text-orange-400">
{!IS_ELECTRON
? t(
'Using private key login is insecure. It is recommended to use a browser extension for login, such as alby, nostr-keyx or nos2x.'
)
: ['unknown', 'basic_text'].includes(storageBackend)
? t('There are no secret keys stored on this device. Your nsec will be unprotected.')
: t('Your nsec will be encrypted using the {{backend}}.', {
backend: storageBackend
})}
{t(
'Using private key login is insecure. It is recommended to use a browser extension for login, such as alby, nostr-keyx or nos2x.'
)}
</div>
<div className="space-y-1">
<Input

9
src/renderer/src/components/LoginDialog/index.tsx → src/components/LoginDialog/index.tsx

@ -1,13 +1,12 @@ @@ -1,13 +1,12 @@
import { Button } from '@renderer/components/ui/button'
import { Button } from '@/components/ui/button'
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle
} from '@renderer/components/ui/dialog'
import { IS_ELECTRON } from '@renderer/lib/env'
import { useNostr } from '@renderer/providers/NostrProvider'
} from '@/components/ui/dialog'
import { useNostr } from '@/providers/NostrProvider'
import { ArrowLeft } from 'lucide-react'
import { Dispatch, useState } from 'react'
import { useTranslation } from 'react-i18next'
@ -54,7 +53,7 @@ export default function LoginDialog({ @@ -54,7 +53,7 @@ export default function LoginDialog({
</>
) : (
<>
{!IS_ELECTRON && !!window.nostr && (
{!!window.nostr && (
<Button onClick={() => nip07Login().then(() => setOpen(false))} className="w-full">
{t('Login with Browser Extension')}
</Button>

2
src/renderer/src/components/Nip05/index.tsx → src/components/Nip05/index.tsx

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
import { useFetchNip05 } from '@renderer/hooks/useFetchNip05'
import { useFetchNip05 } from '@/hooks/useFetchNip05'
import { BadgeAlert, BadgeCheck } from 'lucide-react'
export default function Nip05({ nip05, pubkey }: { nip05?: string; pubkey: string }) {

4
src/renderer/src/components/Note/index.tsx → src/components/Note/index.tsx

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
import { useSecondaryPage } from '@renderer/PageManager'
import { toNote } from '@renderer/lib/link'
import { useSecondaryPage } from '@/PageManager'
import { toNote } from '@/lib/link'
import { Event } from 'nostr-tools'
import Content from '../Content'
import { FormattedTimestamp } from '../FormattedTimestamp'

2
src/renderer/src/components/NoteCard/RepostNoteCard.tsx → src/components/NoteCard/RepostNoteCard.tsx

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
import client from '@renderer/services/client.service'
import client from '@/services/client.service'
import { Event, kinds, verifyEvent } from 'nostr-tools'
import { useMemo } from 'react'
import ShortTextNoteCard from './ShortTextNoteCard'

10
src/renderer/src/components/NoteCard/ShortTextNoteCard.tsx → src/components/NoteCard/ShortTextNoteCard.tsx

@ -1,8 +1,8 @@ @@ -1,8 +1,8 @@
import { useFetchEvent } from '@renderer/hooks'
import { getParentEventId, getRootEventId } from '@renderer/lib/event'
import { toNote } from '@renderer/lib/link'
import { cn } from '@renderer/lib/utils'
import { useSecondaryPage } from '@renderer/PageManager'
import { useFetchEvent } from '@/hooks'
import { getParentEventId, getRootEventId } from '@/lib/event'
import { toNote } from '@/lib/link'
import { cn } from '@/lib/utils'
import { useSecondaryPage } from '@/PageManager'
import { Repeat2 } from 'lucide-react'
import { Event } from 'nostr-tools'
import { useTranslation } from 'react-i18next'

3
src/renderer/src/components/NoteCard/index.tsx → src/components/NoteCard/index.tsx

@ -1,5 +1,4 @@ @@ -1,5 +1,4 @@
import { Event } from 'nostr-tools'
import { kinds } from 'nostr-tools'
import { Event, kinds } from 'nostr-tools'
import RepostNoteCard from './RepostNoteCard'
import ShortTextNoteCard from './ShortTextNoteCard'

29
src/renderer/src/components/NoteList/index.tsx → src/components/NoteList/index.tsx

@ -1,11 +1,11 @@ @@ -1,11 +1,11 @@
import { Button } from '@renderer/components/ui/button'
import { Switch } from '@renderer/components/ui/switch'
import { useFetchRelayInfos } from '@renderer/hooks'
import { isReplyNoteEvent } from '@renderer/lib/event'
import { cn } from '@renderer/lib/utils'
import { useNostr } from '@renderer/providers/NostrProvider'
import { useScreenSize } from '@renderer/providers/ScreenSizeProvider'
import client from '@renderer/services/client.service'
import { Button } from '@/components/ui/button'
import { Switch } from '@/components/ui/switch'
import { useFetchRelayInfos } from '@/hooks'
import { isReplyNoteEvent } from '@/lib/event'
import { cn } from '@/lib/utils'
import { useNostr } from '@/providers/NostrProvider'
import { useScreenSize } from '@/providers/ScreenSizeProvider'
import client from '@/services/client.service'
import dayjs from 'dayjs'
import { Event, Filter, kinds } from 'nostr-tools'
import { useEffect, useMemo, useRef, useState } from 'react'
@ -34,7 +34,6 @@ export default function NoteList({ @@ -34,7 +34,6 @@ export default function NoteList({
const [hasMore, setHasMore] = useState<boolean>(true)
const [initialized, setInitialized] = useState(false)
const [displayReplies, setDisplayReplies] = useState(false)
const observer = useRef<IntersectionObserver | null>(null)
const bottomRef = useRef<HTMLDivElement | null>(null)
const noteFilter = useMemo(() => {
return {
@ -109,19 +108,21 @@ export default function NoteList({ @@ -109,19 +108,21 @@ export default function NoteList({
threshold: 1
}
observer.current = new IntersectionObserver((entries) => {
const observerInstance = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting && hasMore) {
loadMore()
}
}, options)
if (bottomRef.current) {
observer.current.observe(bottomRef.current)
const currentBottomRef = bottomRef.current
if (currentBottomRef) {
observerInstance.observe(currentBottomRef)
}
return () => {
if (observer.current && bottomRef.current) {
observer.current.unobserve(bottomRef.current)
if (observerInstance && currentBottomRef) {
observerInstance.unobserve(currentBottomRef)
}
}
}, [initialized, hasMore, events, timelineKey])

12
src/renderer/src/components/NoteStats/LikeButton.tsx → src/components/NoteStats/LikeButton.tsx

@ -1,8 +1,8 @@ @@ -1,8 +1,8 @@
import { createReactionDraftEvent } from '@renderer/lib/draft-event'
import { cn } from '@renderer/lib/utils'
import { useNostr } from '@renderer/providers/NostrProvider'
import { useNoteStats } from '@renderer/providers/NoteStatsProvider'
import client from '@renderer/services/client.service'
import { createReactionDraftEvent } from '@/lib/draft-event'
import { cn } from '@/lib/utils'
import { useNostr } from '@/providers/NostrProvider'
import { useNoteStats } from '@/providers/NoteStatsProvider'
import client from '@/services/client.service'
import { Heart, Loader } from 'lucide-react'
import { Event } from 'nostr-tools'
import { useEffect, useMemo, useState } from 'react'
@ -37,7 +37,7 @@ export default function LikeButton({ @@ -37,7 +37,7 @@ export default function LikeButton({
if (hasLiked === undefined) {
fetchNoteLikedStatus(event)
}
}, [])
}, [canFetch, event])
const like = async (e: React.MouseEvent) => {
e.stopPropagation()

8
src/renderer/src/components/NoteStats/NoteOptions/RawEventDialog.tsx → src/components/NoteStats/NoteOptions/RawEventDialog.tsx

@ -4,8 +4,8 @@ import { @@ -4,8 +4,8 @@ import {
DialogDescription,
DialogHeader,
DialogTitle
} from '@renderer/components/ui/dialog'
import { ScrollArea, ScrollBar } from '@renderer/components/ui/scroll-area'
} from '@/components/ui/dialog'
import { ScrollArea, ScrollBar } from '@/components/ui/scroll-area'
import { Event } from 'nostr-tools'
export default function RawEventDialog({
@ -25,9 +25,7 @@ export default function RawEventDialog({ @@ -25,9 +25,7 @@ export default function RawEventDialog({
<DialogDescription className="hidden" />
</DialogHeader>
<ScrollArea className="h-full">
<pre className="text-sm overflow-x-auto text-muted-foreground">
{JSON.stringify(event, null, 2)}
</pre>
<pre className="text-sm text-muted-foreground">{JSON.stringify(event, null, 2)}</pre>
<ScrollBar orientation="horizontal" />
</ScrollArea>
</DialogContent>

4
src/renderer/src/components/NoteStats/NoteOptions/index.tsx → src/components/NoteStats/NoteOptions/index.tsx

@ -3,8 +3,8 @@ import { @@ -3,8 +3,8 @@ import {
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger
} from '@renderer/components/ui/dropdown-menu'
import { getSharableEventId } from '@renderer/lib/event'
} from '@/components/ui/dropdown-menu'
import { getSharableEventId } from '@/lib/event'
import { Code, Copy, Ellipsis } from 'lucide-react'
import { Event } from 'nostr-tools'
import { useState } from 'react'

4
src/renderer/src/components/NoteStats/ReplyButton.tsx → src/components/NoteStats/ReplyButton.tsx

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
import { useNostr } from '@renderer/providers/NostrProvider'
import { useNoteStats } from '@renderer/providers/NoteStatsProvider'
import { useNostr } from '@/providers/NostrProvider'
import { useNoteStats } from '@/providers/NoteStatsProvider'
import { MessageCircle } from 'lucide-react'
import { Event } from 'nostr-tools'
import { useMemo, useState } from 'react'

16
src/renderer/src/components/NoteStats/RepostButton.tsx → src/components/NoteStats/RepostButton.tsx

@ -3,13 +3,13 @@ import { @@ -3,13 +3,13 @@ import {
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger
} from '@renderer/components/ui/dropdown-menu'
import { createRepostDraftEvent } from '@renderer/lib/draft-event'
import { getSharableEventId } from '@renderer/lib/event'
import { cn } from '@renderer/lib/utils'
import { useNostr } from '@renderer/providers/NostrProvider'
import { useNoteStats } from '@renderer/providers/NoteStatsProvider'
import client from '@renderer/services/client.service'
} from '@/components/ui/dropdown-menu'
import { createRepostDraftEvent } from '@/lib/draft-event'
import { getSharableEventId } from '@/lib/event'
import { cn } from '@/lib/utils'
import { useNostr } from '@/providers/NostrProvider'
import { useNoteStats } from '@/providers/NoteStatsProvider'
import client from '@/services/client.service'
import { Loader, PencilLine, Repeat } from 'lucide-react'
import { Event } from 'nostr-tools'
import { useEffect, useMemo, useState } from 'react'
@ -45,7 +45,7 @@ export default function RepostButton({ @@ -45,7 +45,7 @@ export default function RepostButton({
if (hasReposted === undefined) {
fetchNoteRepostedStatus(event)
}
}, [])
}, [canFetch, event])
const repost = async (e: React.MouseEvent) => {
e.stopPropagation()

2
src/renderer/src/components/NoteStats/index.tsx → src/components/NoteStats/index.tsx

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
import { cn } from '@renderer/lib/utils'
import { cn } from '@/lib/utils'
import { Event } from 'nostr-tools'
import LikeButton from './LikeButton'
import NoteOptions from './NoteOptions'

0
src/renderer/src/components/NoteStats/utils.ts → src/components/NoteStats/utils.ts

6
src/renderer/src/components/NotificationButton/index.tsx → src/components/NotificationButton/index.tsx

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
import { Button } from '@renderer/components/ui/button'
import { toNotifications } from '@renderer/lib/link'
import { useSecondaryPage } from '@renderer/PageManager'
import { Button } from '@/components/ui/button'
import { toNotifications } from '@/lib/link'
import { useSecondaryPage } from '@/PageManager'
import { Bell } from 'lucide-react'
import { useTranslation } from 'react-i18next'

29
src/renderer/src/components/NotificationList/index.tsx → src/components/NotificationList/index.tsx

@ -1,9 +1,9 @@ @@ -1,9 +1,9 @@
import { useFetchEvent } from '@renderer/hooks'
import { toNote } from '@renderer/lib/link'
import { tagNameEquals } from '@renderer/lib/tag'
import { useSecondaryPage } from '@renderer/PageManager'
import { useNostr } from '@renderer/providers/NostrProvider'
import client from '@renderer/services/client.service'
import { useFetchEvent } from '@/hooks'
import { toNote } from '@/lib/link'
import { tagNameEquals } from '@/lib/tag'
import { useSecondaryPage } from '@/PageManager'
import { useNostr } from '@/providers/NostrProvider'
import client from '@/services/client.service'
import dayjs from 'dayjs'
import { Heart, MessageCircle, Repeat } from 'lucide-react'
import { Event, kinds, nip19, validateEvent } from 'nostr-tools'
@ -22,7 +22,6 @@ export default function NotificationList() { @@ -22,7 +22,6 @@ export default function NotificationList() {
const [notifications, setNotifications] = useState<Event[]>([])
const [until, setUntil] = useState<number | undefined>(dayjs().unix())
const bottomRef = useRef<HTMLDivElement | null>(null)
const observer = useRef<IntersectionObserver | null>(null)
useEffect(() => {
if (!pubkey) {
@ -74,19 +73,21 @@ export default function NotificationList() { @@ -74,19 +73,21 @@ export default function NotificationList() {
threshold: 1
}
observer.current = new IntersectionObserver((entries) => {
const observerInstance = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting) {
loadMore()
}
}, options)
if (bottomRef.current) {
observer.current.observe(bottomRef.current)
const currentBottomRef = bottomRef.current
if (currentBottomRef) {
observerInstance.observe(currentBottomRef)
}
return () => {
if (observer.current && bottomRef.current) {
observer.current.unobserve(bottomRef.current)
if (observerInstance && currentBottomRef) {
observerInstance.unobserve(currentBottomRef)
}
}
}, [until, initialized, timelineKey])
@ -141,7 +142,7 @@ function ReactionNotification({ notification }: { notification: Event }) { @@ -141,7 +142,7 @@ function ReactionNotification({ notification }: { notification: Event }) {
return eventId
? nip19.neventEncode(author ? { id: eventId, author } : { id: eventId })
: undefined
}, [notification.id])
}, [notification])
const { event } = useFetchEvent(bech32Id)
if (!event || !bech32Id || event.kind !== kinds.ShortTextNote) return null
@ -191,7 +192,7 @@ function RepostNotification({ notification }: { notification: Event }) { @@ -191,7 +192,7 @@ function RepostNotification({ notification }: { notification: Event }) {
} catch {
return null
}
}, [])
}, [notification.content])
if (!event) return null
return (

2
src/renderer/src/components/NsfwOverlay/index.tsx → src/components/NsfwOverlay/index.tsx

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
import { cn } from '@renderer/lib/utils'
import { cn } from '@/lib/utils'
import { useState } from 'react'
export default function NsfwOverlay({ className }: { className?: string }) {

4
src/renderer/src/components/ParentNotePreview/index.tsx → src/components/ParentNotePreview/index.tsx

@ -1,7 +1,7 @@ @@ -1,7 +1,7 @@
import { cn } from '@/lib/utils'
import { Event } from 'nostr-tools'
import UserAvatar from '../UserAvatar'
import { cn } from '@renderer/lib/utils'
import { useTranslation } from 'react-i18next'
import UserAvatar from '../UserAvatar'
export default function ParentNotePreview({
event,

4
src/renderer/src/components/PostButton/index.tsx → src/components/PostButton/index.tsx

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
import PostDialog from '@renderer/components/PostDialog'
import { Button } from '@renderer/components/ui/button'
import PostDialog from '@/components/PostDialog'
import { Button } from '@/components/ui/button'
import { PencilLine } from 'lucide-react'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'

10
src/renderer/src/components/PostDialog/Metions.tsx → src/components/PostDialog/Metions.tsx

@ -1,7 +1,7 @@ @@ -1,7 +1,7 @@
import { Button } from '@renderer/components/ui/button'
import { Popover, PopoverContent, PopoverTrigger } from '@renderer/components/ui/popover'
import { extractMentions } from '@renderer/lib/event'
import { useNostr } from '@renderer/providers/NostrProvider'
import { Button } from '@/components/ui/button'
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
import { extractMentions } from '@/lib/event'
import { useNostr } from '@/providers/NostrProvider'
import { Event } from 'nostr-tools'
import { useEffect, useState } from 'react'
import UserAvatar from '../UserAvatar'
@ -23,7 +23,7 @@ export default function Mentions({ @@ -23,7 +23,7 @@ export default function Mentions({
extractMentions(content, parentEvent).then(({ pubkeys }) =>
setPubkeys(pubkeys.filter((p) => p !== pubkey))
)
}, [content])
}, [content, parentEvent, pubkey])
return (
<Popover>

2
src/renderer/src/components/PostDialog/Preview.tsx → src/components/PostDialog/Preview.tsx

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
import { Card } from '@renderer/components/ui/card'
import { Card } from '@/components/ui/card'
import dayjs from 'dayjs'
import Content from '../Content'

10
src/renderer/src/components/PostDialog/Uploader.tsx → src/components/PostDialog/Uploader.tsx

@ -1,8 +1,9 @@ @@ -1,8 +1,9 @@
import { Button } from '@renderer/components/ui/button'
import { useToast } from '@renderer/hooks/use-toast'
import { useNostr } from '@renderer/providers/NostrProvider'
import { Button } from '@/components/ui/button'
import { useToast } from '@/hooks/use-toast'
import { useNostr } from '@/providers/NostrProvider'
import { ImageUp, LoaderCircle } from 'lucide-react'
import { useRef, useState } from 'react'
import { z } from 'zod'
export default function Uploader({
setContent
@ -38,7 +39,8 @@ export default function Uploader({ @@ -38,7 +39,8 @@ export default function Uploader({
}
const data = await response.json()
const imageUrl = data.nip94_event?.tags.find(([tagName]) => tagName === 'url')?.[1]
const tags = z.array(z.array(z.string())).parse(data.nip94_event?.tags ?? [])
const imageUrl = tags.find(([tagName]) => tagName === 'url')?.[1]
if (imageUrl) {
setContent((prevContent) => `${prevContent}\n${imageUrl}`)
} else {

16
src/renderer/src/components/PostDialog/index.tsx → src/components/PostDialog/index.tsx

@ -1,17 +1,17 @@ @@ -1,17 +1,17 @@
import { Button } from '@renderer/components/ui/button'
import { Button } from '@/components/ui/button'
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle
} from '@renderer/components/ui/dialog'
import { ScrollArea } from '@renderer/components/ui/scroll-area'
import { Textarea } from '@renderer/components/ui/textarea'
import { useToast } from '@renderer/hooks/use-toast'
import { createShortTextNoteDraftEvent } from '@renderer/lib/draft-event'
import { useNostr } from '@renderer/providers/NostrProvider'
import client from '@renderer/services/client.service'
} from '@/components/ui/dialog'
import { ScrollArea } from '@/components/ui/scroll-area'
import { Textarea } from '@/components/ui/textarea'
import { useToast } from '@/hooks/use-toast'
import { createShortTextNoteDraftEvent } from '@/lib/draft-event'
import { useNostr } from '@/providers/NostrProvider'
import client from '@/services/client.service'
import { LoaderCircle } from 'lucide-react'
import { Event } from 'nostr-tools'
import { Dispatch, useState } from 'react'

0
src/renderer/src/components/ProfileAbout/index.tsx → src/components/ProfileAbout/index.tsx

4
src/renderer/src/components/ProfileBanner/index.tsx → src/components/ProfileBanner/index.tsx

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
import { Image } from '@nextui-org/image'
import { generateImageByPubkey } from '@renderer/lib/pubkey'
import { cn } from '@renderer/lib/utils'
import { generateImageByPubkey } from '@/lib/pubkey'
import { cn } from '@/lib/utils'
import { useEffect, useMemo, useState } from 'react'
export default function ProfileBanner({

6
src/renderer/src/components/ProfileCard/index.tsx → src/components/ProfileCard/index.tsx

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
import { Avatar, AvatarFallback, AvatarImage } from '@renderer/components/ui/avatar'
import { useFetchProfile } from '@renderer/hooks'
import { generateImageByPubkey } from '@renderer/lib/pubkey'
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
import { useFetchProfile } from '@/hooks'
import { generateImageByPubkey } from '@/lib/pubkey'
import { useMemo } from 'react'
import FollowButton from '../FollowButton'
import Nip05 from '../Nip05'

4
src/renderer/src/components/RefreshButton/index.tsx → src/components/RefreshButton/index.tsx

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
import { Button } from '@renderer/components/ui/button'
import { usePrimaryPage } from '@renderer/PageManager'
import { Button } from '@/components/ui/button'
import { usePrimaryPage } from '@/PageManager'
import { RefreshCcw } from 'lucide-react'
import { useTranslation } from 'react-i18next'

8
src/renderer/src/components/RelaySettings/RelayGroup.tsx → src/components/RelaySettings/RelayGroup.tsx

@ -1,12 +1,12 @@ @@ -1,12 +1,12 @@
import { Button } from '@renderer/components/ui/button'
import { Button } from '@/components/ui/button'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger
} from '@renderer/components/ui/dropdown-menu'
import { Input } from '@renderer/components/ui/input'
import { useRelaySettings } from '@renderer/providers/RelaySettingsProvider'
} from '@/components/ui/dropdown-menu'
import { Input } from '@/components/ui/input'
import { useRelaySettings } from '@/providers/RelaySettingsProvider'
import { Check, ChevronDown, Circle, CircleCheck, EllipsisVertical } from 'lucide-react'
import { useState } from 'react'
import RelayUrls from './RelayUrl'

27
src/renderer/src/components/RelaySettings/RelayUrl.tsx → src/components/RelaySettings/RelayUrl.tsx

@ -1,19 +1,20 @@ @@ -1,19 +1,20 @@
import { Button } from '@renderer/components/ui/button'
import { Input } from '@renderer/components/ui/input'
import { useFetchRelayInfos } from '@renderer/hooks'
import { isWebsocketUrl, normalizeUrl } from '@renderer/lib/url'
import { useRelaySettings } from '@renderer/providers/RelaySettingsProvider'
import client from '@renderer/services/client.service'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { useFetchRelayInfos } from '@/hooks'
import { isWebsocketUrl, normalizeUrl } from '@/lib/url'
import { useRelaySettings } from '@/providers/RelaySettingsProvider'
import client from '@/services/client.service'
import { CircleX, SearchCheck } from 'lucide-react'
import { useEffect, useState } from 'react'
import { useEffect, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
export default function RelayUrls({ groupName }: { groupName: string }) {
const { t } = useTranslation()
const { relayGroups, updateRelayGroupRelayUrls } = useRelaySettings()
const rawRelayUrls = relayGroups.find((group) => group.groupName === groupName)?.relayUrls ?? []
const isActive = relayGroups.find((group) => group.groupName === groupName)?.isActive ?? false
const isActive = useMemo(
() => relayGroups.find((group) => group.groupName === groupName)?.isActive ?? false,
[relayGroups, groupName]
)
const [newRelayUrl, setNewRelayUrl] = useState('')
const [newRelayUrlError, setNewRelayUrlError] = useState<string | null>(null)
const [relays, setRelays] = useState<
@ -21,7 +22,11 @@ export default function RelayUrls({ groupName }: { groupName: string }) { @@ -21,7 +22,11 @@ export default function RelayUrls({ groupName }: { groupName: string }) {
url: string
isConnected: boolean
}[]
>(rawRelayUrls.map((url) => ({ url, isConnected: false })))
>(
relayGroups
.find((group) => group.groupName === groupName)
?.relayUrls.map((url) => ({ url, isConnected: false })) ?? []
)
useEffect(() => {
const interval = setInterval(() => {

8
src/renderer/src/components/RelaySettings/TemporaryRelayGroup.tsx → src/components/RelaySettings/TemporaryRelayGroup.tsx

@ -1,7 +1,7 @@ @@ -1,7 +1,7 @@
import { Button } from '@renderer/components/ui/button'
import { useFetchRelayInfos } from '@renderer/hooks'
import { useRelaySettings } from '@renderer/providers/RelaySettingsProvider'
import client from '@renderer/services/client.service'
import { Button } from '@/components/ui/button'
import { useFetchRelayInfos } from '@/hooks'
import { useRelaySettings } from '@/providers/RelaySettingsProvider'
import client from '@/services/client.service'
import { Save, SearchCheck } from 'lucide-react'
import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'

8
src/renderer/src/components/RelaySettings/index.tsx → src/components/RelaySettings/index.tsx

@ -1,7 +1,7 @@ @@ -1,7 +1,7 @@
import { Button } from '@renderer/components/ui/button'
import { Input } from '@renderer/components/ui/input'
import { Separator } from '@renderer/components/ui/separator'
import { useRelaySettings } from '@renderer/providers/RelaySettingsProvider'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Separator } from '@/components/ui/separator'
import { useRelaySettings } from '@/providers/RelaySettingsProvider'
import { useEffect, useRef, useState } from 'react'
import { RelaySettingsComponentProvider } from './provider'
import RelayGroup from './RelayGroup'

0
src/renderer/src/components/RelaySettings/provider.tsx → src/components/RelaySettings/provider.tsx

0
src/renderer/src/components/RelaySettings/types.ts → src/components/RelaySettings/types.ts

14
src/renderer/src/components/RelaySettingsButton/index.tsx → src/components/RelaySettingsButton/index.tsx

@ -1,10 +1,10 @@ @@ -1,10 +1,10 @@
import RelaySettings from '@renderer/components/RelaySettings'
import { Button } from '@renderer/components/ui/button'
import { Popover, PopoverContent, PopoverTrigger } from '@renderer/components/ui/popover'
import { ScrollArea } from '@renderer/components/ui/scroll-area'
import { toRelaySettings } from '@renderer/lib/link'
import { SecondaryPageLink } from '@renderer/PageManager'
import { useScreenSize } from '@renderer/providers/ScreenSizeProvider'
import RelaySettings from '@/components/RelaySettings'
import { Button } from '@/components/ui/button'
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
import { ScrollArea } from '@/components/ui/scroll-area'
import { toRelaySettings } from '@/lib/link'
import { SecondaryPageLink } from '@/PageManager'
import { useScreenSize } from '@/providers/ScreenSizeProvider'
import { Server } from 'lucide-react'
import { useTranslation } from 'react-i18next'

0
src/renderer/src/components/ReplyNote/index.tsx → src/components/ReplyNote/index.tsx

20
src/renderer/src/components/ReplyNoteList/index.tsx → src/components/ReplyNoteList/index.tsx

@ -1,10 +1,10 @@ @@ -1,10 +1,10 @@
import { Separator } from '@renderer/components/ui/separator'
import { isReplyNoteEvent } from '@renderer/lib/event'
import { isReplyETag, isRootETag } from '@renderer/lib/tag'
import { cn } from '@renderer/lib/utils'
import { useNostr } from '@renderer/providers/NostrProvider'
import { useNoteStats } from '@renderer/providers/NoteStatsProvider'
import client from '@renderer/services/client.service'
import { Separator } from '@/components/ui/separator'
import { isReplyNoteEvent } from '@/lib/event'
import { isReplyETag, isRootETag } from '@/lib/tag'
import { cn } from '@/lib/utils'
import { useNostr } from '@/providers/NostrProvider'
import { useNoteStats } from '@/providers/NoteStatsProvider'
import client from '@/services/client.service'
import dayjs from 'dayjs'
import { Event as NEvent, kinds } from 'nostr-tools'
import { useEffect, useRef, useState } from 'react'
@ -43,7 +43,7 @@ export default function ReplyNoteList({ event, className }: { event: NEvent; cla @@ -43,7 +43,7 @@ export default function ReplyNoteList({ event, className }: { event: NEvent; cla
return () => {
client.removeEventListener('eventPublished', handleEventPublished)
}
}, [])
}, [event])
useEffect(() => {
if (loading) return
@ -87,7 +87,7 @@ export default function ReplyNoteList({ event, className }: { event: NEvent; cla @@ -87,7 +87,7 @@ export default function ReplyNoteList({ event, className }: { event: NEvent; cla
return () => {
promise.then((closer) => closer?.())
}
}, [])
}, [event])
useEffect(() => {
updateNoteReplyCount(event.id, replies.length)
@ -123,7 +123,7 @@ export default function ReplyNoteList({ event, className }: { event: NEvent; cla @@ -123,7 +123,7 @@ export default function ReplyNoteList({ event, className }: { event: NEvent; cla
replyMap[reply.id] = { event: reply, level: level + 1, parent }
}
setReplyMap(replyMap)
}, [replies])
}, [replies, event.id, updateNoteReplyCount])
const loadMore = async () => {
if (loading || !until || !timelineKey) return

4
src/renderer/src/components/ScrollToTopButton/index.tsx → src/components/ScrollToTopButton/index.tsx

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
import { Button } from '@renderer/components/ui/button'
import { cn } from '@renderer/lib/utils'
import { Button } from '@/components/ui/button'
import { cn } from '@/lib/utils'
import { ChevronUp } from 'lucide-react'
export default function ScrollToTopButton({

2
src/renderer/src/components/SearchButton/index.tsx → src/components/SearchButton/index.tsx

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
import { Button } from '@renderer/components/ui/button'
import { Button } from '@/components/ui/button'
import { Search } from 'lucide-react'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'

30
src/renderer/src/components/SearchDialog/index.tsx → src/components/SearchDialog/index.tsx

@ -1,17 +1,11 @@ @@ -1,17 +1,11 @@
import { SecondaryPageLink } from '@renderer/PageManager'
import { Avatar, AvatarFallback, AvatarImage } from '@renderer/components/ui/avatar'
import {
CommandDialog,
CommandInput,
CommandItem,
CommandList
} from '@renderer/components/ui/command'
import { useSearchProfiles } from '@renderer/hooks'
import { isMacOS } from '@renderer/lib/env'
import { toNote, toNoteList, toProfile, toProfileList } from '@renderer/lib/link'
import { generateImageByPubkey } from '@renderer/lib/pubkey'
import { useRelaySettings } from '@renderer/providers/RelaySettingsProvider'
import { TProfile } from '@renderer/types'
import { SecondaryPageLink } from '@/PageManager'
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
import { CommandDialog, CommandInput, CommandItem, CommandList } from '@/components/ui/command'
import { useSearchProfiles } from '@/hooks'
import { toNote, toNoteList, toProfile, toProfileList } from '@/lib/link'
import { generateImageByPubkey } from '@/lib/pubkey'
import { useRelaySettings } from '@/providers/RelaySettingsProvider'
import { TProfile } from '@/types'
import { Hash, Notebook, UserRound } from 'lucide-react'
import { nip19 } from 'nostr-tools'
import { Dispatch, useEffect, useMemo, useState } from 'react'
@ -68,7 +62,7 @@ export function SearchDialog({ open, setOpen }: { open: boolean; setOpen: Dispat @@ -68,7 +62,7 @@ export function SearchDialog({ open, setOpen }: { open: boolean; setOpen: Dispat
)}
</>
)
}, [input, profiles])
}, [input, profiles, setOpen])
useEffect(() => {
const handler = setTimeout(() => {
@ -81,11 +75,7 @@ export function SearchDialog({ open, setOpen }: { open: boolean; setOpen: Dispat @@ -81,11 +75,7 @@ export function SearchDialog({ open, setOpen }: { open: boolean; setOpen: Dispat
}, [input])
return (
<CommandDialog
open={open}
onOpenChange={setOpen}
classNames={{ content: isMacOS() ? 'max-sm:top-9' : 'max-sm:top-0' }}
>
<CommandDialog open={open} onOpenChange={setOpen} classNames={{ content: 'max-sm:top-0' }}>
<CommandInput value={input} onValueChange={setInput} />
<CommandList>{list}</CommandList>
</CommandDialog>

25
src/renderer/src/components/Sidebar/index.tsx → src/components/Sidebar/index.tsx

@ -1,6 +1,5 @@ @@ -1,6 +1,5 @@
import Logo from '@renderer/assets/Logo'
import { Button } from '@renderer/components/ui/button'
import { IS_ELECTRON } from '@renderer/lib/env'
import Logo from '@/assets/Logo'
import { Button } from '@/components/ui/button'
import { Info } from 'lucide-react'
import { useTranslation } from 'react-i18next'
import AboutInfoDialog from '../AboutInfoDialog'
@ -15,24 +14,22 @@ export default function PrimaryPageSidebar() { @@ -15,24 +14,22 @@ export default function PrimaryPageSidebar() {
const { t } = useTranslation()
return (
<div className="w-52 h-full shrink-0 hidden xl:flex flex-col pb-8 pt-10 pl-4 justify-between relative">
<div className="draggable absolute top-0 left-0 h-11 w-full" />
<div className="absolute top-0 left-0 h-11 w-full" />
<div className="space-y-2">
<div className="draggable ml-4 mb-8 w-40">
<div className="ml-4 mb-8 w-40">
<Logo />
</div>
<PostButton variant="sidebar" />
<RelaySettingsButton variant="sidebar" />
<NotificationButton variant="sidebar" />
<SearchButton variant="sidebar" />
{IS_ELECTRON && <RefreshButton variant="sidebar" />}
{!IS_ELECTRON && (
<AboutInfoDialog>
<Button variant="sidebar" size="sidebar">
<Info />
{t('About')}
</Button>
</AboutInfoDialog>
)}
<RefreshButton variant="sidebar" />
<AboutInfoDialog>
<Button variant="sidebar" size="sidebar">
<Info />
{t('About')}
</Button>
</AboutInfoDialog>
</div>
<AccountButton variant="sidebar" />
</div>

4
src/renderer/src/components/ThemeToggle/index.tsx → src/components/ThemeToggle/index.tsx

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
import { Button } from '@renderer/components/ui/button'
import { useTheme } from '@renderer/providers/ThemeProvider'
import { Button } from '@/components/ui/button'
import { useTheme } from '@/providers/ThemeProvider'
import { Moon, Sun, SunMoon } from 'lucide-react'
import { useTranslation } from 'react-i18next'

23
src/components/Titlebar/index.tsx

@ -0,0 +1,23 @@ @@ -0,0 +1,23 @@
import { cn } from '@/lib/utils'
export function Titlebar({
children,
className,
visible = true
}: {
children?: React.ReactNode
className?: string
visible?: boolean
}) {
return (
<div
className={cn(
'absolute top-0 w-full h-9 max-sm:h-11 z-50 bg-background/80 backdrop-blur-md flex items-center font-semibold gap-1 px-2 duration-700 transition-transform',
visible ? '' : '-translate-y-full',
className
)}
>
{children}
</div>
)
}

16
src/renderer/src/components/UserAvatar/index.tsx → src/components/UserAvatar/index.tsx

@ -1,11 +1,11 @@ @@ -1,11 +1,11 @@
import { Avatar, AvatarFallback, AvatarImage } from '@renderer/components/ui/avatar'
import { HoverCard, HoverCardContent, HoverCardTrigger } from '@renderer/components/ui/hover-card'
import { Skeleton } from '@renderer/components/ui/skeleton'
import { useFetchProfile } from '@renderer/hooks'
import { generateImageByPubkey } from '@renderer/lib/pubkey'
import { toProfile } from '@renderer/lib/link'
import { cn } from '@renderer/lib/utils'
import { SecondaryPageLink } from '@renderer/PageManager'
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
import { HoverCard, HoverCardContent, HoverCardTrigger } from '@/components/ui/hover-card'
import { Skeleton } from '@/components/ui/skeleton'
import { useFetchProfile } from '@/hooks'
import { generateImageByPubkey } from '@/lib/pubkey'
import { toProfile } from '@/lib/link'
import { cn } from '@/lib/utils'
import { SecondaryPageLink } from '@/PageManager'
import ProfileCard from '../ProfileCard'
import { useMemo } from 'react'

10
src/renderer/src/components/UserItem/index.tsx → src/components/UserItem/index.tsx

@ -1,8 +1,8 @@ @@ -1,8 +1,8 @@
import FollowButton from '@renderer/components/FollowButton'
import Nip05 from '@renderer/components/Nip05'
import UserAvatar from '@renderer/components/UserAvatar'
import Username from '@renderer/components/Username'
import { useFetchProfile } from '@renderer/hooks'
import FollowButton from '@/components/FollowButton'
import Nip05 from '@/components/Nip05'
import UserAvatar from '@/components/UserAvatar'
import Username from '@/components/Username'
import { useFetchProfile } from '@/hooks'
export default function UserItem({ pubkey }: { pubkey: string }) {
const { profile } = useFetchProfile(pubkey)

12
src/renderer/src/components/Username/index.tsx → src/components/Username/index.tsx

@ -1,9 +1,9 @@ @@ -1,9 +1,9 @@
import { HoverCard, HoverCardContent, HoverCardTrigger } from '@renderer/components/ui/hover-card'
import { Skeleton } from '@renderer/components/ui/skeleton'
import { useFetchProfile } from '@renderer/hooks'
import { toProfile } from '@renderer/lib/link'
import { cn } from '@renderer/lib/utils'
import { SecondaryPageLink } from '@renderer/PageManager'
import { HoverCard, HoverCardContent, HoverCardTrigger } from '@/components/ui/hover-card'
import { Skeleton } from '@/components/ui/skeleton'
import { useFetchProfile } from '@/hooks'
import { toProfile } from '@/lib/link'
import { cn } from '@/lib/utils'
import { SecondaryPageLink } from '@/PageManager'
import ProfileCard from '../ProfileCard'
export default function Username({

2
src/renderer/src/components/VideoPlayer/index.tsx → src/components/VideoPlayer/index.tsx

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
import { cn } from '@renderer/lib/utils'
import { cn } from '@/lib/utils'
import NsfwOverlay from '../NsfwOverlay'
export default function VideoPlayer({

4
src/renderer/src/components/WebPreview/index.tsx → src/components/WebPreview/index.tsx

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
import { Image } from '@nextui-org/image'
import { useFetchWebMetadata } from '@renderer/hooks/useFetchWebMetadata'
import { cn } from '@renderer/lib/utils'
import { useFetchWebMetadata } from '@/hooks/useFetchWebMetadata'
import { cn } from '@/lib/utils'
import { useMemo } from 'react'
export default function WebPreview({

15
src/renderer/src/components/ui/avatar.tsx → src/components/ui/avatar.tsx

@ -1,7 +1,7 @@ @@ -1,7 +1,7 @@
import * as React from "react"
import * as AvatarPrimitive from "@radix-ui/react-avatar"
import * as React from 'react'
import * as AvatarPrimitive from '@radix-ui/react-avatar'
import { cn } from "@renderer/lib/utils"
import { cn } from '@/lib/utils'
const Avatar = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Root>,
@ -9,10 +9,7 @@ const Avatar = React.forwardRef< @@ -9,10 +9,7 @@ const Avatar = React.forwardRef<
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Root
ref={ref}
className={cn(
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
className
)}
className={cn('relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full', className)}
{...props}
/>
))
@ -24,7 +21,7 @@ const AvatarImage = React.forwardRef< @@ -24,7 +21,7 @@ const AvatarImage = React.forwardRef<
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Image
ref={ref}
className={cn("aspect-square h-full w-full", className)}
className={cn('aspect-square h-full w-full', className)}
{...props}
/>
))
@ -37,7 +34,7 @@ const AvatarFallback = React.forwardRef< @@ -37,7 +34,7 @@ const AvatarFallback = React.forwardRef<
<AvatarPrimitive.Fallback
ref={ref}
className={cn(
"flex h-full w-full items-center justify-center rounded-full bg-muted",
'flex h-full w-full items-center justify-center rounded-full bg-muted',
className
)}
{...props}

29
src/renderer/src/components/ui/button.tsx → src/components/ui/button.tsx

@ -2,29 +2,30 @@ import * as React from 'react' @@ -2,29 +2,30 @@ import * as React from 'react'
import { Slot } from '@radix-ui/react-slot'
import { cva, type VariantProps } from 'class-variance-authority'
import { cn } from '@renderer/lib/utils'
import { cn } from '@/lib/utils'
const buttonVariants = cva(
'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
{
variants: {
variant: {
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
secondary: 'bg-secondary text-secondary-foreground hover:bg-muted/80',
default: 'bg-primary text-primary-foreground shadow hover:bg-primary/90',
destructive: 'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90',
outline:
'border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground',
secondary: 'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80',
'secondary-2': 'bg-secondary text-secondary-foreground hover:bg-highlight',
ghost: 'text-muted-foreground hover:bg-accent hover:text-foreground',
ghost: 'hover:bg-accent hover:text-accent-foreground',
link: 'text-primary underline-offset-4 hover:underline',
titlebar: 'non-draggable hover:bg-accent hover:text-accent-foreground',
sidebar: 'non-draggable hover:bg-accent hover:text-accent-foreground',
'small-screen-titlebar': 'non-draggable hover:bg-accent hover:text-accent-foreground'
titlebar: 'hover:bg-accent hover:text-accent-foreground',
sidebar: 'hover:bg-accent hover:text-accent-foreground',
'small-screen-titlebar': 'hover:bg-accent hover:text-accent-foreground'
},
size: {
default: 'h-8 rounded-lg px-3',
sm: 'h-8 rounded-lg px-2',
lg: 'h-10 px-4 py-2',
icon: 'h-8 w-8 rounded-full',
default: 'h-9 px-4 py-2',
sm: 'h-8 rounded-md px-3 text-xs',
lg: 'h-10 rounded-md px-8',
icon: 'h-9 w-9',
titlebar: 'h-7 w-7 rounded-full',
sidebar: 'w-full flex py-2 px-4 rounded-full justify-start gap-4 text-lg font-semibold',
'small-screen-titlebar': 'h-8 w-8 rounded-full'

19
src/renderer/src/components/ui/card.tsx → src/components/ui/card.tsx

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
import * as React from 'react'
import { cn } from '@renderer/lib/utils'
import { cn } from '@/lib/utils'
const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
@ -20,23 +20,22 @@ const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDiv @@ -20,23 +20,22 @@ const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDiv
)
CardHeader.displayName = 'CardHeader'
const CardTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>(
const CardTitle = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<h3
<div
ref={ref}
className={cn('text-2xl font-semibold leading-none tracking-tight', className)}
className={cn('font-semibold leading-none tracking-tight', className)}
{...props}
/>
)
)
CardTitle.displayName = 'CardTitle'
const CardDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<p ref={ref} className={cn('text-sm text-muted-foreground', className)} {...props} />
))
const CardDescription = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div ref={ref} className={cn('text-sm text-muted-foreground', className)} {...props} />
)
)
CardDescription.displayName = 'CardDescription'
const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save