add a custom fact renderer
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import {h} from 'preact'
|
||||
import {ComponentChild, h} from 'preact'
|
||||
|
||||
import logo from '../../assets/logo_full_name.svg'
|
||||
import {Like as LikeIcon} from '../icons/Like'
|
||||
@@ -7,6 +7,7 @@ import {Repost as RepostIcon} from '../icons/Repost'
|
||||
import {Robot as RobotIcon} from '../icons/Robot'
|
||||
import {CONTENT_LABELS} from '../labels'
|
||||
import * as app from '../lexicons/app'
|
||||
import {FacetRenderer} from '../util/facet-renderer'
|
||||
import {niceDate} from '../util/nice-date'
|
||||
import {prettyNumber} from '../util/pretty-number'
|
||||
import {getRkey} from '../util/rkey'
|
||||
@@ -146,65 +147,51 @@ function PostContent({record}: {record: app.bsky.feed.post.Main | null}) {
|
||||
// render an empty <p> that adds an extra flex gap above the embed
|
||||
if (!record?.text) return null
|
||||
|
||||
// const rt = new RichText({
|
||||
// text: record.text,
|
||||
// facets: record.facets,
|
||||
// })
|
||||
const rt = new FacetRenderer({text: record.text, facets: record.facets})
|
||||
const richText: ComponentChild[] = []
|
||||
|
||||
// const richText = []
|
||||
let counter = 0
|
||||
for (const segment of rt.segments()) {
|
||||
if (segment.link) {
|
||||
richText.push(
|
||||
<Link
|
||||
key={counter}
|
||||
href={segment.link.uri}
|
||||
className="text-brand hover:underline"
|
||||
disableTracking={
|
||||
!segment.link.uri.startsWith('https://bsky.app') &&
|
||||
!segment.link.uri.startsWith('https://go.bsky.app')
|
||||
}>
|
||||
{segment.text}
|
||||
</Link>,
|
||||
)
|
||||
} else if (segment.mention) {
|
||||
richText.push(
|
||||
<Link
|
||||
key={counter}
|
||||
href={`/profile/${segment.mention.did}`}
|
||||
className="text-brand hover:underline">
|
||||
{segment.text}
|
||||
</Link>,
|
||||
)
|
||||
} else if (segment.tag) {
|
||||
richText.push(
|
||||
<Link
|
||||
key={counter}
|
||||
href={`/hashtag/${segment.tag.tag}`}
|
||||
className="text-brand hover:underline">
|
||||
{segment.text}
|
||||
</Link>,
|
||||
)
|
||||
} else {
|
||||
richText.push(segment.text)
|
||||
}
|
||||
counter++
|
||||
}
|
||||
|
||||
// let counter = 0
|
||||
// for (const segment of rt.segments()) {
|
||||
// if (
|
||||
// segment.link &&
|
||||
// app.bsky.richtext.facet.link.$safeValidate(segment.link).success
|
||||
// ) {
|
||||
// richText.push(
|
||||
// <Link
|
||||
// key={counter}
|
||||
// href={segment.link.uri}
|
||||
// className="text-brand hover:underline"
|
||||
// disableTracking={
|
||||
// !segment.link.uri.startsWith('https://bsky.app') &&
|
||||
// !segment.link.uri.startsWith('https://go.bsky.app')
|
||||
// }>
|
||||
// {segment.text}
|
||||
// </Link>,
|
||||
// )
|
||||
// } else if (
|
||||
// segment.mention &&
|
||||
// app.bsky.richtext.facet.mention.safeValidate(segment.mention).success
|
||||
// ) {
|
||||
// richText.push(
|
||||
// <Link
|
||||
// key={counter}
|
||||
// href={`/profile/${segment.mention.did}`}
|
||||
// className="text-brand hover:underline">
|
||||
// {segment.text}
|
||||
// </Link>,
|
||||
// )
|
||||
// } else if (
|
||||
// segment.tag &&
|
||||
// app.bsky.richtext.facet.tag.safeValidate(segment.tag).success
|
||||
// ) {
|
||||
// richText.push(
|
||||
// <Link
|
||||
// key={counter}
|
||||
// href={`/hashtag/${segment.tag.tag}`}
|
||||
// className="text-brand hover:underline">
|
||||
// {segment.text}
|
||||
// </Link>,
|
||||
// )
|
||||
// } else {
|
||||
// richText.push(segment.text)
|
||||
// }
|
||||
|
||||
// counter++
|
||||
// }
|
||||
|
||||
// return (
|
||||
// <p className="text-md min-[400px]:text-lg leading-snug min-[400px]:leading-snug break-word break-words whitespace-pre-wrap">
|
||||
// {richText}
|
||||
// </p>
|
||||
// )
|
||||
return (
|
||||
<p className="text-md min-[400px]:text-lg leading-snug min-[400px]:leading-snug break-word break-words whitespace-pre-wrap">
|
||||
{richText}
|
||||
</p>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
import * as facetDefs from '../lexicons/app/bsky/richtext/facet.defs'
|
||||
|
||||
type Facet = facetDefs.Main
|
||||
type Link = facetDefs.Link
|
||||
type Mention = facetDefs.Mention
|
||||
type Tag = facetDefs.Tag
|
||||
|
||||
const encoder = new TextEncoder()
|
||||
const decoder = new TextDecoder()
|
||||
|
||||
export class FacetSegment {
|
||||
constructor(
|
||||
public text: string,
|
||||
public facet?: Facet,
|
||||
) {}
|
||||
|
||||
get link(): Link | undefined {
|
||||
return this.facet?.features.find(
|
||||
f => f.$type === 'app.bsky.richtext.facet#link',
|
||||
) as Link | undefined
|
||||
}
|
||||
|
||||
get mention(): Mention | undefined {
|
||||
return this.facet?.features.find(
|
||||
f => f.$type === 'app.bsky.richtext.facet#mention',
|
||||
) as Mention | undefined
|
||||
}
|
||||
|
||||
get tag(): Tag | undefined {
|
||||
return this.facet?.features.find(
|
||||
f => f.$type === 'app.bsky.richtext.facet#tag',
|
||||
) as Tag | undefined
|
||||
}
|
||||
}
|
||||
|
||||
export class FacetRenderer {
|
||||
private utf8: Uint8Array
|
||||
private facets: Facet[]
|
||||
|
||||
constructor({text, facets}: {text: string; facets?: Facet[]}) {
|
||||
this.utf8 = encoder.encode(text)
|
||||
this.facets = (facets ?? [])
|
||||
.filter(f => f.index.byteStart <= f.index.byteEnd)
|
||||
.sort((a, b) => a.index.byteStart - b.index.byteStart)
|
||||
}
|
||||
|
||||
private slice(start: number, end: number): string {
|
||||
return decoder.decode(this.utf8.slice(start, end))
|
||||
}
|
||||
|
||||
*segments(): Generator<FacetSegment, void, void> {
|
||||
if (!this.facets.length) {
|
||||
yield new FacetSegment(decoder.decode(this.utf8))
|
||||
return
|
||||
}
|
||||
|
||||
let textCursor = 0
|
||||
let facetCursor = 0
|
||||
do {
|
||||
const currFacet = this.facets[facetCursor]
|
||||
if (textCursor < currFacet.index.byteStart) {
|
||||
yield new FacetSegment(
|
||||
this.slice(textCursor, currFacet.index.byteStart),
|
||||
)
|
||||
} else if (textCursor > currFacet.index.byteStart) {
|
||||
facetCursor++
|
||||
continue
|
||||
}
|
||||
if (currFacet.index.byteStart < currFacet.index.byteEnd) {
|
||||
const subtext = this.slice(
|
||||
currFacet.index.byteStart,
|
||||
currFacet.index.byteEnd,
|
||||
)
|
||||
if (!subtext.trim()) {
|
||||
yield new FacetSegment(subtext)
|
||||
} else {
|
||||
yield new FacetSegment(subtext, currFacet)
|
||||
}
|
||||
}
|
||||
textCursor = currFacet.index.byteEnd
|
||||
facetCursor++
|
||||
} while (facetCursor < this.facets.length)
|
||||
if (textCursor < this.utf8.byteLength) {
|
||||
yield new FacetSegment(this.slice(textCursor, this.utf8.byteLength))
|
||||
}
|
||||
}
|
||||
}
|
||||
+29
-4
@@ -35,10 +35,7 @@
|
||||
// (https://embed.bsky.app)
|
||||
window.BSKY_DEV_EMBED_URL = 'http://localhost:5173'
|
||||
</script>
|
||||
<script
|
||||
async
|
||||
src="http://localhost:3000/embed.js"
|
||||
charset="utf-8"></script>
|
||||
<script async src="http://localhost:3000/embed.js" charset="utf-8"></script>
|
||||
</head>
|
||||
<body class="p12">
|
||||
<!-- Base embeds -->
|
||||
@@ -1048,6 +1045,34 @@
|
||||
|
||||
<div>Verified quoted</div>
|
||||
</div>
|
||||
|
||||
<div class="item">
|
||||
<blockquote
|
||||
class="bluesky-embed"
|
||||
data-bluesky-uri="at://did:plc:mbsyz5d4psc7m5ri57ikievz/app.bsky.feed.post/3mljg3kmf4226"
|
||||
data-bluesky-cid="bafyreidho2pt4sdq3bwgnb62pbnuvucjsxvgk3jq2t3iqzblbdb2pmjwpe"
|
||||
data-bluesky-embed-color-mode="system">
|
||||
<p lang="en">
|
||||
#tangled is everything I wanted from a modern #git forge: -
|
||||
#atproto based (this is HUGE!) - European - Decentralized -
|
||||
Federated - Social with #vouching - Built with #golang and using a
|
||||
#permissive #MIT license What not to like there?<br /><br /><a
|
||||
href="https://bsky.app/profile/did:plc:mbsyz5d4psc7m5ri57ikievz/post/3mljg3kmf4226?ref_src=embed"
|
||||
>[image or embed]</a
|
||||
>
|
||||
</p>
|
||||
— perpetual screaming 🇺🇦 (<a
|
||||
href="https://bsky.app/profile/did:plc:mbsyz5d4psc7m5ri57ikievz?ref_src=embed"
|
||||
>@bazub.zubko.cc</a
|
||||
>)
|
||||
<a
|
||||
href="https://bsky.app/profile/did:plc:mbsyz5d4psc7m5ri57ikievz/post/3mljg3kmf4226?ref_src=embed"
|
||||
>10 May 2026 at 21:43</a
|
||||
>
|
||||
</blockquote>
|
||||
|
||||
<div>Lots of hashtags</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user