diff --git a/src/app/chats/components/list.tsx b/src/app/chats/components/list.tsx index c40ea7ad..d4960fc2 100644 --- a/src/app/chats/components/list.tsx +++ b/src/app/chats/components/list.tsx @@ -36,10 +36,6 @@ export function ChatsList() {
-
-
-
-
); } diff --git a/src/app/notes/text.tsx b/src/app/notes/text.tsx index 4d5635a2..7f3055e3 100644 --- a/src/app/notes/text.tsx +++ b/src/app/notes/text.tsx @@ -51,7 +51,7 @@ export function TextNoteScreen() { const renderKind = (event: NDKEvent) => { switch (event.kind) { case NDKKind.Text: - return ; + return ; case NDKKind.Article: return ; case 1063: diff --git a/src/app/users/index.tsx b/src/app/users/index.tsx index c6a59505..d05a53ba 100644 --- a/src/app/users/index.tsx +++ b/src/app/users/index.tsx @@ -56,7 +56,7 @@ export function UserScreen() { ref={virtualizer.measureElement} > - +
); diff --git a/src/index.css b/src/index.css index f757e792..7a234f57 100644 --- a/src/index.css +++ b/src/index.css @@ -6,6 +6,11 @@ html { font-size: 14px; } +input::-ms-reveal, +input::-ms-clear { + display: none; +} + a { @apply cursor-default no-underline !important; } @@ -15,7 +20,7 @@ button { } .markdown { - @apply prose prose-white max-w-none select-text hyphens-auto text-white prose-p:mb-2 prose-p:mt-0 prose-p:break-words prose-p:[word-break:break-word] prose-p:last:mb-0 prose-a:break-words prose-a:break-all prose-a:font-normal hover:prose-a:text-fuchsia-500 prose-blockquote:mb-1 prose-blockquote:mt-1 prose-blockquote:border-l-[2px] prose-blockquote:border-fuchsia-500 prose-blockquote:pl-2 prose-pre:whitespace-pre-wrap prose-pre:break-words prose-pre:break-all 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; + @apply prose prose-white max-w-none select-text hyphens-auto text-white prose-p:mb-0 prose-p:mt-0 prose-p:break-words prose-p:[word-break:break-word] prose-p:last:mb-0 prose-a:break-words prose-a:break-all prose-a:font-normal hover:prose-a:text-fuchsia-500 prose-blockquote:mb-1 prose-blockquote:mt-1 prose-blockquote:border-l-[2px] prose-blockquote:border-fuchsia-500 prose-blockquote:pl-2 prose-pre:whitespace-pre-wrap prose-pre:break-words prose-pre:break-all 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; } .ProseMirror p.is-empty::before { diff --git a/src/libs/storage/provider.tsx b/src/libs/storage/provider.tsx index 3eb1f01a..73d02183 100644 --- a/src/libs/storage/provider.tsx +++ b/src/libs/storage/provider.tsx @@ -1,5 +1,6 @@ import { message } from '@tauri-apps/api/dialog'; import { platform } from '@tauri-apps/api/os'; +import { appConfigDir } from '@tauri-apps/api/path'; import { PropsWithChildren, createContext, useContext, useEffect, useState } from 'react'; import Database from 'tauri-plugin-sql-api'; @@ -18,10 +19,13 @@ const StorageProvider = ({ children }: PropsWithChildren) => { async function initLumeStorage() { try { + const dir = await appConfigDir(); const sqlite = await Database.load('sqlite:lume.db'); const platformName = await platform(); const lumeStorage = new LumeStorage(sqlite, platformName); + console.log('App config dir: ', dir); + if (!lumeStorage.account) await lumeStorage.getActiveAccount(); setDB(lumeStorage); } catch (e) { diff --git a/src/shared/notes/actions.tsx b/src/shared/notes/actions.tsx index 7e58fdcf..b09d2983 100644 --- a/src/shared/notes/actions.tsx +++ b/src/shared/notes/actions.tsx @@ -28,7 +28,7 @@ export function NoteActions({ return ( -
+
diff --git a/src/shared/notes/child.tsx b/src/shared/notes/child.tsx index 839f7e42..9b0d67bd 100644 --- a/src/shared/notes/child.tsx +++ b/src/shared/notes/child.tsx @@ -39,7 +39,7 @@ export function ChildNote({ id, root }: { id: string; root?: string }) { const renderKind = (event: NDKEvent) => { switch (event.kind) { case NDKKind.Text: - return ; + return ; case NDKKind.Article: return ; case 1063: diff --git a/src/shared/notes/content.tsx b/src/shared/notes/content.tsx deleted file mode 100644 index 01f30e49..00000000 --- a/src/shared/notes/content.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import ReactMarkdown from 'react-markdown'; -import remarkGfm from 'remark-gfm'; - -import { - Hashtag, - ImagePreview, - LinkPreview, - MentionNote, - MentionUser, - VideoPreview, -} from '@shared/notes'; - -import { RichContent } from '@utils/types'; - -export function NoteContent({ content, long }: { content: RichContent; long?: boolean }) { - if (long) { - return ( - - {content as unknown as string} - - ); - } - - return ( - <> - { - const key = children[0] as string; - if (key.startsWith('pub') && key.length > 50 && key.length < 100) - return ; - if (key.startsWith('tag')) return ; - }, - }} - > - {content?.parsed} - - {content?.images?.length > 0 && } - {content?.videos?.length > 0 && } - {content?.links?.length > 0 && } - {content?.notes?.length > 0 && - content?.notes.map((note: string) => )} - - ); -} diff --git a/src/shared/notes/index.tsx b/src/shared/notes/index.ts similarity index 97% rename from src/shared/notes/index.tsx rename to src/shared/notes/index.ts index 0591f2f0..ca3f3828 100644 --- a/src/shared/notes/index.tsx +++ b/src/shared/notes/index.ts @@ -24,7 +24,6 @@ export * from './kinds/repost'; export * from './child'; export * from './skeleton'; export * from './actions'; -export * from './content'; export * from './mentions/hashtag'; export * from './stats'; export * from './wrapper'; diff --git a/src/shared/notes/kinds/articleDetail.tsx b/src/shared/notes/kinds/articleDetail.tsx index 5309fa19..cff9fbe7 100644 --- a/src/shared/notes/kinds/articleDetail.tsx +++ b/src/shared/notes/kinds/articleDetail.tsx @@ -1,13 +1,9 @@ import { NDKEvent } from '@nostr-dev-kit/ndk'; -import { useMemo } from 'react'; import ReactMarkdown from 'react-markdown'; -import { Link } from 'react-router-dom'; import remarkGfm from 'remark-gfm'; -import { Image } from '@shared/image'; - export function ArticleDetailNote({ event }: { event: NDKEvent }) { - const metadata = useMemo(() => { + /*const metadata = useMemo(() => { const title = event.tags.find((tag) => tag[0] === 'title')?.[1]; const image = event.tags.find((tag) => tag[0] === 'image')?.[1]; const summary = event.tags.find((tag) => tag[0] === 'summary')?.[1]; @@ -27,7 +23,7 @@ export function ArticleDetailNote({ event }: { event: NDKEvent }) { publishedAt, summary, }; - }, [event.id]); + }, [event.id]);*/ return ( diff --git a/src/shared/notes/kinds/repost.tsx b/src/shared/notes/kinds/repost.tsx index c2b0dc3e..77223c81 100644 --- a/src/shared/notes/kinds/repost.tsx +++ b/src/shared/notes/kinds/repost.tsx @@ -16,22 +16,22 @@ import { useEvent } from '@utils/hooks/useEvent'; export function Repost({ event }: { event: NDKEvent }) { const repostID = event.tags.find((el) => el[0] === 'e')[1] ?? ''; - const { status, data } = useEvent(repostID, event.content as unknown as string); + const { status, data } = useEvent(repostID, event.content); const renderKind = useCallback( - (event: NDKEvent) => { - switch (event.kind) { + (repostEvent: NDKEvent) => { + switch (repostEvent.kind) { case NDKKind.Text: - return ; + return ; case NDKKind.Article: - return ; + return ; case 1063: - return ; + return ; default: - return ; + return ; } }, - [event] + [data] ); if (status === 'loading') { diff --git a/src/shared/notes/kinds/text.tsx b/src/shared/notes/kinds/text.tsx index 6237f7f8..974a2d10 100644 --- a/src/shared/notes/kinds/text.tsx +++ b/src/shared/notes/kinds/text.tsx @@ -1,5 +1,3 @@ -import { NDKEvent } from '@nostr-dev-kit/ndk'; -import { useMemo } from 'react'; import ReactMarkdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; @@ -14,8 +12,24 @@ import { import { parser } from '@utils/parser'; -export function TextNote({ event }: { event: NDKEvent }) { - const content = useMemo(() => parser(event), [event.id]); +export function TextNote({ content }: { content: string }) { + const richContent = parser(content) ?? null; + + if (!richContent) { + return ( +
+ + {content} + +
+ ); + } return (
@@ -38,13 +52,15 @@ export function TextNote({ event }: { event: NDKEvent }) { unwrapDisallowed={true} linkTarget={'_blank'} > - {content?.parsed} + {richContent.parsed} - {content?.images?.length > 0 && } - {content?.videos?.length > 0 && } - {content?.links?.length > 0 && } - {content?.notes?.length > 0 && - content?.notes.map((note: string) => )} +
+ {richContent.images.length > 0 && } + {richContent.videos.length > 0 && } + {richContent.links.length > 0 && } + {richContent.notes.length > 0 && + richContent.notes.map((note: string) => )} +
); } diff --git a/src/shared/notes/mentions/note.tsx b/src/shared/notes/mentions/note.tsx index d1463efb..79895e68 100644 --- a/src/shared/notes/mentions/note.tsx +++ b/src/shared/notes/mentions/note.tsx @@ -31,6 +31,19 @@ export const MentionNote = memo(function MentionNote({ id }: { id: string }) { } }; + const renderKind = (event: NDKEvent) => { + switch (event.kind) { + case NDKKind.Text: + return ; + case NDKKind.Article: + return ; + case 1063: + return ; + default: + return ; + } + }; + if (status === 'loading') { return (
@@ -42,31 +55,18 @@ export const MentionNote = memo(function MentionNote({ id }: { id: string }) { if (status === 'error') { return (
-

Can't get event from relay

+

Can't get event from relay, ID: {id}

); } - const renderKind = (event: NDKEvent) => { - switch (event.kind) { - case NDKKind.Text: - return ; - case NDKKind.Article: - return ; - case 1063: - return ; - default: - return ; - } - }; - return (
openThread(e, id)} onKeyDown={(e) => openThread(e, id)} role="button" tabIndex={0} - className="cursor-default rounded-lg bg-white/10 px-3 py-3 backdrop-blur-xl" + className="mt-3 cursor-default rounded-lg bg-white/10 px-3 py-3 backdrop-blur-xl" >
{renderKind(data)}
diff --git a/src/shared/notes/preview/image.tsx b/src/shared/notes/preview/image.tsx index 98f4aa0e..3d2964e6 100644 --- a/src/shared/notes/preview/image.tsx +++ b/src/shared/notes/preview/image.tsx @@ -12,7 +12,7 @@ export function ImagePreview({ urls, truncate }: { urls: string[]; truncate?: bo }; return ( -
+
{urls.map((url) => (
diff --git a/src/shared/notes/preview/link.tsx b/src/shared/notes/preview/link.tsx index ea7942fe..9d7bbdb6 100644 --- a/src/shared/notes/preview/link.tsx +++ b/src/shared/notes/preview/link.tsx @@ -7,7 +7,7 @@ export function LinkPreview({ urls }: { urls: string[] }) { const domain = new URL(urls[0]); return ( -
+
{status === 'loading' ? (
diff --git a/src/shared/notes/preview/video.tsx b/src/shared/notes/preview/video.tsx index 320cd412..a03cb621 100644 --- a/src/shared/notes/preview/video.tsx +++ b/src/shared/notes/preview/video.tsx @@ -2,7 +2,7 @@ import ReactPlayer from 'react-player/es6'; export function VideoPreview({ urls }: { urls: string[] }) { return ( -
+
{urls.map((url) => (
- +
diff --git a/src/shared/notes/replies/sub.tsx b/src/shared/notes/replies/sub.tsx index b2633482..004aa474 100644 --- a/src/shared/notes/replies/sub.tsx +++ b/src/shared/notes/replies/sub.tsx @@ -10,7 +10,7 @@ export function SubReply({ event }: { event: NDKEvent }) {
- +
diff --git a/src/shared/widgets/global/hashtag.tsx b/src/shared/widgets/global/hashtag.tsx index df9d6ab2..13693946 100644 --- a/src/shared/widgets/global/hashtag.tsx +++ b/src/shared/widgets/global/hashtag.tsx @@ -58,7 +58,7 @@ export function GlobalHashtagWidget({ params }: { params: Widget }) { ref={virtualizer.measureElement} > - +
); diff --git a/src/shared/widgets/local/feeds.tsx b/src/shared/widgets/local/feeds.tsx index ae11a61a..fcd29983 100644 --- a/src/shared/widgets/local/feeds.tsx +++ b/src/shared/widgets/local/feeds.tsx @@ -60,7 +60,7 @@ export function LocalFeedsWidget({ params }: { params: Widget }) { ref={virtualizer.measureElement} > - +
); diff --git a/src/shared/widgets/local/network.tsx b/src/shared/widgets/local/network.tsx index 7ebdd8a8..9ef29d39 100644 --- a/src/shared/widgets/local/network.tsx +++ b/src/shared/widgets/local/network.tsx @@ -62,7 +62,7 @@ export function LocalNetworkWidget() { ref={virtualizer.measureElement} > - +
); @@ -122,7 +122,7 @@ export function LocalNetworkWidget() { useEffect(() => { if (db.account && db.account.network) { const filter: NDKFilter = { - kinds: [1, 6], + kinds: [NDKKind.Text, NDKKind.Repost], authors: db.account.network, since: db.account.last_login_at ?? Math.floor(Date.now() / 1000), }; diff --git a/src/shared/widgets/local/thread.tsx b/src/shared/widgets/local/thread.tsx index 4fda8166..6087e129 100644 --- a/src/shared/widgets/local/thread.tsx +++ b/src/shared/widgets/local/thread.tsx @@ -28,7 +28,7 @@ export function LocalThreadWidget({ params }: { params: Widget }) { (event: NDKEvent) => { switch (event.kind) { case NDKKind.Text: - return ; + return ; case NDKKind.Article: return ; case 1063: diff --git a/src/shared/widgets/local/user.tsx b/src/shared/widgets/local/user.tsx index 703adf5a..0759beee 100644 --- a/src/shared/widgets/local/user.tsx +++ b/src/shared/widgets/local/user.tsx @@ -64,7 +64,7 @@ export function LocalUserWidget({ params }: { params: Widget }) { ref={virtualizer.measureElement} > - +
); diff --git a/src/shared/widgets/nostrBand/trendingNotes.tsx b/src/shared/widgets/nostrBand/trendingNotes.tsx index 84caf6bc..321b2b88 100644 --- a/src/shared/widgets/nostrBand/trendingNotes.tsx +++ b/src/shared/widgets/nostrBand/trendingNotes.tsx @@ -52,7 +52,7 @@ export function TrendingNotesWidget({ params }: { params: Widget }) {
{data.map((item) => ( - + ))}
diff --git a/src/utils/hooks/useEvent.ts b/src/utils/hooks/useEvent.ts index e76f8b7e..70074be8 100644 --- a/src/utils/hooks/useEvent.ts +++ b/src/utils/hooks/useEvent.ts @@ -23,7 +23,7 @@ export function useEvent(id: string, embed?: string) { // get event from relay if event in db not present const event = await ndk.fetchEvent(id); - if (!event) throw new Error(`Event not found: ${id}`); + if (!event) throw new Error(`Event not found: ${id.toString()}`); let root: string; let reply: string; @@ -50,10 +50,8 @@ export function useEvent(id: string, embed?: string) { }, { enabled: !!ndk, - staleTime: Infinity, refetchOnMount: false, refetchOnWindowFocus: false, - refetchOnReconnect: false, } ); diff --git a/src/utils/parser.ts b/src/utils/parser.ts index 4bc330cc..762eb68d 100644 --- a/src/utils/parser.ts +++ b/src/utils/parser.ts @@ -1,73 +1,87 @@ -import { NDKEvent } from '@nostr-dev-kit/ndk'; -import getUrls from 'get-urls'; -import { Event, parseReferences } from 'nostr-tools'; +import { nip19 } from 'nostr-tools'; +import { EventPointer } from 'nostr-tools/lib/nip19'; import { RichContent } from '@utils/types'; -export function parser(event: NDKEvent) { - const references = parseReferences(event as unknown as Event); - const urls = getUrls(event.content as unknown as string); - - const content: RichContent = { - parsed: event.content as unknown as string, - notes: [], - images: [], - videos: [], - links: [], - }; - - // parse nostr references - references?.forEach((item) => { - const profile = item.profile; - const event = item.event; - const addr = item.address; - if (event) { - content.notes.push(event.id); - content.parsed = content.parsed.replace(item.text, ''); - } - if (profile) { - content.parsed = content.parsed.replace(item.text, `~pub-${item.profile.pubkey}~`); - } - if (addr) { - content.notes.push(addr.identifier); - content.parsed = content.parsed.replace(item.text, ''); - } - }); - - // parse urls - urls?.forEach((url: string) => { - if (url.match(/\.(jpg|jpeg|gif|png|webp|avif)$/)) { - // image url - content.images.push(url); - // remove url from original content - content.parsed = content.parsed.replace(url, ''); - } - - if (url.match(/\.(mp4|mov|webm|wmv|flv|mts|avi|ogv|mkv|mp3|m3u8)$/)) { - // video - content.videos.push(url); - // remove url from original content - content.parsed = content.parsed.replace(url, ''); - } - - /* - if (content.links.length < 1) { - // push to store - content.links.push(url); - // remove url from original content - content.parsed = content.parsed.replace(url, ''); - } - */ - }); - - // parse hashtag - const hashtags = content.parsed.split(/\s/gm).filter((s) => s.startsWith('#')); - if (hashtags) { - const uniqTags = new Set(hashtags); - uniqTags.forEach((tag) => { - content.parsed = content.parsed.replaceAll(tag, `~tag-${tag}~`); - }); - } - - return content; +function isURL(str: string) { + const pattern = new RegExp( + '^(https?:\\/\\/)?' + // protocol + '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + // domain name + '((\\d{1,3}\\.){3}\\d{1,3}))' + // OR ip (v4) address + '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + // port and path + '(\\?[;&a-z\\d%_.~+=-]*)?' + // query string + '(\\#[-a-z\\d_]*)?$', // fragment locator + 'i' + ); + return !!pattern.test(str); +} + +export function parser(eventContent: string) { + try { + const content: RichContent = { + parsed: null, + images: [], + videos: [], + links: [], + notes: [], + }; + + const parse = eventContent.split(/\s/gm).map((word) => { + // url + if (isURL(word)) { + const url = new URL(word); + url.search = ''; + + if (url.toString().match(/\.(jpg|jpeg|gif|png|webp|avif)$/)) { + // image url + content.images.push(word); + // remove url from original content + return word.replace(word, ''); + } + + if (url.toString().match(/\.(mp4|mov|webm|wmv|flv|mts|avi|ogv|mkv|mp3|m3u8)$/)) { + // video + content.videos.push(word); + // remove url from original content + word = word.replace(word, ''); + } + } + + // hashtag + if (word.startsWith('#') && word.length > 1) { + return word.replace(word, `~tag-${word}~`); + } + + // nostr account references + if (word.startsWith('nostr:npub1')) { + const npub = word.replace('nostr:', '').replace(/[^a-zA-Z0-9 ]/g, ''); + return word.replace(word, `~pub-${nip19.decode(npub).data}~`); + } + + // nostr account references + if (word.startsWith('nostr:note1')) { + const note = word.replace('nostr:', '').replace(/[^a-zA-Z0-9 ]/g, ''); + content.notes.push(nip19.decode(note).data as string); + return word.replace(word, ''); + } + + // nostr event references + if (word.startsWith('nostr:nevent1')) { + const nevent = word.replace('nostr:', '').replace(/[^a-zA-Z0-9 ]/g, ''); + const decoded = nip19.decode(nevent).data as EventPointer; + content.notes.push(decoded.id); + return word.replace(word, ''); + } + + // normal word + return word; + }); + + // update content with parsed version + content.parsed = parse.join(' '); + + return content; + } catch (e) { + console.error('cannot parse content, error: ', e); + } } diff --git a/src/utils/types.d.ts b/src/utils/types.d.ts index b9e1b66b..bab45a61 100644 --- a/src/utils/types.d.ts +++ b/src/utils/types.d.ts @@ -2,10 +2,10 @@ import { NDKEvent, NDKUserProfile } from '@nostr-dev-kit/ndk'; export interface RichContent { parsed: string; - notes: string[]; images: string[]; videos: string[]; links: string[]; + notes: string[]; } export interface DBEvent {