ok fine
This commit is contained in:
@@ -6,7 +6,7 @@ import { Link } from 'react-router-dom';
|
||||
|
||||
import { useStorage } from '@libs/storage/provider';
|
||||
|
||||
import { HorizontalDotsIcon } from '@shared/icons';
|
||||
import { AccountMoreActions } from '@shared/accounts/more';
|
||||
|
||||
import { useActivities } from '@stores/activities';
|
||||
|
||||
@@ -80,9 +80,7 @@ export function ActiveAccount() {
|
||||
</Avatar.Root>
|
||||
<span className="absolute bottom-0 right-0 block h-2 w-2 rounded-full bg-teal-500 ring-2 ring-neutral-100 dark:ring-neutral-900" />
|
||||
</Link>
|
||||
<div className="inline-flex items-center justify-center rounded-md">
|
||||
<HorizontalDotsIcon className="h-4 w-4" />
|
||||
</div>
|
||||
<AccountMoreActions pubkey={db.account.pubkey} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -13,17 +13,17 @@ export function AccountMoreActions({ pubkey }: { pubkey: string }) {
|
||||
<DropdownMenu.Trigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="group ml-auto inline-flex h-7 w-7 items-center justify-center rounded-md hover:bg-white/10"
|
||||
className="inline-flex items-center justify-center rounded-md"
|
||||
>
|
||||
<HorizontalDotsIcon className="h-5 w-5 text-white/80 group-hover:text-white" />
|
||||
<HorizontalDotsIcon className="h-4 w-4" />
|
||||
</button>
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Portal>
|
||||
<DropdownMenu.Content className="flex w-[200px] flex-col overflow-hidden rounded-xl border border-white/10 bg-white/20 p-2 backdrop-blur-3xl focus:outline-none">
|
||||
<DropdownMenu.Content className="ml-2 flex w-[200px] flex-col overflow-hidden rounded-xl bg-blue-500 p-2 focus:outline-none">
|
||||
<DropdownMenu.Item asChild>
|
||||
<Link
|
||||
to={`/users/${pubkey}`}
|
||||
className="inline-flex h-10 items-center rounded-lg px-2 text-sm font-medium text-white hover:bg-white/10 focus:outline-none"
|
||||
className="inline-flex h-10 items-center rounded-lg px-2 text-sm font-medium text-white hover:bg-blue-600 focus:outline-none"
|
||||
>
|
||||
Profile
|
||||
</Link>
|
||||
@@ -31,7 +31,7 @@ export function AccountMoreActions({ pubkey }: { pubkey: string }) {
|
||||
<DropdownMenu.Item asChild>
|
||||
<Link
|
||||
to={`/settings/backup`}
|
||||
className="inline-flex h-10 items-center rounded-lg px-2 text-sm font-medium text-white hover:bg-white/10 focus:outline-none"
|
||||
className="inline-flex h-10 items-center rounded-lg px-2 text-sm font-medium text-white hover:bg-blue-600 focus:outline-none"
|
||||
>
|
||||
Backup
|
||||
</Link>
|
||||
@@ -39,7 +39,7 @@ export function AccountMoreActions({ pubkey }: { pubkey: string }) {
|
||||
<DropdownMenu.Item asChild>
|
||||
<Link
|
||||
to={`/settings/`}
|
||||
className="inline-flex h-10 items-center rounded-lg px-2 text-sm font-medium text-white hover:bg-white/10 focus:outline-none"
|
||||
className="inline-flex h-10 items-center rounded-lg px-2 text-sm font-medium text-white hover:bg-blue-600 focus:outline-none"
|
||||
>
|
||||
Settings
|
||||
</Link>
|
||||
|
||||
@@ -41,7 +41,8 @@ export function Composer() {
|
||||
content: JSON.parse(localStorage.getItem('editor-content') || '{}'),
|
||||
editorProps: {
|
||||
attributes: {
|
||||
class: 'h-full markdown break-all overflow-y-auto outline-none pr-2',
|
||||
class:
|
||||
'h-full prose prose-neutral max-w-none select-text whitespace-pre-line leading-normal dark:prose-invert prose-headings:mb-1 prose-headings:mt-3 prose-p:mb-0 prose-p:mt-0 prose-p:last:mb-1 prose-a:font-normal prose-a:text-blue-500 prose-blockquote:mb-1 prose-blockquote:mt-1 prose-blockquote:border-l-[2px] prose-blockquote:border-blue-500 prose-blockquote:pl-2 prose-pre:whitespace-pre-wrap prose-pre:break-words prose-pre:break-all prose-pre:bg-white/10 prose-ol:m-0 prose-ol:mb-1 prose-ul:mb-1 prose-ul:mt-1 prose-img:mb-2 prose-img:mt-3 prose-hr:mx-0 prose-hr:my-2 hover:prose-a:text-blue-500 break-all overflow-y-auto outline-none pr-2',
|
||||
},
|
||||
},
|
||||
onUpdate: ({ editor }) => {
|
||||
@@ -114,7 +115,7 @@ export function Composer() {
|
||||
<div className="flex h-full flex-col">
|
||||
<div className="flex h-full w-full gap-3 px-4 pb-4">
|
||||
<div className="flex w-10 shrink-0 items-center justify-center">
|
||||
<div className="h-full w-[2px] bg-white/10 backdrop-blur-xl" />
|
||||
<div className="h-full w-[2px] bg-neutral-100 dark:bg-neutral-900" />
|
||||
</div>
|
||||
<div className="w-full">
|
||||
<EditorContent
|
||||
@@ -134,15 +135,15 @@ export function Composer() {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => clearReply()}
|
||||
className="absolute right-3 top-3 inline-flex h-6 w-6 items-center justify-center rounded bg-white/10 px-2 backdrop-blur-xl"
|
||||
className="absolute right-3 top-3 inline-flex h-6 w-6 items-center justify-center rounded bg-neutral-300 px-2 dark:bg-neutral-700"
|
||||
>
|
||||
<CancelIcon className="h-4 w-4 text-white" />
|
||||
<CancelIcon className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-between rounded-b-xl border-t border-white/10 bg-white/5 p-2">
|
||||
<div className="flex items-center justify-between rounded-b-xl border-t border-neutral-200 bg-neutral-100 p-2 dark:border-neutral-800 dark:bg-neutral-900">
|
||||
<div className="inline-flex items-center gap-1">
|
||||
<MediaUploader editor={editor} />
|
||||
<MentionPopup editor={editor} />
|
||||
@@ -150,13 +151,9 @@ export function Composer() {
|
||||
<button
|
||||
onClick={() => submit()}
|
||||
disabled={editor && editor.isEmpty}
|
||||
className="inline-flex h-10 w-20 items-center justify-center rounded-lg bg-blue-500 px-2 font-semibold hover:bg-blue-600 disabled:opacity-50"
|
||||
className="inline-flex h-9 w-20 items-center justify-center rounded-lg bg-blue-500 px-2 font-medium text-white hover:bg-blue-600 disabled:opacity-50"
|
||||
>
|
||||
{loading === true ? (
|
||||
<LoaderIcon className="h-5 w-5 animate-spin text-white" />
|
||||
) : (
|
||||
'Post'
|
||||
)}
|
||||
{loading === true ? <LoaderIcon className="h-5 w-5 animate-spin" /> : 'Post'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { message } from '@tauri-apps/plugin-dialog';
|
||||
import { UnlistenFn, listen } from '@tauri-apps/api/event';
|
||||
import { message } from '@tauri-apps/plugin-dialog';
|
||||
import { Editor } from '@tiptap/react';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
@@ -53,9 +53,9 @@ export function MediaUploader({ editor }: { editor: Editor }) {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => uploadToNostrBuild()}
|
||||
className="ml-2 inline-flex h-10 w-max items-center justify-center gap-1.5 rounded-lg px-2 text-sm font-medium text-white/80 hover:bg-white/10 hover:backdrop-blur-xl"
|
||||
className="ml-2 inline-flex h-10 w-max items-center justify-center gap-1.5 rounded-lg px-2 text-sm font-medium text-neutral-600 hover:bg-neutral-200 dark:bg-neutral-800 dark:text-neutral-400"
|
||||
>
|
||||
<MediaIcon className="h-5 w-5 text-white/80" />
|
||||
<MediaIcon className="h-5 w-5" />
|
||||
{loading ? 'Uploading...' : 'Add media'}
|
||||
</button>
|
||||
);
|
||||
|
||||
@@ -19,9 +19,9 @@ export function MentionPopup({ editor }: { editor: Editor }) {
|
||||
<Popover.Trigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex h-10 w-10 items-center justify-center rounded-lg hover:bg-white/10 hover:backdrop-blur-xl"
|
||||
className="inline-flex h-10 w-10 items-center justify-center rounded-lg text-neutral-600 hover:bg-neutral-200 dark:text-neutral-400 dark:hover:bg-neutral-800"
|
||||
>
|
||||
<MentionIcon className="h-5 w-5 text-white/80" />
|
||||
<MentionIcon className="h-5 w-5" />
|
||||
</button>
|
||||
</Popover.Trigger>
|
||||
<Popover.Content className="h-full max-h-[200px] w-[250px] overflow-hidden overflow-y-auto rounded-lg bg-white/10 backdrop-blur-xl focus:outline-none">
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import * as Dialog from '@radix-ui/react-dialog';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
import { useStorage } from '@libs/storage/provider';
|
||||
|
||||
@@ -9,19 +8,13 @@ import {
|
||||
ChevronDownIcon,
|
||||
ChevronRightIcon,
|
||||
ComposeIcon,
|
||||
ExpandIcon,
|
||||
} from '@shared/icons';
|
||||
|
||||
import { useComposer } from '@stores/composer';
|
||||
|
||||
export function ComposerModal() {
|
||||
const { db } = useStorage();
|
||||
|
||||
const [toggleModal, open] = useComposer((state) => [state.toggleModal, state.open]);
|
||||
const [toggleExpand, expand] = useComposer((state) => [
|
||||
state.toggleExpand,
|
||||
state.expand,
|
||||
]);
|
||||
|
||||
return (
|
||||
<Dialog.Root open={open} onOpenChange={toggleModal}>
|
||||
@@ -34,35 +27,21 @@ export function ComposerModal() {
|
||||
</button>
|
||||
</Dialog.Trigger>
|
||||
<Dialog.Portal>
|
||||
<Dialog.Overlay className="fixed inset-0 z-50 bg-white dark:bg-black" />
|
||||
<Dialog.Overlay className="fixed inset-0 z-50 bg-black/50 backdrop-blur-xl dark:bg-white/50" />
|
||||
<Dialog.Content className="fixed inset-0 z-50 flex min-h-full items-center justify-center">
|
||||
<div
|
||||
className={twMerge(
|
||||
'relative h-min w-full rounded-xl bg-neutral-100 dark:bg-neutral-900',
|
||||
expand ? 'max-w-4xl' : 'max-w-2xl'
|
||||
)}
|
||||
>
|
||||
<div className="relative h-min w-full max-w-2xl rounded-xl bg-white dark:bg-black">
|
||||
<div className="flex items-center justify-between px-4 py-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<ComposerUser pubkey={db.account.pubkey} />
|
||||
<ChevronRightIcon className="h-4 w-4 text-neutral-400 dark:text-neutral-600" />
|
||||
<ChevronRightIcon className="h-4 w-4 text-neutral-600 dark:text-neutral-400" />
|
||||
<div className="inline-flex h-7 w-max items-center justify-center gap-0.5 rounded bg-neutral-200 pl-3 pr-1.5 text-sm font-medium text-neutral-900 dark:bg-neutral-800 dark:text-neutral-100">
|
||||
New Post
|
||||
<ChevronDownIcon className="h-4 w-4" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="inline-flex items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => toggleExpand()}
|
||||
className="inline-flex h-10 w-10 items-center justify-center rounded-lg text-neutral-400 hover:bg-neutral-200 hover:text-neutral-500 dark:text-neutral-600 dark:hover:bg-neutral-800 dark:hover:text-neutral-400"
|
||||
>
|
||||
<ExpandIcon className="h-5 w-5" />
|
||||
</button>
|
||||
<Dialog.Close className="inline-flex h-10 w-10 items-center justify-center rounded-lg text-neutral-400 hover:bg-neutral-200 hover:text-neutral-500 dark:text-neutral-600 dark:hover:bg-neutral-800 dark:hover:text-neutral-400">
|
||||
<CancelIcon className="h-5 w-5" />
|
||||
</Dialog.Close>
|
||||
</div>
|
||||
<Dialog.Close className="inline-flex h-10 w-10 items-center justify-center rounded-lg text-neutral-600 hover:bg-neutral-200 hover:text-neutral-500 dark:text-neutral-400 dark:hover:bg-neutral-800 dark:hover:text-neutral-400">
|
||||
<CancelIcon className="h-5 w-5" />
|
||||
</Dialog.Close>
|
||||
</div>
|
||||
<Composer />
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { Image } from '@shared/image';
|
||||
|
||||
import { useProfile } from '@utils/hooks/useProfile';
|
||||
import { displayNpub } from '@utils/shortenKey';
|
||||
|
||||
@@ -8,12 +6,12 @@ export function ComposerUser({ pubkey }: { pubkey: string }) {
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-3">
|
||||
<Image
|
||||
<img
|
||||
src={user?.picture || user?.image}
|
||||
alt={pubkey}
|
||||
className="h-10 w-10 shrink-0 rounded-lg"
|
||||
/>
|
||||
<h5 className="font-medium text-white">
|
||||
<h5 className="font-medium text-neutral-900 dark:text-neutral-100">
|
||||
{user?.display_name || user?.name || user?.displayName || displayNpub(pubkey, 16)}
|
||||
</h5>
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,22 @@
|
||||
import { Outlet } from 'react-router-dom';
|
||||
import { Outlet, ScrollRestoration } from 'react-router-dom';
|
||||
import { WindowTitlebar } from 'tauri-controls';
|
||||
|
||||
import { useStorage } from '@libs/storage/provider';
|
||||
|
||||
export function NoteLayout() {
|
||||
const { db } = useStorage();
|
||||
|
||||
return (
|
||||
<div className="relative h-screen w-screen">
|
||||
<div className="absolute left-0 top-0 z-50 h-16 w-full" data-tauri-drag-region />
|
||||
<Outlet />
|
||||
<div className="h-screen w-screen bg-neutral-50 dark:bg-neutral-950">
|
||||
{db.platform !== 'macos' ? (
|
||||
<WindowTitlebar />
|
||||
) : (
|
||||
<div data-tauri-drag-region className="h-9" />
|
||||
)}
|
||||
<div className="h-full w-full">
|
||||
<Outlet />
|
||||
<ScrollRestoration />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,8 +3,6 @@ import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { useStorage } from '@libs/storage/provider';
|
||||
|
||||
import { LogoutIcon } from '@shared/icons';
|
||||
|
||||
export function Logout() {
|
||||
const { db } = useStorage();
|
||||
|
||||
@@ -22,9 +20,9 @@ export function Logout() {
|
||||
<AlertDialog.Trigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex h-9 w-9 items-center justify-center rounded-r-lg hover:bg-white/10"
|
||||
className="inline-flex h-10 items-center rounded-lg px-2 text-sm font-medium text-white hover:bg-blue-600 focus:outline-none"
|
||||
>
|
||||
<LogoutIcon className="h-5 w-5 text-white" />
|
||||
Logout
|
||||
</button>
|
||||
</AlertDialog.Trigger>
|
||||
<AlertDialog.Portal>
|
||||
|
||||
@@ -61,9 +61,7 @@ export const NIP05 = memo(function NIP05({
|
||||
|
||||
return (
|
||||
<div className={twMerge('inline-flex items-center gap-1', className)}>
|
||||
<div>
|
||||
<p className="text-sm">{nip05}</p>
|
||||
</div>
|
||||
<p className="text-sm">{nip05}</p>
|
||||
<div className="shrink-0">
|
||||
{data === true ? (
|
||||
<VerifiedIcon className="h-3 w-3 text-green-500" />
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
|
||||
import * as Tooltip from '@radix-ui/react-tooltip';
|
||||
import { writeText } from '@tauri-apps/plugin-clipboard-manager';
|
||||
import { nip19 } from 'nostr-tools';
|
||||
import { EventPointer } from 'nostr-tools/lib/nip19';
|
||||
import { EventPointer } from 'nostr-tools/lib/types/nip19';
|
||||
import { useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
@@ -25,19 +24,20 @@ export function MoreActions({ id, pubkey }: { id: string; pubkey: string }) {
|
||||
|
||||
return (
|
||||
<DropdownMenu.Root open={open} onOpenChange={setOpen}>
|
||||
<DropdownMenu.Trigger>
|
||||
<HorizontalDotsIcon className="h-5 w-5 text-neutral-800 hover:text-blue-500 dark:text-neutral-200" />
|
||||
<DropdownMenu.Trigger asChild>
|
||||
<button type="button" className="inline-flex h-7 w-7 items-center justify-center">
|
||||
<HorizontalDotsIcon className="h-5 w-5 text-neutral-800 hover:text-blue-500 dark:text-neutral-200" />
|
||||
</button>
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Portal>
|
||||
<DropdownMenu.Content className="flex w-[200px] flex-col overflow-hidden rounded-xl border border-neutral-300 bg-neutral-200 focus:outline-none dark:border-neutral-700 dark:bg-neutral-800">
|
||||
<DropdownMenu.Item asChild>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => copyLink()}
|
||||
<Link
|
||||
to={`/notes/text/${id}`}
|
||||
className="inline-flex h-10 items-center px-4 text-sm text-neutral-900 hover:bg-neutral-300 focus:outline-none dark:text-neutral-100 dark:hover:bg-neutral-700"
|
||||
>
|
||||
Focus
|
||||
</button>
|
||||
</Link>
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item asChild>
|
||||
<button
|
||||
|
||||
@@ -62,7 +62,7 @@ export function NoteReaction({ id, pubkey }: { id: string; pubkey: string }) {
|
||||
<Popover.Trigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="group inline-flex h-7 w-7 items-center justify-center text-neutral-500 dark:text-neutral-300"
|
||||
className="group inline-flex h-7 w-7 items-center justify-center text-neutral-600 dark:text-neutral-400"
|
||||
>
|
||||
{reaction ? (
|
||||
<img src={getReactionImage(reaction)} alt={reaction} className="h-5 w-5" />
|
||||
|
||||
@@ -21,7 +21,7 @@ export function NoteReply({
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setReply(id, pubkey, root)}
|
||||
className="group inline-flex h-7 w-7 items-center justify-center text-neutral-500 dark:text-neutral-300"
|
||||
className="group inline-flex h-7 w-7 items-center justify-center text-neutral-600 dark:text-neutral-400"
|
||||
>
|
||||
<ReplyIcon className="h-5 w-5 group-hover:text-blue-500" />
|
||||
</button>
|
||||
|
||||
@@ -44,7 +44,7 @@ export function NoteRepost({ id, pubkey }: { id: string; pubkey: string }) {
|
||||
<AlertDialog.Trigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="group inline-flex h-7 w-7 items-center justify-center text-neutral-500 dark:text-neutral-300"
|
||||
className="group inline-flex h-7 w-7 items-center justify-center text-neutral-600 dark:text-neutral-400"
|
||||
>
|
||||
<RepostIcon
|
||||
className={twMerge(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { webln } from '@getalby/sdk';
|
||||
import { SendPaymentResponse } from '@getalby/sdk/dist/types';
|
||||
import * as Dialog from '@radix-ui/react-dialog';
|
||||
import { invoke } from '@tauri-apps/api';
|
||||
import { message } from '@tauri-apps/plugin-dialog';
|
||||
import { QRCodeSVG } from 'qrcode.react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
@@ -79,9 +80,10 @@ export function NoteZap({ id, pubkey }: { id: string; pubkey: string }) {
|
||||
|
||||
useEffect(() => {
|
||||
async function getWalletConnectURL() {
|
||||
// const uri: string = await invoke('secure_load', { key: 'nwc' });
|
||||
setWalletConnectURL('todo');
|
||||
const uri: string = await invoke('secure_load', { key: 'nwc' });
|
||||
if (uri) setWalletConnectURL(uri);
|
||||
}
|
||||
|
||||
getWalletConnectURL();
|
||||
|
||||
return () => {
|
||||
@@ -97,7 +99,7 @@ export function NoteZap({ id, pubkey }: { id: string; pubkey: string }) {
|
||||
<Dialog.Trigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="group inline-flex h-7 w-7 items-center justify-center text-neutral-500 dark:text-neutral-300"
|
||||
className="group inline-flex h-7 w-7 items-center justify-center text-neutral-600 dark:text-neutral-400"
|
||||
>
|
||||
<ZapIcon className="h-5 w-5 group-hover:text-blue-500" />
|
||||
</button>
|
||||
|
||||
@@ -80,7 +80,7 @@ export function ChildNote({ id, root }: { id: string; root?: string }) {
|
||||
<>
|
||||
<div className="absolute bottom-0 left-[18px] h-[calc(100%-3.6rem)] w-0.5 bg-gradient-to-t from-black/20 to-black/10 dark:from-white/20 dark:to-white/10" />
|
||||
<div className="mb-5 flex flex-col">
|
||||
<User pubkey={data.pubkey} time={data.created_at} />
|
||||
<User pubkey={data.pubkey} time={data.created_at} eventId={data.id} />
|
||||
<div className="-mt-4 flex items-start gap-3">
|
||||
<div className="w-10 shrink-0" />
|
||||
<div className="relative z-20 flex-1">
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { NDKEvent } from '@nostr-dev-kit/ndk';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import { Link } from 'react-router-dom';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
|
||||
import { Boost, Hashtag, Invoice, MentionUser } from '@shared/notes';
|
||||
|
||||
export function ArticleDetailNote({ event }: { event: NDKEvent }) {
|
||||
/*const metadata = useMemo(() => {
|
||||
const title = event.tags.find((tag) => tag[0] === 'title')?.[1];
|
||||
@@ -26,7 +29,38 @@ export function ArticleDetailNote({ event }: { event: NDKEvent }) {
|
||||
}, [event.id]);*/
|
||||
|
||||
return (
|
||||
<ReactMarkdown className="markdown-article" remarkPlugins={[remarkGfm]}>
|
||||
<ReactMarkdown
|
||||
className="prose prose-neutral max-w-none select-text whitespace-pre-line leading-normal dark:prose-invert prose-headings:mb-1 prose-headings:mt-3 prose-p:mb-0 prose-p:mt-0 prose-p:last:mb-1 prose-a:font-normal prose-a:text-blue-500 prose-blockquote:mb-1 prose-blockquote:mt-1 prose-blockquote:border-l-[2px] prose-blockquote:border-blue-500 prose-blockquote:pl-2 prose-pre:whitespace-pre-wrap prose-pre:break-words prose-pre:break-all prose-pre:bg-white/10 prose-ol:m-0 prose-ol:mb-1 prose-ul:mb-1 prose-ul:mt-1 prose-img:mb-2 prose-img:mt-3 prose-hr:mx-0 prose-hr:my-2 hover:prose-a:text-blue-500"
|
||||
remarkPlugins={[remarkGfm]}
|
||||
components={{
|
||||
a: ({ href }) => {
|
||||
const cleanURL = new URL(href);
|
||||
cleanURL.search = '';
|
||||
return (
|
||||
<Link to={href} target="_blank" className="w-max break-all hover:!underline">
|
||||
{cleanURL.hostname + cleanURL.pathname}
|
||||
</Link>
|
||||
);
|
||||
},
|
||||
del: ({ children }) => {
|
||||
const key = children[0] as string;
|
||||
if (typeof key !== 'string') return;
|
||||
if (key.startsWith('pub') && key.length > 50 && key.length < 100) {
|
||||
return <MentionUser pubkey={key.replace('pub-', '')} />;
|
||||
}
|
||||
if (key.startsWith('tag')) {
|
||||
return <Hashtag tag={key.replace('tag-', '')} />;
|
||||
}
|
||||
if (key.startsWith('boost')) {
|
||||
return <Boost boost={key.replace('boost-', '')} />;
|
||||
}
|
||||
if (key.startsWith('lnbc')) {
|
||||
return <Invoice invoice={key.replace('lnbc-', '')} />;
|
||||
}
|
||||
},
|
||||
}}
|
||||
linkTarget={'_blank'}
|
||||
>
|
||||
{event.content}
|
||||
</ReactMarkdown>
|
||||
);
|
||||
|
||||
@@ -70,7 +70,11 @@ export function Repost({
|
||||
>
|
||||
<User pubkey={event.pubkey} time={event.created_at} variant="repost" />
|
||||
<div className="relative flex flex-col">
|
||||
<User pubkey={embedEvent.pubkey} time={embedEvent.created_at} />
|
||||
<User
|
||||
pubkey={embedEvent.pubkey}
|
||||
time={embedEvent.created_at}
|
||||
eventId={embedEvent.id}
|
||||
/>
|
||||
<div className="-mt-4 flex items-start gap-3">
|
||||
<div className="w-10 shrink-0" />
|
||||
<div className="relative z-20 flex-1">
|
||||
@@ -147,7 +151,7 @@ export function Repost({
|
||||
>
|
||||
<User pubkey={event.pubkey} time={event.created_at} variant="repost" />
|
||||
<div className="relative flex flex-col">
|
||||
<User pubkey={data.pubkey} time={data.created_at} />
|
||||
<User pubkey={data.pubkey} time={data.created_at} eventId={data.id} />
|
||||
<div className="-mt-4 flex items-start gap-3">
|
||||
<div className="w-10 shrink-0" />
|
||||
<div className="relative z-20 flex-1">
|
||||
|
||||
@@ -16,13 +16,13 @@ export function ImagePreview({ urls }: { urls: string[] }) {
|
||||
<div key={url} className="group relative">
|
||||
<img
|
||||
src={url}
|
||||
alt="image"
|
||||
className="h-auto w-full rounded-lg border border-neutral-200 object-cover dark:border-neutral-800"
|
||||
alt={url}
|
||||
className="h-auto w-full rounded-lg border border-neutral-300 object-cover dark:border-neutral-700"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => downloadImage(url)}
|
||||
className="absolute right-2 top-2 hidden h-8 w-8 items-center justify-center rounded-md bg-black/50 backdrop-blur-md group-hover:inline-flex hover:bg-black/40"
|
||||
className="absolute right-2 top-2 hidden h-10 w-10 items-center justify-center rounded-lg bg-black/50 backdrop-blur-xl group-hover:inline-flex hover:bg-blue-500"
|
||||
>
|
||||
<DownloadIcon className="h-5 w-5 text-white" />
|
||||
</button>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { Button } from '@shared/button';
|
||||
import { Image } from '@shared/image';
|
||||
|
||||
import { useNostr } from '@utils/hooks/useNostr';
|
||||
import { useProfile } from '@utils/hooks/useProfile';
|
||||
@@ -24,34 +23,32 @@ export function NoteReplyForm({ id, pubkey }: { id: string; pubkey: string }) {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mt-3 flex flex-col rounded-xl bg-white/10 backdrop-blur-xl">
|
||||
<div className="mt-3 flex flex-col rounded-xl bg-neutral-200 dark:bg-neutral-800">
|
||||
<div className="relative w-full flex-1 overflow-hidden">
|
||||
<textarea
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
placeholder="Reply to this thread..."
|
||||
className=" relative h-24 w-full resize-none rounded-md bg-transparent px-3 py-3 text-base text-white !outline-none placeholder:text-white/50"
|
||||
className=" relative h-24 w-full resize-none rounded-md bg-transparent px-3 py-3 text-base text-neutral-900 !outline-none placeholder:text-neutral-600 dark:text-neutral-100 dark:placeholder:text-neutral-400"
|
||||
spellCheck={false}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full border-t border-white/10 px-3 py-3">
|
||||
<div className="w-full border-t border-neutral-300 px-3 py-3 dark:border-neutral-700">
|
||||
{status === 'loading' ? (
|
||||
<div>
|
||||
<p>Loading...</p>
|
||||
</div>
|
||||
<div>Loading</div>
|
||||
) : (
|
||||
<div className="flex w-full items-center justify-between">
|
||||
<div className="inline-flex items-center gap-3">
|
||||
<div className="inline-flex items-center gap-2">
|
||||
<div className="relative h-11 w-11 shrink-0 rounded">
|
||||
<Image
|
||||
<img
|
||||
src={user?.picture || user?.image}
|
||||
alt={pubkey}
|
||||
className="h-11 w-11 rounded-lg bg-white object-cover"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<p className="mb-1 text-sm leading-none text-white/50">Reply as</p>
|
||||
<p className="text-sm font-medium leading-none text-white">
|
||||
<p className="text-sm text-neutral-600 dark:text-neutral-400">Reply as</p>
|
||||
<p className="font-semibold text-neutral-900 dark:text-neutral-100">
|
||||
{user?.name || displayNpub(pubkey, 16)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -30,7 +30,7 @@ export function NoteWrapper({
|
||||
<div className="relative">{root && <ChildNote id={root} />}</div>
|
||||
<div className="relative">{reply && <ChildNote id={reply} root={root} />}</div>
|
||||
<div className="relative flex flex-col">
|
||||
<User pubkey={event.pubkey} time={event.created_at} />
|
||||
<User pubkey={event.pubkey} time={event.created_at} eventId={event.id} />
|
||||
<div className="-mt-4 flex items-start gap-3">
|
||||
<div className="w-10 shrink-0" />
|
||||
<div className="relative z-20 flex-1">
|
||||
|
||||
@@ -15,12 +15,12 @@ export function TitleBar({ id, title }: { id?: string; title?: string }) {
|
||||
{id === '9999' ? (
|
||||
<div className="isolate flex -space-x-2">
|
||||
{db.account.circles
|
||||
?.slice(0, 4)
|
||||
?.slice(0, 10)
|
||||
.map((item) => <User key={item} pubkey={item} variant="ministacked" />)}
|
||||
{db.account.circles?.length > 4 ? (
|
||||
<div className="inline-flex h-6 w-6 items-center justify-center rounded-full bg-neutral-200 text-neutral-900 ring-1 ring-neutral-300 dark:bg-neutral-800 dark:text-neutral-100 dark:ring-neutral-700">
|
||||
{db.account.circles?.length > 10 ? (
|
||||
<div className="inline-flex h-6 w-6 items-center justify-center rounded-full bg-neutral-200 text-neutral-900 ring-1 ring-neutral-300 dark:bg-neutral-800 dark:text-neutral-100 dark:ring-neutral-700">
|
||||
<span className="text-xs font-medium">
|
||||
+{db.account.circles?.length - 4}
|
||||
+{db.account.circles?.length - 10}
|
||||
</span>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
@@ -216,13 +216,13 @@ export const User = memo(function User({
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
style={{ contentVisibility: 'auto' }}
|
||||
className="inline-block h-8 w-8 rounded-full ring-1 ring-black"
|
||||
className="inline-block h-8 w-8 rounded-full ring-1 ring-neutral-200 dark:ring-neutral-800"
|
||||
/>
|
||||
<Avatar.Fallback delayMs={300}>
|
||||
<img
|
||||
src={svgURI}
|
||||
alt={pubkey}
|
||||
className="inline-block h-8 w-8 rounded-full bg-black ring-1 ring-black dark:bg-white"
|
||||
className="inline-block h-8 w-8 rounded-full bg-black ring-1 ring-neutral-200 dark:bg-white dark:ring-neutral-800"
|
||||
/>
|
||||
</Avatar.Fallback>
|
||||
</Avatar.Root>
|
||||
@@ -313,7 +313,7 @@ export const User = memo(function User({
|
||||
<h5 className="max-w-[15rem] truncate font-semibold text-neutral-900 dark:text-neutral-100">
|
||||
{user?.name || user?.display_name || user?.displayName || 'Anon'}
|
||||
</h5>
|
||||
<div className="inline-flex items-center gap-2 text-sm text-neutral-500 dark:text-neutral-300">
|
||||
<div className="inline-flex items-center gap-2 text-sm text-neutral-600 dark:text-neutral-400">
|
||||
<span>{createdAt}</span>
|
||||
<span>·</span>
|
||||
<span>{displayNpub(pubkey, 16)}</span>
|
||||
|
||||
@@ -113,7 +113,7 @@ export function LocalNetworkWidget() {
|
||||
) : dbEvents.length === 0 ? (
|
||||
<EventLoader firstTime={true} />
|
||||
) : (
|
||||
<VList className="h-full scrollbar-none" shift={true}>
|
||||
<VList className="h-full scrollbar-none">
|
||||
{!isFetched ? <EventLoader firstTime={false} /> : null}
|
||||
{dbEvents.map((item) => renderItem(item))}
|
||||
<div className="flex items-center justify-center px-3 py-1.5">
|
||||
|
||||
Reference in New Issue
Block a user