refactor: remove electron-related code

This commit is contained in:
codytseng
2024-12-21 23:20:30 +08:00
parent bed8df06e8
commit 2b1e6fe8f5
200 changed files with 2771 additions and 8432 deletions

View File

@@ -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

View File

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

View File

@@ -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 vendored
View File

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

View File

@@ -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
View File

@@ -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?

View File

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

View File

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

39
.vscode/launch.json vendored
View File

@@ -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
View File

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

21
LICENSE
View File

@@ -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.

View File

@@ -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
# 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 ⚡️

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

View File

@@ -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"
}

View File

@@ -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

View File

@@ -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 Normal file
View File

@@ -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'
}
}
)

View File

@@ -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>

7777
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -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 @@
},
"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",
"react-string-replace": "^1.1.1",
"tailwind-merge": "^2.5.4",
"tailwindcss-animate": "^1.0.7",
"yet-another-react-lightbox": "^3.21.6"
},
"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",
"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",
"qrcode.react": "^4.2.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"tailwindcss": "^3.4.14",
"typescript": "^5.5.2",
"vite": "^5.3.1"
"react-i18next": "^15.2.0",
"react-resizable-panels": "^2.1.7",
"react-string-replace": "^1.1.1",
"tailwind-merge": "^2.5.5",
"tailwindcss-animate": "^1.0.7",
"yet-another-react-lightbox": "^3.21.7",
"zod": "^3.24.1"
},
"devDependencies": {
"@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",
"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"
}
}

View File

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

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

View File

@@ -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

View File

@@ -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'

View File

@@ -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({
<span
className={cn('cursor-pointer', className)}
onClick={(e) => {
onClick && onClick(e)
if (onClick) {
onClick(e)
}
push(to)
}}
>

View File

@@ -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
}

View File

@@ -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
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>

View File

@@ -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({

View File

@@ -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({
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}>

View File

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

View File

@@ -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'

View File

@@ -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 {

View File

@@ -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 }) {

View File

@@ -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'

View File

@@ -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 }) {

View File

@@ -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'

View File

@@ -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'

View File

@@ -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'

View File

@@ -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: ()
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: ()
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

View File

@@ -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({
</>
) : (
<>
{!IS_ELECTRON && !!window.nostr && (
{!!window.nostr && (
<Button onClick={() => nip07Login().then(() => setOpen(false))} className="w-full">
{t('Login with Browser Extension')}
</Button>

View File

@@ -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 }) {

View File

@@ -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'

View File

@@ -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'

View File

@@ -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'

View File

@@ -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'

View File

@@ -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({
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({
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])

View File

@@ -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({
if (hasLiked === undefined) {
fetchNoteLikedStatus(event)
}
}, [])
}, [canFetch, event])
const like = async (e: React.MouseEvent) => {
e.stopPropagation()

View File

@@ -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({
<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>

View File

@@ -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'

View File

@@ -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'

View File

@@ -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({
if (hasReposted === undefined) {
fetchNoteRepostedStatus(event)
}
}, [])
}, [canFetch, event])
const repost = async (e: React.MouseEvent) => {
e.stopPropagation()

View File

@@ -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'

View File

@@ -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'

View File

@@ -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() {
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() {
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 }) {
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 }) {
} catch {
return null
}
}, [])
}, [notification.content])
if (!event) return null
return (

View File

@@ -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 }) {

View File

@@ -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,

View File

@@ -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'

View File

@@ -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({
extractMentions(content, parentEvent).then(({ pubkeys }) =>
setPubkeys(pubkeys.filter((p) => p !== pubkey))
)
}, [content])
}, [content, parentEvent, pubkey])
return (
<Popover>

View File

@@ -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'

View File

@@ -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({
}
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 {

View File

@@ -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'

View File

@@ -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({

View File

@@ -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'

View File

@@ -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'

View File

@@ -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'

View File

@@ -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 }) {
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(() => {

View File

@@ -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'

View File

@@ -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'

View File

@@ -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'

View File

@@ -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
return () => {
client.removeEventListener('eventPublished', handleEventPublished)
}
}, [])
}, [event])
useEffect(() => {
if (loading) return
@@ -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
replyMap[reply.id] = { event: reply, level: level + 1, parent }
}
setReplyMap(replyMap)
}, [replies])
}, [replies, event.id, updateNoteReplyCount])
const loadMore = async () => {
if (loading || !until || !timelineKey) return

View File

@@ -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({

View File

@@ -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'

View File

@@ -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
)}
</>
)
}, [input, profiles])
}, [input, profiles, setOpen])
useEffect(() => {
const handler = setTimeout(() => {
@@ -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>

View File

@@ -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() {
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>

View File

@@ -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'

View File

@@ -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>
)
}

View File

@@ -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'

View File

@@ -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)

View File

@@ -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({

View File

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

View File

@@ -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({

View File

@@ -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<
>(({ 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<
>(({ 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<
<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}

View File

@@ -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'

View File

@@ -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
)
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