diff --git a/ROADMAP.md b/ROADMAP.md index f6a9e73c..42b90cde 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,5 +1,7 @@ # Current +- [ ] Support other kinds + - Fix note truncation - [ ] Feeds load forever if a modal is open - [ ] Support other list types than 30001 - [ ] Fix connection management stuff. Have GPT help diff --git a/src/app/shared/FeedControls.svelte b/src/app/shared/FeedControls.svelte index 2497d21f..93cd2983 100644 --- a/src/app/shared/FeedControls.svelte +++ b/src/app/shared/FeedControls.svelte @@ -97,7 +97,7 @@ onChange(newFilter) } - const applySearch = debounce(200, applyFilter) + const applySearch = debounce(400, applyFilter) const clearSearch = () => { _filter.search = "" diff --git a/src/app/shared/NoteContent.svelte b/src/app/shared/NoteContent.svelte index a4a32b2c..272f8281 100644 --- a/src/app/shared/NoteContent.svelte +++ b/src/app/shared/NoteContent.svelte @@ -1,21 +1,20 @@
@@ -156,64 +74,34 @@
{/if} - {#each content as { type, value }, i} - {#if type === "newline" && i > 0} - {#each value as _} -
- {/each} - {:else if type === "topic"} - openTopic(value)}>#{value} - {:else if type === "link"} - - {value.replace(/https?:\/\/(www\.)?/, "")} - + {#each shortContent as { type, value }, i} + {#if type === NEWLINE} + + {:else if type === TOPIC} + + {:else if type === INVOICE} +
+ +
+ {:else if type === LINK} + + {:else if type.match(/^nostr:np(rofile|ub)$/)} + + {:else if type.startsWith("nostr:") && showMedia && isStandalone(i) && value.id !== anchorId} + +
+ +
+
{:else if type.startsWith("nostr:")} - {#if showMedia && value.id && isStandalone(i) && value.id !== anchorId} - openQuote(value.id)}> - {#await loadQuote(value)} - - {:then quote} - {@const person = getPersonWithFallback(quote.pubkey)} -
- - openPerson(quote.pubkey)}> -

{displayPerson(person)}

-
-
- - {:catch} -

- Unable to load a preview for quoted event -

- {/await} -
- {:else if type.match(/np(rofile|ub)$/)} - @ openPerson(value.pubkey)}> - {displayPerson(getPersonWithFallback(value.pubkey))} - - {:else} - - {value.entity.slice(0, 16) + "..."} - - {/if} + {:else} {value} {/if} {" "} {/each}

- {#if invoices.length > 0} -
- -
- {/if} - {#if showMedia && links.length > 0} -
- -
+ {#if showMedia && extraLinks.length > 0} + {/if} diff --git a/src/app/shared/NoteContentEntity.svelte b/src/app/shared/NoteContentEntity.svelte new file mode 100644 index 00000000..d5c8402f --- /dev/null +++ b/src/app/shared/NoteContentEntity.svelte @@ -0,0 +1,9 @@ + + + + {value.entity.slice(0, 16) + "..."} + diff --git a/src/app/shared/NoteContentLink.svelte b/src/app/shared/NoteContentLink.svelte new file mode 100644 index 00000000..4854d880 --- /dev/null +++ b/src/app/shared/NoteContentLink.svelte @@ -0,0 +1,26 @@ + + +{#if showMedia && value.canDisplay} +
+ +
+{:else} + + {value.url.replace(/https?:\/\/(www\.)?/, "")} + +{/if} diff --git a/src/app/shared/NoteContentNewline.svelte b/src/app/shared/NoteContentNewline.svelte new file mode 100644 index 00000000..04078a7d --- /dev/null +++ b/src/app/shared/NoteContentNewline.svelte @@ -0,0 +1,7 @@ + + +{#each value as _} +
+{/each} diff --git a/src/app/shared/NoteContentPerson.svelte b/src/app/shared/NoteContentPerson.svelte new file mode 100644 index 00000000..3a3cdfd6 --- /dev/null +++ b/src/app/shared/NoteContentPerson.svelte @@ -0,0 +1,14 @@ + + +@ + {displayPerson(getPersonWithFallback(value.pubkey))} + diff --git a/src/app/shared/NoteContentQuote.svelte b/src/app/shared/NoteContentQuote.svelte new file mode 100644 index 00000000..f833f25d --- /dev/null +++ b/src/app/shared/NoteContentQuote.svelte @@ -0,0 +1,65 @@ + + +
+ + {#await loadQuote()} + + {:then quote} + {@const person = getPersonWithFallback(quote.pubkey)} +
+ + openPerson(quote.pubkey)}> +

{displayPerson(person)}

+
+
+ + {:catch} +

+ Unable to load a preview for quoted event +

+ {/await} +
+
diff --git a/src/app/shared/NoteContentTopic.svelte b/src/app/shared/NoteContentTopic.svelte new file mode 100644 index 00000000..98aa7f68 --- /dev/null +++ b/src/app/shared/NoteContentTopic.svelte @@ -0,0 +1,12 @@ + + + openTopic(value)}>#{value} diff --git a/src/app/shared/PersonAbout.svelte b/src/app/shared/PersonAbout.svelte index d8e497e5..7424b4c0 100644 --- a/src/app/shared/PersonAbout.svelte +++ b/src/app/shared/PersonAbout.svelte @@ -19,8 +19,8 @@
{/each} {:else if type === "link"} - - {value.replace(/https?:\/\/(www\.)?/, "")} + + {value.url.replace(/https?:\/\/(www\.)?/, "")} {:else if type.startsWith("nostr:")} diff --git a/src/app/views/Search.svelte b/src/app/views/Search.svelte index 76b0f340..9fb24fef 100644 --- a/src/app/views/Search.svelte +++ b/src/app/views/Search.svelte @@ -75,13 +75,13 @@ $or: [{"kind0.name": {$type: "string"}}, {"kind0.display_name": {$type: "string"}}], }) .map(person => { - const {name, about, display_name} = person.kind0 + const {name, nip05, about, display_name} = person.kind0 return { person, type: "person", id: person.pubkey, - text: "@" + [name, about, display_name].filter(identity).join(" "), + text: "@" + [name, about, nip05, display_name].filter(identity).join(" "), } }) ) @@ -116,7 +116,7 @@ camera icon to scan with your device's camera instead.

- + import {sortBy} from "ramda" - import {slide} from "svelte/transition" import {annotateMedia} from "src/util/misc" import Media from "src/partials/Media.svelte" + import Anchor from "src/partials/Anchor.svelte" import Content from "src/partials/Content.svelte" import Modal from "src/partials/Modal.svelte" export let links - export let onClose = null - let hidden = false let showModal = false // Put previews last since we need to load them asynchronously - const annotated = sortBy(l => (l.type === "preview" ? 1 : 0), links.map(annotateMedia)) - - const close = () => { - onClose?.() - hidden = true - } + const annotated = sortBy( + l => (l.type === "preview" ? 1 : 0), + links.map(link => annotateMedia(link.url)) + ) const openModal = () => { showModal = true } + const closeModal = () => { showModal = false } -{#if !hidden} -
- - {#if annotated.length > 1} -

- Show all {annotated.length} link previews -

- {/if} -
-{/if} +
+ + Show all {annotated.length} link previews + +
{#if showModal} diff --git a/src/util/nostr.ts b/src/util/nostr.ts index 020ac573..f0ed54a6 100644 --- a/src/util/nostr.ts +++ b/src/util/nostr.ts @@ -6,6 +6,7 @@ import {tryJson, avg} from "src/util/misc" import {invoiceAmount} from "src/util/lightning" export const noteKinds = [1, 1985, 30023, 30018, 10001, 1063, 9802] +// export const noteKinds = [9802] export const personKinds = [0, 2, 3, 10001, 10002] export const userKinds = personKinds.concat([10000, 30001, 30078]) export const appDataKeys = [ diff --git a/src/util/notes.ts b/src/util/notes.ts index bf26dabe..21672cd2 100644 --- a/src/util/notes.ts +++ b/src/util/notes.ts @@ -3,6 +3,19 @@ import {nip19} from "nostr-tools" import {first} from "hurdak/lib/hurdak" import {fromNostrURI} from "src/util/nostr" +export const NEWLINE = "newline" +export const TEXT = "text" +export const TOPIC = "topic" +export const LINK = "link" +export const INVOICE = "invoice" +export const NOSTR_NOTE = "nostr:note" +export const NOSTR_NEVENT = "nostr:nevent" +export const NOSTR_NPUB = "nostr:npub" +export const NOSTR_NPROFILE = "nostr:nprofile" +export const NOSTR_NADDR = "nostr:naddr" + +const canDisplayUrl = url => !url.match(/\.(apk|docx|xlsx|csv|dmg)/) + export const parseContent = ({content, tags = []}) => { const result = [] let text = content.trim() @@ -12,7 +25,7 @@ export const parseContent = ({content, tags = []}) => { const newline = first(text.match(/^\n+/)) if (newline) { - return ["newline", newline, newline] + return [NEWLINE, newline, newline] } } @@ -48,7 +61,7 @@ export const parseContent = ({content, tags = []}) => { // Skip numeric topics if (topic && !topic.match(/^#\d+$/)) { - return ["topic", topic, topic.slice(1)] + return [TOPIC, topic, topic.slice(1)] } } @@ -77,11 +90,11 @@ export const parseContent = ({content, tags = []}) => { } } - const parseLNUrl = () => { - const lnurl = first(text.match(/^ln(bc|url)[\d\w]{50,1000}/i)) + const parseInvoice = () => { + const invoice = first(text.match(/^ln(bc|url)[\d\w]{50,1000}/i)) - if (lnurl) { - return ["lnurl", lnurl, lnurl] + if (invoice) { + return [INVOICE, invoice, invoice] } } @@ -107,7 +120,7 @@ export const parseContent = ({content, tags = []}) => { url = "https://" + url } - return ["link", raw, url] + return [LINK, raw, {url, canDisplay: canDisplayUrl(url)}] } } @@ -118,7 +131,7 @@ export const parseContent = ({content, tags = []}) => { parseTopic() || parseBech32() || parseUrl() || - parseLNUrl() + parseInvoice() if (part) { if (buffer) { @@ -141,7 +154,42 @@ export const parseContent = ({content, tags = []}) => { } if (buffer) { - result.push({type: "text", value: buffer}) + result.push({type: TEXT, value: buffer}) + } + + return result +} + +export const truncateContent = (content, {showEntire, maxLength, showMedia}) => { + if (showEntire) { + return content + } + + let length = 0 + const result = [] + const truncateAt = maxLength * 0.6 + + for (const part of content) { + const isText = [TOPIC, TEXT].includes(part.type) + const isMedia = [LINK, INVOICE].includes(part.type) || part.type.startsWith("nostr:") + + if (isText) { + length += part.value.length + } + + if (isMedia) { + length += showMedia ? maxLength / 3 : part.value.length + } + + result.push(part) + + if (length > truncateAt) { + if (isText || (isMedia && !showMedia)) { + result.push({type: TEXT, value: "..."}) + } + + break + } } return result