a collection of lightweight TypeScript packages for AT Protocol, the protocol powering Bluesky
atproto bluesky typescript npm
99
fork

Configure Feed

Select the types of activity you want to include in your feed.

README.md

@atcute/bluesky-richtext-segmenter#

segments Bluesky rich text into tokens for rendering.

npm install @atcute/bluesky-richtext-segmenter

Bluesky posts contain text and facets (byte-range annotations for mentions, links, etc). this package splits the text into segments, each with its associated features, so you can render them appropriately.

usage#

import { segmentize } from '@atcute/bluesky-richtext-segmenter';

// text and facets from a post record
const text = 'hello @bsky.app!';
const facets = [
	{
		index: { byteStart: 6, byteEnd: 15 },
		features: [
			{ $type: 'app.bsky.richtext.facet#mention', did: 'did:plc:z72i7hdynmk6r22z27h6tvur' },
		],
	},
];

const segments = segmentize(text, facets);
// -> [
//   { text: 'hello ', features: undefined },
//   { text: '@bsky.app', features: [{ $type: '...#mention', did: '...' }] },
//   { text: '!', features: undefined }
// ]

rendering segments#

each segment contains text and optionally features. render based on the feature type:

import { segmentize, type RichtextSegment } from '@atcute/bluesky-richtext-segmenter';

const renderSegment = (segment: RichtextSegment, index: number) => {
	const { text, features } = segment;

	if (!features) {
		return <span key={index}>{text}</span>;
	}

	// segments can have multiple features, use the first one
	const feature = features[0];

	switch (feature.$type) {
		case 'app.bsky.richtext.facet#mention':
			return (
				<a key={index} href={`/profile/${feature.did}`}>
					{text}
				</a>
			);

		case 'app.bsky.richtext.facet#link':
			return (
				<a key={index} href={feature.uri} target="_blank" rel="noopener noreferrer">
					{text}
				</a>
			);

		case 'app.bsky.richtext.facet#tag':
			return (
				<a key={index} href={`/search?q=${encodeURIComponent('#' + feature.tag)}`}>
					{text}
				</a>
			);

		default:
			return <span key={index}>{text}</span>;
	}
};

const RichText = ({ text, facets }: { text: string; facets?: Facet[] }) => {
	const segments = segmentize(text, facets);
	return <>{segments.map(renderSegment)}</>;
};