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
+4 -4
View File
@@ -3092,7 +3092,7 @@
}, },
"packages/content": { "packages/content": {
"name": "@welshman/content", "name": "@welshman/content",
"version": "0.0.1", "version": "0.0.3",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"insane": "^2.6.2", "insane": "^2.6.2",
@@ -3110,7 +3110,7 @@
"version": "0.0.9", "version": "0.0.9",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@welshman/util": "0.0.11" "@welshman/util": "0.0.12"
}, },
"devDependencies": { "devDependencies": {
"gts": "^5.0.1", "gts": "^5.0.1",
@@ -3148,7 +3148,7 @@
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@welshman/lib": "0.0.8", "@welshman/lib": "0.0.8",
"@welshman/util": "0.0.11", "@welshman/util": "0.0.12",
"isomorphic-ws": "^5.0.0", "isomorphic-ws": "^5.0.0",
"ws": "^8.16.0" "ws": "^8.16.0"
}, },
@@ -3160,7 +3160,7 @@
}, },
"packages/util": { "packages/util": {
"name": "@welshman/util", "name": "@welshman/util",
"version": "0.0.11", "version": "0.0.12",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@welshman/lib": "0.0.8", "@welshman/lib": "0.0.8",
+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:\/?\/?/, "") 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 // Copy some types from nostr-tools because I can't import them
type AddressPointer = { type AddressPointer = {
@@ -37,8 +40,7 @@ export type ParseContext = {
export enum ParsedType { export enum ParsedType {
Address = "address", Address = "address",
Cashu = "cashu", Cashu = "cashu",
CodeBlock = "code_block", Code = "code",
CodeInline = "code_inline",
Ellipsis = "ellipsis", Ellipsis = "ellipsis",
Event = "event", Event = "event",
Invoice = "invoice", Invoice = "invoice",
@@ -55,14 +57,8 @@ export type ParsedCashu = {
raw: string raw: string
} }
export type ParsedCodeBlock = { export type ParsedCode = {
type: ParsedType.CodeBlock type: ParsedType.Code
value: string
raw: string
}
export type ParsedCodeInline = {
type: ParsedType.CodeInline
value: string value: string
raw: string raw: string
} }
@@ -130,8 +126,7 @@ export type ParsedAddress = {
export type Parsed = export type Parsed =
ParsedAddress | ParsedAddress |
ParsedCashu | ParsedCashu |
ParsedCodeBlock | ParsedCode |
ParsedCodeInline |
ParsedEllipsis | ParsedEllipsis |
ParsedEvent | ParsedEvent |
ParsedInvoice | ParsedInvoice |
@@ -141,6 +136,20 @@ export type Parsed =
ParsedText | ParsedText |
ParsedTopic 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 // Parsers for known formats
export const parseAddress = (text: string, context: ParseContext): ParsedAddress | void => { 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) || [] const [code, value] = text.match(/^```([^]*?)```/i) || []
if (code) { 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) || [] const [code, value] = text.match(/^`(.*?)`/i) || []
if (code) { 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 => { 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 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 // 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 return
} }
// Make sure there's a protocol // Parse using URL, make sure there's a protocol
if (!link.match(/^\w+:\/\//)) {
link = "https://" + link
}
// Parse using URL
let url let url
try { try {
url = new URL(link) url = new URL(link.match(/^\w+:\/\//) ? link : "https://" + link)
} catch (e) { } catch (e) {
return return
} }
@@ -241,13 +240,7 @@ export const parseLink = (text: string, context: ParseContext): ParsedLink | voi
} }
} }
const isMedia = Boolean( return {type: ParsedType.Link, value: {url, meta, isMedia: urlIsMedia(url.pathname)}, raw: link}
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}
} }
export const parseNewline = (text: string, context: ParseContext): ParsedNewline | void => { 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 // Skip numeric topics
if (value && !value.match(/^#\d+$/)) { 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>`) HTML.useSafely(`<a href=${href} target="_blank">${display}</a>`)
static buildEntityLink = (entity: string, options: RenderOptions) => 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) => export const renderCashu = (parsed: ParsedCashu, options: RenderOptions) =>
HTML.useSafely(parsed.value) HTML.useSafely(parsed.value)
export const renderCodeBlock = (parsed: ParsedCodeBlock, options: RenderOptions) => export const renderCode = (parsed: ParsedCode, options: RenderOptions) =>
HTML.useSafely(parsed.value) HTML.useSafely(parsed.value)
export const renderCodeInline = (parsed: ParsedCodeInline, options: RenderOptions) => export const renderEllipsis = (parsed: ParsedEllipsis, options: RenderOptions) => "…"
HTML.useSafely(parsed.value)
export const renderEllipsis = (parsed: ParsedEllipsis, options: RenderOptions) => "..."
export const renderInvoice = (parsed: ParsedInvoice, options: RenderOptions) => export const renderInvoice = (parsed: ParsedInvoice, options: RenderOptions) =>
HTML.useSafely(parsed.value) HTML.useSafely(parsed.value)
@@ -496,8 +486,7 @@ export const render = (parsed: Parsed, options: RenderOptions = {}) => {
switch (parsed.type) { switch (parsed.type) {
case ParsedType.Address: return renderAddress(parsed as ParsedAddress, options) case ParsedType.Address: return renderAddress(parsed as ParsedAddress, options)
case ParsedType.Cashu: return renderCashu(parsed as ParsedCashu, options) case ParsedType.Cashu: return renderCashu(parsed as ParsedCashu, options)
case ParsedType.CodeBlock: return renderCodeBlock(parsed as ParsedCodeBlock, options) case ParsedType.Code: return renderCode(parsed as ParsedCode, options)
case ParsedType.CodeInline: return renderCodeInline(parsed as ParsedCodeInline, options)
case ParsedType.Ellipsis: return renderEllipsis(parsed as ParsedEllipsis, options) case ParsedType.Ellipsis: return renderEllipsis(parsed as ParsedEllipsis, options)
case ParsedType.Event: return renderEvent(parsed as ParsedEvent, options) case ParsedType.Event: return renderEvent(parsed as ParsedEvent, options)
case ParsedType.Invoice: return renderInvoice(parsed as ParsedInvoice, options) case ParsedType.Invoice: return renderInvoice(parsed as ParsedInvoice, options)
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@welshman/content", "name": "@welshman/content",
"version": "0.0.2", "version": "0.0.3",
"author": "hodlbod", "author": "hodlbod",
"license": "MIT", "license": "MIT",
"description": "A collection of utilities for parsing nostr note content.", "description": "A collection of utilities for parsing nostr note content.",
+1 -1
View File
@@ -31,6 +31,6 @@
"typescript": "~5.1.6" "typescript": "~5.1.6"
}, },
"dependencies": { "dependencies": {
"@welshman/util": "0.0.11" "@welshman/util": "0.0.12"
} }
} }
+1 -1
View File
@@ -32,7 +32,7 @@
}, },
"dependencies": { "dependencies": {
"@welshman/lib": "0.0.8", "@welshman/lib": "0.0.8",
"@welshman/util": "0.0.11", "@welshman/util": "0.0.12",
"isomorphic-ws": "^5.0.0", "isomorphic-ws": "^5.0.0",
"ws": "^8.16.0" "ws": "^8.16.0"
} }
+6 -6
View File
@@ -1,8 +1,8 @@
import {throttle} from 'throttle-debounce' 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 {DELETE} from './Kinds'
import {EPOCH, matchFilter} from './Filters' import {EPOCH, matchFilter} from './Filters'
import {isReplaceable, isTrustedEvent} from './Events' import {getIdAndAddress, isReplaceable, isTrustedEvent} from './Events'
import {getAddress} from './Address' import {getAddress} from './Address'
import type {Filter} from './Filters' import type {Filter} from './Filters'
import type {TrustedEvent} from './Events' import type {TrustedEvent} from './Events'
@@ -68,8 +68,8 @@ export class Repository extends Emitter {
this.emit('event', event) this.emit('event', event)
} }
notifyDelete = (event: TrustedEvent) => { notifyDelete = (values: string[]) => {
this.emit('delete', event) this.emit('delete', new Set(values))
} }
// API // API
@@ -162,7 +162,7 @@ export class Repository extends Emitter {
// Delete our duplicate first // Delete our duplicate first
if (duplicate) { if (duplicate) {
this.notifyDelete(duplicate) this.notifyDelete(getIdAndAddress(duplicate))
this.eventsById.delete(duplicate.id) this.eventsById.delete(duplicate.id)
if (isReplaceable(duplicate)) { 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 // Deletes are tricky, re-evaluate all subscriptions if that's what we're dealing with
if (event.kind === DELETE) { 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", "name": "@welshman/util",
"version": "0.0.11", "version": "0.0.12",
"author": "hodlbod", "author": "hodlbod",
"license": "MIT", "license": "MIT",
"description": "A collection of nostr-related utilities.", "description": "A collection of nostr-related utilities.",