From 0896623253c53ef9566e5042693f35436555d108 Mon Sep 17 00:00:00 2001
From: Jonathan Staab
Date: Thu, 30 Mar 2023 08:49:12 -0500
Subject: [PATCH] Clean up and improve content parsing
---
src/util/html.ts | 11 ++--
src/views/notes/Note.svelte | 16 ++----
src/views/notes/NoteContent.svelte | 87 ++++++++++++++++++++++++++++++
3 files changed, 95 insertions(+), 19 deletions(-)
create mode 100644 src/views/notes/NoteContent.svelte
diff --git a/src/util/html.ts b/src/util/html.ts
index 2ccfc041..12cc4153 100644
--- a/src/util/html.ts
+++ b/src/util/html.ts
@@ -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 => {
diff --git a/src/views/notes/Note.svelte b/src/views/notes/Note.svelte
index c8aa2aca..e459eed3 100644
--- a/src/views/notes/Note.svelte
+++ b/src/views/notes/Note.svelte
@@ -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.
{:else}
-
-
{@html renderNote(note, {showEntire})}
- {#if user.getSetting("showMedia") && links.length > 0}
-
- {/if}
-
+
+ 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 = `
${c}
`
+
+ 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(`
${escaped}
`, "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 `@
${name}`
+ }),
+ ])
+
+
+
+
{@html content}
+ {#if user.getSetting("showMedia") && links.length > 0}
+
+ {/if}
+