Fix mention parsing

This commit is contained in:
Jonathan Staab 2023-04-03 09:46:35 -05:00
parent ce6993b214
commit 9bc5a35957
9 changed files with 38 additions and 117 deletions

View File

@ -1,5 +1,7 @@
# Current # Current
- [ ] Fix issues
- [ ] Don't escape html in parseContent
- [ ] Look into AUTH not working for mazin - [ ] Look into AUTH not working for mazin
- [ ] Write NIP to support proxies. Update COUNT NIP to mention how proxies are a good use case for COUNT - [ ] Write NIP to support proxies. Update COUNT NIP to mention how proxies are a good use case for COUNT
- [ ] Write blog post on multiplexer - [ ] Write blog post on multiplexer
@ -11,17 +13,6 @@
- [ ] Add onError handler to subscriptions for when sockets fail to connect? - [ ] Add onError handler to subscriptions for when sockets fail to connect?
- [ ] Add bugsnag to multiplexr - [ ] Add bugsnag to multiplexr
# Others
- Recommendations
- Indexer/multiplexer
- relay.coracle.social for people nip-05 verified via coracle
- Improve overall design
- Stripped down easy version of coracle
- Extract library?
- Parameterize color scheme
- Deploy to special domains with relays built in
# Custom views # Custom views
- [ ] Add customize icon and route with editable custom view cards using "lists" nip - [ ] Add customize icon and route with editable custom view cards using "lists" nip
@ -30,6 +21,7 @@
# More # More
- [ ] Build per-relay pagination, put it in paravel https://github.com/nostr-protocol/nips/pull/408
- [ ] Linkify topics - [ ] Linkify topics
- [ ] Add suggestion list for topics on compose so people know there are suggestions - [ ] Add suggestion list for topics on compose so people know there are suggestions
- [ ] Badges link to https://badges.page/p/97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322 - [ ] Badges link to https://badges.page/p/97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322

View File

@ -42,7 +42,7 @@
{#if !hidden} {#if !hidden}
<div in:slide class="relative"> <div in:slide class="relative">
<CarouselItem link={annotated[0]} showLoading={false} onClose={close} /> <CarouselItem link={annotated[0]} onClose={close} />
{#if annotated.length > 1} {#if annotated.length > 1}
<p class="text-gray-500 py-4 text-center underline" on:click={openModal}> <p class="text-gray-500 py-4 text-center underline" on:click={openModal}>
<i class="fa fa-plus" /> Show {annotated.length} link previews <i class="fa fa-plus" /> Show {annotated.length} link previews

View File

@ -1,7 +1,7 @@
<script> <script>
import cx from "classnames" import cx from "classnames"
import {ellipsize} from "hurdak/lib/hurdak" import {ellipsize} from "hurdak/lib/hurdak"
import {fly} from "svelte/transition" import {fly, slide} from "svelte/transition"
import Anchor from "src/partials/Anchor.svelte" import Anchor from "src/partials/Anchor.svelte"
import Spinner from "src/partials/Spinner.svelte" import Spinner from "src/partials/Spinner.svelte"
import user from "src/agent/user" import user from "src/agent/user"
@ -9,7 +9,6 @@
export let link export let link
export let onClick = null export let onClick = null
export let onClose = null export let onClose = null
export let showLoading = true
const loadPreview = async () => { const loadPreview = async () => {
const res = await fetch(user.dufflepud("/link/preview"), { const res = await fetch(user.dufflepud("/link/preview"), {
@ -36,18 +35,14 @@
href={onClick ? null : link.url} href={onClick ? null : link.url}
on:click={onClick} on:click={onClick}
style="background-color: rgba(15, 15, 14, 0.5)" style="background-color: rgba(15, 15, 14, 0.5)"
class={cx("relative flex flex-col overflow-hidden rounded border-solid border-gray-6", { class={cx("relative flex flex-col overflow-hidden rounded border border-solid border-gray-6")}>
border: showLoading || link.type !== "preview",
})}>
{#if link.type === "image"} {#if link.type === "image"}
<img alt="Link preview" src={link.url} class="max-h-96 object-contain object-center" /> <img alt="Link preview" src={link.url} class="max-h-96 object-contain object-center" />
{:else if link.type === "video"} {:else if link.type === "video"}
<video controls src={link.url} class="max-h-96 object-contain object-center" /> <video controls src={link.url} class="max-h-96 object-contain object-center" />
{:else} {:else}
{#await loadPreview()} {#await loadPreview()}
{#if showLoading}
<Spinner /> <Spinner />
{/if}
{:then { title, description, image }} {:then { title, description, image }}
{#if image} {#if image}
<img alt="Link preview" src={image} class="max-h-96 object-contain object-center" /> <img alt="Link preview" src={image} class="max-h-96 object-contain object-center" />
@ -68,11 +63,9 @@
</div> </div>
{/if} {/if}
{:catch} {:catch}
{#if showLoading} <p class="mb-1 py-24 px-12 text-center text-gray-5">
<p class="mb-1 py-24 px-12 text-center text-gray-5" in:fly={{y: 20}}>
Unable to load a preview for {link.url} Unable to load a preview for {link.url}
</p> </p>
{/if}
{/await} {/await}
{/if} {/if}
</Anchor> </Anchor>

View File

@ -14,8 +14,10 @@
<p class="overflow-hidden text-ellipsis"> <p class="overflow-hidden text-ellipsis">
{#each content as { type, value }} {#each content as { type, value }}
{#if type === "br"} {#if type === "newline"}
{@html value} {#each value as _}
<br />
{/each}
{:else if type === "link"} {:else if type === "link"}
<Anchor external href={value}> <Anchor external href={value}>
{value.replace(/https?:\/\/(www\.)?/, "")} {value.replace(/https?:\/\/(www\.)?/, "")}

View File

@ -1,72 +0,0 @@
<script>
import {onMount} from "svelte"
import {slide} from "svelte/transition"
import Anchor from "src/partials/Anchor.svelte"
import user from "src/agent/user"
export let url
export let onClose = null
let preview
const close = () => {
onClose?.()
preview = null
}
onMount(async () => {
if (url.match(".(jpg|jpeg|png|gif)")) {
preview = {image: url}
} else if (url.match(".(mov|mp4)")) {
preview = {video: url}
} else {
try {
const res = await fetch(user.dufflepud("/link/preview"), {
method: "POST",
body: JSON.stringify({url}),
headers: {
"Content-Type": "application/json",
},
})
const json = await res.json()
if (json.title || json.image) {
preview = json
}
} catch (e) {
return
}
}
})
</script>
{#if preview}
<div in:slide>
<Anchor
external
href={url}
style="background-color: rgba(15, 15, 14, 0.5)"
class="relative flex flex-col overflow-hidden rounded border border-solid border-gray-6">
{#if preview.image}
<img alt="Link preview" src={preview.image} class="max-h-96 object-contain object-center" />
{/if}
{#if preview.video}
<video controls src={preview.video} class="max-h-96 object-contain object-center" />
{/if}
<div class="h-px bg-gray-6" />
{#if preview.title}
<div class="flex flex-col bg-white px-4 py-2 text-black">
<strong class="overflow-hidden text-ellipsis whitespace-nowrap">{preview.title}</strong>
<small>{preview.description}</small>
</div>
{/if}
<div
on:click|preventDefault={close}
class="absolute top-0 right-0 m-1 flex h-6 w-6 items-center justify-center
rounded-full border border-solid border-gray-6 bg-white text-black opacity-50 shadow">
<i class="fa fa-times" />
</div>
</Anchor>
</div>
{/if}

View File

@ -103,7 +103,7 @@ export const noEvent = f => e => {
} }
export const parseContent = content => { export const parseContent = content => {
const text = escapeHtml(content.trim()) const text = content.trim()
const result = [] const result = []
let buffer = "", let buffer = "",
i = 0 i = 0
@ -121,10 +121,10 @@ export const parseContent = content => {
for (; i < text.length; ) { for (; i < text.length; ) {
const tail = text.slice(i) const tail = text.slice(i)
const brMatch = tail.match(/^(<br>)+/) const newLine = tail.match(/^\n+/)
if (brMatch) { if (newLine) {
push("br", brMatch[0]) push("newline", newLine[0])
continue continue
} }

View File

@ -20,7 +20,7 @@
import PersonCircle from "src/partials/PersonCircle.svelte" import PersonCircle from "src/partials/PersonCircle.svelte"
import RelayCard from "src/views/relays/RelayCard.svelte" import RelayCard from "src/views/relays/RelayCard.svelte"
import Modal from "src/partials/Modal.svelte" import Modal from "src/partials/Modal.svelte"
import Preview from "src/partials/Preview.svelte" import CarouselItem from "src/partials/CarouselItem.svelte"
import Anchor from "src/partials/Anchor.svelte" import Anchor from "src/partials/Anchor.svelte"
import {toast, modal} from "src/app/ui" import {toast, modal} from "src/app/ui"
import Compose from "src/partials/Compose.svelte" import Compose from "src/partials/Compose.svelte"
@ -512,8 +512,8 @@
</div> </div>
{#if image} {#if image}
<div class="bg-gray-7 p-2"> <div class="bg-gray-7 p-2">
<Preview <CarouselItem
url={image} link={{type: 'image', url: image}}
onClose={() => { onClose={() => {
image = null image = null
}} /> }} />

View File

@ -24,6 +24,7 @@
const entities = [] const entities = []
const shouldTruncate = !showEntire && note.content.length > 500 const shouldTruncate = !showEntire && note.content.length > 500
const content = parseContent(note.content) const content = parseContent(note.content)
const showMedia = user.getSetting("showMedia")
let l = 0 let l = 0
for (let i = 0; i < content.length; i++) { for (let i = 0; i < content.length; i++) {
@ -42,11 +43,11 @@
// If the link is surrounded by line breaks (or content start/end), remove // If the link is surrounded by line breaks (or content start/end), remove
// the link along with trailing whitespace // the link along with trailing whitespace
if ((!prev || prev.type === "br") && (!next || next.type === "br")) { if (showMedia && (!prev || prev.type === "newline") && (!next || next.type === "newline")) {
let n = 0 let n = 0
for (let j = i + 1; j < content.length; j++) { for (let j = i + 1; j < content.length; j++) {
if (content[j].type !== "br") { if (content[j].type !== "newline") {
break break
} }
@ -59,10 +60,10 @@
} }
// Keep track of total characters, if we're not dealing with a string just guess // Keep track of total characters, if we're not dealing with a string just guess
if (value instanceof String) { if (typeof value === 'string') {
l += value.length l += value.length
if (shouldTruncate && l > 350 && type !== "br") { if (shouldTruncate && l > 350 && type !== "newline") {
content[i].value = value.trim() content[i].value = value.trim()
content.splice(i + 1, content.length, {type: "text", value: "..."}) content.splice(i + 1, content.length, {type: "text", value: "..."})
break break
@ -75,9 +76,12 @@
const getMentionPubkey = text => { const getMentionPubkey = text => {
const i = parseInt(first(text.match(/\d+/))) const i = parseInt(first(text.match(/\d+/)))
// Some implementations count only p tags when calculating index // Some implementations count only p tags when calculating index, and some
// implementations are 1-indexed
if (note.tags[i]?.[0] === "p") { if (note.tags[i]?.[0] === "p") {
return note.tags[i][1] return note.tags[i][1]
} else if (note.tags[i-1]?.[0] === "p") {
return note.tags[i-1][1]
} else { } else {
return Tags.from(note).type("p").values().nth(i) return Tags.from(note).type("p").values().nth(i)
} }
@ -104,8 +108,10 @@
<div class="flex flex-col gap-2 overflow-hidden text-ellipsis"> <div class="flex flex-col gap-2 overflow-hidden text-ellipsis">
<p> <p>
{#each content as { type, value }} {#each content as { type, value }}
{#if type === "br"} {#if type === "newline"}
{@html value} {#each value as _}
<br />
{/each}
{:else if type === "link"} {:else if type === "link"}
<Anchor external href={value}> <Anchor external href={value}>
{value.replace(/https?:\/\/(www\.)?/, "")} {value.replace(/https?:\/\/(www\.)?/, "")}
@ -132,7 +138,7 @@
{/if} {/if}
{/each} {/each}
</p> </p>
{#if user.getSetting("showMedia") && links.length > 0} {#if showMedia && links.length > 0}
<div on:click={e => e.stopPropagation()}> <div on:click={e => e.stopPropagation()}>
<Carousel {links} /> <Carousel {links} />
</div> </div>

View File

@ -8,7 +8,7 @@
import Button from "src/partials/Button.svelte" import Button from "src/partials/Button.svelte"
import Compose from "src/partials/Compose.svelte" import Compose from "src/partials/Compose.svelte"
import ImageInput from "src/partials/ImageInput.svelte" import ImageInput from "src/partials/ImageInput.svelte"
import Preview from "src/partials/Preview.svelte" import CarouselItem from "src/partials/CarouselItem.svelte"
import Input from "src/partials/Input.svelte" import Input from "src/partials/Input.svelte"
import RelayCardSimple from "src/partials/RelayCardSimple.svelte" import RelayCardSimple from "src/partials/RelayCardSimple.svelte"
import Content from "src/partials/Content.svelte" import Content from "src/partials/Content.svelte"
@ -113,8 +113,8 @@
</div> </div>
</div> </div>
{#if image} {#if image}
<Preview <CarouselItem
url={image} link={{type: 'image', url: image}}
onClose={() => { onClose={() => {
image = null image = null
}} /> }} />