flotilla/src/app/components/ContentMinimal.svelte

154 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-27 23:20:27 +00:00
import {fromNostrURI, getTagValue} 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,
isEvent,
isAddress,
isNewline,
} from "@welshman/content"
import type {Parsed} from "@welshman/content"
import Link from "@lib/components/Link.svelte"
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 {entityLink} from "@app/env"
import {userSettingsValues} from "@app/settings"
2025-10-06 18:23:19 +00:00
interface Props {
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]
if (!parsed || isNewline(parsed)) return true
if (isText(parsed)) return Boolean(parsed.value.match(/^\s+$/))
return false
}
const isStart = (i: number) => isBoundary(i - 1)
const isEnd = (i: number) => isBoundary(i + 1)
const isStartAndEnd = (i: number) => isStart(i) && isEnd(i)
const isQuote = (p: Parsed) => isEvent(p) || isAddress(p)
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-27 23:20:27 +00:00
: $userSettingsValues.hide_sensitive && getTagValue("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",
)}>
2025-10-06 18:23:19 +00:00
{#each shortContent as parsed, i}
{#if isNewline(parsed)}
2026-06-22 20:36:57 +00:00
{#if singleLine}
{" "}
{: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}
isBlock={isStartAndEnd(i) || parsed.value.includes("\n")} />
{:else if isCashu(parsed) || isInvoice(parsed)}
<ContentToken value={parsed.value} />
{:else if isLink(parsed)}
2026-01-12 18:12:47 +00:00
<ContentLinkInline value={parsed.value} {event} />
2025-10-06 18:23:19 +00:00
{:else if isProfile(parsed)}
<ContentMention value={parsed.value} {url} />
{:else if isQuote(parsed)}
<Link
external
class="overflow-hidden text-ellipsis whitespace-nowrap underline"
href={entityLink(parsed.raw)}>
{fromNostrURI(parsed.raw).slice(0, 16) + "…"}
</Link>
{:else}
{@html renderAsHtml(parsed)}
{/if}
{/each}
</div>
{/if}
</div>