Clean up and improve content parsing

This commit is contained in:
Jonathan Staab 2023-03-30 08:49:12 -05:00
parent 26ba4fba71
commit 0896623253
3 changed files with 95 additions and 19 deletions

View File

@ -114,14 +114,11 @@ export const fromParentOffset = (element, offset): [HTMLElement, number] => {
}
export const extractUrls = content => {
const regex = /(https?:\/\/)?[-a-z0-9@:%._\+~#=\.]+\.[a-z]{1,6}[-a-z0-9@:%_\+.~#?!&//=;]*/gi
const urls = content.match(regex)
const regex = /((http|ws)s?:\/\/)?[-a-z0-9@:%_\+~#=\.]+\.[a-z]{1,6}[-a-z0-9:%_\+~#\?!&\/=;\.]*/gi
const urls = content.match(regex) || []
return (
(urls || [])
// Skip decimals like 3.5 and ellipses which have more than one dot in a row
.filter(url => !url.match(/^[\d\.]+$/) && !url.match(/\.{2}/))
)
// Skip stuff like 3.5 or U.S. and ellipses which have more than one dot in a row
return urls.filter(url => !url.match(/^[.\.]+$/) && !url.match(/\.{2}/))
}
export const renderContent = content => {

View File

@ -8,7 +8,7 @@
import {quantify} from "hurdak/lib/hurdak"
import {Tags, findRootId, findReplyId, displayPerson, isLike} from "src/util/nostr"
import {formatTimestamp, now, tryJson, formatSats, fetchJson} from "src/util/misc"
import {extractUrls, isMobile} from "src/util/html"
import {isMobile} from "src/util/html"
import {invoiceAmount} from "src/util/lightning"
import QRCode from "src/partials/QRCode.svelte"
import ImageInput from "src/partials/ImageInput.svelte"
@ -17,12 +17,12 @@
import Content from "src/partials/Content.svelte"
import PersonSummary from "src/views/person/PersonSummary.svelte"
import Popover from "src/partials/Popover.svelte"
import PersonCircle from "src/partials/PersonCircle.svelte"
import RelayCard from "src/views/relays/RelayCard.svelte"
import Modal from "src/partials/Modal.svelte"
import Preview from "src/partials/Preview.svelte"
import Anchor from "src/partials/Anchor.svelte"
import {toast, modal} from "src/app/ui"
import {renderNote} from "src/app"
import Compose from "src/partials/Compose.svelte"
import Card from "src/partials/Card.svelte"
import user from "src/agent/user"
@ -35,7 +35,7 @@
import cmd from "src/agent/cmd"
import {routes} from "src/app/ui"
import {publishWithToast} from "src/app"
import PersonCircle from "src/partials/PersonCircle.svelte"
import NoteContent from "src/views/notes/NoteContent.svelte"
export let note
export let depth = 0
@ -58,7 +58,6 @@
const {profile, canPublish, mutes} = user
const timestamp = formatTimestamp(note.created_at)
const borderColor = invertColors ? "gray-6" : "gray-7"
const links = extractUrls(note.content)
const showEntire = anchorId === note.id
const interactive = !anchorId || !showEntire
const person = watch("people", () => getPersonWithFallback(note.pubkey))
@ -402,14 +401,7 @@
You have muted this note.
</p>
{:else}
<div class="flex flex-col gap-2 overflow-hidden text-ellipsis">
<p>{@html renderNote(note, {showEntire})}</p>
{#if user.getSetting("showMedia") && links.length > 0}
<button class="inline-block" on:click={e => e.stopPropagation()}>
<Preview url={last(links)} />
</button>
{/if}
</div>
<NoteContent {note} {showEntire} />
{/if}
<div class="flex justify-between text-gray-1">
<div

View File

@ -0,0 +1,87 @@
<script lang="ts">
import {last, uniq, trim} from "ramda"
import {doPipe, ellipsize} from "hurdak/lib/hurdak"
import {extractUrls, escapeHtml} from "src/util/html"
import {displayPerson} from "src/util/nostr"
import Preview from "src/partials/Preview.svelte"
import user from "src/agent/user"
import {getPersonWithFallback} from "src/agent/tables"
import {routes} from "src/app/ui"
const canPreview = url => url.match("\.(jpg|jpeg|png|gif|mov|mp4)")
export let note
export let showEntire
const links = uniq(extractUrls(note.content))
const content = doPipe(note.content, [
trim,
escapeHtml,
c => (showEntire || c.length < 800 ? c : ellipsize(c, 400)),
c => {
// Pad content with whitespace to simplify our regular expressions
c = `<br>${c}<br>`
for (let url of links) {
// It's common for punctuation to end a url, trim it off
if (url.match(/[\.\?,:]$/)) {
url = url.slice(0, -1)
}
const href = url.includes("://") ? url : "https://" + url
const display = url.replace(/(http|ws)s?:\/\/(www\.)?/, "").replace(/[\.\/?;,:]$/, "")
const escaped = url.replace(/([.*+?^${}()|[\]\\])/g, "\\$1")
const wsRegex = new RegExp(`<br>${escaped}<br>`, "g")
const slashRegex = new RegExp(`\/${escaped}`, "g")
// Skip stuff that's just at the end of a filepath
if (c.match(slashRegex)) {
continue
}
// If the url is on its own line, remove it entirely
if (c.match(wsRegex) && canPreview(url)) {
c = c.replace(wsRegex, '')
continue
}
// Avoid matching urls inside quotes to avoid double-replacing
const quoteRegex = new RegExp(`([^"]*)(${escaped})([^"]*)`, "g")
const $a = document.createElement("a")
$a.href = href
$a.target = "_blank"
$a.className = "underline"
$a.innerText = ellipsize(display, 50)
c = c.replace(quoteRegex, `$1${$a.outerHTML}$3`)
}
return c.trim()
},
// Mentions
c =>
c.replace(/#\[(\d+)\]/g, (tag, i) => {
if (!note.tags[parseInt(i)]) {
return tag
}
const pubkey = note.tags[parseInt(i)][1]
const person = getPersonWithFallback(pubkey)
const name = displayPerson(person)
const path = routes.person(pubkey)
return `@<a href="${path}" class="underline">${name}</a>`
}),
])
</script>
<div class="flex flex-col gap-2 overflow-hidden text-ellipsis">
<p>{@html content}</p>
{#if user.getSetting("showMedia") && links.length > 0}
<button class="inline-block" on:click={e => e.stopPropagation()}>
<Preview url={last(links)} />
</button>
{/if}
</div>