import RichtextBuilder, { type BakedRichtext } from '@atcute/bluesky-richtext-builder'; import { tokenize, type Token } from '$lib/richtext/parser'; import type { Did, GenericUri, Handle } from '@atcute/lexicons'; import { resolveHandle } from '$lib/at/client.svelte'; export const parseToRichText = (text: string): ReturnType => processTokens(tokenize(text)); const processTokens = async (tokens: Token[]): Promise => { const rt = new RichtextBuilder(); for (const token of tokens) { switch (token.type) { case 'text': rt.addText(token.content); break; case 'mention': { let did: Did | undefined = token.did as Did | undefined; if (!did) { const handle = token.handle as Handle; const result = await resolveHandle(handle); if (result.ok) did = result.value; } if (did) rt.addMention(token.raw, did); else rt.addText(token.raw); break; } case 'topic': rt.addTag(token.name); break; case 'autolink': rt.addLink(token.url, token.url as GenericUri); break; case 'link': { // flatten children to text const text = flattenToText(token.children); rt.addLink(text, token.url as GenericUri); break; } case 'escape': rt.addText(token.escaped); break; // formatting tokens (strong, emphasis, etc.) don't map to facets // so just extract their text content case 'strong': case 'emphasis': case 'underline': case 'delete': rt.addText(flattenToText(token.children)); break; case 'code': rt.addText(token.content); break; case 'emote': // handle emotes as needed rt.addText(token.raw); break; } } return rt.build(); }; const flattenToText = (tokens: Token[]): string => { return tokens .map((t) => { if ('content' in t) return t.content; if ('children' in t) return flattenToText(t.children); return t.raw; }) .join(''); };