import React from "react"; import type { AppBskyRichtextFacet } from "@atcute/bluesky"; import { createTextSegments, type TextSegment } from "../utils/richtext"; import { useAtProto } from "../providers/AtProtoProvider"; export interface RichTextProps { text: string; facets?: AppBskyRichtextFacet.Main[]; style?: React.CSSProperties; } /** * RichText component that renders text with facets (mentions, links, hashtags). * Properly handles byte offsets and multi-byte characters. */ export const RichText: React.FC = ({ text, facets, style }) => { const { blueskyAppBaseUrl } = useAtProto(); const segments = createTextSegments(text, facets); return ( {segments.map((segment, idx) => ( ))} ); }; interface RichTextSegmentProps { segment: TextSegment; blueskyAppBaseUrl: string; } const RichTextSegment: React.FC = ({ segment, blueskyAppBaseUrl }) => { if (!segment.facet) { return <>{segment.text}; } // Find the first feature in the facet const feature = segment.facet.features?.[0]; if (!feature) { return <>{segment.text}; } const featureType = (feature as { $type?: string }).$type; // Render based on feature type switch (featureType) { case "app.bsky.richtext.facet#link": { const linkFeature = feature as AppBskyRichtextFacet.Link; return ( { e.currentTarget.style.textDecoration = "underline"; }} onMouseLeave={(e) => { e.currentTarget.style.textDecoration = "none"; }} > {segment.text} ); } case "app.bsky.richtext.facet#mention": { const mentionFeature = feature as AppBskyRichtextFacet.Mention; const profileUrl = `${blueskyAppBaseUrl}/profile/${mentionFeature.did}`; return ( { e.currentTarget.style.textDecoration = "underline"; }} onMouseLeave={(e) => { e.currentTarget.style.textDecoration = "none"; }} > {segment.text} ); } case "app.bsky.richtext.facet#tag": { const tagFeature = feature as AppBskyRichtextFacet.Tag; const tagUrl = `${blueskyAppBaseUrl}/hashtag/${encodeURIComponent(tagFeature.tag)}`; return ( { e.currentTarget.style.textDecoration = "underline"; }} onMouseLeave={(e) => { e.currentTarget.style.textDecoration = "none"; }} > {segment.text} ); } default: return <>{segment.text}; } }; export default RichText;