mirror of
https://github.com/coracle-social/coracle.git
synced 2024-09-18 19:23:40 +00:00
Refactor NoteContent
This commit is contained in:
parent
61f44e340a
commit
7c9c2ee692
@ -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
|
||||
|
@ -97,7 +97,7 @@
|
||||
onChange(newFilter)
|
||||
}
|
||||
|
||||
const applySearch = debounce(200, applyFilter)
|
||||
const applySearch = debounce(400, applyFilter)
|
||||
|
||||
const clearSearch = () => {
|
||||
_filter.search = ""
|
||||
|
@ -1,21 +1,20 @@
|
||||
<script lang="ts">
|
||||
import {objOf, reverse} from "ramda"
|
||||
import {fly} from "svelte/transition"
|
||||
import {splice, switcher, switcherFn} from "hurdak/lib/hurdak"
|
||||
import {warn} from "src/util/logger"
|
||||
import {pluck, without} from "ramda"
|
||||
import {switcher, switcherFn} from "hurdak/lib/hurdak"
|
||||
import {displayPerson, getLabelQuality, displayRelay, Tags} from "src/util/nostr"
|
||||
import {parseContent} from "src/util/notes"
|
||||
import {parseContent, truncateContent, LINK, INVOICE, NEWLINE, TOPIC} from "src/util/notes"
|
||||
import {modal} from "src/partials/state"
|
||||
import MediaSet from "src/partials/MediaSet.svelte"
|
||||
import QRCode from "src/partials/QRCode.svelte"
|
||||
import Card from "src/partials/Card.svelte"
|
||||
import Spinner from "src/partials/Spinner.svelte"
|
||||
import Anchor from "src/partials/Anchor.svelte"
|
||||
import Rating from "src/partials/Rating.svelte"
|
||||
import PersonCircle from "src/app/shared/PersonCircle.svelte"
|
||||
import {sampleRelays} from "src/agent/relays"
|
||||
import NoteContentNewline from "src/app/shared/NoteContentNewline.svelte"
|
||||
import NoteContentTopic from "src/app/shared/NoteContentTopic.svelte"
|
||||
import NoteContentLink from "src/app/shared/NoteContentLink.svelte"
|
||||
import NoteContentPerson from "src/app/shared/NoteContentPerson.svelte"
|
||||
import NoteContentQuote from "src/app/shared/NoteContentQuote.svelte"
|
||||
import NoteContentEntity from "src/app/shared/NoteContentEntity.svelte"
|
||||
import user from "src/agent/user"
|
||||
import network from "src/agent/network"
|
||||
import {getPersonWithFallback} from "src/agent/db"
|
||||
|
||||
export let note
|
||||
@ -24,107 +23,26 @@
|
||||
export let showEntire = false
|
||||
export let showMedia = user.getSetting("showMedia")
|
||||
|
||||
const truncateAt = maxLength * 0.6
|
||||
const shouldTruncate = !showEntire && note.content.length > maxLength
|
||||
|
||||
let content = parseContent(note)
|
||||
let rating = note.kind === 1985 ? getLabelQuality("review/relay", note) : null
|
||||
|
||||
const links = []
|
||||
const invoices = []
|
||||
const ranges = []
|
||||
|
||||
// Find links and preceding whitespace
|
||||
for (let i = 0; i < content.length; i++) {
|
||||
const {type, value} = content[i]
|
||||
|
||||
if (type === "link") {
|
||||
links.push(value)
|
||||
}
|
||||
|
||||
if (type === "lnurl") {
|
||||
invoices.push(value)
|
||||
}
|
||||
|
||||
if (["link", "lnurl"].includes(type) && !value.startsWith("ws")) {
|
||||
const prev = content[i - 1]
|
||||
const next = content[i + 1]
|
||||
|
||||
if ((!prev || prev.type === "newline") && (!next || next.type === "newline")) {
|
||||
let n = 1
|
||||
for (let j = i - 1; ; j--) {
|
||||
if (content[j]?.type === "newline") {
|
||||
n += 1
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
ranges.push({i: i + 1, n})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove links and preceding line breaks if they're on their own line
|
||||
if (showMedia) {
|
||||
for (const {i, n} of reverse(ranges)) {
|
||||
content = splice(i - n, n, content)
|
||||
}
|
||||
}
|
||||
|
||||
// Truncate content if needed
|
||||
let l = 0
|
||||
if (shouldTruncate) {
|
||||
for (let i = 0; i < content.length; i++) {
|
||||
const prev = content[i - 1]
|
||||
|
||||
// Avoid adding an ellipsis right after a newline
|
||||
if (l > truncateAt && prev?.type != "newline") {
|
||||
content = content.slice(0, i).concat({type: "text", value: "..."})
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
if (typeof content[i].value === "string") {
|
||||
l += content[i].value.length
|
||||
}
|
||||
}
|
||||
}
|
||||
const getLinks = parts =>
|
||||
pluck(
|
||||
"value",
|
||||
parts.filter(x => x.type === LINK && x.canDisplay)
|
||||
)
|
||||
|
||||
const isStandalone = i => {
|
||||
return (
|
||||
!content[i - 1] ||
|
||||
content[i - 1].type === "newline" ||
|
||||
!content[i + 1] ||
|
||||
content[i + 1].type === "newline"
|
||||
!shortContent[i - 1] ||
|
||||
shortContent[i - 1].type === NEWLINE ||
|
||||
!shortContent[i + 1] ||
|
||||
shortContent[i + 1].type === NEWLINE
|
||||
)
|
||||
}
|
||||
|
||||
const loadQuote = async ({id, relays}) => {
|
||||
// Follow relay hints
|
||||
relays = (relays || []).map(objOf("url")).concat(Tags.from(note).equals(id).relays())
|
||||
|
||||
try {
|
||||
const [event] = await network.load({
|
||||
relays: sampleRelays(relays),
|
||||
filter: [{ids: [id]}],
|
||||
})
|
||||
|
||||
return event || Promise.reject()
|
||||
} catch (e) {
|
||||
warn(e)
|
||||
}
|
||||
}
|
||||
|
||||
const openPerson = pubkey => modal.push({type: "person/feed", pubkey})
|
||||
|
||||
const openQuote = id => {
|
||||
modal.push({type: "note/detail", note: {id}})
|
||||
}
|
||||
|
||||
const openTopic = topic => {
|
||||
modal.push({type: "topic/feed", topic})
|
||||
}
|
||||
const fullContent = parseContent(note)
|
||||
const shortContent = truncateContent(fullContent, {maxLength, showEntire, showMedia})
|
||||
const rating = note.kind === 1985 ? getLabelQuality("review/relay", note) : null
|
||||
const links = getLinks(shortContent)
|
||||
const extraLinks = without(links, getLinks(fullContent))
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-2 overflow-hidden text-ellipsis">
|
||||
@ -156,64 +74,34 @@
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{#each content as { type, value }, i}
|
||||
{#if type === "newline" && i > 0}
|
||||
{#each value as _}
|
||||
<br />
|
||||
{/each}
|
||||
{:else if type === "topic"}
|
||||
<Anchor killEvent on:click={() => openTopic(value)}>#{value}</Anchor>
|
||||
{:else if type === "link"}
|
||||
<Anchor external href={value}>
|
||||
{value.replace(/https?:\/\/(www\.)?/, "")}
|
||||
</Anchor>
|
||||
{:else if type.startsWith("nostr:")}
|
||||
{#if showMedia && value.id && isStandalone(i) && value.id !== anchorId}
|
||||
<Card interactive invertColors class="my-2" on:click={() => openQuote(value.id)}>
|
||||
{#await loadQuote(value)}
|
||||
<Spinner />
|
||||
{:then quote}
|
||||
{@const person = getPersonWithFallback(quote.pubkey)}
|
||||
<div class="mb-4 flex items-center gap-4">
|
||||
<PersonCircle size={6} {person} />
|
||||
<Anchor
|
||||
stopPropagation
|
||||
type="unstyled"
|
||||
class="flex items-center gap-2"
|
||||
on:click={() => openPerson(quote.pubkey)}>
|
||||
<h2 class="text-lg">{displayPerson(person)}</h2>
|
||||
</Anchor>
|
||||
{#each shortContent as { type, value }, i}
|
||||
{#if type === NEWLINE}
|
||||
<NoteContentNewline {value} />
|
||||
{:else if type === TOPIC}
|
||||
<NoteContentTopic {value} />
|
||||
{:else if type === INVOICE}
|
||||
<div on:click|stopPropagation>
|
||||
<QRCode fullWidth onClick="copy" code={value} />
|
||||
</div>
|
||||
{:else if type === LINK}
|
||||
<NoteContentLink {value} showMedia={showMedia && isStandalone(i)} />
|
||||
{:else if type.match(/^nostr:np(rofile|ub)$/)}
|
||||
<NoteContentPerson {value} />
|
||||
{:else if type.startsWith("nostr:") && showMedia && isStandalone(i) && value.id !== anchorId}
|
||||
<NoteContentQuote {note} {value}>
|
||||
<div slot="note-content" let:quote>
|
||||
<svelte:self note={quote} />
|
||||
{:catch}
|
||||
<p class="mb-1 py-24 text-center text-gray-5" in:fly={{y: 20}}>
|
||||
Unable to load a preview for quoted event
|
||||
</p>
|
||||
{/await}
|
||||
</Card>
|
||||
{:else if type.match(/np(rofile|ub)$/)}
|
||||
@<Anchor killEvent on:click={() => openPerson(value.pubkey)}>
|
||||
{displayPerson(getPersonWithFallback(value.pubkey))}
|
||||
</Anchor>
|
||||
{:else}
|
||||
<Anchor killEvent href={"/" + value.entity}>
|
||||
{value.entity.slice(0, 16) + "..."}
|
||||
</Anchor>
|
||||
{/if}
|
||||
</div>
|
||||
</NoteContentQuote>
|
||||
{:else if type.startsWith("nostr:")}
|
||||
<NoteContentEntity {value} />
|
||||
{:else}
|
||||
{value}
|
||||
{/if}
|
||||
{" "}
|
||||
{/each}
|
||||
</p>
|
||||
{#if invoices.length > 0}
|
||||
<div on:click|stopPropagation>
|
||||
<QRCode fullWidth onClick="copy" code={invoices[0]} />
|
||||
</div>
|
||||
{/if}
|
||||
{#if showMedia && links.length > 0}
|
||||
<div on:click|stopPropagation>
|
||||
<MediaSet {links} />
|
||||
</div>
|
||||
{#if showMedia && extraLinks.length > 0}
|
||||
<MediaSet links={extraLinks} />
|
||||
{/if}
|
||||
</div>
|
||||
|
9
src/app/shared/NoteContentEntity.svelte
Normal file
9
src/app/shared/NoteContentEntity.svelte
Normal file
@ -0,0 +1,9 @@
|
||||
<script lang="ts">
|
||||
import Anchor from "src/partials/Anchor.svelte"
|
||||
|
||||
export let value
|
||||
</script>
|
||||
|
||||
<Anchor killEvent href={"/" + value.entity}>
|
||||
{value.entity.slice(0, 16) + "..."}
|
||||
</Anchor>
|
26
src/app/shared/NoteContentLink.svelte
Normal file
26
src/app/shared/NoteContentLink.svelte
Normal file
@ -0,0 +1,26 @@
|
||||
<script lang="ts">
|
||||
import {annotateMedia} from "src/util/misc"
|
||||
import Anchor from "src/partials/Anchor.svelte"
|
||||
import Media from "src/partials/Media.svelte"
|
||||
|
||||
export let value
|
||||
export let showMedia
|
||||
|
||||
const close = () => {
|
||||
hidden = true
|
||||
}
|
||||
|
||||
console.log(value)
|
||||
|
||||
let hidden = false
|
||||
</script>
|
||||
|
||||
{#if showMedia && value.canDisplay}
|
||||
<div class="py-2">
|
||||
<Media link={annotateMedia(value.url)} onClose={close} />
|
||||
</div>
|
||||
{:else}
|
||||
<Anchor external href={value.url}>
|
||||
{value.url.replace(/https?:\/\/(www\.)?/, "")}
|
||||
</Anchor>
|
||||
{/if}
|
7
src/app/shared/NoteContentNewline.svelte
Normal file
7
src/app/shared/NoteContentNewline.svelte
Normal file
@ -0,0 +1,7 @@
|
||||
<script lang="ts">
|
||||
export let value
|
||||
</script>
|
||||
|
||||
{#each value as _}
|
||||
<br />
|
||||
{/each}
|
14
src/app/shared/NoteContentPerson.svelte
Normal file
14
src/app/shared/NoteContentPerson.svelte
Normal file
@ -0,0 +1,14 @@
|
||||
<script lang="ts">
|
||||
import {displayPerson} from "src/util/nostr"
|
||||
import {modal} from "src/partials/state"
|
||||
import Anchor from "src/partials/Anchor.svelte"
|
||||
import {getPersonWithFallback} from "src/agent/db"
|
||||
|
||||
export let value
|
||||
|
||||
const openPerson = () => modal.push({type: "person/feed", pubkey: value.pubkey})
|
||||
</script>
|
||||
|
||||
@<Anchor killEvent on:click={openPerson}>
|
||||
{displayPerson(getPersonWithFallback(value.pubkey))}
|
||||
</Anchor>
|
65
src/app/shared/NoteContentQuote.svelte
Normal file
65
src/app/shared/NoteContentQuote.svelte
Normal file
@ -0,0 +1,65 @@
|
||||
<script lang="ts">
|
||||
import {objOf} from "ramda"
|
||||
import {fly} from "svelte/transition"
|
||||
import {warn} from "src/util/logger"
|
||||
import {displayPerson, Tags} from "src/util/nostr"
|
||||
import {modal} from "src/partials/state"
|
||||
import Anchor from "src/partials/Anchor.svelte"
|
||||
import Card from "src/partials/Card.svelte"
|
||||
import Spinner from "src/partials/Spinner.svelte"
|
||||
import PersonCircle from "src/app/shared/PersonCircle.svelte"
|
||||
import {getPersonWithFallback} from "src/agent/db"
|
||||
import {sampleRelays} from "src/agent/relays"
|
||||
import network from "src/agent/network"
|
||||
|
||||
export let note
|
||||
export let value
|
||||
|
||||
const openPerson = pubkey => modal.push({type: "person/feed", pubkey})
|
||||
|
||||
const loadQuote = async () => {
|
||||
const {id, relays} = value
|
||||
|
||||
try {
|
||||
const [event] = await network.load({
|
||||
relays: sampleRelays(
|
||||
(relays || []).map(objOf("url")).concat(Tags.from(note).equals(id).relays())
|
||||
),
|
||||
filter: [{ids: [id]}],
|
||||
})
|
||||
|
||||
return event || Promise.reject()
|
||||
} catch (e) {
|
||||
warn(e)
|
||||
}
|
||||
}
|
||||
|
||||
const openQuote = () => {
|
||||
modal.push({type: "note/detail", note: {id: value.id}})
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="py-2">
|
||||
<Card interactive invertColors class="my-2" on:click={openQuote}>
|
||||
{#await loadQuote()}
|
||||
<Spinner />
|
||||
{:then quote}
|
||||
{@const person = getPersonWithFallback(quote.pubkey)}
|
||||
<div class="mb-4 flex items-center gap-4">
|
||||
<PersonCircle size={6} {person} />
|
||||
<Anchor
|
||||
stopPropagation
|
||||
type="unstyled"
|
||||
class="flex items-center gap-2"
|
||||
on:click={() => openPerson(quote.pubkey)}>
|
||||
<h2 class="text-lg">{displayPerson(person)}</h2>
|
||||
</Anchor>
|
||||
</div>
|
||||
<slot name="note-content" {quote} />
|
||||
{:catch}
|
||||
<p class="mb-1 py-24 text-center text-gray-5" in:fly={{y: 20}}>
|
||||
Unable to load a preview for quoted event
|
||||
</p>
|
||||
{/await}
|
||||
</Card>
|
||||
</div>
|
12
src/app/shared/NoteContentTopic.svelte
Normal file
12
src/app/shared/NoteContentTopic.svelte
Normal file
@ -0,0 +1,12 @@
|
||||
<script lang="ts">
|
||||
import {modal} from "src/partials/state"
|
||||
import Anchor from "src/partials/Anchor.svelte"
|
||||
|
||||
export let value
|
||||
|
||||
const openTopic = topic => {
|
||||
modal.push({type: "topic/feed", topic})
|
||||
}
|
||||
</script>
|
||||
|
||||
<Anchor killEvent on:click={() => openTopic(value)}>#{value}</Anchor>
|
@ -19,8 +19,8 @@
|
||||
<br />
|
||||
{/each}
|
||||
{:else if type === "link"}
|
||||
<Anchor external href={value}>
|
||||
{value.replace(/https?:\/\/(www\.)?/, "")}
|
||||
<Anchor external href={value.url}>
|
||||
{value.url.replace(/https?:\/\/(www\.)?/, "")}
|
||||
</Anchor>
|
||||
{:else if type.startsWith("nostr:")}
|
||||
<Anchor external href={"/" + value.entity}>
|
||||
|
@ -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.
|
||||
</p>
|
||||
</div>
|
||||
<Input bind:value={q} placeholder="Search for people or topics">
|
||||
<Input autofocus bind:value={q} placeholder="Search for people or topics">
|
||||
<i slot="before" class="fa-solid fa-search" />
|
||||
<i
|
||||
slot="after"
|
||||
|
@ -33,6 +33,8 @@
|
||||
"w-10 h-10 flex justify-center items-center rounded-full bg-gray-8 text-white whitespace-nowrap border border-solid border-gray-7",
|
||||
"button-accent":
|
||||
"py-2 px-4 rounded-full bg-accent text-white whitespace-nowrap border border-solid border-accent-light hover:bg-accent-light",
|
||||
"button-minimal":
|
||||
"py-2 px-4 rounded-full whitespace-nowrap border border-solid border-gray-2",
|
||||
})
|
||||
)
|
||||
|
||||
|
@ -1,43 +1,35 @@
|
||||
<script lang="ts">
|
||||
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
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if !hidden}
|
||||
<div in:slide class="relative">
|
||||
<Media link={annotated[0]} onClose={close} />
|
||||
{#if annotated.length > 1}
|
||||
<p class="text-gray-500 cursor-pointer py-4 text-center underline" on:click={openModal}>
|
||||
<div class="my-8 flex justify-center">
|
||||
<Anchor type="button-minimal" on:click={openModal}>
|
||||
<i class="fa fa-plus" /> Show all {annotated.length} link previews
|
||||
</p>
|
||||
{/if}
|
||||
</Anchor>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if showModal}
|
||||
<Modal onEscape={closeModal}>
|
||||
|
@ -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 = [
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user