Something went wrong. Try again.
Migrating content using the Ghost API to Offprint / Standard Site on atproto
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116import type { Logger } from './logger.js';
export interface TextSegment { text: string; features: FacetFeature[];}
export interface Facet { index: { byteStart: number; byteEnd: number }; features: FacetFeature[];}
export type FacetFeature = | { $type: 'app.offprint.richtext.facet#bold' } | { $type: 'app.offprint.richtext.facet#italic' } | { $type: 'app.offprint.richtext.facet#underline' } | { $type: 'app.offprint.richtext.facet#strikethrough' } | { $type: 'app.offprint.richtext.facet#code' } | { $type: 'app.offprint.richtext.facet#link'; uri: string } | { $type: 'app.offprint.richtext.facet#highlight'; color?: string };
export interface TextBlock { plaintext: string; facets?: Facet[];}
export function buildTextFromInline(node: Node, activeFeatures: FacetFeature[], log: Logger): TextSegment[] { const segments: TextSegment[] = [];
for (const child of Array.from(node.childNodes) as Node[]) { if (child.nodeType === 3) { // text node const text = (child as Text).data ?? ''; if (text) { segments.push({ text, features: [...activeFeatures] }); } } else if (child.nodeType === 1) { const el = child as Element; const tag = el.tagName.toLowerCase(); const href = el.getAttribute('href'); const newFeatures = [...activeFeatures];
switch (tag) { case 'strong': case 'b': newFeatures.push({ $type: 'app.offprint.richtext.facet#bold' }); break; case 'em': case 'i': newFeatures.push({ $type: 'app.offprint.richtext.facet#italic' }); break; case 'u': newFeatures.push({ $type: 'app.offprint.richtext.facet#underline' }); break; case 's': case 'del': newFeatures.push({ $type: 'app.offprint.richtext.facet#strikethrough' }); break; case 'code': newFeatures.push({ $type: 'app.offprint.richtext.facet#code' }); break; case 'mark': newFeatures.push({ $type: 'app.offprint.richtext.facet#highlight' }); break; case 'a': if (href) { newFeatures.push({ $type: 'app.offprint.richtext.facet#link', uri: href }); } break; case 'br': segments.push({ text: '\n', features: [...activeFeatures] }); continue; default: // fall through to recursive processing without adding features break; }
segments.push(...buildTextFromInline(el, newFeatures, log)); } }
return segments;}
export function assembleTextBlock(segments: TextSegment[]): TextBlock { const plaintext = segments.map((s) => s.text).join(''); const encoder = new TextEncoder(); let byteCursor = 0; const facetList: Facet[] = [];
for (const segment of segments) { const segmentBytes = encoder.encode(segment.text); const byteStart = byteCursor; const byteEnd = byteCursor + segmentBytes.length;
for (const feature of segment.features) { facetList.push({ index: { byteStart, byteEnd }, features: [feature], }); }
byteCursor = byteEnd; }
return { plaintext, facets: facetList.length > 0 ? facetList : undefined, };}
export function inlineToTextBlock(node: Node, log: Logger): TextBlock { const segments = buildTextFromInline(node, [], log); return assembleTextBlock(segments);}