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;
}
})}
>
);
}