diff --git a/src/routes/rss/+server.ts b/src/routes/rss/+server.ts index 147573d..33d312b 100644 --- a/src/routes/rss/+server.ts +++ b/src/routes/rss/+server.ts @@ -15,37 +15,169 @@ function escapeXML(str: string): string { // Helper function to convert content to HTML for full content function convertContentToHTML(content: any): string { - if (!content || !content.blocks) return '' + if (!content) return '' + + // Handle legacy content format (if it's just a string) + if (typeof content === 'string') { + return `

${escapeXML(content)}

` + } + + // Handle TipTap/EditorJS JSON format + if (content.blocks && Array.isArray(content.blocks)) { + return content.blocks + .map((block: any) => { + switch (block.type) { + case 'paragraph': + // Handle both data.text and content formats + const paragraphText = block.data?.text || block.content || '' + return paragraphText ? `

${escapeXML(paragraphText)}

` : '' + case 'heading': + case 'header': + const level = block.data?.level || block.level || 2 + const headingText = block.data?.text || block.content || '' + return headingText ? `${escapeXML(headingText)}` : '' + case 'list': + const items = (block.data?.items || block.content || []) + .map((item: any) => { + const itemText = typeof item === 'string' ? item : item.content || item.text || '' + return itemText ? `
  • ${escapeXML(itemText)}
  • ` : '' + }) + .filter((item: string) => item) + .join('') + if (!items) return '' + const listType = block.data?.style || block.listType + return listType === 'ordered' ? `
      ${items}
    ` : `` + case 'image': + const imageUrl = block.data?.file?.url || block.data?.url || '' + const caption = block.data?.caption || '' + if (!imageUrl) return '' + return `
    ${escapeXML(caption)}${caption ? `
    ${escapeXML(caption)}
    ` : ''}
    ` + case 'code': + const code = block.data?.code || block.content || '' + return code ? `
    ${escapeXML(code)}
    ` : '' + case 'quote': + case 'blockquote': + const quoteText = block.data?.text || block.content || '' + const citation = block.data?.caption || '' + if (!quoteText) return '' + return `
    ${escapeXML(quoteText)}${citation ? `${escapeXML(citation)}` : ''}
    ` + default: + // Fallback for unknown block types + const defaultText = block.data?.text || block.content || '' + return defaultText ? `

    ${escapeXML(defaultText)}

    ` : '' + } + }) + .filter((html: string) => html) // Remove empty blocks + .join('\n') + } + + // Handle TipTap format with doc root + if (content.type === 'doc' && content.content && Array.isArray(content.content)) { + return content.content + .map((node: any) => { + switch (node.type) { + case 'paragraph': + const text = extractTextFromNode(node) + return text ? `

    ${text}

    ` : '' + case 'heading': + const headingText = extractTextFromNode(node) + const level = node.attrs?.level || 2 + return headingText ? `${headingText}` : '' + case 'bulletList': + case 'orderedList': + const listItems = (node.content || []) + .map((item: any) => { + if (item.type === 'listItem') { + const itemText = extractTextFromNode(item) + return itemText ? `
  • ${itemText}
  • ` : '' + } + return '' + }) + .filter((item: string) => item) + .join('') + if (!listItems) return '' + return node.type === 'orderedList' ? `
      ${listItems}
    ` : `` + case 'blockquote': + const quoteText = extractTextFromNode(node) + return quoteText ? `
    ${quoteText}
    ` : '' + case 'codeBlock': + const code = extractTextFromNode(node) + return code ? `
    ${code}
    ` : '' + case 'image': + const src = node.attrs?.src || '' + const alt = node.attrs?.alt || '' + return src ? `
    ${escapeXML(alt)}
    ` : '' + default: + const defaultText = extractTextFromNode(node) + return defaultText ? `

    ${defaultText}

    ` : '' + } + }) + .filter((html: string) => html) + .join('\n') + } + + return '' +} - return content.blocks - .map((block: any) => { - switch (block.type) { - case 'paragraph': - return `

    ${escapeXML(block.content || '')}

    ` - case 'heading': - const level = block.level || 2 - return `${escapeXML(block.content || '')}` - case 'list': - const items = (block.content || []) - .map((item: any) => `
  • ${escapeXML(item)}
  • `) - .join('') - return block.listType === 'ordered' ? `
      ${items}
    ` : `` - default: - return `

    ${escapeXML(block.content || '')}

    ` - } - }) - .join('\n') +// Helper to extract text from TipTap nodes +function extractTextFromNode(node: any): string { + if (!node) return '' + + // Direct text content + if (node.text) return escapeXML(node.text) + + // Nested content + if (node.content && Array.isArray(node.content)) { + return node.content + .map((child: any) => { + if (child.type === 'text') { + return escapeXML(child.text || '') + } + return extractTextFromNode(child) + }) + .join('') + } + + return '' } // Helper function to extract text summary from content function extractTextSummary(content: any, maxLength: number = 300): string { - if (!content || !content.blocks) return '' - - const text = content.blocks - .filter((block: any) => block.type === 'paragraph' && block.content) - .map((block: any) => block.content) - .join(' ') - + if (!content) return '' + + let text = '' + + // Handle string content + if (typeof content === 'string') { + text = content + } + // Handle EditorJS format + else if (content.blocks && Array.isArray(content.blocks)) { + text = content.blocks + .filter((block: any) => block.type === 'paragraph') + .map((block: any) => block.data?.text || block.content || '') + .filter((t: string) => t) + .join(' ') + } + // Handle TipTap format + else if (content.type === 'doc' && content.content && Array.isArray(content.content)) { + text = content.content + .filter((node: any) => node.type === 'paragraph') + .map((node: any) => { + if (node.content && Array.isArray(node.content)) { + return node.content + .filter((child: any) => child.type === 'text') + .map((child: any) => child.text || '') + .join('') + } + return '' + }) + .filter((t: string) => t) + .join(' ') + } + + // Clean up and truncate + text = text.replace(/\s+/g, ' ').trim() return text.length > maxLength ? text.substring(0, maxLength) + '...' : text } @@ -77,7 +209,11 @@ export const GET: RequestHandler = async (event) => { section: 'universe', id: post.id.toString(), title: - post.title || `${post.postType.charAt(0).toUpperCase() + post.postType.slice(1)} Post`, + post.title || new Date(post.publishedAt || post.createdAt).toLocaleDateString('en-US', { + year: 'numeric', + month: 'long', + day: 'numeric' + }), description: extractTextSummary(post.content) || '', content: convertContentToHTML(post.content), link: `${event.url.origin}/universe/${post.slug}`, @@ -163,6 +299,10 @@ ${ ` + : item.type === 'post' && item.featuredImage + ? ` + +` : '' } ${item.location ? `${escapeXML(item.location)}` : ''} @@ -175,14 +315,32 @@ ${item.location ? `${escapeXML(item.location)}= new Date(lastBuildDate)) { + return new Response(null, { status: 304 }) + } + return new Response(rssXml, { headers: { 'Content-Type': 'application/rss+xml; charset=utf-8', - 'Cache-Control': 'public, max-age=3600, stale-while-revalidate=86400', + 'Cache-Control': 'public, max-age=300, s-maxage=600, stale-while-revalidate=86400', 'Last-Modified': lastBuildDate, - ETag: `"${Buffer.from(rssXml).toString('base64').slice(0, 16)}"`, + 'ETag': etag, 'X-Content-Type-Options': 'nosniff', - Vary: 'Accept-Encoding' + 'Vary': 'Accept-Encoding', + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, HEAD, OPTIONS', + 'Access-Control-Max-Age': '86400' } }) } catch (error) {