Fix parser bugs, fix repository delete

This commit is contained in:
Jon Staab
2024-06-04 17:38:22 -07:00
parent 71cdf5582b
commit 0b74b00d85
7 changed files with 49 additions and 60 deletions
+35 -46
View File
@@ -5,6 +5,9 @@ const last = <T>(xs: T[], ...args: unknown[]) => xs[xs.length - 1]
const fromNostrURI = (s: string) => s.replace(/^nostr:\/?\/?/, "")
export const urlIsMedia = (url: string) =>
Boolean(url.match(/\.(jpe?g|png|wav|mp3|mp4|mov|avi|webm|webp|gif|bmp|svg)$/))
// Copy some types from nostr-tools because I can't import them
type AddressPointer = {
@@ -37,8 +40,7 @@ export type ParseContext = {
export enum ParsedType {
Address = "address",
Cashu = "cashu",
CodeBlock = "code_block",
CodeInline = "code_inline",
Code = "code",
Ellipsis = "ellipsis",
Event = "event",
Invoice = "invoice",
@@ -55,14 +57,8 @@ export type ParsedCashu = {
raw: string
}
export type ParsedCodeBlock = {
type: ParsedType.CodeBlock
value: string
raw: string
}
export type ParsedCodeInline = {
type: ParsedType.CodeInline
export type ParsedCode = {
type: ParsedType.Code
value: string
raw: string
}
@@ -130,8 +126,7 @@ export type ParsedAddress = {
export type Parsed =
ParsedAddress |
ParsedCashu |
ParsedCodeBlock |
ParsedCodeInline |
ParsedCode |
ParsedEllipsis |
ParsedEvent |
ParsedInvoice |
@@ -141,6 +136,20 @@ export type Parsed =
ParsedText |
ParsedTopic
// Matchers
export const isAddress = (parsed: Parsed): parsed is ParsedAddress => parsed.type === ParsedType.Address
export const isCashu = (parsed: Parsed): parsed is ParsedCashu => parsed.type === ParsedType.Cashu
export const isCode = (parsed: Parsed): parsed is ParsedCode => parsed.type === ParsedType.Code
export const isEllipsis = (parsed: Parsed): parsed is ParsedEllipsis => parsed.type === ParsedType.Ellipsis
export const isEvent = (parsed: Parsed): parsed is ParsedEvent => parsed.type === ParsedType.Event
export const isInvoice = (parsed: Parsed): parsed is ParsedInvoice => parsed.type === ParsedType.Invoice
export const isLink = (parsed: Parsed): parsed is ParsedLink => parsed.type === ParsedType.Link
export const isNewline = (parsed: Parsed): parsed is ParsedNewline => parsed.type === ParsedType.Newline
export const isProfile = (parsed: Parsed): parsed is ParsedProfile => parsed.type === ParsedType.Profile
export const isText = (parsed: Parsed): parsed is ParsedText => parsed.type === ParsedType.Text
export const isTopic = (parsed: Parsed): parsed is ParsedTopic => parsed.type === ParsedType.Topic
// Parsers for known formats
export const parseAddress = (text: string, context: ParseContext): ParsedAddress | void => {
@@ -165,19 +174,19 @@ export const parseCashu = (text: string, context: ParseContext): ParsedCashu | v
}
}
export const parseCodeBlock = (text: string, context: ParseContext): ParsedCodeBlock | void => {
export const parseCodeBlock = (text: string, context: ParseContext): ParsedCode | void => {
const [code, value] = text.match(/^```([^]*?)```/i) || []
if (code) {
return {type: ParsedType.CodeBlock, value, raw: code}
return {type: ParsedType.Code, value, raw: code}
}
}
export const parseCodeInline = (text: string, context: ParseContext): ParsedCodeInline | void => {
export const parseCodeInline = (text: string, context: ParseContext): ParsedCode | void => {
const [code, value] = text.match(/^`(.*?)`/i) || []
if (code) {
return {type: ParsedType.CodeInline, value, raw: code}
return {type: ParsedType.Code, value, raw: code}
}
}
@@ -207,28 +216,18 @@ export const parseInvoice = (text: string, context: ParseContext): ParsedInvoice
}
export const parseLink = (text: string, context: ParseContext): ParsedLink | void => {
let [link] = text.match(/^([a-z\+:]{2,30}:\/\/)?[^<>\(\)\s]+\.[a-z]{2,6}[^\s]*[^<>"'\.!?,:\s\)\(]/gi) || []
if (!link) {
return
}
const prev = last(context.results)
const [link] = text.match(/^([a-z\+:]{2,30}:\/\/)?[^<>\(\)\s]+\.[a-z]{2,6}[^\s]*[^<>"'\.!?,:\s\)\(]/gi) || []
// Skip url if it's just the end of a filepath or an ellipse
if (prev?.type === ParsedType.Text && prev.value.endsWith("/") || link.match(/\.\./)) {
if (!link || prev?.type === ParsedType.Text && prev.value.endsWith("/") || link.match(/\.\./)) {
return
}
// Make sure there's a protocol
if (!link.match(/^\w+:\/\//)) {
link = "https://" + link
}
// Parse using URL
// Parse using URL, make sure there's a protocol
let url
try {
url = new URL(link)
url = new URL(link.match(/^\w+:\/\//) ? link : "https://" + link)
} catch (e) {
return
}
@@ -241,13 +240,7 @@ export const parseLink = (text: string, context: ParseContext): ParsedLink | voi
}
}
const isMedia = Boolean(
url.pathname.match(/\.(jpe?g|png|wav|mp3|mp4|mov|avi|webm|webp|gif|bmp|svg)$/)
)
const value = {url, meta, isMedia}
return {type: ParsedType.Link, value, raw: link}
return {type: ParsedType.Link, value: {url, meta, isMedia: urlIsMedia(url.pathname)}, raw: link}
}
export const parseNewline = (text: string, context: ParseContext): ParsedNewline | void => {
@@ -280,7 +273,7 @@ export const parseTopic = (text: string, context: ParseContext): ParsedTopic | v
// Skip numeric topics
if (value && !value.match(/^#\d+$/)) {
return {type: ParsedType.Topic, value, raw: value}
return {type: ParsedType.Topic, value: value.slice(1), raw: value}
}
}
@@ -448,19 +441,16 @@ export class HTML {
HTML.useSafely(`<a href=${href} target="_blank">${display}</a>`)
static buildEntityLink = (entity: string, options: RenderOptions) =>
HTML.buildLink(options.entityBaseUrl + entity, entity.slice(0, 16))
HTML.buildLink(options.entityBaseUrl + entity, entity.slice(0, 16) + '…')
}
export const renderCashu = (parsed: ParsedCashu, options: RenderOptions) =>
HTML.useSafely(parsed.value)
export const renderCodeBlock = (parsed: ParsedCodeBlock, options: RenderOptions) =>
export const renderCode = (parsed: ParsedCode, options: RenderOptions) =>
HTML.useSafely(parsed.value)
export const renderCodeInline = (parsed: ParsedCodeInline, options: RenderOptions) =>
HTML.useSafely(parsed.value)
export const renderEllipsis = (parsed: ParsedEllipsis, options: RenderOptions) => "..."
export const renderEllipsis = (parsed: ParsedEllipsis, options: RenderOptions) => "…"
export const renderInvoice = (parsed: ParsedInvoice, options: RenderOptions) =>
HTML.useSafely(parsed.value)
@@ -496,8 +486,7 @@ export const render = (parsed: Parsed, options: RenderOptions = {}) => {
switch (parsed.type) {
case ParsedType.Address: return renderAddress(parsed as ParsedAddress, options)
case ParsedType.Cashu: return renderCashu(parsed as ParsedCashu, options)
case ParsedType.CodeBlock: return renderCodeBlock(parsed as ParsedCodeBlock, options)
case ParsedType.CodeInline: return renderCodeInline(parsed as ParsedCodeInline, options)
case ParsedType.Code: return renderCode(parsed as ParsedCode, options)
case ParsedType.Ellipsis: return renderEllipsis(parsed as ParsedEllipsis, options)
case ParsedType.Event: return renderEvent(parsed as ParsedEvent, options)
case ParsedType.Invoice: return renderInvoice(parsed as ParsedInvoice, options)
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@welshman/content",
"version": "0.0.2",
"version": "0.0.3",
"author": "hodlbod",
"license": "MIT",
"description": "A collection of utilities for parsing nostr note content.",
+1 -1
View File
@@ -31,6 +31,6 @@
"typescript": "~5.1.6"
},
"dependencies": {
"@welshman/util": "0.0.11"
"@welshman/util": "0.0.12"
}
}
+1 -1
View File
@@ -32,7 +32,7 @@
},
"dependencies": {
"@welshman/lib": "0.0.8",
"@welshman/util": "0.0.11",
"@welshman/util": "0.0.12",
"isomorphic-ws": "^5.0.0",
"ws": "^8.16.0"
}
+6 -6
View File
@@ -1,8 +1,8 @@
import {throttle} from 'throttle-debounce'
import {flatten, Emitter, sortBy, inc, chunk, sleep, uniq, omit, now, range, identity} from '@welshman/lib'
import {flatten, nth, Emitter, sortBy, inc, chunk, sleep, uniq, omit, now, range, identity} from '@welshman/lib'
import {DELETE} from './Kinds'
import {EPOCH, matchFilter} from './Filters'
import {isReplaceable, isTrustedEvent} from './Events'
import {getIdAndAddress, isReplaceable, isTrustedEvent} from './Events'
import {getAddress} from './Address'
import type {Filter} from './Filters'
import type {TrustedEvent} from './Events'
@@ -68,8 +68,8 @@ export class Repository extends Emitter {
this.emit('event', event)
}
notifyDelete = (event: TrustedEvent) => {
this.emit('delete', event)
notifyDelete = (values: string[]) => {
this.emit('delete', new Set(values))
}
// API
@@ -162,7 +162,7 @@ export class Repository extends Emitter {
// Delete our duplicate first
if (duplicate) {
this.notifyDelete(duplicate)
this.notifyDelete(getIdAndAddress(duplicate))
this.eventsById.delete(duplicate.id)
if (isReplaceable(duplicate)) {
@@ -200,7 +200,7 @@ export class Repository extends Emitter {
// Deletes are tricky, re-evaluate all subscriptions if that's what we're dealing with
if (event.kind === DELETE) {
this.notifyDelete(event)
this.notifyDelete(event.tags.map(nth(1)))
}
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@welshman/util",
"version": "0.0.11",
"version": "0.0.12",
"author": "hodlbod",
"license": "MIT",
"description": "A collection of nostr-related utilities.",