Something went wrong. Try again.
Ghost is lightweight headless server that enables Ghost self hosters to bridge to standard.site with ease..
Something went wrong. Try again.
2.3 kB · 70 lines
TypeScript
at main
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071// Pure transform: Ghost post object → site.standard.document payload.// No I/O, no client state — caller passes the publication AT-URI and (optionally)// an already-uploaded cover-image blob ref.
import { convert as htmlToText } from 'html-to-text';import type { BlobRef } from '@atproto/api';import { buildMarkpubContent } from './content-shape.js';
export const DOCUMENT_COLLECTION = 'site.standard.document';
export type { BlobRef };
export interface GhostPostLike { id: string; title: string; slug: string; status: string; published_at: string; updated_at?: string | null; custom_excerpt?: string | null; excerpt?: string | null; html?: string | null; feature_image?: string | null; tags?: { name: string }[];}
export function ghostPostToRecord( post: GhostPostLike, publicationUri: string, coverImageBlob: BlobRef | null,): Record<string, unknown> { const tags = (post.tags ?? []) .map((t) => t.name) .filter((name): name is string => !!name && !name.startsWith('#'));
const textContent = post.html ? htmlToText(post.html, { wordwrap: false, selectors: [ { selector: 'a', options: { ignoreHref: true } }, { selector: 'img', format: 'skip' }, ], }) : '';
const record: Record<string, unknown> = { $type: DOCUMENT_COLLECTION, site: publicationUri, title: post.title, publishedAt: post.published_at, path: `/${post.slug}/`, };
const description = post.custom_excerpt || post.excerpt; if (description) record.description = description.slice(0, 3000); if (tags.length) record.tags = tags; if (textContent) record.textContent = textContent; if (post.updated_at && post.updated_at !== post.published_at) { record.updatedAt = post.updated_at; } if (coverImageBlob) record.coverImage = coverImageBlob;
// Post body as at.markpub.markdown — the document content union is open // (`closed: false`), so no lexicon registration is needed; the shared $type // is what markpub-aware readers key on. const content = buildMarkpubContent(post.html ?? ''); if (content) record.content = content;
return record;}