mirror of
https://github.com/luminous-devs/lume.git
synced 2024-10-02 18:00:47 +00:00
polish
This commit is contained in:
parent
a66770989b
commit
236131087a
30
package.json
30
package.json
@ -19,14 +19,15 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@getalby/sdk": "^2.4.0",
|
"@getalby/sdk": "^2.4.0",
|
||||||
"@nostr-dev-kit/ndk": "^1.2.1",
|
"@nostr-dev-kit/ndk": "^1.3.0",
|
||||||
"@nostr-fetch/adapter-ndk": "^0.12.2",
|
"@nostr-fetch/adapter-ndk": "^0.12.2",
|
||||||
"@radix-ui/react-alert-dialog": "^1.0.4",
|
"@radix-ui/react-alert-dialog": "^1.0.5",
|
||||||
"@radix-ui/react-collapsible": "^1.0.3",
|
"@radix-ui/react-collapsible": "^1.0.3",
|
||||||
"@radix-ui/react-dialog": "^1.0.4",
|
"@radix-ui/react-dialog": "^1.0.5",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.0.5",
|
"@radix-ui/react-dropdown-menu": "^2.0.6",
|
||||||
"@radix-ui/react-popover": "^1.0.6",
|
"@radix-ui/react-hover-card": "^1.0.7",
|
||||||
"@radix-ui/react-tooltip": "^1.0.6",
|
"@radix-ui/react-popover": "^1.0.7",
|
||||||
|
"@radix-ui/react-tooltip": "^1.0.7",
|
||||||
"@tanstack/react-query": "^4.35.3",
|
"@tanstack/react-query": "^4.35.3",
|
||||||
"@tanstack/react-virtual": "3.0.0-beta.54",
|
"@tanstack/react-virtual": "3.0.0-beta.54",
|
||||||
"@tauri-apps/api": "^1.4.0",
|
"@tauri-apps/api": "^1.4.0",
|
||||||
@ -50,20 +51,19 @@
|
|||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-currency-input-field": "^3.6.11",
|
"react-currency-input-field": "^3.6.11",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-hook-form": "^7.46.1",
|
"react-hook-form": "^7.46.2",
|
||||||
"react-markdown": "^8.0.7",
|
"react-markdown": "^8.0.7",
|
||||||
"react-organizational-chart": "^2.2.1",
|
|
||||||
"react-player": "^2.13.0",
|
"react-player": "^2.13.0",
|
||||||
"react-router-dom": "^6.16.0",
|
"react-router-dom": "^6.16.0",
|
||||||
"react-textarea-autosize": "^8.5.3",
|
"react-textarea-autosize": "^8.5.3",
|
||||||
"react-virtuoso": "^4.6.0",
|
"react-virtuoso": "^4.6.0",
|
||||||
"react-zoom-pan-pinch": "^3.1.0",
|
|
||||||
"reactflow": "^11.8.3",
|
"reactflow": "^11.8.3",
|
||||||
"remark-gfm": "^3.0.1",
|
"remark-gfm": "^3.0.1",
|
||||||
"tauri-plugin-sql-api": "github:tauri-apps/tauri-plugin-sql#v1",
|
"tauri-plugin-sql-api": "github:tauri-apps/tauri-plugin-sql#v1",
|
||||||
"tauri-plugin-store-api": "github:tauri-apps/tauri-plugin-store#v1",
|
"tauri-plugin-store-api": "github:tauri-apps/tauri-plugin-store#v1",
|
||||||
"tauri-plugin-stronghold-api": "github:tauri-apps/tauri-plugin-stronghold#v1",
|
"tauri-plugin-stronghold-api": "github:tauri-apps/tauri-plugin-stronghold#v1",
|
||||||
"tauri-plugin-upload-api": "github:tauri-apps/tauri-plugin-upload#v1",
|
"tauri-plugin-upload-api": "github:tauri-apps/tauri-plugin-upload#v1",
|
||||||
|
"tippy.js": "^6.3.7",
|
||||||
"zustand": "^4.4.1"
|
"zustand": "^4.4.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -71,19 +71,19 @@
|
|||||||
"@tauri-apps/cli": "^1.4.0",
|
"@tauri-apps/cli": "^1.4.0",
|
||||||
"@trivago/prettier-plugin-sort-imports": "^4.2.0",
|
"@trivago/prettier-plugin-sort-imports": "^4.2.0",
|
||||||
"@types/html-to-text": "^9.0.2",
|
"@types/html-to-text": "^9.0.2",
|
||||||
"@types/node": "^20.6.3",
|
"@types/node": "^20.7.0",
|
||||||
"@types/react": "^18.2.22",
|
"@types/react": "^18.2.22",
|
||||||
"@types/react-dom": "^18.2.7",
|
"@types/react-dom": "^18.2.7",
|
||||||
"@types/youtube-player": "^5.5.7",
|
"@types/youtube-player": "^5.5.8",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.7.2",
|
"@typescript-eslint/eslint-plugin": "^6.7.3",
|
||||||
"@typescript-eslint/parser": "^6.7.2",
|
"@typescript-eslint/parser": "^6.7.3",
|
||||||
"@vitejs/plugin-react-swc": "^3.3.2",
|
"@vitejs/plugin-react-swc": "^3.4.0",
|
||||||
"autoprefixer": "^10.4.16",
|
"autoprefixer": "^10.4.16",
|
||||||
"clsx": "^2.0.0",
|
"clsx": "^2.0.0",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"csstype": "^3.1.2",
|
"csstype": "^3.1.2",
|
||||||
"encoding": "^0.1.13",
|
"encoding": "^0.1.13",
|
||||||
"eslint": "^8.49.0",
|
"eslint": "^8.50.0",
|
||||||
"eslint-config-prettier": "^9.0.0",
|
"eslint-config-prettier": "^9.0.0",
|
||||||
"eslint-plugin-jsx-a11y": "^6.7.1",
|
"eslint-plugin-jsx-a11y": "^6.7.1",
|
||||||
"eslint-plugin-react": "^7.33.2",
|
"eslint-plugin-react": "^7.33.2",
|
||||||
|
755
pnpm-lock.yaml
755
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
@ -90,7 +90,7 @@ export const UserWithDrawer = memo(function UserWithDrawer({
|
|||||||
{displayNpub(pubkey, 16)}
|
{displayNpub(pubkey, 16)}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{user.about ? <TextNote content={user.about} /> : null}
|
{user?.about ? <TextNote content={user?.about} /> : null}
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-3 inline-flex items-center gap-2">
|
<div className="mt-3 inline-flex items-center gap-2">
|
||||||
{followed ? (
|
{followed ? (
|
||||||
|
@ -38,7 +38,10 @@ export function ChatsListItem({ pubkey }: { pubkey: string }) {
|
|||||||
/>
|
/>
|
||||||
<div className="inline-flex w-full flex-1 items-center justify-between">
|
<div className="inline-flex w-full flex-1 items-center justify-between">
|
||||||
<h5 className="max-w-[10rem] truncate">
|
<h5 className="max-w-[10rem] truncate">
|
||||||
{user?.name || user?.display_name || displayNpub(pubkey, 16)}
|
{user?.name ||
|
||||||
|
user?.display_name ||
|
||||||
|
user?.displayName ||
|
||||||
|
displayNpub(pubkey, 16)}
|
||||||
</h5>
|
</h5>
|
||||||
</div>
|
</div>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
|
@ -22,7 +22,7 @@ export function ChatSidebar({ pubkey }: { pubkey: string }) {
|
|||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
<h3 className="text-lg font-semibold leading-none">
|
<h3 className="text-lg font-semibold leading-none">
|
||||||
{user?.display_name || user?.name}
|
{user?.name || user?.display_name || user?.displayName}
|
||||||
</h3>
|
</h3>
|
||||||
{user?.nip05 ? (
|
{user?.nip05 ? (
|
||||||
<NIP05
|
<NIP05
|
||||||
|
@ -25,7 +25,7 @@ export function NotiUser({ pubkey }: { pubkey: string }) {
|
|||||||
className="h-8 w-8 shrink-0 rounded-md object-cover"
|
className="h-8 w-8 shrink-0 rounded-md object-cover"
|
||||||
/>
|
/>
|
||||||
<span className="max-w-[10rem] truncate font-medium leading-none text-white">
|
<span className="max-w-[10rem] truncate font-medium leading-none text-white">
|
||||||
{user?.name || user?.display_name || displayNpub(pubkey, 16)}
|
{user?.name || user?.display_name || user?.displayName || displayNpub(pubkey, 16)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -71,7 +71,7 @@ export function UserProfile({ pubkey }: { pubkey: string }) {
|
|||||||
<div className="flex flex-col items-center gap-1">
|
<div className="flex flex-col items-center gap-1">
|
||||||
<div className="inline-flex flex-col items-center gap-1.5">
|
<div className="inline-flex flex-col items-center gap-1.5">
|
||||||
<h5 className="text-center text-xl font-semibold leading-none">
|
<h5 className="text-center text-xl font-semibold leading-none">
|
||||||
{user.display_name || user.displayName || user.name || 'No name'}
|
{user.name || user.display_name || user.displayName || 'No name'}
|
||||||
</h5>
|
</h5>
|
||||||
{user.nip05 ? (
|
{user.nip05 ? (
|
||||||
<NIP05
|
<NIP05
|
||||||
|
@ -5,6 +5,7 @@ import { fetch } from '@tauri-apps/api/http';
|
|||||||
import { NostrFetcher } from 'nostr-fetch';
|
import { NostrFetcher } from 'nostr-fetch';
|
||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
|
import TauriAdapter from '@libs/ndk/cache';
|
||||||
import { useStorage } from '@libs/storage/provider';
|
import { useStorage } from '@libs/storage/provider';
|
||||||
|
|
||||||
export const NDKInstance = () => {
|
export const NDKInstance = () => {
|
||||||
@ -13,6 +14,7 @@ export const NDKInstance = () => {
|
|||||||
const [ndk, setNDK] = useState<NDK | undefined>(undefined);
|
const [ndk, setNDK] = useState<NDK | undefined>(undefined);
|
||||||
const [relayUrls, setRelayUrls] = useState<string[]>([]);
|
const [relayUrls, setRelayUrls] = useState<string[]>([]);
|
||||||
|
|
||||||
|
const cacheAdapter = useMemo(() => new TauriAdapter(), []);
|
||||||
const fetcher = useMemo(
|
const fetcher = useMemo(
|
||||||
() => (ndk ? NostrFetcher.withCustomPool(ndkAdapter(ndk)) : null),
|
() => (ndk ? NostrFetcher.withCustomPool(ndkAdapter(ndk)) : null),
|
||||||
[ndk]
|
[ndk]
|
||||||
@ -58,6 +60,7 @@ export const NDKInstance = () => {
|
|||||||
const explicitRelayUrls = await getExplicitRelays();
|
const explicitRelayUrls = await getExplicitRelays();
|
||||||
const instance = new NDK({
|
const instance = new NDK({
|
||||||
explicitRelayUrls,
|
explicitRelayUrls,
|
||||||
|
cacheAdapter,
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -75,6 +78,10 @@ export const NDKInstance = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!ndk) initNDK();
|
if (!ndk) initNDK();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
cacheAdapter.saveCache();
|
||||||
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { NDKEvent } from '@nostr-dev-kit/ndk';
|
import { NDKEvent, NDKUserProfile } from '@nostr-dev-kit/ndk';
|
||||||
import { BaseDirectory, removeFile } from '@tauri-apps/api/fs';
|
import { BaseDirectory, removeFile } from '@tauri-apps/api/fs';
|
||||||
import Database from 'tauri-plugin-sql-api';
|
import Database from 'tauri-plugin-sql-api';
|
||||||
import { Stronghold } from 'tauri-plugin-stronghold-api';
|
import { Stronghold } from 'tauri-plugin-stronghold-api';
|
||||||
@ -302,6 +302,13 @@ export class LumeStorage {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async createProfile(pubkey: string, profile: NDKUserProfile) {
|
||||||
|
return await this.db.execute(
|
||||||
|
'INSERT OR REPLACE INTO metadata (id, event, author, kind, created_at) VALUES ($1, $2, $3, $4, $5);',
|
||||||
|
[pubkey, JSON.stringify(profile), pubkey, 0, Math.round(Date.now() / 1000)]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public async getMetadataByPubkey(pubkey: string) {
|
public async getMetadataByPubkey(pubkey: string) {
|
||||||
const results: DBEvent[] = await this.db.select(
|
const results: DBEvent[] = await this.db.select(
|
||||||
'SELECT * FROM metadata WHERE author = $1 AND kind = "0" LIMIT 1;',
|
'SELECT * FROM metadata WHERE author = $1 AND kind = "0" LIMIT 1;',
|
||||||
|
@ -35,7 +35,9 @@ export function ActiveAccount() {
|
|||||||
since: Math.floor(Date.now() / 1000),
|
since: Math.floor(Date.now() / 1000),
|
||||||
};
|
};
|
||||||
|
|
||||||
sub(filter, async (event) => {
|
sub(
|
||||||
|
filter,
|
||||||
|
async (event) => {
|
||||||
addActivity(event);
|
addActivity(event);
|
||||||
|
|
||||||
switch (event.kind) {
|
switch (event.kind) {
|
||||||
@ -52,7 +54,9 @@ export function ActiveAccount() {
|
|||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
false
|
||||||
|
);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (status === 'loading') {
|
if (status === 'loading') {
|
||||||
@ -74,7 +78,7 @@ export function ActiveAccount() {
|
|||||||
/>
|
/>
|
||||||
<div className="flex w-full flex-1 flex-col items-start gap-0.5">
|
<div className="flex w-full flex-1 flex-col items-start gap-0.5">
|
||||||
<p className="max-w-[10rem] truncate font-semibold leading-none text-white">
|
<p className="max-w-[10rem] truncate font-semibold leading-none text-white">
|
||||||
{user?.name || user?.display_name}
|
{user?.name || user?.display_name || user?.displayName}
|
||||||
</p>
|
</p>
|
||||||
<span className="max-w-[7rem] truncate text-sm leading-none text-white/50">
|
<span className="max-w-[7rem] truncate text-sm leading-none text-white/50">
|
||||||
{user?.nip05 || displayNpub(db.account.pubkey, 12)}
|
{user?.nip05 || displayNpub(db.account.pubkey, 12)}
|
||||||
|
@ -3,4 +3,6 @@ export * from './modal';
|
|||||||
export * from './composer';
|
export * from './composer';
|
||||||
export * from './mention/item';
|
export * from './mention/item';
|
||||||
export * from './mention/popup';
|
export * from './mention/popup';
|
||||||
|
export * from './mention/suggestion';
|
||||||
|
export * from './mention/inlineList';
|
||||||
export * from './mediaUploader';
|
export * from './mediaUploader';
|
||||||
|
83
src/shared/composer/mention/inlineList.tsx
Normal file
83
src/shared/composer/mention/inlineList.tsx
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import { type SuggestionProps } from '@tiptap/suggestion';
|
||||||
|
import {
|
||||||
|
ForwardedRef,
|
||||||
|
forwardRef,
|
||||||
|
useEffect,
|
||||||
|
useImperativeHandle,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
|
import { MentionItem } from '@shared/composer';
|
||||||
|
|
||||||
|
export const MentionInlineList = forwardRef(
|
||||||
|
(props: SuggestionProps, ref: ForwardedRef<unknown>) => {
|
||||||
|
const [selectedIndex, setSelectedIndex] = useState(0);
|
||||||
|
|
||||||
|
const selectItem = (index) => {
|
||||||
|
const item = props.items[index];
|
||||||
|
|
||||||
|
if (item) {
|
||||||
|
props.command({ id: item });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const upHandler = () => {
|
||||||
|
setSelectedIndex((selectedIndex + props.items.length - 1) % props.items.length);
|
||||||
|
};
|
||||||
|
|
||||||
|
const downHandler = () => {
|
||||||
|
setSelectedIndex((selectedIndex + 1) % props.items.length);
|
||||||
|
};
|
||||||
|
|
||||||
|
const enterHandler = () => {
|
||||||
|
selectItem(selectedIndex);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => setSelectedIndex(0), [props.items]);
|
||||||
|
|
||||||
|
useImperativeHandle(ref, () => ({
|
||||||
|
onKeyDown: ({ event }) => {
|
||||||
|
if (event.key === 'ArrowUp') {
|
||||||
|
upHandler();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.key === 'ArrowDown') {
|
||||||
|
downHandler();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.key === 'Enter') {
|
||||||
|
enterHandler();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex w-[250px] flex-col rounded-xl bg-white/10 px-3 py-3 backdrop-blur-xl">
|
||||||
|
{props.items.length ? (
|
||||||
|
props.items.map((item: string, index: number) => (
|
||||||
|
<button
|
||||||
|
className={twMerge(
|
||||||
|
'h-11 w-full rounded-lg px-2 text-start text-sm font-medium hover:bg-white/10',
|
||||||
|
`${index === selectedIndex ? 'is-selected' : ''}`
|
||||||
|
)}
|
||||||
|
key={index}
|
||||||
|
onClick={() => selectItem(index)}
|
||||||
|
>
|
||||||
|
<MentionItem embed={item} />
|
||||||
|
</button>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<div>No result</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
MentionInlineList.displayName = 'MentionList';
|
@ -3,12 +3,12 @@ import { Image } from '@shared/image';
|
|||||||
import { useProfile } from '@utils/hooks/useProfile';
|
import { useProfile } from '@utils/hooks/useProfile';
|
||||||
import { displayNpub } from '@utils/shortenKey';
|
import { displayNpub } from '@utils/shortenKey';
|
||||||
|
|
||||||
export function MentionItem({ pubkey }: { pubkey: string }) {
|
export function MentionItem({ pubkey, embed }: { pubkey: string; embed?: string }) {
|
||||||
const { status, user } = useProfile(pubkey);
|
const { status, user } = useProfile(pubkey, embed);
|
||||||
|
|
||||||
if (status === 'loading') {
|
if (status === 'loading') {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2.5 px-2">
|
||||||
<div className="relative h-8 w-8 shrink-0 animate-pulse rounded bg-white/10 backdrop-blur-xl" />
|
<div className="relative h-8 w-8 shrink-0 animate-pulse rounded bg-white/10 backdrop-blur-xl" />
|
||||||
<div className="flex w-full flex-1 flex-col items-start gap-1 text-start">
|
<div className="flex w-full flex-1 flex-col items-start gap-1 text-start">
|
||||||
<span className="h-4 w-1/2 animate-pulse rounded bg-white/10 backdrop-blur-xl" />
|
<span className="h-4 w-1/2 animate-pulse rounded bg-white/10 backdrop-blur-xl" />
|
||||||
|
68
src/shared/composer/mention/suggestion.tsx
Normal file
68
src/shared/composer/mention/suggestion.tsx
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import { ReactRenderer } from '@tiptap/react';
|
||||||
|
import tippy from 'tippy.js';
|
||||||
|
|
||||||
|
import { MentionInlineList } from '@shared/composer';
|
||||||
|
|
||||||
|
export const Suggestion = {
|
||||||
|
items: async ({ query }) => {
|
||||||
|
const users = [];
|
||||||
|
return users
|
||||||
|
.filter((item) => item.ident.toLowerCase().startsWith(query.toLowerCase()))
|
||||||
|
.slice(0, 5);
|
||||||
|
},
|
||||||
|
|
||||||
|
render: () => {
|
||||||
|
let component;
|
||||||
|
let popup;
|
||||||
|
|
||||||
|
return {
|
||||||
|
onStart: (props) => {
|
||||||
|
component = new ReactRenderer(MentionInlineList, {
|
||||||
|
props,
|
||||||
|
editor: props.editor,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!props.clientRect) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
popup = tippy('body', {
|
||||||
|
getReferenceClientRect: props.clientRect,
|
||||||
|
appendTo: () => document.body,
|
||||||
|
content: component.element,
|
||||||
|
showOnCreate: true,
|
||||||
|
interactive: true,
|
||||||
|
trigger: 'manual',
|
||||||
|
placement: 'bottom-start',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onUpdate(props) {
|
||||||
|
component.updateProps(props);
|
||||||
|
|
||||||
|
if (!props.clientRect) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
popup[0].setProps({
|
||||||
|
getReferenceClientRect: props.clientRect,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onKeyDown(props) {
|
||||||
|
if (props.event.key === 'Escape') {
|
||||||
|
popup[0].hide();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return component.ref?.onKeyDown(props);
|
||||||
|
},
|
||||||
|
|
||||||
|
onExit() {
|
||||||
|
popup[0].destroy();
|
||||||
|
component.destroy();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
@ -14,7 +14,7 @@ export function ComposerUser({ pubkey }: { pubkey: string }) {
|
|||||||
className="h-10 w-10 shrink-0 rounded-lg"
|
className="h-10 w-10 shrink-0 rounded-lg"
|
||||||
/>
|
/>
|
||||||
<h5 className="text-base font-semibold leading-none text-white">
|
<h5 className="text-base font-semibold leading-none text-white">
|
||||||
{user?.name || user?.display_name || displayNpub(pubkey, 16)}
|
{user?.name || user?.display_name || user?.displayName || displayNpub(pubkey, 16)}
|
||||||
</h5>
|
</h5>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -60,7 +60,7 @@ export function NoteZap({ id, pubkey }: { id: string; pubkey: string }) {
|
|||||||
if (send) {
|
if (send) {
|
||||||
await sendNativeNotification(
|
await sendNativeNotification(
|
||||||
`You've tipped ${compactNumber.format(send.amount)} sats to ${
|
`You've tipped ${compactNumber.format(send.amount)} sats to ${
|
||||||
user?.display_name || user?.name
|
user?.name || user?.display_name || user?.displayName
|
||||||
}`
|
}`
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -106,7 +106,7 @@ export function NoteZap({ id, pubkey }: { id: string; pubkey: string }) {
|
|||||||
<div className="inline-flex w-full shrink-0 items-center justify-between px-5 py-3">
|
<div className="inline-flex w-full shrink-0 items-center justify-between px-5 py-3">
|
||||||
<div className="w-6" />
|
<div className="w-6" />
|
||||||
<Dialog.Title className="text-center text-sm font-semibold leading-none text-white">
|
<Dialog.Title className="text-center text-sm font-semibold leading-none text-white">
|
||||||
Send tip to {user?.display_name || user?.name}
|
Send tip to {user?.name || user?.display_name || user?.displayName}
|
||||||
</Dialog.Title>
|
</Dialog.Title>
|
||||||
<Dialog.Close className="inline-flex h-6 w-6 items-center justify-center rounded-md backdrop-blur-xl hover:bg-white/10">
|
<Dialog.Close className="inline-flex h-6 w-6 items-center justify-center rounded-md backdrop-blur-xl hover:bg-white/10">
|
||||||
<CancelIcon className="h-4 w-4 text-white/50" />
|
<CancelIcon className="h-4 w-4 text-white/50" />
|
||||||
|
@ -18,20 +18,24 @@ export function MentionUser({ pubkey }: { pubkey: string }) {
|
|||||||
onClick={() =>
|
onClick={() =>
|
||||||
setWidget(db, {
|
setWidget(db, {
|
||||||
kind: WidgetKinds.local.user,
|
kind: WidgetKinds.local.user,
|
||||||
title: user?.name || user?.display_name,
|
title: user?.name || user?.display_name || user?.displayName,
|
||||||
content: pubkey,
|
content: pubkey,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
onKeyDown={() =>
|
onKeyDown={() =>
|
||||||
setWidget(db, {
|
setWidget(db, {
|
||||||
kind: WidgetKinds.local.user,
|
kind: WidgetKinds.local.user,
|
||||||
title: user?.name || user?.display_name,
|
title: user?.name || user?.display_name || user?.displayName,
|
||||||
content: pubkey,
|
content: pubkey,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
className="break-words text-fuchsia-400 hover:text-fuchsia-500"
|
className="break-words text-fuchsia-400 hover:text-fuchsia-500"
|
||||||
>
|
>
|
||||||
{user?.name || user?.display_name || user?.username || displayNpub(pubkey, 16)}
|
{user?.name ||
|
||||||
|
user?.display_name ||
|
||||||
|
user?.displayName ||
|
||||||
|
user?.username ||
|
||||||
|
displayNpub(pubkey, 16)}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import * as Popover from '@radix-ui/react-popover';
|
import * as HoverCard from '@radix-ui/react-hover-card';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import ReactMarkdown from 'react-markdown';
|
import ReactMarkdown from 'react-markdown';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
@ -70,7 +70,10 @@ export const User = memo(function User({
|
|||||||
</button>
|
</button>
|
||||||
<div className="flex flex-1 items-baseline gap-2">
|
<div className="flex flex-1 items-baseline gap-2">
|
||||||
<h5 className="max-w-[10rem] truncate font-semibold leading-none text-white">
|
<h5 className="max-w-[10rem] truncate font-semibold leading-none text-white">
|
||||||
{user?.display_name || user?.name || displayNpub(pubkey, 16)}
|
{user?.name ||
|
||||||
|
user?.display_name ||
|
||||||
|
user?.displayName ||
|
||||||
|
displayNpub(pubkey, 16)}
|
||||||
</h5>
|
</h5>
|
||||||
<span className="leading-none text-white/50">·</span>
|
<span className="leading-none text-white/50">·</span>
|
||||||
<span className="leading-none text-white/50">{createdAt}</span>
|
<span className="leading-none text-white/50">{createdAt}</span>
|
||||||
@ -90,7 +93,7 @@ export const User = memo(function User({
|
|||||||
<div className="flex h-full flex-col items-start justify-between">
|
<div className="flex h-full flex-col items-start justify-between">
|
||||||
<div className="flex flex-col items-start gap-1 text-start">
|
<div className="flex flex-col items-start gap-1 text-start">
|
||||||
<p className="max-w-[15rem] truncate text-lg font-semibold leading-none text-white">
|
<p className="max-w-[15rem] truncate text-lg font-semibold leading-none text-white">
|
||||||
{user?.name || user?.display_name}
|
{user?.name || user?.display_name || user?.displayName}
|
||||||
</p>
|
</p>
|
||||||
<ReactMarkdown
|
<ReactMarkdown
|
||||||
remarkPlugins={[remarkGfm]}
|
remarkPlugins={[remarkGfm]}
|
||||||
@ -110,7 +113,7 @@ export const User = memo(function User({
|
|||||||
className="inline-flex items-center gap-2 text-sm text-white/70"
|
className="inline-flex items-center gap-2 text-sm text-white/70"
|
||||||
>
|
>
|
||||||
<WorldIcon className="h-4 w-4" />
|
<WorldIcon className="h-4 w-4" />
|
||||||
<p className="max-w-[10rem] truncate">{user.website}</p>
|
<p className="max-w-[10rem] truncate">{user?.website}</p>
|
||||||
</Link>
|
</Link>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
@ -129,7 +132,7 @@ export const User = memo(function User({
|
|||||||
/>
|
/>
|
||||||
<div className="flex w-full flex-col gap-1">
|
<div className="flex w-full flex-col gap-1">
|
||||||
<h3 className="max-w-[15rem] truncate font-medium leading-none text-white">
|
<h3 className="max-w-[15rem] truncate font-medium leading-none text-white">
|
||||||
{user?.name || user?.display_name}
|
{user?.name || user?.display_name || user?.displayName}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-sm leading-none text-white/70">
|
<p className="text-sm leading-none text-white/70">
|
||||||
{user?.nip05 || user?.username || displayNpub(pubkey, 16)}
|
{user?.nip05 || user?.username || displayNpub(pubkey, 16)}
|
||||||
@ -163,7 +166,10 @@ export const User = memo(function User({
|
|||||||
/>
|
/>
|
||||||
<div className="inline-flex items-baseline gap-1">
|
<div className="inline-flex items-baseline gap-1">
|
||||||
<h5 className="max-w-[10rem] truncate font-medium leading-none text-white/80">
|
<h5 className="max-w-[10rem] truncate font-medium leading-none text-white/80">
|
||||||
{user?.display_name || user?.name || displayNpub(pubkey, 16)}
|
{user?.name ||
|
||||||
|
user?.display_name ||
|
||||||
|
user?.displayName ||
|
||||||
|
displayNpub(pubkey, 16)}
|
||||||
</h5>
|
</h5>
|
||||||
<span className="text-blue-500">reposted</span>
|
<span className="text-blue-500">reposted</span>
|
||||||
</div>
|
</div>
|
||||||
@ -182,7 +188,7 @@ export const User = memo(function User({
|
|||||||
/>
|
/>
|
||||||
<div className="flex flex-1 flex-col gap-2">
|
<div className="flex flex-1 flex-col gap-2">
|
||||||
<h5 className="max-w-[15rem] truncate font-semibold leading-none text-white">
|
<h5 className="max-w-[15rem] truncate font-semibold leading-none text-white">
|
||||||
{user?.display_name || user?.name}
|
{user?.name || user?.display_name || user?.displayName}
|
||||||
</h5>
|
</h5>
|
||||||
<div className="inline-flex items-center gap-2">
|
<div className="inline-flex items-center gap-2">
|
||||||
<span className="leading-none text-white/50">{createdAt}</span>
|
<span className="leading-none text-white/50">{createdAt}</span>
|
||||||
@ -195,9 +201,9 @@ export const User = memo(function User({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover.Root>
|
<HoverCard.Root>
|
||||||
<div className="relative z-10 flex items-start gap-3">
|
<div className="relative z-10 flex items-start gap-3">
|
||||||
<Popover.Trigger asChild>
|
<HoverCard.Trigger asChild>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="relative z-40 h-10 w-10 shrink-0 overflow-hidden"
|
className="relative z-40 h-10 w-10 shrink-0 overflow-hidden"
|
||||||
@ -208,18 +214,21 @@ export const User = memo(function User({
|
|||||||
className="h-10 w-10 rounded-lg object-cover"
|
className="h-10 w-10 rounded-lg object-cover"
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
</Popover.Trigger>
|
</HoverCard.Trigger>
|
||||||
<div className="flex flex-1 items-baseline gap-2">
|
<div className="flex flex-1 items-baseline gap-2">
|
||||||
<h5 className="max-w-[15rem] truncate font-semibold leading-none text-white">
|
<h5 className="max-w-[15rem] truncate font-semibold leading-none text-white">
|
||||||
{user?.display_name || user?.name || displayNpub(pubkey, 16)}
|
{user?.name ||
|
||||||
|
user?.display_name ||
|
||||||
|
user?.displayName ||
|
||||||
|
displayNpub(pubkey, 16)}
|
||||||
</h5>
|
</h5>
|
||||||
<span className="leading-none text-white/50">·</span>
|
<span className="leading-none text-white/50">·</span>
|
||||||
<span className="leading-none text-white/50">{createdAt}</span>
|
<span className="leading-none text-white/50">{createdAt}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Popover.Portal>
|
<HoverCard.Portal>
|
||||||
<Popover.Content
|
<HoverCard.Content
|
||||||
className="w-[300px] overflow-hidden rounded-xl border border-white/10 bg-white/10 backdrop-blur-3xl focus:outline-none"
|
className="w-[300px] overflow-hidden rounded-xl border border-white/10 bg-white/10 backdrop-blur-3xl focus:outline-none data-[side=bottom]:animate-slideUpAndFade data-[side=left]:animate-slideRightAndFade data-[side=right]:animate-slideLeftAndFade data-[side=top]:animate-slideDownAndFade data-[state=open]:transition-all"
|
||||||
sideOffset={5}
|
sideOffset={5}
|
||||||
>
|
>
|
||||||
<div className="flex gap-2.5 border-b border-white/5 px-3 py-3">
|
<div className="flex gap-2.5 border-b border-white/5 px-3 py-3">
|
||||||
@ -231,12 +240,15 @@ export const User = memo(function User({
|
|||||||
<div className="flex flex-1 flex-col gap-2">
|
<div className="flex flex-1 flex-col gap-2">
|
||||||
<div className="inline-flex flex-col gap-1">
|
<div className="inline-flex flex-col gap-1">
|
||||||
<h5 className="text-sm font-semibold leading-none">
|
<h5 className="text-sm font-semibold leading-none">
|
||||||
{user?.display_name || user?.name || user?.username}
|
{user?.name ||
|
||||||
|
user?.display_name ||
|
||||||
|
user?.displayName ||
|
||||||
|
user?.username}
|
||||||
</h5>
|
</h5>
|
||||||
{user.nip05 ? (
|
{user?.nip05 ? (
|
||||||
<NIP05
|
<NIP05
|
||||||
pubkey={pubkey}
|
pubkey={pubkey}
|
||||||
nip05={user.nip05}
|
nip05={user?.nip05}
|
||||||
className="max-w-[15rem] truncate text-sm leading-none text-white/50"
|
className="max-w-[15rem] truncate text-sm leading-none text-white/50"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
@ -266,8 +278,8 @@ export const User = memo(function User({
|
|||||||
Message
|
Message
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</Popover.Content>
|
</HoverCard.Content>
|
||||||
</Popover.Portal>
|
</HoverCard.Portal>
|
||||||
</Popover.Root>
|
</HoverCard.Root>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -14,7 +14,7 @@ export const useSidebar = create<SidebarState>()(
|
|||||||
persist(
|
persist(
|
||||||
(set) => ({
|
(set) => ({
|
||||||
feeds: true,
|
feeds: true,
|
||||||
chats: true,
|
chats: false,
|
||||||
integrations: true,
|
integrations: true,
|
||||||
toggleFeeds: () => set((state) => ({ feeds: !state.feeds })),
|
toggleFeeds: () => set((state) => ({ feeds: !state.feeds })),
|
||||||
toggleChats: () => set((state) => ({ chats: !state.chats })),
|
toggleChats: () => set((state) => ({ chats: !state.chats })),
|
||||||
|
@ -36,10 +36,17 @@ export function useNostr() {
|
|||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
const sub = async (filter: NDKFilter, callback: (event: NDKEvent) => void) => {
|
const sub = async (
|
||||||
|
filter: NDKFilter,
|
||||||
|
callback: (event: NDKEvent) => void,
|
||||||
|
groupable?: boolean
|
||||||
|
) => {
|
||||||
if (!ndk) throw new Error('NDK instance not found');
|
if (!ndk) throw new Error('NDK instance not found');
|
||||||
|
|
||||||
const subEvent = ndk.subscribe(filter, { closeOnEose: false });
|
const subEvent = ndk.subscribe(filter, {
|
||||||
|
closeOnEose: false,
|
||||||
|
groupable: groupable ?? true,
|
||||||
|
});
|
||||||
subManager.set(JSON.stringify(filter), subEvent);
|
subManager.set(JSON.stringify(filter), subEvent);
|
||||||
|
|
||||||
subEvent.addListener('event', (event: NDKEvent) => {
|
subEvent.addListener('event', (event: NDKEvent) => {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { NDKKind, NDKUserProfile } from '@nostr-dev-kit/ndk';
|
import { NDKUserProfile } from '@nostr-dev-kit/ndk';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
import { useNDK } from '@libs/ndk/provider';
|
import { useNDK } from '@libs/ndk/provider';
|
||||||
@ -14,30 +14,20 @@ export function useProfile(pubkey: string, embed?: string) {
|
|||||||
} = useQuery(
|
} = useQuery(
|
||||||
['user', pubkey],
|
['user', pubkey],
|
||||||
async () => {
|
async () => {
|
||||||
if (!embed) {
|
if (embed) {
|
||||||
const cleanPubkey = pubkey.replace('-', '');
|
|
||||||
const dbEvent = await db.getMetadataByPubkey(cleanPubkey);
|
|
||||||
if (dbEvent) {
|
|
||||||
return JSON.parse(dbEvent.content) as NDKUserProfile;
|
|
||||||
}
|
|
||||||
|
|
||||||
const events = await ndk.fetchEvents(
|
|
||||||
{
|
|
||||||
kinds: [NDKKind.Metadata],
|
|
||||||
authors: [cleanPubkey],
|
|
||||||
limit: 100,
|
|
||||||
},
|
|
||||||
{ closeOnEose: true }
|
|
||||||
);
|
|
||||||
if (!events) throw new Error(`User not found: ${pubkey}`);
|
|
||||||
const latestEvent = [...events].sort((a, b) => b.created_at - a.created_at)[0];
|
|
||||||
await db.createMetadata(latestEvent);
|
|
||||||
|
|
||||||
return JSON.parse(latestEvent.content) as NDKUserProfile;
|
|
||||||
} else {
|
|
||||||
const profile: NDKUserProfile = JSON.parse(embed);
|
const profile: NDKUserProfile = JSON.parse(embed);
|
||||||
return profile;
|
return profile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const cleanPubkey = pubkey.replace('-', '');
|
||||||
|
const user = ndk.getUser({ hexpubkey: cleanPubkey });
|
||||||
|
|
||||||
|
const profile = await user.fetchProfile({ closeOnEose: true });
|
||||||
|
if (!user.profile) return Promise.reject(new Error('profile not found'));
|
||||||
|
|
||||||
|
await db.createProfile(cleanPubkey, profile);
|
||||||
|
|
||||||
|
return user.profile;
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
enabled: !!ndk,
|
enabled: !!ndk,
|
||||||
|
Loading…
Reference in New Issue
Block a user