flotilla/src/app/components/ContentMinimal.svelte

153 lines
4.4 KiB
Svelte
Raw Normal View History

2025-10-06 18:23:19 +00:00
<script lang="ts">
2026-06-22 20:36:57 +00:00
import cx from "classnames"
2026-07-28 16:16:26 +00:00
import {fromNostrURI, tagValue, tagSpec} from "@welshman/util"
2025-10-06 18:23:19 +00:00
import {
parse,
truncate,
renderAsHtml,
isText,
isEmoji,
isEmail,
2025-10-06 18:23:19 +00:00
isTopic,
isCode,
isCashu,
isInvoice,
isLink,
isProfile,
isNewline,
} from "@welshman/content"
import Danger from "@assets/icons/danger-triangle.svg?dataurl"
import Icon from "@lib/components/Icon.svelte"
import Button from "@lib/components/Button.svelte"
import ContentToken from "@app/components/ContentToken.svelte"
import ContentEmoji from "@app/components/ContentEmoji.svelte"
import ContentEmail from "@app/components/ContentEmail.svelte"
2025-10-06 18:23:19 +00:00
import ContentCode from "@app/components/ContentCode.svelte"
import ContentLinkInline from "@app/components/ContentLinkInline.svelte"
import ContentNewline from "@app/components/ContentNewline.svelte"
import ContentTopic from "@app/components/ContentTopic.svelte"
import ContentMention from "@app/components/ContentMention.svelte"
import {goToQuote, isQuote} from "@app/quotes"
import {userSettingsValues} from "@app/settings"
2025-10-06 18:23:19 +00:00
type Props = {
2025-10-06 18:23:19 +00:00
event: any
trimParent?: boolean
2026-06-22 20:36:57 +00:00
singleLine?: boolean
2025-10-06 18:23:19 +00:00
url?: string
}
2026-06-22 20:36:57 +00:00
const {event, trimParent = false, singleLine = false, url}: Props = $props()
2025-10-06 18:23:19 +00:00
2026-07-08 21:13:04 +00:00
const fullContent = $derived(parse(event))
2025-10-06 18:23:19 +00:00
const isBoundary = (i: number) => {
const parsed = fullContent[i]
2026-09-21 16:48:35 +00:00
if (!parsed || isNewline(parsed)) {
return true
}
if (isText(parsed)) {
return Boolean(parsed.value.match(/^\s+$/))
}
2025-10-06 18:23:19 +00:00
return false
}
const isStart = (i: number) => isBoundary(i - 1)
const isEnd = (i: number) => isBoundary(i + 1)
const isStartAndEnd = (i: number) => isStart(i) && isEnd(i)
let warningDismissed = $state(false)
2025-10-06 18:23:19 +00:00
const ignoreWarning = () => {
warningDismissed = true
2025-10-06 18:23:19 +00:00
}
const warning = $derived(
warningDismissed
? undefined
2026-07-28 16:16:26 +00:00
: $userSettingsValues.hide_sensitive && tagValue(tagSpec("content-warning"), event.tags),
2025-10-06 18:23:19 +00:00
)
const dropWhile = <T,>(f: (x: T) => boolean, xs: Iterable<T>) => {
const result: T[] = []
for (const x of xs) {
if (result.length === 0 && f(x)) {
continue
}
result.push(x)
}
return result
}
const shortContent = $derived.by(() => {
let result = fullContent
if (trimParent && result.length > 0 && isQuote(result[0])) {
result = dropWhile(p => isQuote(p) || isNewline(p), result)
}
return truncate(result, {minLength: 200, maxLength: 300, mediaLength: 20})
})
</script>
<div class="relative">
{#if warning}
<div class="card card-sm shadow-none flex gap-2">
2025-10-06 18:23:19 +00:00
<Icon icon={Danger} />
<p>
This note has been flagged by the author as "{warning}".<br />
<Button onclick={ignoreWarning} class="button button-link">Show anyway</Button>
2025-10-06 18:23:19 +00:00
</p>
</div>
{:else}
2026-06-22 20:36:57 +00:00
<div
class={cx(
"overflow-hidden text-ellipsis",
singleLine ? "whitespace-nowrap" : "wrap-break-word",
)}>
2026-08-22 17:32:51 +00:00
{#each shortContent as parsed, i (i)}
2025-10-06 18:23:19 +00:00
{#if isNewline(parsed)}
2026-06-22 20:36:57 +00:00
{#if singleLine}
2026-08-22 17:32:51 +00:00
<!-- eslint-disable-next-line svelte/no-useless-mustaches -->
2026-06-22 20:36:57 +00:00
{" "}
{:else}
<ContentNewline value={parsed.value} />
{/if}
2025-10-06 18:23:19 +00:00
{:else if isTopic(parsed)}
<ContentTopic value={parsed.value} />
{:else if isEmoji(parsed)}
<ContentEmoji value={parsed.value} />
{:else if isEmail(parsed)}
<ContentEmail value={parsed.value} />
2025-10-06 18:23:19 +00:00
{:else if isCode(parsed)}
<ContentCode
value={parsed.value}
{singleLine}
isBlock={!singleLine && (isStartAndEnd(i) || parsed.value.includes("\n"))} />
2025-10-06 18:23:19 +00:00
{:else if isCashu(parsed) || isInvoice(parsed)}
<ContentToken value={parsed.value} />
{:else if isLink(parsed)}
<ContentLinkInline url={parsed.value.url.toString()} {event} {singleLine} />
2025-10-06 18:23:19 +00:00
{:else if isProfile(parsed)}
<ContentMention value={parsed.value} {url} />
{:else if isQuote(parsed)}
<Button
class="max-w-full cursor-pointer overflow-hidden text-ellipsis whitespace-nowrap underline"
onclick={() => goToQuote(parsed.value, event, url)}>
2025-10-06 18:23:19 +00:00
{fromNostrURI(parsed.raw).slice(0, 16) + "…"}
</Button>
2025-10-06 18:23:19 +00:00
{:else}
{@html renderAsHtml(parsed)}
{/if}
{/each}
</div>
{/if}
</div>