Fix some tag utilities, re-work content rendering

This commit is contained in:
Jon Staab
2024-12-05 15:30:39 -08:00
parent d450f532ea
commit 7982bebb35
5 changed files with 99 additions and 65 deletions
+1
View File
@@ -3664,6 +3664,7 @@
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@braintree/sanitize-url": "^7.0.2", "@braintree/sanitize-url": "^7.0.2",
"@welshman/lib": "^0.0.28",
"nostr-tools": "^2.7.2" "nostr-tools": "^2.7.2"
}, },
"devDependencies": { "devDependencies": {
+1
View File
@@ -32,6 +32,7 @@
}, },
"dependencies": { "dependencies": {
"@braintree/sanitize-url": "^7.0.2", "@braintree/sanitize-url": "^7.0.2",
"@welshman/lib": "^0.0.28",
"nostr-tools": "^2.7.2" "nostr-tools": "^2.7.2"
} }
} }
+91 -59
View File
@@ -419,97 +419,129 @@ export const truncate = (
return content return content
} }
// Renderers // Renderer
export type RenderOptions = { export class Renderer {
entityBaseUrl?: string private value = ""
}
export const defaultRenderOptions = { constructor(readonly options: RenderOptions) {}
entityBaseUrl: 'https://njump.me/'
}
export class HTML {
constructor(readonly value: string) {
this.value = value
}
toString = () => this.value toString = () => this.value
static useDangerously = (value: string) => new HTML(value) addText = (value: string) => {
static useSafely = (value: string) => {
const element = document.createElement('div') const element = document.createElement('div')
element.innerText = value element.innerText = value
return new HTML(element.innerHTML) this.value += element.innerHTML
} }
static buildLink = (href: string, display: string) => { addNewlines = (count: number) => {
for (let i = 0; i < count; i++) {
this.value += this.options.newline
}
}
addLink = (href: string, display: string) => {
this.value += this.options.renderLink(href, display)
}
addEntityLink = (entity: string) => {
this.addLink(this.options.entityBase + entity, this.options.renderEntity(entity))
}
}
export type RenderOptions = {
newline: string
entityBase: string
renderLink: (href: string, display: string) => string
renderEntity: (entity: string) => string
}
export const textRenderOptions = {
newline: '\n',
entityBase: '',
renderLink: (href: string, display: string) => href,
renderEntity: (entity: string) => entity.slice(0, 16) + '…',
}
export const htmlRenderOptions = {
newline: '\n',
entityBase: 'https://njump.me/',
renderLink: (href: string, display: string) => {
const element = document.createElement('a') const element = document.createElement('a')
element.href = sanitizeUrl(href) element.href = sanitizeUrl(href)
element.target = "_blank" element.target = "_blank"
element.innerText = display element.innerText = display
return HTML.useDangerously(element.outerHTML) return element.outerHTML
} },
renderEntity: (entity: string) => entity.slice(0, 16) + '…',
static buildEntityLink = (entity: string, options: RenderOptions) =>
HTML.buildLink(options.entityBaseUrl + entity, entity.slice(0, 16) + '…')
} }
export const renderCashu = (parsed: ParsedCashu, options: RenderOptions) => export const makeTextRenderer = (options: Partial<RenderOptions> = {}) =>
HTML.useSafely(parsed.value) new Renderer({...textRenderOptions, ...options})
export const renderCode = (parsed: ParsedCode, options: RenderOptions) => export const makeHtmlRenderer = (options: Partial<RenderOptions> = {}) =>
HTML.useSafely(parsed.value) new Renderer({...htmlRenderOptions, ...options})
export const renderEllipsis = (parsed: ParsedEllipsis, options: RenderOptions) => "…" // Top level render methods
export const renderInvoice = (parsed: ParsedInvoice, options: RenderOptions) => export const renderCashu = (p: ParsedCashu, r: Renderer) => r.addText(p.value)
HTML.useSafely(parsed.value)
export const renderLink = (parsed: ParsedLink, options: RenderOptions) => { export const renderCode = (p: ParsedCode, r: Renderer) => r.addText(p.value)
const href = parsed.value.url.toString()
const display = parsed.value.url.host + parsed.value.url.pathname
return HTML.buildLink(href, display) export const renderEllipsis = (p: ParsedEllipsis, r: Renderer) => "…"
}
export const renderNewline = (parsed: ParsedNewline, options: RenderOptions) => export const renderInvoice = (p: ParsedInvoice, r: Renderer) => r.addText(p.value)
HTML.useSafely(Array.from(parsed.value).map(() => '<br />').join(''))
export const renderText = (parsed: ParsedText, options: RenderOptions) => export const renderLink = (p: ParsedLink, r: Renderer) =>
HTML.useSafely(parsed.value) r.addLink(p.value.url.toString(), p.value.url.host + p.value.url.pathname)
export const renderTopic = (parsed: ParsedTopic, options: RenderOptions) => export const renderNewline = (p: ParsedNewline, r: Renderer) => r.addNewlines(Array.from(p.value).length)
HTML.useSafely(parsed.value)
export const renderEvent = (parsed: ParsedEvent, options: RenderOptions) => export const renderText = (p: ParsedText, r: Renderer) => r.addText(p.value)
HTML.buildEntityLink(nip19.neventEncode(parsed.value), options)
export const renderProfile = (parsed: ParsedProfile, options: RenderOptions) => export const renderTopic = (p: ParsedTopic, r: Renderer) => r.addText(p.value)
HTML.buildEntityLink(nip19.nprofileEncode(parsed.value), options)
export const renderAddress = (parsed: ParsedAddress, options: RenderOptions) => export const renderEvent = (p: ParsedEvent, r: Renderer) => r.addEntityLink(nip19.neventEncode(p.value))
HTML.buildEntityLink(nip19.naddrEncode(parsed.value), options)
export const render = (parsed: Parsed, options: RenderOptions = {}) => { export const renderProfile = (p: ParsedProfile, r: Renderer) => r.addEntityLink(nip19.nprofileEncode(p.value))
options = {...defaultRenderOptions, ...options}
export const renderAddress = (p: ParsedAddress, r: Renderer) => r.addEntityLink(nip19.naddrEncode(p.value))
export const renderOne = (parsed: Parsed, renderer: Renderer) => {
switch (parsed.type) { switch (parsed.type) {
case ParsedType.Address: return renderAddress(parsed as ParsedAddress, options) case ParsedType.Address: renderAddress(parsed as ParsedAddress, renderer); break
case ParsedType.Cashu: return renderCashu(parsed as ParsedCashu, options) case ParsedType.Cashu: renderCashu(parsed as ParsedCashu, renderer); break
case ParsedType.Code: return renderCode(parsed as ParsedCode, options) case ParsedType.Code: renderCode(parsed as ParsedCode, renderer); break
case ParsedType.Ellipsis: return renderEllipsis(parsed as ParsedEllipsis, options) case ParsedType.Ellipsis: renderEllipsis(parsed as ParsedEllipsis, renderer); break
case ParsedType.Event: return renderEvent(parsed as ParsedEvent, options) case ParsedType.Event: renderEvent(parsed as ParsedEvent, renderer); break
case ParsedType.Invoice: return renderInvoice(parsed as ParsedInvoice, options) case ParsedType.Invoice: renderInvoice(parsed as ParsedInvoice, renderer); break
case ParsedType.Link: return renderLink(parsed as ParsedLink, options) case ParsedType.Link: renderLink(parsed as ParsedLink, renderer); break
case ParsedType.Newline: return renderNewline(parsed as ParsedNewline, options) case ParsedType.Newline: renderNewline(parsed as ParsedNewline, renderer); break
case ParsedType.Profile: return renderProfile(parsed as ParsedProfile, options) case ParsedType.Profile: renderProfile(parsed as ParsedProfile, renderer); break
case ParsedType.Text: return renderText(parsed as ParsedText, options) case ParsedType.Text: renderText(parsed as ParsedText, renderer); break
case ParsedType.Topic: return renderTopic(parsed as ParsedTopic, options) case ParsedType.Topic: renderTopic(parsed as ParsedTopic, renderer); break
} }
return renderer
} }
export const renderMany = (parsed: Parsed[], renderer: Renderer) => {
for (const p of parsed) {
renderOne(p, renderer)
}
return renderer
}
export const render = (parsed: Parsed | Parsed[], renderer: Renderer) =>
Array.isArray(parsed) ? renderMany(parsed, renderer) : renderOne(parsed, renderer)
export const renderAsText = (parsed: Parsed | Parsed[], options: Partial<RenderOptions> = {}) =>
render(parsed, makeTextRenderer(options))
export const renderAsHtml = (parsed: Parsed | Parsed[], options: Partial<RenderOptions> = {}) =>
render(parsed, makeHtmlRenderer(options))
+4 -4
View File
@@ -63,23 +63,23 @@ export const removeFromListByPredicate = (list: List, pred: (t: string[]) => boo
export const removeFromList = (list: List, value: string) => export const removeFromList = (list: List, value: string) =>
removeFromListByPredicate(list, nthEq(1, value)) removeFromListByPredicate(list, nthEq(1, value))
export const addToListPublicly = (list: List, tag: string[]) => { export const addToListPublicly = (list: List, ...tags: string[][]) => {
const template = { const template = {
kind: list.kind, kind: list.kind,
content: list.event?.content || "", content: list.event?.content || "",
tags: uniqTags(append(tag, list.publicTags)), tags: uniqTags([...list.publicTags, ...tags]),
} }
return new Encryptable(template, {}) return new Encryptable(template, {})
} }
export const addToListPrivately = (list: List, tag: string[]) => { export const addToListPrivately = (list: List, ...tags: string[][]) => {
const template = { const template = {
kind: list.kind, kind: list.kind,
tags: list.publicTags, tags: list.publicTags,
} }
return new Encryptable(template, { return new Encryptable(template, {
content: JSON.stringify(uniqTags(append(tag, list.privateTags))), content: JSON.stringify(uniqTags([...list.privateTags, ...tags])),
}) })
} }
+2 -2
View File
@@ -194,12 +194,12 @@ export const getTopicTags = (tags: string[][]) => tags.filter(t => ["t"].include
export const getTopicTagValues = (tags: string[][]) => getTopicTags(tags).map(nth(1)) export const getTopicTagValues = (tags: string[][]) => getTopicTags(tags).map(nth(1))
export const getRelayTags = (tags: string[][]) => export const getRelayTags = (tags: string[][]) =>
tags.filter(t => ["r", "relay"].includes(t[0]) && isRelayUrl(t[1])) tags.filter(t => ["r", "relay"].includes(t[0]) && isRelayUrl(t[1] || ""))
export const getRelayTagValues = (tags: string[][]) => getRelayTags(tags).map(nth(1)) export const getRelayTagValues = (tags: string[][]) => getRelayTags(tags).map(nth(1))
export const getGroupTags = (tags: string[][]) => export const getGroupTags = (tags: string[][]) =>
tags.filter(t => ["h", "group"].includes(t[0]) && t[1] && isRelayUrl(t[2])) tags.filter(t => ["h", "group"].includes(t[0]) && t[1] && isRelayUrl(t[2] || ""))
export const getGroupTagValues = (tags: string[][]) => getGroupTags(tags).map(nth(1)) export const getGroupTagValues = (tags: string[][]) => getGroupTags(tags).map(nth(1))