import { PubLeafletBlocksText } from "npm:@atcute/leaflet"; interface TextBlockProps { plaintext: string; facets?: PubLeafletBlocksText.Main["facets"]; } interface LinkFeature { $type: "pub.leaflet.richtext.facet#link"; uri: string; } function byteToCharIndex(text: string, byteIndex: number): number { const textEncoder = new TextEncoder(); const textDecoder = new TextDecoder(); const fullBytes = textEncoder.encode(text); const bytes = fullBytes.slice(0, byteIndex); return textDecoder.decode(bytes).length; } export function TextBlock({ plaintext, facets }: TextBlockProps) { // Only process facets if at least one facet has features if (!facets || !facets.some((f) => f.features && f.features.length > 0)) { return <>{plaintext}; } const parts: (string | { text: string; type: string; uri?: string })[] = []; let lastIndex = 0; facets.forEach((facet) => { // Convert byte positions to character positions const charStart = byteToCharIndex(plaintext, facet.index.byteStart); const charEnd = byteToCharIndex(plaintext, facet.index.byteEnd); const charLastIndex = byteToCharIndex(plaintext, lastIndex); if (charStart > charLastIndex) { parts.push(plaintext.slice(charLastIndex, charStart)); } const text = plaintext.slice(charStart, charEnd); const feature = facet.features?.[0]; if (!feature) { parts.push(text); return; } if (feature.$type === "pub.leaflet.richtext.facet#bold") { parts.push({ text, type: feature.$type }); } else if (feature.$type === "pub.leaflet.richtext.facet#highlight") { parts.push({ text, type: feature.$type }); } else if (feature.$type === "pub.leaflet.richtext.facet#italic") { parts.push({ text, type: feature.$type }); } else if (feature.$type === "pub.leaflet.richtext.facet#strikethrough") { parts.push({ text, type: feature.$type }); } else if (feature.$type === "pub.leaflet.richtext.facet#underline") { parts.push({ text, type: feature.$type }); } else if (feature.$type === "pub.leaflet.richtext.facet#link") { const linkFeature = feature as LinkFeature; parts.push({ text, type: feature.$type, uri: linkFeature.uri }); } else { parts.push(text); } lastIndex = facet.index.byteEnd; }); // Convert final lastIndex from bytes to characters const charLastIndex = byteToCharIndex(plaintext, lastIndex); if (charLastIndex < plaintext.length) { parts.push(plaintext.slice(charLastIndex)); } return ( <> {parts.map((part, i) => { if (typeof part === "string") { return part; } switch (part.type) { case "pub.leaflet.richtext.facet#bold": return {part.text}; case "pub.leaflet.richtext.facet#highlight": return ( {part.text} ); case "pub.leaflet.richtext.facet#italic": return {part.text}; case "pub.leaflet.richtext.facet#strikethrough": return {part.text}; case "pub.leaflet.richtext.facet#underline": return {part.text}; case "pub.leaflet.richtext.facet#link": return ( {part.text} ); default: return part.text; } })} ); }